作者:2021-01-26阅读次数:44818评论:0
公布时间:SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成,由服务端发起请求的一个安全漏洞。SSRF是笔者比较喜欢的一个漏洞,因为它见证了攻防两端的对抗过程。本篇文章详细介绍了SSRF的原理,在不同语言中的危害及利用方式,常见的绕过手段,新的攻击手法以及修复方案。
SSRF是Server-side Request Forge的缩写,中文翻译为服务端请求伪造。产生的原因是由于服务端提供了从其他服务器应用获取数据的功能且没有对地址和协议等做过滤和限制。常见的一个场景就是,通过用户输入的URL来获取图片。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器。这种形式的攻击称为服务端请求伪造攻击。
以PHP为例,常见的缺陷代码如下:
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_GET['url'];
curl($url);
从上面的示例代码可以看出,请求是从服务器发出的,那么攻击者可以通过构造恶意的url来访问原本访问不到的内网信息,攻击内网或者本地其他服务。这里根据后续处理逻辑不同,还会分为回显型ssrf和非回显型ssrf,所谓的回显型的ssrf就是会将访问到的信息返回给攻击者,而非回显的ssrf则不会,但是可以通过dns log或者访问开放/未开放的端口导致的延时来判断。
SSRF的最大的危害在于穿透了网络边界,但具体能做到哪种程度还需要根据业务环境来判断。例如我们在SSRF的利用中,如果需要更深一步扩展,第一反应通常是去攻击可利用的redis或者memcache等内网服务拿shell,但需要注意的是操作redis,memcache的数据包中是需要换行的,而http/https协议一般无法满足我们要求,所以即使内网存在可利用的redis,也并非所有的ssrf都能利用成功的。但是,对于memcache来说,即使只能使用https协议,利用memcache来getshell却并非不可能,本文会详细介绍一种新型的攻击方式。
在PHP中,经常出现SSRF的函数有cURL、file_get_contents等。
cURL支持http、https、ftp、gopher、telnet、dict、file 和 ldap 等协议,其中gopher协议和dict协议就是我们需要的。利用gopher,dict协议,我们可以构造出相应payload直接攻击内网的redis服务。
需要注意的是:
1. file_get_contents的gopher协议不能 UrlEncode
2. file_get_contents关于Gopher的302跳转有bug,导致利用失败
3. curl/libcurl 7.43上gopher协议存在bug(截断),7.45以上无此bug
4. curl_exec()默认不跟踪跳转
5. file_get_contents() 支持php://input协议
在Python中,常用的函数有urllib(urllib2)和requests库。以urllib(urllib2)为例, urllib并不支持gopher,dict协议,所以按照常理来讲ssrf在python中的危害也应该不大,但是当SSRF遇到CRLF,奇妙的事情就发生了。
urllib曾爆出CVE-2019-9740、CVE-2019-9947两个漏洞,这两个漏洞都是urllib(urllib2)的CRLF漏洞,只是触发点不一样,其影响范围都在urllib2 in Python 2.x through 2.7.16 and urllib in Python 3.x through 3.7.3之间,目前大部分服务器的python2版本都在2.7.10以下,python3都在3.6.x,这两个CRLF漏洞的影响力就非常可观了。其实之前还有一个CVE-2016-5699,同样的urllib(urllib2)的CRLF问题,但是由于时间比较早,影响范围没有这两个大,这里也不再赘叙
python2代码如下:
import sys
import urllib2
host = "127.0.0.1:7777?a=1 HTTP/1.1\r\nCRLF-injection: test\r\nTEST: 123"
url = "http://"+ host + ":8080/test/?test=a"
try:
info = urllib2.urlopen(url).info()
print(info)
except Exception as e:
print(e)
可以看到我们成功注入了一个header头,利用CLRF漏洞,我们可以实现换行对redis的攻击
除开CRLF之外,urllib还有一个鲜有人知的漏洞CVE-2019-9948,该漏洞只影响urllib,范围在Python 2.x到2.7.16,这个版本间的urllib支持local_file/local-file协议,可以读取任意文件,如果file协议被禁止后,不妨试试这个协议来读取文件。
相对于php,在java中SSRF的利用局限较大,一般利用http协议来探测端口,利用file协议读取任意文件。常见的类中如HttpURLConnection,URLConnection,HttpClients中只支持sun.net.www.protocol (java 1.8)里的所有协议:http,https,file,ftp,mailto,jar,netdoc。
但这里需要注意一个漏洞,那就是weblogic的ssrf,这个ssrf是可以攻击可利用的redis拿shell的。在开始看到这个漏洞的时候,笔者感到很奇怪,因为一般java中的ssrf是无法攻击redis的,但是网上并没有找到太多的分析文章,所以特地看了下weblogic的实现代码。
详细的分析细节就不说了,只挑重点说下过程,调用栈如下
我们跟进sendMessage函数(UDDISoapMessage.java)
sendMessage将传入的url赋值给BindingInfo的实例,然后通过BindingFactory工厂类,来创建一个Binding实例,该实例会通过传入的url决定使用哪个接口。
这里使用HttpClientBinding来调用send方法,
send方法使用createSocket来发送请求,这里可以看到直接将传入的url代入到了socket接口中
这里的逻辑就很清晰了,weblogic并没有采用常见的网络库,而是自己实现了一套socket方法,将用户传入的url直接带入到socket接口,而且并没有校验url中的CRLF。
SSRF的攻防过程也是人们对SSRF漏洞认知不断提升的一个过程,从开始各大厂商不认可SSRF漏洞->攻击者通过SSRF拿到服务器的权限->厂商开始重视这个问题,开始使用各种方法防御->被攻击者绕过->更新防御手段,在这个过程中,攻击者和防御者的手段呈螺旋式上升的趋势,也涌现了大量绕过方案。
常见的修复方案如下:
用伪代码来表示的话就是
if check_ssrf(url):
do_curl(url)
else:
print(“error”)
图中的获取IP地址和判断IP地址即是check_ssrf的检验,所有的攻防都是针对check_ssrf这个函数的绕过与更新,限于篇幅原因,这里取几个经典绕过方案讲解一下
30x跳转也是SSRF漏洞利用中的一个经典绕过方式,当防御方限制只允许http(s)访问或者对请求的host做了正确的校验后,可以通过30x方式跳转进行绕过。
针对只允许http(s)协议的情况,我们可以通过
Location: dict://127.0.0.1:6379跳转到dict协议,从而扩大我们攻击面,来进行更深入的利用
针对没有禁止url跳转,但是对请求host做了正确判断的情况,我们则可以通过Location: http://127.0.0.1:6379的方式来绕过限制
其实在ssrf利用的过程中也零星有利用url解析导致绕过check_ssrf的payload,但大部分利用payload之所以能成功是因为防御者在编写代码时使用的正则匹配不当。第一个正式的深入利用是orange在blackhat大会上提出的A-New-Era-Of-SSRF-Exploiting,利用语言本身自带的解析函数差异来绕过检测,在该ppt中举例了大量不同编程语言的url解析函数对url解析的差异,从而导致check_ssrf和do_curl解析不同导致的绕过,有兴趣的同学可以参看附录一,这里以笔者发现的一个例子作为讲解。
在python3中,笔者发现对于同一个url:http://baidu.com\@qq.com,urllib和urllib3的解析就不一致。
可以看到,对于http://baidu.com\@qq.com 来说,urllib3取到的host是baidu.com,而urllib取到的host是qq.com。笔者曾就此问题与python官方联系过,但是官方并不认为这是一个安全问题,表示urllib3与chrome浏览器的解析保持一致(将\视为/),所以这个问题会一直存在。如果在check_ssrf中解析url函数用的是urllib3,而业务代码发送请时采用的是urllib,两者之间的解析差异就会导致绕过的情况。
这里多说一句,其实url解析问题涉及到了web安全的底层逻辑,不仅仅是在ssrf中有利用,其危害范围很广,包括不限于url跳转,oauth认证,同源策略(如postMessage中origin的判断)等一切会涉及到host判断的场景。from twisted.internet import reactor, defer from twisted.names import client, dns, error, server record={} class DynamicResolver(object): def _doDynamicResponse(self, query): name = query.name.name if name not in record or record[name]<1: # 随意一个 IP,绕过检查即可 ip="104.160.43.154" else: ip="127.0.0.1" if name not in record: record[name]=0 record[name]+=1 print name+" ===> "+ip answer = dns.RRHeader( name=name, type=dns.A, cls=dns.IN, ttl=0, payload=dns.Record_A(address=b'%s'%ip,ttl=0) ) answers = [answer] authority = [] additional = [] return answers, authority, additional def query(self, query, timeout=None): return defer.succeed(self._doDynamicResponse(query)) def main(): factory = server.DNSServerFactory( clients=[DynamicResolver(), client.Resolver(resolv='/etc/resolv.conf')] ) protocol = dns.DNSDatagramProtocol(controller=factory) reactor.listenUDP(53, protocol) reactor.run() if __name__ == '__main__': raise SystemExit(main())
效果如下:
捐款成功,感谢您的无私奉献
评论留言