AI攻防 外部资源加载利用

一个"分享对话"功能,把用户输入原样写进 JSONP 文件,前端再用 innerHTML 渲染;

一个"检测 XSS"的验证接口,谁调用都发 Flag。 两个靶场串起同一条链:为安全而生的功能,最后自己成了缺口。
⚠️ 本文所有测试均在授权靶场中完成,仅用于安全学习与防御研究,请勿用于未授权目标。


🏁 〇、先看通关记录

# 关卡 难度 卡在哪 一句话打法 关键 payload
1 看看我的分享功能怎么啦 3 级 分享内容直写 JSONP,前端 innerHTML 渲染 事件型 XSS 触发 alert;也可直接调 /xssapi <img src=x onerror="alert('XSS')">
2 看看我的分享功能怎么啦2 4 级 新增 URL 过滤,但相对路径放行 只能走完整 XSS 链触发 alert <img src=x onerror="alert('XSS')">

两关的命门是同一个:后端把用户输入当"数据"存进分享文件,前端却把它当"代码"渲染。


📚 一、原理:什么叫"外部资源加载利用"

📖 1.1 靶场是什么应用

一个 AI 聊天站,核心有两个功能:

功能 接口 说明
对话 POST /chat 用户发消息,大模型回复
分享 POST /share 把 user_message + ai_reply 打包,返回一个 share_id
分享页 /ai.html?#/ai/Dialogue?Url=/share/<id>.js 用 <script> 加载 JSONP 文件并渲染聊天记录
验证 POST /xssapi 接收弹窗内容,校验通过后返回 Flag

💡 一个环境细节:靶场的公共 API Key 余额不足(对话报错 402),需自备 DeepSeek Key(https://api.deepseek.com/v1/chat/completions,模型 deepseek-chat)才能恢复对话功能。漏洞本身与这一层无关,但没它就没法正常走业务流程。

🔍 1.2 三处关键机制

审计 ai.html 源码,三处逻辑决定了整条攻击链:

① 分享文件是 JSONP

前端点击分享后 POST /share 拿到 share_id,再通过 <script> 标签加载 /share/<id>.js ------ 也就是说,分享内容会以 JavaScript 文件的形式被加载执行。

② 用户输入未转义,直写进 JSON

后端生成 .js 时,把 user_message、ai_reply 直接拼进 JSON,未做 HTML 实体编码或过滤。

③ alert 被重写,绑定了验证接口

复制代码
(function() {
    const originalAlert = window.alert;
    window.alert = function(message) {
        fetch('/xssapi', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                message: message,
                url: window.location.href,
                timestamp: new Date().toISOString()
            })
        }).then(r => r.json())
          .then(data => {
              if (data.success) {
                  showMessage(data.message);   // <-- 这里显示真正的 Flag
              }
          });
        return originalAlert.apply(this, arguments);
    };
})();

靶场把"拿 Flag"的条件绑定在了"触发 alert"这个动作上 :只要页面上有一次弹窗,前端就会把弹窗内容发给 /xssapi,校验通过后把 Flag 显示在页面顶部的蓝色提示框里。

🎯 1.3 为什么归类为"业务伴生漏洞"

出题人为了验证 XSS 是否被触发,额外加了 /xssapi 这个检测端点。这个端点的初衷是保护业务,却成了新的攻击面:它不校验请求从哪来、由谁触发、上下文是否真实。

📌 这正是"业务伴生漏洞"的典型形态:为业务/安全功能补的机制,自身没经过安全设计。


⚔️ 二、逐关复盘

🧩 2.1 靶场 1:看看我的分享功能怎么啦

题目信息
项目 内容
靶场名称 看看我的分享功能怎么啦
难度 3 级
类型 Agent 安全 / 外部资源加载利用(业务伴生漏洞)
标签 Agent安全、分享、CChen.
入口 http://<靶场地址>/ai.html
Flag flag{...}
核心考点 存储型 XSS、JSONP 注入、验证端点未授权调用
信息收集

按 1.2 节的思路审计源码后,攻击面已经清晰:

  • 注入点:POST /share 的 user_message、ai_reply;

  • 执行点:分享页加载 /share/<id>.js 后的 innerHTML 渲染;

  • 出口:window.alert → POST /xssapi → 蓝色提示框显示 Flag。

