注入、XSS、SSRF、文件与路径防护:不可信输入怎样被约束
把数据库、HTML、URL、文件名和解析器视为不同解释器,建立从输入到副作用的防御链。
OWASP File Upload 与 SSRF Prevention Cheat Sheet 强调扩展/签名/存储隔离、URL 允许列表、解析校验和出站网络控制;单次字符串检查不足以覆盖重定向与解析差异。
每个解释器都有自己的安全 API
SQL 使用参数绑定,不能把值拼入语句结构;HTML 按文本、属性、URL 或脚本上下文编码,不能用一个通用 escape 覆盖所有位置;Shell 尽量传参数数组并避免解释器;模板表达式、JSONPath 和排序字段需要结构化允许列表。输入校验控制业务形状,安全 API 控制解释边界,两者不能互换。
存储型 XSS 在写入时难以知道未来输出上下文,通常保存原始受限数据并在输出处编码;允许富文本时使用成熟 sanitizer 和固定策略。CSP 是纵深控制,不是省略输出编码的许可证。
SSRF 防护必须跟随完整网络请求
URL 解析后限制 scheme、端口、主机和业务允许列表,DNS 解析结果拒绝环回、私网、链路本地与保留地址;建立连接时防 DNS rebinding,重定向每一跳重新校验。代理环境还要控制 HTTP_PROXY、NO_PROXY 和客户端自动协议降级。
应用校验之外,用出站防火墙、独立下载服务、无云元数据权限的运行身份和响应大小/时间限制缩小后果。日志记录策略决策与目标分类,但脱敏查询串中的令牌。
文件上传是进入解析器与存储的供应链
客户端文件名只用于展示;服务端生成对象键,规范化后证明仍在基目录。扩展名、Content-Type 与 magic bytes 交叉校验,允许列表优于危险后缀黑名单。文件存储在 Web Root 外或对象存储私有桶,下载使用受控响应头。
解压限制条目数、展开后总量、压缩比和路径;图片、文档与媒体解析放入资源受限隔离进程,异步扫描通过后再发布。解析失败、超时和恶意判定都进入隔离终态,不能因扫描服务不可用自动放行。
规范化必须只做一次并保留原始证据
重复 URL decode、Unicode 归一化、大小写折叠和路径分隔符转换可能让校验看到的值与解释器不同。入口定义唯一解析器和规范形式,业务只传结构化对象;审计保存脱敏后的原始摘要与规范值,便于解释拒绝。数据库标识符、排序字段等不能参数绑定的位置,用枚举映射到固定结构。
SSRF 下载器要在 DNS 解析和实际连接之间绑定目标,限制重定向、响应大小、解压大小、读取时间和内容类型。HTTP 客户端若自动使用系统代理或支持 file、ftp 等额外 scheme,策略层必须显式关闭。网络层 egress policy 只允许代理或伙伴网段,形成独立第二道控制。
文件从隔离区到可见区需要状态机
上传先写隔离对象,记录 owner、期望用途、摘要、大小与扫描状态;解析器使用只读输入、临时目录、CPU/内存/时间限额和无外网身份。扫描通过后复制或原子标记为可见,失败进入可清理终态。公开下载使用不可执行内容类型、Content-Disposition 和独立域名降低主动内容风险。
反向实验包含双扩展名、伪 Content-Type、路径穿越、符号链接、压缩炸弹、解析超时、重定向到私网、DNS 变化和扫描服务失联。每个失败都验证隔离文件不可公开、临时资源有界且重试不会重复发布。
注入防护要覆盖动态结构而不止 value
预编译参数能保护值,却不能参数化表名、列名、排序方向和操作符。报表 API 若允许客户端传 orderBy、projection 或 filter expression,应把外部枚举映射到固定 AST,再由查询构建器生成结构;禁止把“只允许字母数字”当成动态 SQL 安全证明。批量 IN 参数还要有数量上限,避免合法结构造成解析与计划缓存压力。
反序列化同样是解释边界。只接受明确 DTO,限制多态类型、深度、集合大小和数字范围;领域实体与框架内部类型不直接作为外部输入。XML 关闭外部实体与 DTD,压缩与归档解析累计展开预算。失败响应报告字段级业务错误,不回显解析器堆栈或原始敏感片段。
下载与预览必须重新建立授权
对象键不可猜并不等于有权限。下载端根据当前主体、租户、对象关系和文件状态授权,再生成短期、限定方法与对象的临时 URL;URL 泄露后的窗口由期限决定,敏感文件可再绑定下载会话或一次性使用。Content-Disposition 文件名需要安全编码,不能把原始 CR/LF 带进 Header。
预览服务经常需要解码复杂格式,应与主应用分离网络、身份和文件系统,输出经过重编码的安全格式而不是原文件主动内容。图片转码完成不代表源文件可公开,原件保留策略、扫描结果和派生文件关系都要可追踪。
容量测试用大文件、慢上传、许多小分片、超高压缩比、解析器长尾和下载放大验证。限制必须在读取前或流式过程中生效;把整个文件读入内存后再检查大小,控制已经太晚。
安全解析器需要可观测的资源账本
每次上传或远程抓取记录声明大小、实际读取、展开后字节、条目数、解析 CPU、墙钟时间、内存峰值、重定向次数和最终状态。限额按租户与任务累计,而不是只限制单文件;攻击者可以用大量刚低于阈值的文件耗尽隔离池。队列显示最老隔离任务,扫描积压超过预算时入口限流而不是自动放行。
SSRF 拒绝日志记录规范 host、解析地址类别、scheme、port、redirect hop 与策略版本,不记录 URL 中的凭证和敏感查询。DNS 策略变化或伙伴地址迁移需要审计的允许列表变更,不能让业务人员提交任意 CIDR。临时放行设期限,并由出站网络层和应用层同时更新。
文件清理区分上传未完成、隔离失败、扫描通过、已发布和法律保留。删除对象前验证数据库引用与版本,避免重试清理删掉新上传的同名对象。对象键由服务生成且不可复用,原始文件名只作为经过编码的元数据。
安全状态机必须让失败停在安全位置
一次安全决策沿“解析—认证—授权—执行—审计”推进。任何阶段超时或证据不足都不能伪装成允许;同时要区分未认证、无权限、输入拒绝、依赖不可用和结果未知,避免客户端盲目重试。
反向验证要证明旁路也被关闭
测试不止提交非法字符串,还要更换租户、对象、HTTP 方法、内容类型、转发头、异步线程、重定向目标和旧版本凭证;在认证服务、审计存储、DNS、密钥源和策略缓存超时时观察默认行为。拒绝必须无业务副作用,错误响应不泄露资源存在性或内部实现。
容量同样属于安全边界。密码派生、签名验证、文件扫描、令牌内省和审计写入都可能被放大;为主体、租户、IP、操作和下游分别设置预算,并记录拒绝原因。限流不能只按可伪造 Header,也不能让攻击流量占满合法用户恢复通道。
生产证据与治理
上线门禁要求:威胁场景有 owner;允许和拒绝路径都有测试;默认拒绝与应急旁路已演练;秘密可轮换;审计可查询且防越权;依赖与构件来源可证明;旧版本兼容窗口和撤销路径明确。安全控制只有在故障、升级和应急状态仍成立时才算完成。
用两个 Java 17 模型固定安全不变量
模型不尝试实现密码算法或攻击载荷,只把控制输入、判定顺序和确定输出固化,真实系统再替换为框架、密钥服务与持久化策略。
javac --release 17 -Xlint:all -Werror examples/backend-development/security/input-file-network-defense/CanonicalPathDemo.java examples/backend-development/security/input-file-network-defense/OutboundPolicyDemo.java
java -cp examples/backend-development/security/input-file-network-defense CanonicalPathDemo
java -cp examples/backend-development/security/input-file-network-defense OutboundPolicyDemobase=/srv/uploads candidate=../secret canonicalInsideBase=false accepted=false
scheme=https host=api.partner.example resolvedPrivate=false redirectRechecked=true allowed=true输出变化必须对应一条明确策略或迁移决定;禁止为了让测试通过而放宽 issuer、audience、tenant、路径、算法或来源校验。
把控制成本写进容量与事故恢复
“数据不得通过拼接变成另一种语言、地址、路径或可执行内容”不是免费承诺。认证与密码派生消耗 CPU,远程策略和 Session 占用连接,签名与哈希增加计算,扫描和隔离消耗磁盘与队列,审计增加写放大。容量模型按峰值合法流量叠加恶意放大:每请求验证次数、下游往返、最大输入、并发隔离任务、失败重试、日志体积和安全状态保留期。平均延迟正常而拒绝队列持续增长,仍然意味着控制即将失效。
事故恢复按“阻止继续扩大—保存证据—撤销资格—修复权威状态—重建派生状态—验证不变量”推进。只修代码却不撤销 token、secret 或错误策略缓存,攻击窗口仍然存在;只轮换凭证却不检查已发生业务写,也无法证明资产恢复。对 CanonicalPathDemo 与 OutboundPolicyDemo 对应的决策输出建立线上指标或审计查询,才能把模型与生产证据连接起来。
升级时先部署严格兼容的读取端,再切换签发者或写入端,观察旧版本使用归零后删除兼容分支。任何临时兼容都记录 owner、截止、拒绝指标和回滚条件;为了兼容而跳过验证不属于迁移策略,而是新增旁路。
