前言
SSRF,Server-Side Request Forgery,服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求的一个漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。
漏洞形成的原因大多是因为服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作过滤和限制。
攻击者可以利用 SSRF 实现的攻击主要有 5 种:
- 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的 banner 信息
- 攻击运行在内网或本地的应用程序(比如溢出)
- 对内网 WEB 应用进行指纹识别,通过访问默认文件实现
- 攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(比如 Struts2,sqli 等)
- 利用 file 协议读取本地文件等
工具
gopherus
gopher协议是一个古老且强大的协议,可以理解为是 http 协议的前身,它可以实现多个数据包整合发送。通过 gopher 协议可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求。
很多时候在 SSRF 下,我们无法通过 HTTP 协议来传递 POST 数据,这时候就需要用到 gopher 协议来发起 POST 请求了。
而 gopherus 工具可以帮助我们生成 gopher 链接,用于利用 SSRF 并在各种服务器中获得 RCE。
项目链接:https://github.com/tarunkant/Gopherus
做题
内网访问
先来道最简单的题,大概了解下 SSRF 到底是怎么回事。
题目说得明明白白了,flag 就在 127.0.0.1 的 flag.php 中。
这里我先用 php 的伪协议 file:/// 来访问一下服务器上的 /var/www/html/flag.php 文件,因为 /var/www/html 是 Apache 等 web 服务器的默认根目录。所以输入 payload
1 | ?url=file:///var/www/html/flag.php |
果不其然,可以看到源代码如下,$_SERVER[“REMOTE_ADDR”] 的值是发起请求的机器IP,因此当我们需要让服务器自己请求 /flag.php 才能获取到 flag。
index.php 的内容我就懒得看了,想必它里面的逻辑就是请求参数 url 里的地址,所以注入 payload
1 | ?url=127.0.0.1/flag.php |
即可拿到 flag!
伪协议读取文件
这一题让我们获取 Web 目录下的 flag.php 。
这次如果直接用 ?url=127.0.0.1/flag.php 的话,只能看到几个问号……
flag 有可能是在源代码中,但是代码执行后,并没有输出 flag,所以直接用伪协议 file:/// 读取源代码看看,payload 是 ?url=file:///var/www/html/flag.php
仍然是几个问号,但是别急,看看网页源代码,原来是被注释掉了。
端口扫描
题目直接告诉我们需要扫描端口,并且端口范围也给出来了。
用 Burp Suite 直接扫。
但是 Burp Suite 实在是太慢太慢了!所以我干脆自己写了个脚本来扫,结果一两分钟就扫出来了……
python 脚本如下,可以参考参考。
1 | import requests |
POST 请求
这次要我们发 POST 请求。
先访问一下 flag.php,没怎么明白什么意思。1
payload:?url=127.0.0.1/flag.php
尝试看一下源码,这下看懂了,当服务器向它自己的 /flag.php 发出一个 POST 请求时,会校验 key 的值是否与 flag 的 md5 一致,如果一致则会打印 flag,而 key 的值其实在上图有给我们提示。1
?url=file:///var/www/html/flag.php
所以我们需要构造一个 gopher,使得服务器访问本地的 flag.php。
在构造 gopher 之前,我们需要先构造一个 POST 请求,最终服务器执行的 POST 请求就是这个请求啦,构造出来的请求如下:
1 | POST /flag.php HTTP/1.1 |
注:在使用 gopher 协议发送 POST 请求包时,Host、Content-Type 和 Content-Lnegth 请求头都是必不可少的,不过在 GET 请求中可以没有。
接下来就要把需要对构造的请求包,进行2次 URL 编码,因为 index.php 接收到请求时会解码,而 index.php 执行 curl 后,flag.php 接收到请求时,又会解码,因此我们需要进行两次编码。
另外,在第一次编码后的数据中,我们需要将 %0A 替换成 %0D0A。因为 gopher 协议包含的请求数据包中,可能有 =、& 等特殊字符,为避免与服务器解析传入的参数键值对混淆,所以对数据包进行 URL 编码,这样服务端会把 % 后的字节当成普通字节。
我这里写了个脚本,可以利用这个脚本进行两次编码,并且替换 %0A。
1 | import urllib.parse |
执行后,得到 gopher 数据包,以此作为 ?url= 后的内容。
1 | gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253D6cfa9eec7e302cb9f5463e2fb2b46c35 |
成功拿到 flag!
上传文件
这一题要我们上传文件。
先请求页面看看, ?url=127.0.0.1/flag.php,可以看到有个上传文件的按钮,不过看不出什么。
看一下源代码吧!很简单,只要我们能上传文件,就直接打印 flag 出来。
由于原来的页面中,没有提交按钮,所以自己在表单里面添加一个提交的按钮。
再随便找个文件上传,下面是抓包的内容。
拿到 flag!
FastCGI 协议
这次要攻击 FastCGI 协议了,可以点击一下“题目附件”了解一下什么是 FastCGI,攻击 FastCGI 所使用到的技术原理是什么。
FastCGI
Wikipedia对FastCGI的解释:快速通用网关接口(FastCommon Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。
php-fpm官方对php-fpm的解释是FPM(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的。也就是说php-fpm是FastCGI的一个具体实现,其默认监听9000端口。
php-fpm攻击实现原理
想要分析它的攻击原理需要从FastCGI协议封装数据内容来看,这里仅对攻击原理做简要描述,CGI 和 FastCGI 协议的运行原理这篇文章中详细介绍了FastCGI协议的内容,其攻击原理就是在设置环境变量实际请求中会出现一个SCRIPT_FILENAME’: ‘/var/www/html/index.php这样的键值对,它的意思是php-fpm会执行这个文件,但是这样即使能够控制这个键值对的值,但也只能控制php-fpm去执行某个已经存在的文件,不能够实现一些恶意代码的执行。
而在php5.3.9后来的版本中,php增加了安全选项导致只能控制php-fpm执行一些php、php4这样的文件,这也增大了攻击的难度。但是好在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控制为恶意代码,这样就在理论上实现了php-fpm任意代码执行的攻击。
使用 gopherus 工具,可以生成攻击 FastCGI 的 payload 。
1 | python3 gopherus.py --exploit fastcgi |
使用该工具构造 FastCGI 的 payload 时,会要求我们输入一个服务器存在的 php 脚本的路径,以及一个命令。这里的原理其实就是上面提到的 SCRIPT_FILENAME 键值对、auto_prepend_file 。
用工具得到 payload 之后,别忘了对其进行2次 URL 编码,再放到 ?url 后面进行发送。
这时候就可以看到服务器执行了 ls / 命令,一眼就可以看到 flag 文件了。
按照前面的步骤,如法炮制,读取 flag 文件里的内容,即可拿到 flag 。
Redis 协议
这下到 redis 了,原理大概是用 gopher 发送 redis 协议 payload,让 redis 执行 set key value,在让其保存 RDB 快照,这样就能把恶意注入的内容保存成文件。
- 修改 redis 配置,更改快照保存路径
- 写入恶意内容
- 触发落盘
使用 gopherus 构造 payload
1 | python3 gopherus.py --exploit redis |
注:拿到 payload 后,别忘了2次编码
把 payload 拼到 ?url= 后请求,就能向服务器注入一句话木马了,最后用蚁剑连接上服务器,找到 flag 即可。
URL Bypass
题目要求 url 必须包含 http://notfound.ctfhub.com
使用 URL 里用户认证 (UserInfo) 语法来绕过即可
1 | http://notfound.ctfhub.com@127.0.0.1/flag.php |
在这个 URL 里,notfound.ctfhub.com 被解析为用户名(userinfo部分),127.0.0.1 才是真正的 host,最终实际请求的是 http://127.0.0.1/flag.php
数字IP Bypass
这次明确不能使用 127、172 这种点分十进制IP,该怎么访问本机器的资源呢?
用 localhost …………
302跳转 Bypass
这里提示我们可以用 302跳转绕过一些检测规则。
所以我们随便找个短链接生成的网站,让其跳转到 127.0.0.1/flag.php 即可。
这里我随便找了个网站:httpsshort-link.mecn
DNS重绑定 Bypass
附件链接讲述了 DNS 重绑定漏洞,并且给了一个网站,可以让一个域名随机的绑定两个IP:https://lock.cmpxchg8b.com/rebinder.html?tdsourcetag=s_pctim_aiomsg
绑定一下域名,其中一个解析为 127.0.0.1
解析一下看看,没什么问题
拿到 flag!
总结
SSRF攻击主要有以下方法:
- php伪协议构造,访问内网资源
- gopher协议伪造 HTTP 请求,或攻击 MySQL、Redis、FastCGI 等,注入命令,别忘了两次编码
- URL 规则绕过姿势有:更改IP地址写法(十进制改十六进制、十进制整数、十六进制整数等)、URL解析问题(如用户认证语法)、302跳转、DNS重绑定等