一键导入
csp-audit
Content Security Policy 策略静态审计——分解 directive、识别 unsafe-inline / unsafe-eval / 过宽 source-list / 缺 frame-ancestors,比对最小权限基线。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Content Security Policy 策略静态审计——分解 directive、识别 unsafe-inline / unsafe-eval / 过宽 source-list / 缺 frame-ancestors,比对最小权限基线。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
白盒建模——一次深度建模产出五张共享模型:项目架构(技术栈/分层/框架自有封装/闭源依赖位置)、 入口点(路由/中间件信任边界)、认证与权限(会话/角色/多租户/归属字段)、业务(实体/流程状态机/ 业务不变量/高价值资产)、全局威胁(攻击面×漏洞类别的适用性映射,供下游逐一全测)。本能力不做漏洞判定,为后续 漏洞维度分析建立共享底图。
OS 命令注入综合检测——覆盖直接命令拼接 / shell 元字符 / 参数注入 / 间接 RCE,按回显 / 时间 / 带外通道分诊。 流量中参数值含主机名 / URL / 文件名 / 命令片段、响应里出现 shell 错误关键字(`/bin/sh` / `cmd.exe` / `command not found`)、注入 `sleep N` 耗时增加、上传后异步处理链路时使用。
文件上传多策略综合检测——覆盖扩展名 / MIME / 魔术数 / 解析漏洞 / 上传链组合(LFI / Zip Slip / SVG XSS / SVG XXE)。 流量含 multipart/form-data 上传、响应回显落盘路径、上传后文件可被 GET 解析、上传字段含 filename / path 可控时使用。
IDOR 水平越权检测 — 通过替换资源标识符(ID/UUID/路径)访问他人资源的风险;适用于用户资料、订单、文档、租户隔离场景。
检测路径穿越和本地文件包含(LFI)风险;当目标存在文件读取/下载/预览功能且含路径参数时触发;适用于文件下载、日志查看、模板加载等场景。
黑盒建模 — 迭代式攻击面建模,按站点级 / 页面级 / 功能级三粒度推进;进入新页面即触发一次页面级建模、切换新身份各刷新一次,产出端点账本与页面语义模型,为后续漏洞维度的适用性判断提供依据。
| name | csp-audit |
| description | Content Security Policy 策略静态审计——分解 directive、识别 unsafe-inline / unsafe-eval / 过宽 source-list / 缺 frame-ancestors,比对最小权限基线。 |
| when-to-use | 当项目设置了 CSP header 或 CSP meta 标签时 |
| allowed-tools | bash,read_file,list_files,rg |
| user-invocable | true |
按"代码 pattern + 配置文件 + 中间件" 三维识别本能力命中场景。与 frontmatter description 同步。
代码 pattern 维度(grep 命中模式):
res.setHeader('Content-Security-Policy', ...) / response.headers['Content-Security-Policy'] = ... / w.Header().Set("Content-Security-Policy", ...)<meta http-equiv="Content-Security-Policy" content="..."><script nonce="${nonce}"> / <script nonce="{{csp_nonce}}">框架配置 / 中间件维度:
http.headers().contentSecurityPolicy("...") / WebSecurityConfigurerAdapter / HeadersConfigurerHandlerInterceptor / OncePerRequestFilter 里写 CSP headerhelmet.contentSecurityPolicy({...}) / 自定义 middleware 写 headersettings.py 含 CSP_DEFAULT_SRC / CSP_SCRIPT_SRC / MIDDLEWARE 含 csp.middleware.CSPMiddlewareconfig/initializers/content_security_policy.rb 含 Rails.application.config.content_security_policy do |policy| ... endapp/Http/Middleware/ 含 header('Content-Security-Policy', ...)app.UseCsp(...) / NWebsec 中间件next.config.js 的 headers() 函数 / nuxt.config.ts 的 security.headers.contentSecurityPolicy配置文件 / 反向代理维度:
add_header Content-Security-Policy "..." always;Header set Content-Security-Policy "..."header Content-Security-Policy "..."业务命名只作粗筛——只要最终落到 Content-Security-Policy header 或 meta 标签的位置就是审计候选。
CSP 设计为浏览器侧的纵深防御层——当应用层过滤失效让 XSS payload 进入页面后,CSP 通过浏览器拒绝执行未授权脚本来兜底拦截。策略本身弱化、缺失或包含过宽 source-list,让浏览器丧失这一兜底能力。
具体成因:
unsafe-inline:允许内联 <script> 与事件属性(onclick=...)执行,注入的 XSS payload 直接生效,CSP 等同未启用unsafe-eval:允许 eval() / Function() / setTimeout(string) 动态执行字符串,DOM XSS 与模板注入路径不被拦截* / https: / data:):攻击者可托管恶意脚本到匹配域;data: 在 script-src 里等价于任意脚本执行frame-ancestors:浏览器不阻止页面被嵌入恶意 iframe,clickjacking 攻击仍可达unsafe-inline 兜底任何 source 未经最小权限收敛就被声明到 sink directive,即构成 CSP 弱化——浏览器侧防御层被打穿,应用层一旦出 XSS 没有补救机会。
代码层 source 集合(用户可控的脚本拼接点——CSP 是兜底层,source 与 XSS 同源):
th:utext / Freemarker ${var?no_esc} / Jinja2 {{ var | safe }} / EJS <%- var %>dangerouslySetInnerHTML / Vue v-html / Angular [innerHTML] 绑定eval(userInput) / new Function(userInput) / setTimeout(string) / setInterval(string)<script> 拼接用户数据:<script>var x = "${user}";</script> 形态代码层 sink 集合(每个 directive 是一个独立 sink 维度,审计单位是 directive 而非 CSP 字符串整体):
script-src / script-src-elem / script-src-attr:内联脚本与外部脚本的来源style-src / style-src-elem / style-src-attr:内联与外部样式来源img-src / font-src / media-src / connect-src:资源加载与 XHR / fetch 目标frame-src / child-src / frame-ancestors:iframe 嵌入控制object-src / base-uri / form-action:插件 / base 标签劫持 / 表单提交default-src:未单独设的 directive 兜底report-uri / report-to:策略违规上报通道数据流追踪规则:
'nonce-{value}' 的 value 生成位置 + 模板里 <script nonce="..."> 的 value 注入位置;两端必须用同一随机源每请求生成add_header 可能覆盖或被应用层 header 覆盖——以浏览器实际接收顺序为准HeadersConfigurer / Express helmet 默认值可能被业务代码覆盖;Django CSP_* 设置被 view 级 @csp_update 装饰器局部修改CSP 弱化的主流变体(按已知主流覆盖,不追求穷举):
| 类型 | 静态识别特征 | 白盒识别难点 |
|---|---|---|
unsafe-inline on script-src | script-src 含 'unsafe-inline' 且无 nonce / hash | 易识别;需进一步看是否声明 nonce 但模板未使用 |
unsafe-eval on script-src | script-src 含 'unsafe-eval' | 需评估业务是否真的依赖 eval(如 Angular JIT、模板引擎运行时编译) |
| 过宽 source-list | * / https: / http: / 含通配子域 *.example.com | 通配域下托管的 JSONP / 老旧文件上传可成绕过点(参 §10) |
data: schema in script-src | script-src 含 data: | 等价 unsafe-inline,浏览器执行任意脚本 |
缺 frame-ancestors | 未声明 frame-ancestors 且未配 X-Frame-Options | 仅按 default-src 兜底无效,frame-ancestors 不 fall back 到 default-src |
缺 object-src 'none' | 未显式禁用插件 | Flash / Java applet 已淘汰但部分浏览器仍解析 |
缺 base-uri 'self' | 未限制 <base> 标签 | 注入 <base> 可改变相对路径解析,把脚本指向攻击者域 |
| Report-Only 部署 | header 用 Content-Security-Policy-Report-Only | 策略不实际阻断;若无监控等同未设 |
| nonce 固定值 | nonce 是常量 / 配置项 / 弱随机源 | 攻击者可猜到 nonce 后注入合法脚本 |
| nonce 声明但未使用 | header 含 nonce 但模板里没打 nonce | 业务依赖 unsafe-inline 兜底,nonce 形同虚设 |
按项目结构找 CSP 策略声明位置——CSP 通常集中在 1-3 处,定位后可枚举所有 directive。
下列框架 / 项目类型仅作类似项目示例 不限于此;以目标实际栈为准。
*SecurityConfig.java / WebSecurityConfigurerAdapter 子类:http.headers().contentSecurityPolicy("...")HandlerInterceptor / OncePerRequestFilter 实现里写 headerapplication*.yml / *.properties 含 spring.security.headers.csp 类配置pom.xml 是否含 spring-security-web 确认有无内置 CSP 支持app.js / server.js / index.js:app.use(helmet.contentSecurityPolicy({...}))middleware/*.js):res.setHeader('Content-Security-Policy', ...)next.config.js / next.config.mjs 的 async headers() 返回数组里 Content-Security-Policypackage.json 看 helmet / @nestjs/helmet / next 版本settings.py 含 CSP_* 配置项;MIDDLEWARE 含 csp.middleware.CSPMiddleware;view 级 @csp_update / @csp_replace / @csp_exemptflask-talisman 配置块requirements.txt 看 django-csp / flask-talisman 版本config/initializers/content_security_policy.rb:Rails.application.config.content_security_policy do |policy| ... endApplicationController 里 content_security_policy block 局部覆盖app/Http/Middleware/ 含写 CSP header 的中间件app/Providers/AppServiceProvider.php 可能注册全局 headernginx.conf / sites-available/*.conf 含 add_header Content-Security-Policy "..." always;.htaccess / httpd.conf 含 Header set Content-Security-Policygrep -r 'http-equiv="Content-Security-Policy"' templates/ views/ public/rg -i 'content-security-policy' --type-add 'web:*.{html,erb,vue,jsx,tsx,ejs}' --type web -t js -t ts -t java -t py -t rb -t php -t conf 一次性枚举所有声明位置| 框架 / 平台 | 安全形态(最小权限 + nonce) | 危险形态(弱化) |
|---|---|---|
| Spring Security | csp.policyDirectives("script-src 'self' 'nonce-{nonce}'; object-src 'none'; base-uri 'self'") + 配套 nonce filter | csp.policyDirectives("script-src 'self' 'unsafe-inline' 'unsafe-eval'") |
| Express + helmet | helmet.contentSecurityPolicy({ directives: { scriptSrc: ["'self'", (req,res) => 'nonce-${res.locals.nonce}'], objectSrc: ["'none'"] }}) | helmet.contentSecurityPolicy({ directives: { scriptSrc: ["'self'", "'unsafe-inline'", "*"] }}) |
| Express 原生 | res.setHeader('Content-Security-Policy', script-src 'self' 'nonce-${nonce}'; object-src 'none') | res.setHeader('Content-Security-Policy', "default-src *; script-src * 'unsafe-inline'") |
| Django (django-csp) | CSP_SCRIPT_SRC = ("'self'",), CSP_INCLUDE_NONCE_IN = ('script-src',) + 模板 <script nonce="{{ request.csp_nonce }}"> | CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", "'unsafe-eval'") |
| Rails | policy.script_src :self, "'nonce-#{SecureRandom.base64(16)}'" + nonce_generator -> request { SecureRandom.base64(16) } | policy.script_src :self, :unsafe_inline, :unsafe_eval, '*' |
| Next.js | headers() 返回带 nonce 的 CSP;nonce 通过 middleware 注入 <Script nonce={nonce}> | headers() 返回 script-src 'unsafe-inline' 'unsafe-eval' |
| nginx | add_header Content-Security-Policy "script-src 'self'; object-src 'none'; frame-ancestors 'self'" always; | add_header Content-Security-Policy "default-src *" |
| HTML meta | <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-{{nonce}}'"> | <meta http-equiv="Content-Security-Policy" content="default-src *; script-src * 'unsafe-inline'"> |
通用弱化点(所有平台都适用):
unsafe-inline 兜底,nonce 形同虚设Content-Security-Policy-Report-Only 部署但未配 report-uri 或无监控 → 等同未设@csp_update / Rails controller block)放宽全局策略 → 局部漏洞面加载本 skill 时按这些问题思考:
* / https: / data: 是否有必要,能否收敛到具体域 + nonce?unsafe-inline 兜底frame-ancestors 单独看——它不 fall back 到 default-src,缺失就不防 clickjackingproject-framework-analysis 输出的项目结构 / 框架识别Content-Security-Policy 还是 Content-Security-Policy-Report-Only对每个声明位置:
; 分割成 directive 列表default-src 兜底,但 frame-ancestors / report-uri / base-uri / form-action 不 fall back)对每个 directive 的 source 列表,按 §4 表格识别危险关键字:
'unsafe-inline' / 'unsafe-eval' / 'unsafe-hashes'* / https: / http: / data: / blob: / filesystem:*.example.com(看 example.com 下是否托管用户可控内容)若 directive 声明了 'nonce-...' 或 'sha256-...':
SecureRandom / crypto.randomBytes 而非 Math.random / 时间戳)?<script> / <style> 标签:是否所有 inline 都打了 nonce?unsafe-inline 兜底,nonce 是装饰以 OWASP CSP Cheat Sheet 推荐基线对照:
default-src 'self' / 'none'script-src 'self' 'nonce-{random}'(或 strict-dynamic)object-src 'none'base-uri 'self' / 'none'frame-ancestors 'self' / 'none'form-action 'self'report-uri / report-to?端点是否实际接收并有人看?以下是已知的检查角度,作为基线起点而非必检硬清单。结合目标代码动态调整,按三态标注(
[x]/[-]/[+])处置。
script-src / style-src 已核验 unsafe-inline / unsafe-eval / data: / 过宽 sourceframe-ancestors / base-uri / object-src / form-action 单独看(不 fall back 到 default-src)闭环判定 / 取证完整性以 closure-verification.md 为准,下面只列本能力特有的判定上限与产物契约。
为什么这里是「必须」:本节属交付契约——产物结构关系到下游汇总与 coverage-ledger 一致性消费,聚合或省略会让链路失效,因此是刚性要求。
本能力作为白盒原子能力,判定上限为 static-confirmed(CSP 字符串静态可达且含弱化),不等于动态 confirmed。
| 状态 | 判定条件 | 升级路径 |
|---|---|---|
static-confirmed(落 status=needs_review) | CSP 策略文本静态可达 + 含 unsafe-inline / unsafe-eval / data: 等弱化关键字 / 过宽 source-list / 关键 directive 缺失 | 黑盒在浏览器实际触发 XSS 验证 CSP 未拦截 → confirmed |
static-unknown(落 status=needs_review + 标注 unknown) | CSP 动态构建无法追到最终值 / nonce 注入路径在模板系统的运行时行为不可见 / view 级覆盖装饰器的实际触发条件不可见 | 推 graybox 看运行时实际下发的 CSP header |
not_vulnerable(落 status=not_vulnerable) | CSP 策略静态可达 + 严格最小权限 + nonce 端到端核验通过 + 无危险关键字 | — |
禁止白盒独立判 confirmed——无浏览器实际拦截失败证据,仅静态弱化不构成动态利用。
为什么这里是「必须」:产物结构是下游机器消费的接口,按 (CSP 声明位置, directive) 单元独立成行,聚合 / 省略会让 coverage-ledger 完整性闸门失效。
每确认一条弱化候选立即 append 一行到 shared/coverage-ledger/findings/csp-audit.jsonl,不等汇总阶段回头整理。产物结构对齐 sast-scan §9 jsonl 字段:
{
"id": "csp-001",
"title": "script-src 含 'unsafe-inline' 且无 nonce 兜底",
"severity": "high",
"cwe": "CWE-693",
"source": "user-controlled inline injection",
"sink": "script-src directive",
"entry_point": "GET /dashboard",
"status": "needs_review",
"confidence": "static-confirmed",
"file_location": "config/SecurityConfig.java:48",
"source_report": "csp-audit",
"description": "..."
}
字段约束:
id 带 csp- 前缀全局唯一status ∈ confirmed | needs_review | not_vulnerable | false_positive | superseded(白盒默认 needs_review)confidence ∈ static-confirmed | static-unknown(声明位置, directive) 二元组任一不同即独立成行——同一文件含 unsafe-inline 与 unsafe-eval 分两行entry_point 填该 CSP 实际生效的路由 / 路径前缀;全局生效填 * 或 systemicfile_location 填 file:line,动态构建的 CSP 填生成代码位置why:CSP 审计的"已防护"结论是覆盖完整性产物声明,缺失反向验证会让下游误信浏览器侧防御层有效。
写"CSP 已最小权限"或"已防 XSS 兜底"前,产物必须包含:
static-unknown 单元格的具体原因(动态构建 / 运行时注入 / 边缘节点)清单不完整 → 结论降级 partial-coverage。
反例 1:unsafe-inline 但同时启用 nonce 且所有 inline 都打了 nonce
unsafe-inline 的 directive 同时含 'nonce-...' 时,支持 nonce 的浏览器忽略 unsafe-inline(向后兼容旧浏览器才保留)script-src 'self' 'unsafe-inline' 'nonce-abc123' + 模板里所有 <script> 都打了 nonce="abc123"unsafe-inline;模板渲染层确认所有 inline 标签都打了 noncenot_vulnerable反例 2:Report-Only 模式已配套监控告警
Content-Security-Policy-Report-Only + report-uri /csp-violations + 后端订阅告警not_vulnerable(带注释说明非长期方案)反例 3:某 directive 缺失但被 default-src 兜底
frame-ancestors / base-uri / form-action / report-uri 等特例外,未声明的 fetch directive 由 default-src 兜底default-src 'self'; script-src 'self',未显式声明 img-src / font-src'self' / 'none',缺失 directive 属 fetch 类not_vulnerable,非 fetch 类(frame-ancestors / base-uri 等)独立判反例 4:CSP 看起来严格但 'self' 域下托管了 JSONP / 老旧文件上传
script-src 'self' 只约束域,域下若有 JSONP 端点 / 用户上传的 JS / 老旧 swfobject 等,攻击者可借合法域绕过script-src 'self' + 同域 /api/jsonp?callback=alert(1) 端点存在callback= / jsonp= 类参数处理 / 用户可上传 JS 的端点<script src> 引用的端点,逐一看是否输出用户可控 JS → 升级到 static-confirmed反例 5:nonce 是固定值而非每次随机
'nonce-${process.env.CSP_NONCE}' 从环境变量读 / 'nonce-${Date.now()}' 用时间戳crypto.randomBytes / SecureRandom 等密码学源;或 nonce 在请求间复用static-confirmed反例 6:nonce 声明但模板里未使用
<script> 标签 → 业务靠 unsafe-inline 兜底,nonce 形同虚设HeadersConfigurer 写了 nonce,但 Thymeleaf 模板里全是 <script>...</script> 无 nonce 属性<script> 与 <script nonce 数量差 → 升级到 static-confirmed反例 7:动态构建的 CSP 含用户可控分支
script-src 域 / 根据 feature flag 切换严格-宽松策略+ / template literal / format 拼接static-unknown白盒底线:不假装看到看不到的代码。本能力的可观测能力到源码 / 配置 / 模板的字面量与可达字符串构造为止。
下面这些情形数据流分析无法继续追踪,必须标 static-unknown,不允许默认为 not_vulnerable:
static-unknown。static-unknown,记录边缘配置文件路径(若可读);不可读的配置不在白盒范围。add_header 与应用层 setHeader 的浏览器实际接收顺序取决于代理转发模式(always 修饰符 / proxy_pass_header 配置)。处置:能确定接收顺序则按合并后策略判;不能确定则标 static-unknown。unknown,推反编译 / 文档查阅;不能直接 not_vulnerable。script-src-elem vs script-src 在旧浏览器的支持差异、strict-dynamic 的浏览器覆盖、nonce + unsafe-inline 的回退行为。处置:本能力不评估浏览器兼容性差异下的实际防护效果——超出静态审计范围,标注"按目标浏览器矩阵单独评估"。底线:写"该项目 CSP 已最小权限"前,所有 static-unknown 单元格必须显式列出原因。否则结论降级 partial-coverage。
最小权限基线(参 OWASP CSP Cheat Sheet):
default-src 'self';
script-src 'self' 'nonce-{random}' 'strict-dynamic';
style-src 'self' 'nonce-{random}';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'self';
frame-ancestors 'self';
form-action 'self';
report-uri /csp-violations;
upgrade-insecure-requests;
关键替代:
unsafe-inline:每请求密码学安全随机生成 nonce + 模板渲染层注入到所有 inline <script> / <style>unsafe-eval:不依赖 eval / new Function / setTimeout(string);Angular 等需 JIT 编译的框架改用 AOT* / https:;CDN 资源用 SRI(Subresource Integrity)+ 具体域frame-ancestors / base-uri / object-src / form-action——这些不 fall back 到 default-srcreport-uri / report-to 收集策略违规事件,配套监控告警Content-Security-Policy-Report-Only + report-uri 观测一段时间,确认无业务破坏Content-Security-Policy 实际阻断HttpOnly Cookie 仍是必要前置