自古以来,代理程序都是兵家折戟之地

正向代理的血案

前几天打算使用golang做一个代理程序,golang标准库net/http/httputil已经提供了这样的能力。

一把梭之后发现必然返回403 Forbidden, 我直接在target里面填上游服务实例ip就可以正确返回。

给一个向代理百度官 的简化示例,大家可以体会一下:

package mainimport (	"fmt"	"log"	"net/http"	"net/http/httputil")func ReverseProxyHandler(w http.ResponseWriter, r *http.Request) {	fmt.Println("receive a request from:", r.RemoteAddr, r.Header)	target := "www.baidu.com"	director := func(req *http.Request) {		req.URL.Scheme = "https"		req.URL.Host = target		// req.Host = target	}	proxy := &httputil.ReverseProxy{Director: director}	proxy.ServeHTTP(w, r)}func main() {	fmt.Printf("Starting server at port 8080n")	if err := http.ListenAndServe(":8080", http.HandlerFunc(ReverseProxyHandler)); err != nil {		log.Fatal(err)	}}

郁闷了很久,wireshark抓包也看不出端倪(其实是知识有漏洞,那肯定找不到原因)。

头脑风暴

调试httputil的源代码:

  • 在代理后url中的host已经变成指定域名,但header中的host值没有发生变化还是localhost:8000;
  • 此时我并没有发现问题,因为我笃定url中的host应该决定了请求的具体地址,抱着死马当活马医的态度,我重写了header中的host为目标百度域名
  • req.Host = target // 上面被注释

    竟然真的成功了

    小板凳好好摆一摆

    知识漏洞的关键点在于 :

  • url中已经有host了,为什么header中还要有host?
  • url中的host与request.header中的host到底什么关系?
  • rfc规范(这是个宝藏站点):

    1. Host请求头是在http1.1作为必选被引入,如果请求头没有Host或有多个Host请求头, 将会返回400错误。
    2. 请求中的“Host”提供了目标URI的主机和端口信息。

    最关键的第三点:

    1. 设计Host请求头的动机: 在请求(为多个 站服务的)共享主机时,使初始服务器能够区分目标资源。

    The “Host” header field in a request provides the host and port information from the target URI, enabling the origin server to distinguish among resources while servicing requests for multiple host names

    什么意思呢?

    在微服务架构下,请求在打到业务应用之前都会流经负载均衡器,例如nginx/ 关,这些负载均衡器提供了单负载节点配置多个域名的能力。但是请求打到负载主机,需要有信息能区分目标服务域名,这就依赖请求头中的Host。


    上图来自 阿里云应用型负载均衡

    我们来看在nginx配置基于名字的多虚拟主机的写法:

    在这个配置中,nginx仅仅检查请求的Host头以决定该请求应由哪个虚拟主机来处理。
    如果Host头没有匹配任意一个虚拟主机,或者请求中根本没有包含Host头,那nginx会将请求分发到定义在此端口上的默认虚拟主机。
    在以上配置中,第一个被列出的虚拟主机即nginx的默认虚拟主机——这是nginx的默认行为。而且,可以显式地设置某个主机为默认虚拟主机,即在”listen”指令中设置”default_server”参数:

    server {    listen      80 default_server;    server_name example.net www.example.net;    ...}

    回到最开始的问题,我们写的反向代理程序其实是客户端,虽然重写了url Host, 但是请求打到虚拟主机的时候,请求头中的Host还是最开始的localhost:8080, 这个Host根本无法在虚拟主机中被识别, 所以我们还需要重写请求头中的Host为目标域名。

    进一步, 难道golang的httputil标准库没有考虑到这一点,我又看了一次ReverseProxy源码,其实这个错误姿势在源码注释中已经提醒了。

    NewSingleHostReverseProxy returns a new ReverseProxy that routes URLs to the scheme, host, and base path provided in target. If the target’s path is “/base” and the incoming request was for “/dir”,the target request will be for /base/dir. NewSingleHostReverseProxy does not rewrite the Host header. To rewrite Host headers, use ReverseProxy directly with a custom Director policy.

    结束语

    Host请求头用于在单负载节点支撑多域名。

    文章来自
    https://www.cnblogs.com/JulianHuang/p/16639016.html

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2022年7月27日
    下一篇 2022年7月27日

    相关推荐