目录
前置知识
字符串逃逸-减少
字符串逃逸-增多
前置知识
1.PHP 在反序列化时,语法是以 ; 作为字段的分隔,以 } 作为结尾,在结束符}之后的任何内容不会影响反序列化的后的结果
class people{
public $name='lili';
public $age='20';
}var_dump(unserialize('O:6:"people":2:{s:4:"name";s:4:"lil"";s:3:"age";s:2:"20";}123245dasdsf'));
2.根据长度判断内容
'O:6:"people":2:{s:4:"name";s:4:"lil"";s:3:"age";s:2:"20";}其中lil"是name的值
3.其中字符串必须以双引号包裹,不能不写或以单引号包裹;
注意点,很容易以为序列化后的字符串是;},但对象序列化是直接}结尾
php反序列化字符逃逸,就是通过这个结尾符实现的当长度不对应的时候会出现报错无法完成反序列化
4.什么事字符串逃逸?
就是开发者使用先将对象序列化,然后将序列化后的敏感字符进行过滤或替换,最后再进行反序列化。这个时候就有可能会产生PHP反序列化字符逃逸的漏洞。分为两种情况:
1.字符串减少
O:4:"user":1:{s:8:"username";s:5:"admin";} -->O:4:"user":1:{s:8:"username";s:5:“hack";}
2.字符串增多
O:4:"user":1:{s:8:"username";s:5:"admin";}-->O:4:"user":1:{s:8:"username";s:5:“hacker";}
开发者可能通过这些方法来达到过滤非法字符的目的,但是却会造成其他的问题:字符串逃逸
字符串逃逸-减少
<?php
class a{public $name = "abcp";public $number = "1234";
}$data = serialize(new a());
echo '序列化:'.$data."\n";
$data = str_replace("p","",$data);//注意这个str_replace,它把p都替换为空
echo '序列化:'.$data."\n";
var_dump(unserialize($data));
也就是说它过滤了一个p之后,会吞噬一个字符,但是吞噬之后序列化字符串不符合序列化的语法规范,所以使其无法反序列化成功
这样某种程度起到了安全的作用 ,但是如果我们使其吞噬更多内容会不会达到结果合法的目的?
解释:原acbp中p被替换了,abc”成了一个整体,本质上就是把 ” 给吞噬掉了 ,所以有多少个p就好吞噬掉多少个字符
字符串逃逸就事利用吞噬更多的字符,使序列化的字符串与后面的引号形成闭合,从而构造恶意代码,那么吞噬多少算合适呢?一直吞噬到下一个可控点的值之前,使得下一个可控制点的第一个引号充当上一个值的结束引号,可以吞噬”,也可以不吞,后面会说。如上面例子有两个变量,通过number值构造恶意代码,就需要吞噬到number的值前面,让该值的第一个引号充当name的结束引号。:
O:1:"a":2:{s:4:"name";s:4:"abc";s:6:"number";s:4:"1234";}
或
O:1:"a":2:{s:4:"name";s:4:"abc";s:6:"number";s:4:"1234";}
需要注意的是要构造的字符串通常长度是2位数的,所以应该多逃逸一个字符
O:1:"a":2:{s:4:"name";s:?:"abc";s:6:"number";s:xx:"要构造的字符串";}
所以要写19+1 个p来逃逸字符
属性之间用一个分号分隔,就可以构造逃逸出来的恶意代码了,如下:
O:1:"a":2:{s:4:"name";s:23:"abc";s:6:"number";s:19:";s:3:"age";i:25;}";}
构造了一个age属性,值为25
而上面提到可以把值前面的引号也吞噬掉,其实是一样的,吞噬掉了,我们在构造的是补回来就可以了
O:1:"a":2:{s:4:"name";s:4:"abc";s:6:"number";s:4:"1234";}
如果它过滤的时候不止吞噬一个字符怎么办?那样就可能出现不会正好能吞噬到下一个可控变量引号之前的情况 ,如一次吞噬6个
class a{public $name = "abchacker";public $number = '123';
}$data = serialize(new a());
echo '序列化:'.$data."\n";
$data = str_replace("hacker","",$data);
echo '序列化:'.$data."\n";
var_dump(unserialize($data));
这种情况需要把引号吞掉,因为需要在可控变量里补字符,那么通过上面吞一个的例子,我们知道需要吞21个字符,但是21/6也是除不尽的,这时候可以直接往多了吞就好,吞噬24个是可以被出尽的,也就是4个hacker,那么多出来的,在恶意代码中补上3个任意字符给他吞噬就可以了
字符串逃逸-增多
经过替换后,字符串增多,目标使isadmin=1
<?php
class a{public $name = "php123";public $number = '1234';public $isadmin = '0';
}$data = serialize(new a());
echo '序列化:'.$data."\n";
$data = str_replace("php","hack",$data);
echo '序列化:'.$data."\n";
var_dump(unserialize($data));
字符串增多并需要其他的可控的,使吐出的字符结合到一起最终构造成合法的序列化字符串即可
O:1:"a":3:{s:4:"name";s:xx:"hackxxx";s:6:"number";s:4:"1234";s:7:“isadmin";s:1:"1";}
";s:6:"number";s:4:"1234";s:7:"isadmin";s:1:"0";}
xxx表示还不知需要多少个hack
";s:6:"number";s:4:"1234";s:7:“isadmin";s:1:"1";}如果我们使得这串字符被吐出来,那么这样就最终形成了一个合法的序列化字符串:
O:1:"a":3:{s:4:"name";s:xx:"hackxxx ";s:6:"number";s:4:"1234";s:7:“isadmin";s:1:"1";}
因为}结束后,后面的";s:6:"number";s:4:"1234";s:7:"isadmin";s:1:"0";}会被丢弃
一个php会被替换成hack,一个php吐出一个字符。 “;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}有 49个字符,所以需要输入49个php
假设一个php会被替换成hacker,一个php吐出三个字符。 “;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}有 49个字符,49除以3=16余1,所以需要输入16个php,同时将“;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}改为“;s:6:”number“;s:4:”123“;s:7:”isadmin“;s:1:”1“;}这样就是48个字符,输入16个php刚刚好
吐出的多了就在“;s:6:”number“;s:4:”1234“;s:7:”isadmin“;s:1:”1“;}红色部分增加字符即可,如17个php等于吐出51个字符,原49位多出2位,在number补2位即可