攻击过程

思路一:完整 XSS 利用链(预期解)

步骤 尝试 结果
1 在 user_message 里塞 ../../../../tmp/flag.txt,想让后端直接读文件 ❌ 后端只返回随机 share_id,不拼接文件名;但访问生成的 .js 可见输入被原样写入 JSON
2 用 "};alert(document.domain);// 闭合 JSONP 字符串与回调 ❌ 双引号被转义,闭合失败;且分享内容经 innerHTML 插入,而按 HTML 规范,innerHTML 写入的 <script> 不会执行
3 改用 HTML 事件属性绕过 ✅ <img src=x onerror="alert('XSS')"> 成功弹窗

第 3 步的具体操作:Burp 拦截 POST /share,把 user_message 改成:

复制代码
<img src=x onerror="alert('XSS')">

发送后拿到新的 share_id,浏览器访问分享链接,alert 弹出 → 重写的 alert 捕获调用并请求 /xssapi → 页面顶部蓝色提示框显示 Flag。

最终 Payload(读取文件版) :为了"名正言顺"地读 /tmp/flag.txt,把弹窗内容换成文件读取结果:

复制代码
<img src=x onerror="fetch('/tmp/flag.txt').then(r=>r.text()).then(d=>alert(d))">

访问分享链接 http://<靶场地址>/ai.html?#/ai/Dialogue?Url=/share/19c44dec1204.js 时:

  1. 先弹出一个黑色的 404 Not Found 弹窗 ------ 浏览器读不到服务器本地文件,fetch 拿到的是 404 页面 HTML;

  2. 紧接着蓝色提示框 弹出真正的 Flag:flag{...}。

⚠️ 这里必须点破 :Flag 并不是"读文件读出来的"。/tmp/flag.txt 根本不在 Web 可达路径上,第二次弹窗的 Flag 来自 /xssapi。XSS 的作用只是"制造一次 alert",弹窗内容是什么并不重要。

思路二:直接调用 /xssapi(非预期解)

既然源码已经把验证接口的地址、方法、请求格式全暴露了,那就跳过整个前端流程,直接找后端要:

复制代码
POST /xssapi HTTP/1.1
Host: <靶场地址>
Content-Type: application/json
Cookie: user_agreed=1
Content-Length: 22
​
{"message":"XSS test"}

后端不校验 message 是否真来自浏览器弹窗,收到请求就返回:

复制代码
{
  "message": "flag{...}",
  "success": true
}

📌 靶场实例重建时 Flag 会变(本例前后两次实例给出的 Flag 并不相同)。Flag 与实例一一对应------本文正文统一采用预期解链路(XSS 触发)的结果。

漏洞分析
设计意图 实际效果
分享功能复用 JSONP 加载聊天记录 ❌ 用户输入未转义,等于提供一个"可控的 JS 文件"
前端用 innerHTML 渲染分享内容 ❌ 事件型标签(<img onerror>)可被解析执行
重写 alert 自动上报,检测 XSS ⚠️ 检测生效,但把"是否拿到 Flag"简化为"是否调用过 alert"
/xssapi 返回 Flag 用于验证 ❌ 无鉴权、无来源校验,可直接调用

根因 :整条链缺的是输出编码 。后端把用户输入当"数据"写进 .js,前端却按"代码"渲染 ------ 中间没有任何一层做 HTML 实体编码,于是"分享内容"变成了"可执行脚本"。

认知关键 :本关真正的判定条件是 "有没有触达 /xssapi",不是"有没有读到文件"。理解了这一点,思路一和思路二其实是同一个洞的两种走法。

防御思路
  1. 输出编码 :POST /share 落盘前,对 user_message、ai_reply 做 HTML 实体编码(< → &lt;);JSONP 里的数据只应是纯文本。

  2. 安全渲染 :前端改用 textContent / innerText,不要用 innerHTML 插入用户内容。

  3. 验证接口加固 :/xssapi 需鉴权 + 来源校验,且不应返回 Flag(Flag 只进日志,不进响应体)。

  4. 不要用前端动作当验证条件 :"调过 alert 就算漏洞已复现"这种判据,等于给攻击者留了一条直达后门。


