Home  >  Article  >  Backend Development  >  SSRF attack and defense in Go

SSRF attack and defense in Go

Go语言进阶学习
Go语言进阶学习forward
2023-07-24 14:05:16772browse
What is SSRF

SSRF is spelled in English as Server Side Request Forgery, which is translated as server side request forgery. When the attacker fails to obtain server permissions, he uses the server vulnerability to send a constructed request as the server to the intranet where the server is located. Regarding access control of intranet resources, everyone must be aware of it.

SSRF attack and defense in Go

#If the above statement is not easy to understand, then Lao Xu will just give a practical example. Many writing platforms now support uploading images through URLs. If the server does not strictly verify URLs, malicious attackers may be able to access intranet resources.

"A thousand-mile embankment breaks down in an ant nest." We programmers should not ignore any loopholes that may cause risks, and such loopholes are likely to become a stepping stone for others' performance. In order not to become a stepping stone, Lao Xu will take a look at the offensive and defensive rounds of SSRF with all readers.

Round 1: Ever-changing intranet addresses

Why use the word "ever-changing"? Lao Xu will not answer for now, but readers please read on patiently. Next, Lao Xu uses the IP 182.61.200.7 (an IP address of www.baidu.com) to review the different representations of IPv4 with readers.

SSRF attack and defense in Go

Note⚠️: In the dotted mixed system, each part divided by dots can be written in different bases (limited to ten, eight and hexadecimal).

The above are only different expressions of IPv4, and IPv6 addresses also have three different ways of expression. And these three ways of expression can be written in different ways. The following takes the loopback address 0:0:0:0:0:0:0:1 in IPv6 as an example.

SSRF attack and defense in Go

Note⚠️: The leading 0 of each X in the hexadecimal notation can be omitted, so I can partially omit it, Do not omit the part, thereby writing an IPv6 address in different forms. In the same way as the 0-bit compressed representation and the embedded IPv4 address representation, an IPv6 address can also be written in different representations.

After talking so much, Lao Xu can no longer count how many different ways an IP can be written. Please do some calculations if you are good at math.

Intranet IP Do you think it’s over here? of course not! I don’t know if any readers have heard of the domain name xip.io. xip can help you do customized DNS resolution, and can resolve to any IP address (including intranet).

SSRF attack and defense in Go

We use the domain name resolution provided by xip, and we can also access the intranet IP through the domain name.

Access to the intranet IP will continue here! Anyone who has done Basic authentication should know that you can access resources through http://user:passwd@hostname/. If the attacker changes the way of writing, he may be able to bypass some of the less rigorous logic, as shown below.

SSRF attack and defense in Go

Regarding the intranet address, Lao Xu exhausted all his knowledge reserves to summarize the above content, so Lao Xu can’t say too much about the ever-changing intranet address!

At this moment, Lao Xu just wants to ask, when malicious attackers use these different forms of intranet addresses to upload images, how do you identify them and deny access. There won’t really be anyone who can use regular expressions to complete the above filtering. If so, please leave a message and tell me so that I can learn from it.

We have basically understood the various intranet addresses, so the question now is how to convert it into an IP that we can judge. To summarize, the above intranet addresses can be divided into three categories: 1. It is an IP address itself, but its expression is not uniform; 2. A domain name that points to the intranet IP; 3. An address that contains Basic verification information and the intranet IP. Based on these three types of characteristics, you can identify the intranet address and deny access by following the following steps before initiating a request.

  1. Parse out the HostName in the address.

  2. Initiate DNS resolution and obtain IP.

  3. Determine whether the IP is an intranet address.

In the above steps, when judging the intranet address, please do not ignore the IPv6 loopback address and the IPv6 unique local address. The following is the logic used by Lao Xu to determine whether the IP is an intranet IP.

