欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DDCTF web题复现

程序员文章站 2022-05-12 13:39:19
...

DDCTF自己只做出来第一道web,后面都是一点思路没有,所以在比赛后看wp进行复现了。

web签到题

DDCTF web题复现

进入页面后显示这个

DDCTF web题复现

查看源代码发现这个js文件有点可疑

DDCTF web题复现

虽然第一眼看到phpstorm就想到.idea/workspace.xml,但是这里好像没有,但是看到这个uri,以及设置的header,所以就访问这个url,并且构造didictf_username为admin。

DDCTF web题复现

发现返回了一段json数据,并且其中的数据有一部分是unicode编码,所以解码

DDCTF web题复现

根据返回内容,就去访问图中的这个url

返回的内容中包含了两个文件的源代码

DDCTF web题复现

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 22 Apr 2019 05:54:30 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 4936


<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>HighLightjs</title>
    <link rel="stylesheet" href="../highlight/styles/default.css">
    <script src="../highlight/highlight.pack.js"></script>
<body>
<script>
    hljs.initHighlightingOnLoad();
</script>
<pre>
<code class="php">
url:app/Application.php
</code>
<code class="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();
}
}

</code>
</pre>
<br />
<pre>
<code class="php">
url:app/Session.php
</code>
<code>

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)) {
        return FALSE;
        }

        $session = $_COOKIE[$this->cookie_name];
        if(!isset($session)) {
            parent::response("session not found",'error');
            return FALSE;
        }
        $hash = substr($session,strlen($session)-32);
        $session = substr($session,0,strlen($session)-32);

        if($hash !== md5($this->eancrykey.$session)) {
            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;
        }

        if(!empty($_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();

</code>
</pre>
</body>
</html>

DDCTF web题复现

首先去找可以读到flag的函数,再一步步找引用它的地方,再通过满足题目所设置的条件,来实现flag文件的读取。这道题目看到了魔术方法 _destruct(),最近碰到的反序列化有点多,这里很简单,只要我们传入一个序列化的对象Application并且它的属性path为我们要读取的flag文件,就可以读到flag了。所以现在就去找代码中实现了反序列化的地方。可以看到在函数session_read()函数中的第三个if语句之后有反序列化的实现,所以我们就要满足前面所设置的条件。

1、存在cookie

2、存在session

3、md5判断相等

前两格条件好满足,但是第三个要求我们传入的session,这里的session被赋值为cookie,而hash为cookie的最后32位。也就是说我们传入的cookie最后32位要等于MD5(eancrykey),所以我们现在要知道eancrykey的值是什么。

在我圈出的地方有一个可以读取出eancrykey的方法,我们可以传递一个参数nickname,而后面会用sprintf进行输出,那如果我们将nickname设置成%s,那么在第二次的时候就会将eancrykey输出。第一次为welcome my friend %s,第二次为,welcome my friend %s, eancrykey。这样就可以得到key的值了。所以我们先啥都不设置,去访问session.php,就可以由服务器去设置session和cookie。

DDCTF web题复现

然后我们带上它的cookie再访问一次

DDCTF web题复现

nickname设置为#%s#,key就会在##中间输出。那现在就可以构造我们的payload

首先构造序列化对象 O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}

这里的..././是因为../会被替换为空字符,而flag的目录在get_key函数中提示了。这样又刚好为18个字符。再对这个进行urlencod,因为要满足格式嘛。再把md5加密后的(key+serilize(session))放在后面。

DDCTF web题复现

前一天做出来的时候就去睡觉了,所以忘了给flag截图,找了一个师傅wp上的最后的效果图。。。

 

 

2、homebrew event loop

DDCTF web题复现

这道题目是一个python写的flask框架,传入的参数会被trigger_event函数存入session的log中,在execute_event_loop里参数会被循环处理,action:后面作为函数名 ;后的作为参数值,在try的内容中会执行传入的函数。所以可以通过将第一个函数设置为trigger_event添加函数,后面可以继续添加函数和参数。

DDCTF web题复现

但是要得到flag我们还要去找能读取flag的函数,136行定义的show_flag看似是一个可用的函数,但flag读取的是我们设置的参数的值,就算设置为了FLAG()也无法读取到,而且注释里提示了已经将这个方法禁止了。再看到get_flag_handler,如果 num_items的值大于等于5,就会调用show_flag,并且由服务端设置了参数为FLAG(),所以我们只能通过这个方法才能读到flag。所以现在要求num_items的值大于等于5,发现了 buy函数和consume_point函数,先由buy函数将num_items的值设置为传入的参数值args,再由consume_point函数去判断,points是否大于num_items,如果不大于就会报错。

所以这里出现了一个逻辑漏洞,我们执行buy函数去得到的num_items会先传入trigger_event中,也就是会被写入session的log中,而flask的session其实是保存在本地的,也叫作客户端session,是可以被读取的。

所以我们现在的思路就是,先执行buy函数并且设置参数为5,然后再去执行get_flag函数,虽然consume_point函数会报错,但是我们的flag已经被写到了本地的session中,只要读取就行了。

 

所以构造的payload为/?action:trigger_event%23;action:buy;5%23action:get_flag;

 

#要设置为%23,因为是get传参,直接填#会被认为是锚链接而读取不到后面的参数,为什么buy_handler只填一个buy就可以了,是因为eval中自动添加了。

DDCTF web题复现

然后去解密session,用p神的脚本。最前面的一个点别忘了,不然爆不出来。

DDCTF web题复现

p神讲解的文章链接在这:https://www.leavesongs.com/PENETRATION/client-session-security.html

 

后面的待更