CTF-09:Flag 藏在 JS 文件里

PicoCTF — Includes. “This code is in a separate file!” —— 确实在另一个文件里。 关联:CTF-08_文件上传+Webshell+sudo提权.md


题目

一个介绍 include 指令的静态页面。标题 “On Includes”。

点击按钮弹出:This code is in a separate file!

解题过程

Step 1:查看页面源码

<script src="script.js"></script>

页面引用了外部 JS 文件。

Step 2:直接访问 script.js

http://target/script.js

内容:

function greetings()
{
  alert("This code is in a separate file!");
}

//  f7w_2of2_6edef411}

拿到后半段 flag:f7w_2of2_6edef411}

Step 3:找前半段

检查 style.css 或页面 HTML 注释,发现:

picoCTF{1nclu51v17y_1of2_

Step 4:拼接

picoCTF{1nclu51v17y_1of2_f7w_2of2_6edef411}

知识点

概念 说明
静态文件可访问 <script src="..."> 引用的 JS 文件所有人都能直接访问
flag 分段 出题人把 flag 拆成两段,分别藏在不同的静态资源里
查看页面源码 HTML 注释、CSS、JS 都可能藏信息

反思

  • JS/CSS 文件在浏览器里可以直接通过 URL 访问,不需要任何权限
  • “This code is in a separate file!” 本身是提示——去那个文件看看
  • 静态文件审查是信息收集的第一步,应该最先做

延展知识:前端代码保护与泄露

Source Map(.map 文件)

压缩/混淆后的 JS 无法直接调试。.map 文件是一本"字典",记录压缩后的代码与原始源码的对应关系:

压缩后 .map 文件 原始源码
function a(b){...} → (行:列, 名映射) function sayHello(name){...}

浏览器 DevTools 检测到 .map 文件后自动加载,展示原始源码。问题是很多人.map 文件部署到了生产环境

Claude 泄露事件: 攻击者访问 https://claude.ai/assets/xxx.js.map,还原了前端完整逻辑,包括 API 地址、prompt 模板、客户端校验规则。

防御: 构建工具(Webpack/Vite)默认生成 .map,但生产部署时必须排除。可配置 sourceMap: false 或只在构建阶段保留。

没有 .map 怎么办

JS 仍然可以被逆向,只是难度不同:

混淆程度 特征 逆向难度
Minify 压缩 变量缩短、去空格 低 — 逻辑结构不变,可读性差但能读
重度混淆 字符串加密、死代码插入 中 — 需要工具辅助
控制流平坦化 switch-case + 状态变量 高 — 需要专用工具 + 人工分析

控制流平坦化(Control Flow Flattening)

把正常的 if-else / for / while 结构拍平成一个 while + switch + 状态变量 的统一结构:

// 原始
if (user.role === 'admin') { showPanel(); }
else { denyAccess(); }

// 平坦化后
var state = 2;
while (true) {
    switch (state) {
        case 2: state = (user.role === 'admin') ? 3 : 4; break;
        case 3: showPanel(); state = 5; break;
        case 4: denyAccess(); state = 5; break;
        case 5: return;
    }
}

逻辑等价,但人眼失去了"这段还是那段"的层次感。

de4js 等逆向工具

原理:识别 switch-case 平坦化模式 → 追踪状态变量的流转路径 → 还原成 if-else / 函数调用。

但只能解到中间状态,重度混淆 + 多层变形需要人工补充。

结论

只要浏览器需要执行 JS,就一定能拿到完整可执行的源代码。混淆只能增加阅读成本,不能阻止阅读。 敏感逻辑永远应该放在服务端 API 里,不在前端做任何秘密校验。