// IsLocalIP 判断是否是内网ip
func IsLocalIP(ip net.IP) bool {
if ip == nil {
return false
	}
// 判断是否是回环地址, ipv4时是127.0.0.1;ipv6时是::1
if ip.IsLoopback() {
return true
	}
// 判断ipv4是否是内网
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 || // 10.0.0.0/8
			(ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12
			(ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16
	}
// 判断ipv6是否是内网
if ip16 := ip.To16(); ip16 != nil {
// 参考 https://tools.ietf.org/html/rfc4193#section-3
// 参考 https://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses
// 判断ipv6唯一本地地址
return 0xfd == ip16[0]
	}
// 不是ip直接返回false
return false
}

The picture below shows the result of following the above steps to detect whether the request is an intranet request.

SSRF attack and defense in Go

Summary: URLs come in various forms, and you can use DNS resolution to obtain the standardized IP to determine whether it is an intranet resource.

Round 2: URL Jump

If malicious attackers only attack through different ways of writing IP, then we can naturally sit back and relax, but this battle between spear and shield has just begun.

Let’s review the defense strategy of Round 1. The detection of whether the request is an intranet resource is before the request is officially initiated. If the attacker accesses the intranet resource through URL jump during the request process, it can be completely bypassed. Defensive strategy in turn one. The specific attack process is as follows.

SSRF attack and defense in Go

As shown in the figure, an attacker can obtain intranet resources through URL jump. Before introducing how to defend against URL jump attacks, Lao Xu and readers will first review the HTTP redirection status code-3xx.

According to Wikipedia, there are 9 3xx redirection codes ranging from 300 to 308. Lao Xu took a special look at the source code of go and found that the official http.Client request only supports the following redirection codes.

301: The requested resource has been permanently moved to a new location; the response is cacheable; the redirect request must be a GET request.

302: The client is required to perform a temporary redirect; this response is cacheable only if specified in Cache-Control or Expires; the redirect request must be a GET request.

303: This code can be used when the response to a POST (or PUT / DELETE) request can be found at another URI. This code exists mainly to allow POST requests activated by scripts. Output is redirected to a new resource; 303 responses are prohibited from being cached; redirected requests must be GET requests.

307: Temporary redirection; the request method cannot be changed. If the original request is POST, the redirected request is also POST.

308: Permanent redirection; the request method cannot be changed. If the original request is POST, the redirected request is also POST.

3xx status code review ends here, let’s continue the discussion of SSRF’s offensive and defensive rounds. Since URL jumping on the server side may bring risks, we can completely avoid such risks as long as we disable URL jumping. However, we cannot do this. While this approach avoids risks, it is also very likely to accidentally damage normal requests. So how to prevent such attacks?

看过老许“Go中的HTTP请求之——HTTP1.1请求流程分析”这篇文章的读者应该知道,对于重定向有业务需求时,可以自定义http.Client的CheckRedirect。下面我们先看一下CheckRedirect的定义。

CheckRedirect func(req *Request, via []*Request) error

这里特别说明一下,req是即将发出的请求且请求中包含前一次请求的响应,via是已经发出的请求。在知晓这些条件后,防御URL跳转攻击就变得十分容易了。

  1. 根据前一次请求的响应直接拒绝307308的跳转(此类跳转可以是POST请求,风险极高)。

  2. 解析出请求的IP,并判断是否是内网IP。

根据上述步骤,可如下定义http.Client

client := &http.Client{
	CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 跳转超过10次,也拒绝继续跳转
if len(via) >= 10 {
return fmt.Errorf("redirect too much")
		}
		statusCode := req.Response.StatusCode
if statusCode == 307 || statusCode == 308 {
// 拒绝跳转访问
return fmt.Errorf("unsupport redirect method")
		}
// 判断ip
		ips, err := net.LookupIP(req.URL.Host)
if err != nil {
return err
		}
for _, ip := range ips {
if IsLocalIP(ip) {
return fmt.Errorf("have local ip")
			}
			fmt.Printf("%s -> %s is localip?: %v\n", req.URL, ip.String(), IsLocalIP(ip))
		}
return nil
	},
}