🧩 2.2 靶场 2:看看我的分享功能怎么啦2

题目信息
项目 内容
靶场名称 看看我的分享功能怎么啦2
难度 4 级
类型 Agent 安全 / 外部资源加载利用(业务伴生漏洞)
入口 http://<靶场地址>/
Flag flag{...}
核心考点 前端过滤绕过、事件型 XSS、完整利用链

⚠️ 特别注意 :靶场描述明确写了"请勿使用非预期解提交 WP,否则会直接扣除积分 "。也就是说,第一关那种直接调 API 的取巧方法在本关行不通,必须走完整的 XSS 利用链。

信息收集

应用架构与第一关一致,多了一处新增的 URL 过滤 。在 parseHashParams 中:

复制代码
let url = decodeURIComponent(match[1]);
// 检查是否以靶场域名开头
if (url.startsWith('http://<靶场域名>')) {
    // 检查后缀,如果不是 xlsx,则进行 xss 过滤
    if (!url.toLowerCase().endsWith('.xlsx')) {
        url = url.replace(/[<>\"']/g, '');   // <-- 过滤了尖括号和引号
    }
    return { url: url };
}
else if (url.startsWith('/')) {
    return { url: url };                     // <-- 相对路径直接放行,不过滤
}

分析 :过滤只覆盖 http://<靶场域名> 开头的绝对 URL。而分享页加载的是相对路径 /share/<id>.js,命中 url.startsWith('/') 分支被直接放行 ------ < > 和引号一个都没被过滤。事件型 XSS 的入口完好无损。

验证机制与第一关相同,alert 重写后仍然指向 /xssapi,命中即显示 Flag。

攻击过程

① 复杂 Payload(失败)

第一反应是让 XSS 自动点击"反馈"按钮走一遍业务流程:

复制代码
{
  "user_message": "<img src=x onerror=\"setTimeout(()=>{document.querySelector('.feedback-btn').click(); setTimeout(()=>{document.querySelector('.feedback-option[data-type=helpful]').click(); document.getElementById('feedbackContent').value='test'; document.getElementById('submitFeedback').click();}, 500);}, 500);\">",
  "ai_reply": "test"
}

页面无任何反应。原因有二(都很朴素):

  • <img> 的 onerror 触发时,AI 回复和"反馈"按钮还没渲染出来;

  • 分享页根本没有"反馈"按钮 ------ 那是主聊天页面的组件。

② 最简单的事件型 Payload(成功)

回到 Burp,修正上一条请求缺失的逗号,构造最小 Payload:

复制代码
{
    "user_message": "<img src=x onerror=\"alert('XSS')\">",
    "ai_reply": "test"
}

发送 POST /share,拿到新的 share_id(如 0188059e896e),浏览器访问:

复制代码
http://<靶场地址>/ai.html?#/ai/Dialogue?Url=/share/0188059e896e.js

页面加载后弹出 XSS 提示框,重写的 alert 将内容发往 /xssapi,页面顶部蓝色提示框显示 Flag:

复制代码
flag{...}

💡 别把简单 payload 想复杂 。本关的难点在"想明白过滤漏在哪",不在"payload 有多花"。既然只需触发一次 alert,多写一行自动点击代码都是给自己找麻烦。

漏洞分析
设计意图 实际效果
对 http://<靶场域名> 开头的 URL 过滤 <>" ❌ 遗漏相对路径 /share/,过滤被整体绕过
重写 alert 检测 XSS 是否触发 ✅ 检测有效
/xssapi 返回 Flag ⚠️ 仍然让"触发一次弹窗"等于"拿到 Flag"

攻击链:

  1. 审计分享页 JS,发现 /xssapi 与 alert 重写逻辑;

  2. 发现 URL 过滤只拦绝对路径,相对路径 /share/ 放行;

  3. 构造 <img src=x onerror="alert('XSS')">,经 POST /share 注入;

  4. 访问分享链接触发 XSS → alert → 后端校验;

  5. 拿到 Flag。

本质 :第一关的"输出编码缺失"没修,第二关只在前端入口 加了一层字符串替换。过滤写在错误的位置,等于没写 ------只要存在任何一条绕过它抵达 innerHTML 的路径,前面的洞就原样复发。

防御思路
  1. 过滤要写在后端、写在输出处 :对写入 JSONP 的内容统一做 HTML 实体编码,而不是在前端 parseHashParams 里对某类 URL 做字符串替换。

  2. 白名单优于黑名单 :replace(/[<>\"']/g, '') 是典型黑名单,漏一个字符就失效;应改为"只允许预期字符集"。

  3. 同第一关 :/xssapi 不返回 Flag、需鉴权;前端不要用 innerHTML 渲染不可信内容。

  4. 别让"检测机制"成为通关路径:任何"命中某动作即发放敏感信息"的设计,都应默认它会被直接调用。


📋 三、两关手法总表

手法 原理 用在哪一关
事件型 XSS <script> 能被过滤、innerHTML 不执行脚本,但 <img onerror> 照样触发 靶场 1、2
JSONP 注入 用户输入被直写进可被 <script> 加载的 .js 文件 靶场 1、2
黑名单绕过 过滤只覆盖绝对路径,相对路径 /share/ 直接放行 靶场 2
验证端点直调 从源码拿到 /xssapi 后跳过前端,直接重放请求 靶场 1(非预期解)
最小 payload 原则 通关只需一次 alert,payload 越简单越稳 靶场 2

再往上抽一层,只有两句话:

  1. 数据被当代码执行 ------ 分享内容、聊天记录,凡是"用户可控 + 被渲染"的地方都是注入点。

  2. 验证机制自己就是后门 ------ 为了检测漏洞而加的接口,往往没做鉴权和上下文校验。


🛡️ 四、防御清单(开发者视角)

层面 措施 针对的问题
输出编码(第一优先级) 所有用户输入在写入 HTML / JSONP 前做 HTML 实体编码 存储型 XSS、JSONP 注入
安全渲染 前端用 textContent,禁用 innerHTML 渲染不可信内容 事件型 XSS
过滤位置 过滤/白名单放在后端输出处,不要放在前端 URL 解析里 黑名单绕过
接口鉴权 /xssapi 之类敏感接口必须鉴权 + 校验来源与上下文 未授权调用
最小信息泄露 验证接口只记日志,响应体不含任何敏感信息(含 Flag) 直接调 API 拿 Flag
CSP 配置 Content-Security-Policy,禁止加载不受信任的外部脚本 整体缓解

🏆 五、总结

两关走下来,真正的收获不是"一个 payload",而是这条固定推理:

  1. 先找渲染点 ------ 用户输入最终被谁、以什么方式渲染?(这里是 innerHTML)

  2. 再看过滤写在哪 ------ 过滤在前端还是后端?覆盖了哪些路径?(这里是前端 + 只覆盖绝对路径)

  3. 最后找出口 ------ 拿到 Flag 的判定条件是什么?(这里是"调用过一次 alert")

一句话总结 :分享功能把数据当代码存,验证接口把后门当闸门用。 修复的关键从来不是"过滤得更狠",而是把数据永远当数据 ,以及别让安全机制自己成为攻击面。

相关推荐
一隅论数智1 小时前
给AI Agent一颗“私域大脑“:本体增强的工程化之路
大数据·运维·数据仓库·人工智能·笔记·学习·政务
深蓝AI1 小时前
78%的人写代码更快,交付却没加速:GitLab拆解AI结构性失衡
人工智能·程序员
_风不会停息1 小时前
剥开 Agent 开发:参照 pi-agent 实现一个小 Agent
人工智能·后端
方方洛1 小时前
ai-agent教程-04-记忆管理
人工智能·llm·agent
我是你的开心果7781 小时前
ai全栈软件开发学习day27
人工智能·学习·状态模式
龙腾AI白云1 小时前
【轻量化大模型:低成本AI落地的产业新范式】
大数据·数据库·人工智能·机器学习
W***25921 小时前
2026企业AI办公工具选型全指南
大数据·人工智能
雪雪9081 小时前
AI视频多轨道怎么同步预览
人工智能
DP DPharness1 小时前
拆开 clearai-dsh 的包结构:一个 DSH 预设如何不改引擎加进认识论层
人工智能·dpharness