关于 sast-xss
用于代码库安全评估的 XSS 检测技能,面向执行静态分析的开发者。它分阶段定位输出点、追踪污点并合并结果,同时区分漏洞与安全模式。
主要功能
- 01定位 HTML、DOM 与脚本执行输出点
- 02分批追踪输入与输出间的污点
- 03检查转义、清理等缓解措施
- 04汇总分析结果并排除误报
适用场景
- 01检查 Web 应用的反射型与存储型 XSS
- 02审查前端代码中的 DOM 型 XSS
- 03分析模板或框架中的不安全 HTML 输出
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-xss安装后在 Claude Code 中输入/sast-xss确认已加载。
详细介绍
跨站脚本(XSS)检测
你正在进行有针对性的安全评估,以查找代码库中的跨站脚本漏洞。此技能采用包含子 Agent 的三阶段方法:侦察(查找危险调用点)、分批验证(对每批最多 3 个危险调用点进行污点追踪)和合并(汇总各批结果,生成一份报告)。
前提条件:sast/architecture.md 必须存在。如果不存在,请先运行分析技能。
什么是 XSS
当用户提供的输入未经适当转义或清理,便被插入网页的 HTML、JavaScript 或 DOM 中时,就会发生 XSS。这会使攻击者能够在受害者的浏览器中注入并执行任意脚本,进而导致会话劫持、凭据盗窃、网页篡改和恶意软件传播。
核心模式:未经转义和清理的用户输入到达 HTML/JS 输出接收点。
XSS 类型
- 反射型 XSS:用户输入立即在 HTTP 响应中回显(例如,搜索词被直接渲染到页面 HTML 中)。
- 存储型 XSS:用户输入被保存到持久化存储(数据库、文件)中,之后在面向其他用户的 HTML 中渲染。
- 基于 DOM 的 XSS:客户端 JavaScript 从攻击者可控的来源(
location.search、location.hash、document.cookie)读取数据,并在没有服务器参与的情况下将其写入危险的 DOM 接收点(innerHTML、eval、document.write)。
什么是 XSS
服务端 HTML 接收点 — 将用户数据渲染到 HTML 响应中,但未进行转义:
- Python/Jinja2:
{{ var | safe }}、{% autoescape off %}...{{ var }}...{% endautoescape %} - Python/Django:
mark_safe(var)、使用%s且输入未转义的format_html(...)、模板中的{{ var | safe }} - Python/Flask:
Markup(var)、render_template_string(f"...{var}...") - PHP:未使用
htmlspecialchars()的echo $var、print $var、<?= $var ?> - Ruby/Rails:
raw(var)、var.html_safe、<%= raw var %>、与.html_safe搭配使用的content_tag - Java/JSP:未使用
<c:out>或fn:escapeXml()的<%= var %>、${var} - Java/Thymeleaf:
th:utext="${var}"(未转义)、[(${var})] - Go/html-template 误用:使用
template.HTML(var)、template.JS(var)、template.URL(var)绕过自动转义 - C#/Razor:
@Html.Raw(var)、MvcHtmlString.Create(var) - Node.js/EJS:
<%- var %>(未转义),而<%= var %>是安全的 - Node.js/Handlebars:
{{{ var }}}(三重花括号,未转义) - Node.js/Pug:
!{var}(未转义) - Express:
res.send("<html>..." + var + "...")、res.write("<p>" + var + "</p>")
客户端 DOM 接收点 — JavaScript 将用户可控数据不安全地写入 DOM:
element.innerHTML = varelement.outerHTML = vardocument.write(var)、document.writeln(var)element.insertAdjacentHTML('beforeend', var)- jQuery:
$(element).html(var)、$(element).append(var)(当 var 包含 HTML 时)、$('<div>' + var + '</div>') - React:
dangerouslySetInnerHTML={{ __html: var }} - Angular:
[innerHTML]="var"、bypassSecurityTrustHtml(var)、bypassSecurityTrustScript(var)、bypassSecurityTrustUrl(var) - Vue:
v-html="var"
JavaScript 执行接收点 — 将用户可控数据作为代码求值:
eval(var)setTimeout(var, delay)/setInterval(var, delay)(当var是字符串时)new Function(var)()element.setAttribute('onclick', var)、element.setAttribute('href', 'javascript:' + var)location.href = var、location.replace(var)、location.assign(var)(当 var 由用户控制且可以是javascript:...时)element.src = var、element.action = var(通过javascript:URI 注入脚本)scriptElement.text = var、scriptElement.textContent = var
基于 DOM 的来源 — 客户端 JavaScript 读取的攻击者可控输入:
location.search(URL 查询字符串)location.hash(URL 片段)location.hrefdocument.referrerdocument.URL、document.documentURIdocument.cookiepostMessage事件数据(event.data)window.namelocalStorage.getItem(...)、sessionStorage.getItem(...)(若数据来自 URL 或 postMessage)
什么不是 XSS
不要将以下情况报告为 XSS:
- CSRF:伪造请求并冒充用户发起操作——这是另一类漏洞
- 通过 XSS 实施 SQLi:通过 XSS 向量注入 SQL——主要问题是 SQL 注入本身
- 点击劫持:将页面嵌入 iframe——这是另一类漏洞
- 标头注入:向 HTTP 响应标头注入换行符——这是另一类问题(HTTP 响应拆分)
- 安全的模板输出:启用了自动转义的 Jinja2/Django/Twig/Blade/Handlebars 双花括号语法中的自动转义
{{ var }}——这些是安全的 textContent/innerText:这些属性只写入纯文本,不会解析 HTML——是安全的
可防止 XSS 的模式
看到以下模式时,代码很可能不存在漏洞:
1. 上下文感知的自动转义(大多数模板引擎的默认行为)
# Jinja2 / Django (auto-escape on by default)
{{ var }} # HTML-escaped → safe
# EJS
<%= var %> # HTML-escaped → safe
# Handlebars
{{ var }} # HTML-escaped → safe
# Pug
= var # HTML-escaped → safe
# Thymeleaf
th:text="${var}" # HTML-escaped → safe
# Razor (C#)
@var # HTML-encoded → safe
2. 输出前显式转义
// PHP
echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
# Rails
<%= h(var) %>
<%= ERB::Util.html_escape(var) %>
// JSP with JSTL
<c:out value="${var}"/>
// or fn:escapeXml()
${fn:escapeXml(var)}
// html/template — auto-escapes by context (HTML, JS, URL, CSS)
{{.Var}} // safe inside html/template
3. 使用安全属性操作 DOM
element.textContent = userInput; // plain text, no HTML parsing — safe
element.innerText = userInput; // plain text — safe
4. 使用允许列表 HTML 库进行净化
// DOMPurify
element.innerHTML = DOMPurify.sanitize(userInput);
// sanitize-html with strict config
const clean = sanitizeHtml(userInput, { allowedTags: [], allowedAttributes: {} });
5. React / Angular / Vue 自动转义
// React JSX — auto-escaped
return <div>{userInput}</div>;
<div>{{ userInput }}</div>
<div>{{ userInput }}</div>
易受攻击与安全的示例
Python — Flask / Jinja2
# VULNERABLE: Markup() bypasses Jinja2 auto-escaping
@app.route('/greet')
def greet():
name = request.args.get('name', '')
return render_template_string(f"<h1>Hello, {name}!</h1>") # raw f-string, no template escaping
# VULNERABLE: mark_safe equivalent
@app.route('/profile')
def profile():
bio = request.args.get('bio', '')
return render_template('profile.html', bio=Markup(bio)) # Markup() marks it as safe, bypassing escaping
# SECURE: use template with auto-escaping (never pass Markup around user input)
@app.route('/greet')
def greet():
name = request.args.get('name', '')
return render_template('greet.html', name=name) # template: {{ name }} — auto-escaped
Python — Django
# VULNERABLE: mark_safe() with user input
def user_bio(request):
bio = request.GET.get('bio', '')
safe_bio = mark_safe(bio) # user input bypasses Django's auto-escaping
return render(request, 'bio.html', {'bio': safe_bio})
# SECURE: pass raw string; template handles escaping
def user_bio(request):
bio = request.GET.get('bio', '')
return render(request, 'bio.html', {'bio': bio}) # template: {{ bio }} — auto-escaped
PHP
// VULNERABLE: echo without escaping
function showUsername($username) {
echo "<p>Welcome, " . $username . "</p>";
}
// SECURE: htmlspecialchars
function showUsername($username) {
echo "<p>Welcome, " . htmlspecialchars($username, ENT_QUOTES, 'UTF-8') . "</p>";
}
Node.js — Express(字符串拼接)
// VULNERABLE: user input concatenated into HTML response
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Results for: ${query}</h1>`);
});
// SECURE: use a template engine with auto-escaping, or escape manually
const escapeHtml = require('escape-html');
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Results for: ${escapeHtml(query)}</h1>`);
});
Node.js / EJS
<div><%- userInput %></div>
<div><%= userInput %></div>
Node.js / Handlebars
<div>{{{ userInput }}}</div>
<div>{{ userInput }}</div>
JavaScript — DOM 接收点
// VULNERABLE: innerHTML with URL fragment
const name = location.hash.substring(1);
document.getElementById('greeting').innerHTML = 'Hello, ' + name;
// SECURE: textContent
const name = location.hash.substring(1);
document.getElementById('greeting').textContent = 'Hello, ' + name;
// VULNERABLE: eval with postMessage data
window.addEventListener('message', (event) => {
eval(event.data);
});
// SECURE: parse and validate; never eval postMessage data
window.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
// handle data safely
});
React
// VULNERABLE: dangerouslySetInnerHTML with user input
function Comment({ content }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// SECURE: render as text (auto-escaped by React)
function Comment({ content }) {
return <div>{content}</div>;
}
Angular
// VULNERABLE: bypassing Angular's DomSanitizer
constructor(private sanitizer: DomSanitizer) {}
getUserHtml(input: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(input); // unsafe if input is user-controlled
}
<div [innerHTML]="userInput"></div>
<div>{{ userInput }}</div>
Ruby on Rails
<%# VULNERABLE: raw() or html_safe with user input %>
<%= raw(@user.bio) %>
<%= @user.bio.html_safe %>
<%# SECURE: default ERB escaping %>
<%= @user.bio %>
Java — JSP
<%-- VULNERABLE: scriptlet echo --%>
<p>Hello, <%= request.getParameter("name") %></p>
<%-- VULNERABLE: EL without c:out --%>
<p>Hello, ${param.name}</p>
<%-- SECURE: c:out escaping --%>
<p>Hello, <c:out value="${param.name}"/></p>
Go — html/template 与 text/template
// VULNERABLE: using text/template (no HTML escaping)
import "text/template"
tmpl := template.Must(template.New("").Parse("<h1>Hello, {{.Name}}!</h1>"))
tmpl.Execute(w, data)
// VULNERABLE: using template.HTML() cast to bypass escaping
import "html/template"
name := template.HTML(r.URL.Query().Get("name")) // bypasses auto-escaping
// SECURE: html/template with plain string value
import "html/template"
tmpl := template.Must(template.New("").Parse("<h1>Hello, {{.Name}}!</h1>"))
tmpl.Execute(w, data) // .Name is a plain string — auto-escaped
执行
此技能使用子 Agent 分三个阶段运行。将 sast/architecture.md 的内容作为上下文传递给所有子 Agent。
阶段 1:查找 XSS 接收点
启动一个子 Agent,并向其发送以下指令:
目标:找出代码库中所有将数据渲染到 HTML、JavaScript 或 DOM 中,并可能导致脚本注入的位置——包括任何未转义或显式标记为安全的输出、任何危险的 DOM 属性赋值,以及任何 JavaScript 执行汇点。将结果写入
sast/xss-recon.md。背景:你将获得项目的架构摘要。利用它了解前端技术栈、模板引擎、服务端渲染框架以及客户端 JavaScript 模式。
搜索目标——易受攻击的汇点模式:
标记传递给危险输出汇点的任何动态变量。此阶段暂不检查变量是否由用户控制——这是第 2 阶段的任务。
1. 服务端模板未转义输出:
- Jinja2/Django:
{{ var | safe }}、{% autoescape off %}、Markup(var)、mark_safe(var)、带有直接用户控制格式参数的format_html(...)- EJS:
<%- var %>- Handlebars/Mustache:
{{{ var }}}- Pug:
!{var}- Thymeleaf:
th:utext="${var}"、[(${var})]- Twig:
{{ var | raw }}- Blade(Laravel):
{!! $var !!}- Rails ERB:
raw(var)、var.html_safe、<%= raw var %>- PHP:未经
htmlspecialchars()处理的echo $var、print $var、<?= $var ?>- Go:
template.HTML(var)、template.JS(var)、template.URL(var),以及使用text/template输出 HTML- C#/Razor:
@Html.Raw(var)、MvcHtmlString.Create(var)2. 服务端代码中直接构造 HTML 字符串:
- 拼接或插值字符串以构建 HTML 响应:
res.send("<p>" + var + "</p>")、f"<h1>{var}</h1>"、"<div>" + var + "</div>"- Flask 中的
render_template_string(f"...{var}...")3. 客户端 DOM 汇点:
element.innerHTML = varelement.outerHTML = vardocument.write(var)、document.writeln(var)element.insertAdjacentHTML(position, var)- jQuery:
$(el).html(var)、$(el).append(var)、$('<tag>' + var + '</tag>')、传入 DOM 的$.parseHTML(var)- React:
dangerouslySetInnerHTML={{ __html: var }}- Angular:
[innerHTML]="var"、bypassSecurityTrustHtml(var)、bypassSecurityTrustScript(var)、bypassSecurityTrustUrl(var)、bypassSecurityTrustStyle(var)、bypassSecurityTrustResourceUrl(var)- Vue:
v-html="var"4. JavaScript 执行汇点:
eval(var)setTimeout(var, ...)/setInterval(var, ...),其中var是字符串变量(而非函数引用)new Function(var)()scriptElement.text = var、scriptElement.textContent = varelement.setAttribute('onclick', var)、element.setAttribute('href', 'javascript:' + var)以及类似的事件处理程序属性赋值- 可能执行
javascript:URI 的基于 URL 的汇点:location.href = var、location.replace(var)、element.src = var、element.action = var5. 基于 DOM 的 XSS 模式——客户端代码从攻击者可控的来源读取数据,并将其传递给上述任一汇点:
- 读取来源:
location.search、location.hash、location.href、document.referrer、document.URL、document.cookie、window.name、postMessage处理程序(event.data)、URLSearchParams- 然后未经转义就传递给 HTML 或 JS 汇点
跳过项(这些是安全的输出模式——不要标记):
- 自动转义的模板输出:Jinja2 中的
{{ var }}(启用自动转义时)、EJS 中的<%= var %>、Handlebars 双花括号中的{{ var }}、Razor 中的@var、Thymeleaf 中的th:textelement.textContent = var和element.innerText = var——不会解析 HTML,安全- React JSX
{var}——自动转义- Angular
{{ var }}插值——自动转义- Vue
{{ var }}插值——自动转义- 包裹
innerHTML赋值的DOMPurify.sanitize(var)——通常安全(请核实配置)- 包裹输出的
sanitize-html、xss或类似白名单清理库输出格式——写入
sast/xss-recon.md:# XSS 侦察:[Project Name] ## 摘要 发现 [N] 处将数据渲染到 HTML/JS/DOM 且未保证转义的位置。 ## 汇点位置 ### 1. [描述性名称——例如,“搜索结果处理程序中的 innerHTML 赋值”] - **文件**:`path/to/file.ext`(第 X-Y 行) - **函数 / 端点 / 组件**:[函数名、路由或组件] - **汇点类型**:[服务端模板 / HTML 字符串拼接 / DOM innerHTML / eval / JS 执行汇点 / 基于 DOM 的源到汇点] - **汇点调用**:[使用的确切 API 或属性——例如,`innerHTML`、`mark_safe()`、`<%- %>`] - **插值变量**:`var_name` — [简要说明,例如,“来源未知”或“看起来是用户个人资料字段”] - **XSS 类型**:[反射型 / 存储型 / 基于 DOM——现阶段的最佳判断] - **代码片段**:[存在漏洞的汇点代码]
[每个位置重复此格式]
阶段 1 之后:继续前先检查候选项
阶段 1 完成后,读取 sast/xss-recon.md。如果侦察发现 零个 sink 站点(摘要报告“Found 0”,或者“Sink Sites”部分为空或不存在),则完全跳过阶段 2 和阶段 3。请改为将以下内容写入 sast/xss-results.md,然后停止(写入后可以删除 sast/xss-recon.md):
# XSS Analysis Results
No vulnerabilities found.
只有当阶段 1 发现至少一个 sink 站点时,才继续阶段 2。
阶段 2:验证——追踪用户输入至 sink(分批处理)
阶段 1 完成后,读取 sast/xss-recon.md,并将 sink 站点拆分为每批最多 3 个 sink 站点。为每批并行启动一个子 Agent。每个子 Agent 仅追踪分配给自己的 sink 的污点,并将结果写入自己的批次文件。
分批流程(由你,即编排器执行,而非子 Agent):
- 读取
sast/xss-recon.md,并统计带编号的 sink 部分(### 1.、### 2. 等)。 - 将它们拆分为每批最多 3 个。例如,8 个 sink → 3 批(1-3、4-6、7-8)。
- 从侦察文件中提取各批 sink 部分的完整文本。
- 并行启动所有批次子 Agent,并且只向每个子 Agent传递分配给它的 sink。
- 每个子 Agent 将结果写入
sast/xss-batch-N.md,其中 N 是从 1 开始的批次编号。 - 从
sast/architecture.md中确定项目的主要语言/框架,并从上文“易受攻击与安全示例”部分中仅选择匹配的示例。例如,如果项目使用 React 和 Express API,则包含相关的 Node.js 和 React 示例。在下方[TECH-STACK EXAMPLES]所示的位置,将这些选出的示例加入每个子 Agent 的指令。
向每个批次子 Agent 提供以下指令(替换为对应批次的值):
目标:针对每个分配的 XSS sink 位置,判断用户提供的值是否会流入输出变量。将结果写入
sast/xss-batch-[N].md。分配给你的 sink 位置(来自侦察阶段):
[在此粘贴分配给你的 sink 部分的完整文本,并保留原始编号]
上下文:你将获得项目的架构摘要。利用它了解请求入口、数据流、中间件和客户端数据源。
对于每个 sink 位置,向后追踪插值变量的来源:
需要查找的用户可控来源:
- HTTP 请求来源(服务端):
- 查询参数:
request.GET.get(...)、req.query.x、params[:x]、$_GET['x']、c.Query("x")、r.URL.Query().Get("x")- 路径参数:
request.path_params['id']、req.params.id、params[:id]、$_GET['id']- 请求体/表单字段:
request.POST.get(...)、req.body.x、request.form.get(...)、$_POST['x']- HTTP 标头:
request.headers.get(...)、req.headers['x']、$_SERVER['HTTP_X_CUSTOM']- Cookie:
request.COOKIES.get(...)、req.cookies.x、$_COOKIE['x']- 文件上传的文件名或内容:
request.files['x'].filename- 攻击者可控的 DOM 来源(客户端/基于 DOM 的 XSS):
location.search、location.hash、location.href、document.referrer、document.URLwindow.name、document.cookiepostMessage事件:window.addEventListener('message', (e) => { ... e.data ... })- 从
location.search派生的URLSearchParams值- 从 URL 或
postMessage写入的localStorage/sessionStorage值- 已存储输入(二阶)——变量从持久化存储(数据库、文件、缓存)中读取,但存储的值最初来自用户输入:
- 找出写入路径:此字段存储在何处?写入时是否由用户提供?
- 写入时是否应用了转义或净化?(注意:写入时进行 HTML 转义并不可靠——之后可能会被重复编码或移除)
- 即使已验证或安全存储,存储型 XSS 仍然是漏洞;应追踪回读路径在渲染前是否进行了转义
- 服务端/硬编码值——变量来自配置、环境、硬编码常量,或不受用户影响的服务端逻辑——此位置不可被利用。
对于每个 sink 位置,还要检查是否存在可阻止利用的缓解措施:
- 输出是否在进入 sink 前立即使用安全函数显式转义?(
htmlspecialchars()、escapeHtml()、h()、fn:escapeXml())- 是否使用了带严格允许列表配置的净化库?(
DOMPurify.sanitize(input)——检查配置是否会移除脚本)- HTTP 响应的
Content-Type是否设置为application/json或text/plain(不进行 HTML 渲染)?- 是否存在可阻止内联脚本的内容安全策略标头?(CSP 可降低影响,但不能彻底修复问题)
- 是否存在 WAF 或严格限定预期格式(例如数字 ID)的输入验证?
适用于本项目技术栈的易受攻击与安全示例:
[TECH-STACK EXAMPLES]
分类:
- 易受攻击:有明确证据表明用户输入会到达 sink,且没有有效的转义或净化。
- 很可能易受攻击:用户输入很可能会到达 sink(间接/存储型数据流),或者仅存在较弱的缓解措施(仅 CSP、仅 WAF、部分净化、不完整的允许列表)。
- 不易受攻击:变量仅来自服务端且不受用户影响,或在进入 sink 前立即应用了适用于相应上下文的正确转义。
- 需要人工审查:无法有把握地确定变量来源(不透明的辅助函数、复杂的条件流程、外部库或跨服务数据流)。
输出格式——写入
sast/xss-batch-[N].md:# XSS 批次 [N] 结果 ## 发现 ### [易受攻击] 描述性名称 - **文件**:`path/to/file.ext`(第 X-Y 行) - **端点/函数/组件**:[路由、函数或组件名称] - **XSS 类型**:[反射型/存储型/基于 DOM] - **问题**:[例如,“HTTP 查询参数 `q` 未经转义,直接流入 innerHTML”] - **污点追踪**:[从源到 sink 的逐步追踪——例如,“req.query.q → query → `<h1>${query}</h1>` → res.send()”] - **影响**:[攻击者可以实施的操作——会话劫持、凭据窃取、键盘记录、篡改页面、重定向至恶意网站等] - **修复建议**:[具体修复方法——使用正确的函数进行转义、改用 textContent、使用自动转义的模板语法、应用 DOMPurify] - **动态测试**:[用于确认该发现的 curl 命令或浏览器负载。 展示确切的参数、负载以及需要观察的内容。 示例:curl "https://app.example.com/search?q=" 或:访问 https://app.example.com/#
并观察是否出现警告框]
### [很可能易受攻击] 描述性名称 - **文件**:`path/to/file.ext`(第 X-Y 行) - **端点/函数/组件**:[路由、函数或组件名称] - **XSS 类型**:[反射型/存储型/基于 DOM] - **问题**:[例如,“已确认写入路径接收用户输入;存储的用户简介很可能通过 innerHTML 渲染”] - **污点追踪**:[尽力追踪,并指出不确定的步骤] - **风险说明**:[此处仍存在风险的原因——例如,“存在净化库,但其配置允许可执行脚本的标签”] - **修复建议**:[具体修复方法] - **动态测试**:[尝试使用的负载]
### [不易受攻击] 描述性名称 - **文件**:`path/to/file.ext`(第 X-Y 行) - **端点/函数/组件**:[路由、函数或组件名称] - **原因**:[例如,“输出在 echo 前经过 htmlspecialchars() 处理”或“变量是硬编码的服务端常量”] ### [需要人工审查] 描述性名称 - **文件**:`path/to/file.ext`(第 X-Y 行) - **端点/函数/组件**:[路由、函数或组件名称] - **不确定性**:[无法确定变量来源或转义状态的原因] - **建议**:[需要手动追踪的内容——例如,“跟进 utils.js 中的 `buildProfileHtml()`,检查其返回值的来源”]
阶段 3:合并 — 汇总批次结果
在 所有 阶段 2 批次子 Agent 完成后,读取每个 sast/xss-batch-*.md 文件,并将它们合并到单个 sast/xss-results.md 中。由你(编排器)直接完成此操作——无需子 Agent。
合并流程:
- 读取所有
sast/xss-batch-1.md、sast/xss-batch-2.md……文件。 - 收集各批次文件中的所有发现并合并为一个列表,同时保留原始分类和所有详细字段。
- 汇总所有批次的总数,供执行摘要使用(分析的 sink 位置总数应等于侦察阶段的数量;各分类的数量应跨批次累加)。
- 使用以下格式将合并后的报告写入
sast/xss-results.md:
# XSS Analysis Results: [Project Name]
## Executive Summary
- Sink sites analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]
## Findings
[All findings from all batches, grouped by classification:
VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
Preserve every field from the batch results exactly as written.]
- 写入
sast/xss-results.md后,删除所有中间批次文件(sast/xss-batch-*.md)。
重要提醒
- 读取
sast/architecture.md,并将其内容作为上下文传递给所有子 Agent。 - 阶段 2 必须在阶段 1 完成后运行——它依赖侦察阶段的输出。
- 阶段 3 必须在所有阶段 2 批次完成后运行——它依赖所有批次的输出。
- 每个子 Agent 的批次大小为 3 个 sink 位置。如果总共只有 1–3 个 sink,则使用单个子 Agent。如果有 10 个,则使用 4 个子 Agent(3+3+3+1)。
- 并行启动所有批次子 Agent——不要按顺序运行。
- 每个批次子 Agent 只接收侦察文件中分配给它的 sink 文本,而非整个侦察文件。这样可以让每个子 Agent 的上下文更小、更聚焦。
- 阶段 1 纯粹是结构分析:标记传入 HTML/JS/DOM sink 的任何动态变量,无论其来源如何。阶段 1 不要尝试追踪用户输入——这是阶段 2 的工作。
- 阶段 2 纯粹是污点分析:对于阶段 1 中发现的每个 sink,将变量追溯到其来源。如果它来自用户可控的源,且没有经过有效转义,则该位置属于真实漏洞。
- 上下文很重要:同一变量在一种输出上下文中可能安全(经转义的 HTML 正文),在另一种上下文中却很危险(JavaScript 字符串字面量、URL 属性或事件处理程序属性)。检查确切的渲染上下文。
- 自定义清理(自行编写正则表达式剥离、将
<script>列入黑名单等)并不充分——应标记为“可能存在漏洞”。只有采用严格配置的 DOMPurify 或等效的允许列表库才可接受。 - 存储型 XSS 很容易被漏掉:追踪写入路径以确认字段由用户提供,然后单独验证读取/渲染路径是否缺少转义。两个条件都成立,漏洞才可利用。
- 基于 DOM 的 XSS 完全存在于客户端 JavaScript 中:查找
location.*、document.referrer、event.data及其他攻击者可控属性是否流入 DOM sink,且未经过服务器。 - CSP 标头可以降低 XSS 的可利用性,但不能修复问题——仍应标记底层注入点。
- 如有疑问,应归类为“需要人工审查”,而非“无漏洞”。在安全评估中,漏报比误报更糟。
- Angular 的
DomSanitizer.bypassSecurityTrust*方法始终可疑——只要参数不是硬编码常量,就应标记。 - 对于 JavaScript 执行 sink(
eval、使用字符串参数的setTimeout),即使看似无害的数据(错误消息、ID),只要攻击者能够影响它们,也可能造成危险。 - 清理中间文件:写入最终的
sast/xss-results.md后,删除sast/xss-recon.md和所有sast/xss-batch-*.md文件。