如上自定义CheckRedirect可以防范URL跳转攻击,但此方式会进行多次DNS解析,效率不佳。后文会结合其他攻击方式介绍更加有效率的防御措施。

Summary: You can prevent URL jump attacks by customizing http.Client's CheckRedirect.

Round 3: DNS Rebinding

As we all know, to initiate an HTTP request, you need to first request the DNS service to obtain the IP address corresponding to the domain name. If the attacker has a controllable DNS service, he can bypass the previous defense strategy through DNS rebinding and attack.

The specific process is shown in the figure below.

SSRF attack and defense in Go

When verifying whether the resource is legal, the server performed the first DNS resolution and obtained a non-intranet IP with a TTL of 0. Judge the resolved IP and find that non-intranet IP can be used for subsequent requests. Since the attacker's DNS Server sets the TTL to 0, DNS resolution needs to be performed again when the request is formally initiated. At this time, the DNS Server returns the intranet address. Since it has entered the resource request stage and there are no defensive measures, the attacker can obtain the intranet resources.

As an extra mention, Lao Xu specifically looked at part of the source code of DNS resolution in Go and found that Go did not cache the DNS results, so even if the TTL is not 0, there is still DNS rebinding. risks of.

在发起请求的过程中有DNS解析才让攻击者有机可乘。如果我们能对该过程进行控制,就可以避免DNS重绑定的风险。对HTTP请求控制可以通过自定义http.Transport来实现,而自定义http.Transport也有两个方案。

方案一

dialer := &net.Dialer{}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
	host, port, err := net.SplitHostPort(addr)
// 解析host和 端口
if err != nil {
return nil, err
	}
// dns解析域名
	ips, err := net.LookupIP(host)
if err != nil {
return nil, err
	}
// 对所有的ip串行发起请求
for _, ip := range ips {
		fmt.Printf("%v -> %v is localip?: %v\n", addr, ip.String(), IsLocalIP(ip))
if IsLocalIP(ip) {
continue
		}
// 非内网IP可继续访问
// 拼接地址
		addr := net.JoinHostPort(ip.String(), port)
// 此时的addr仅包含IP和端口信息
		con, err := dialer.DialContext(ctx, network, addr)
if err == nil {
return con, nil
		}
		fmt.Println(err)
	}

return nil, fmt.Errorf("connect failed")
}
// 使用此client请求,可避免DNS重绑定风险
client := &http.Client{
	Transport: transport,
}

transport.DialContext的作用是创建未加密的TCP连接,我们通过自定义此函数可规避DNS重绑定风险。另外特别说明一下,如果传递给dialer.DialContext方法的地址是常规IP格式则可使用net包中的parseIPZone函数直接解析成功,否则会继续发起DNS解析请求。

方案二

dialer := &net.Dialer{}
dialer.Control = func(network, address string, c syscall.RawConn) error {
// address 已经是ip:port的格式
	host, _, err := net.SplitHostPort(address)
if err != nil {
return err
	}
	fmt.Printf("%v is localip?: %v\n", address, IsLocalIP(net.ParseIP(host)))
return nil
}
transport := http.DefaultTransport.(*http.Transport).Clone()
// 使用官方库的实现创建TCP连接
transport.DialContext = dialer.DialContext
// 使用此client请求,可避免DNS重绑定风险
client := &http.Client{
	Transport: transport,
}

dialer.Control在创建网络连接之后实际拨号之前调用,且仅在go版本大于等于1.11时可用,其具体调用位置在sock_posix.go中的(*netFD).dial方法里。

SSRF attack and defense in Go

The above two defense solutions can not only prevent DNS rebinding attacks, but also prevent other attack methods. In fact, Lao Xu even recommended the second option, which is a one-and-done solution!

Summary:

  1. An attacker can conduct a DNS rebinding attack through its own DNS service.

  2. DNS rebinding attacks can be prevented by customizing http.Transport.

The above is the detailed content of SSRF attack and defense in Go. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:Go语言进阶学习. If there is any infringement, please contact admin@php.cn delete