1、[安洵杯 2019]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);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_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']));
}
implode函数的作用是:将$filter_arr这个数组合并为一个字符串,并以|分割开来;所以这个正则表达式是preg_replace(/php|flag|php5|php4|f1lg/i,'',$img);并用filter()函数过滤
implode() 函数
定义和用法
implode() 函数返回一个由数组元素组合成的字符串。
注释:implode() 函数接受两种参数顺序。但是由于历史原因,explode() 是不行的,您必须保证 separator 参数在 string 参数之前才行。
注释:implode() 函数的 separator 参数是可选的。但是为了向后兼容,推荐您使用使用两个参数。
注释:该函数是二进制安全的
语法
implode(separator,array)
参数 描述 separator 可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。 array 必需。要组合为字符串的数组。
unset() 函数
unset() 函数用于销毁给定的变量。
语法
void unset ( mixed $var [, mixed $... ] )参数说明:
- $var: 要销毁的变量。
返回值
没有返回值。
继续分析代码,如果存在$_SESSION,就把它删除,然后为$_SESSION中的属性进行赋值。
extract()函数会将你post传入的参数和值形成一个键值对:如:POST方法传入一个/?_SESSION[user]=flag,他会给你变为$_SESSION["user"]='flag',
extract() 函数
定义和用法
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
语法
extract(array,extract_rules,prefix)
参数 描述 array 必需。规定要使用的数组。 extract_rules 可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。
可能的值:
- EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。
- EXTR_SKIP - 如果有冲突,不覆盖已有的变量。
- EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。
- EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。
- EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix。
- EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。
- EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。
- EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。
prefix 可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。
该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。
接下来用if语句判断是否给$function
传参,如果没有则会返回index.php?f=highlight_file这个界面;如果给img_path不传参数,他会将guest_img.png进行base64编码后赋值给img这个键,如果传参数,会将你传的值进行base64编码后在进行sha1赋值给img,将session数组序列化后进行过滤。
提示说可以在phpinfo()
中查看,尝试查看里面的内容。输入f=phpinfo,查找到一个可能有关的条件。
这是一个反序列化题目,最后一句代码echo file_get_contents(base64_decode($userinfo['img']));中说要输出base64编码且反序列化后的对象,因此要考虑怎样将
d0g3_f1ag.php放到里面输出。当f=show_image是可以读文件的,只要$userinfo['img']是相应的flag.php的base64加密
首先,为了输出想要的数据,要在里面放入d0g3_f1ag.php
又因为userinfo是由serialize_info反序列化得来的 所以想到这里应该将d0g3_f1ag.php进行base64编码得到:
ZDBnM19mMWFnLnBocA==
serialize_info又是由$_SESSION这个数组经过序列化编码,再进行过滤得来
因此我们现在的目标就是如何让序列化后且过滤后的字符串img的值为ZDBnM19mMWFnLnBocA==,这里就需要用到源代码中的覆盖函数extract() 函数,参考一下其他wp:[安洵杯 2019]easy_serialize_php (对象逃逸)-CSDN博客
这题过滤会把匹配到的变为空字符串,如果我们构造一个user=flag,过滤后变为空,现在就多出4个字符,因为";s:8:“function”;s:xx:“a为24个字符(两个x表示function长度为两位数),所以我们user要6个flag,
且[function]=a”;s:8:“function”;s:5:“abcde”;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}
过滤前
a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}过滤后
a:2:{s:7:"【";s:48:】";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
下面的步骤和值替换一样
构造payload:
f=show_image
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
得到/d0g3_fllllllag,发现将其base64编码后也是20位,进行替换后得到flag
f=show_image
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
2、[SWPUCTF 2021 新生赛]ez_unserialize
查看源码后得到一个disallow,考虑扫描
扫出来两个目录,尝试查看里面的内容。
只能打开robots.txt,在里面发现了一个新的php文件。
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}
$p = $_GET['p'];
unserialize($p);
?>
目标是输出flag,因此一定要触发_destruct()函数,这个是析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。(实例化对象结束后会被销毁,也会触发析构函数),我认为它的执行条件相当于要先有实例化,也就是执行_construct()。$this->admin === "admin" && $this->passwd === "ctf"是输出的条件,
尝试构造pop链
得到payload:O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
题目中说反序列化的对象是p,所以传入参数值为p。
得到flag