当前位置: 首页 > news >正文

从原型到上线的安全检查清单

从原型到上线的安全检查清单

原型能运行,上线还差一组边界

Node.js、Next.js、Nuxt 等方案让页面渲染与 API 放在相近的工程里,但服务端代码仍要按后端入口对待。浏览器传来的 URL、对象 ID、富文本和请求头都是外部输入;仓库位置接近,不会让这些输入自动可信。

SSR/API Routes 增加了服务端请求入口。若服务端直接请求用户提供的 URL,可能引入 SSRF 风险;域名解析、重定向和内网地址都应进入出站策略。

WAF 可以作为附加防护,SSR 渲染层和 Node.js 运行时仍需完成参数校验、对象授权与出站请求控制。上线检查的目的,是确认这些判断发生在实际副作用之前,并且拒绝路径也能被观察和复验。


需要逐项检查的入口

1. SSR 渲染层的反射型与存储型 XSS

React、Vue 等框架在普通文本位置会进行转义,但dangerouslySetInnerHTMLv-html和手工模板属于逃生口。若产品确实允许用户提交 HTML,应使用维护中的 HTML sanitizer,并限制允许的标签和属性。清洗后的内容不要再交给会修改字符串的未知库;若只需显示文本,继续使用框架默认文本渲染更简单。

2. SSRF (Server-Side Request Forgery,服务端请求伪造)

链接预览、OpenGraph 抓取和图片代理会让服务器访问用户提供的地址。只检查 URL 字符串不够:主机名解析后的地址、IPv4 与 IPv6、重定向后的每一跳和实际连接目标都要受限制。能够列出允许域名的场景优先采用白名单,并关闭客户端自动跟随未经重新校验的重定向。

3. 对象级授权失效 (BOLA / IDOR)

API 收到documentId等对象标识时,要验证当前身份是否有权对该对象执行当前动作。只比较“对象所有者是否等于当前用户”只能覆盖一部分模型;共享文档、组织角色和委托访问需要按业务授权规则判断。对象标识不可猜也不能替代授权。

4. 环境变量与 API 密钥透传漏洞

框架通常约定某些前缀的变量可进入客户端包。命名扫描只能发现明显问题,更可靠的办法是维护允许公开的变量清单,并检查最终构建产物。服务端密钥通过部署平台受控注入,不写进仓库、示例和错误日志;怀疑泄露时先轮换,不能只改变量名。


防护放在实际执行位置

出站 HTTP 应收口到统一客户端,负责协议、解析地址、重定向、超时、响应大小和网络出口策略。API 授权根据当前会话、目标资源与动作执行,不能信任请求体传来的用户 ID。公开环境变量采用允许清单,并在候选制品中复查。三类控制分别解决不同问题,不要用一个“安全中间件”标签掩盖职责。


一个 TypeScript 检查示例及其局限

下面的FullstackSafetyGateway演示富文本清洗、字面 IP 检查和简单所有者比较。它不能作为完整 SSRF 防线:主机名没有解析,IPv6 与更多保留地址未覆盖,也没有重新校验重定向和实际连接地址。assertResourceOwnership同样只适合单一所有者模型。保留这段代码是为了说明检查位置,接入应用前必须按网络和授权模型补全。

import { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import createDOMPurify from 'dompurify'; import { JSDOM } from 'jsdom'; import * as net from 'net'; import { URL } from 'url'; // 在 Node.js 环境中初始化 DOMPurify const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window as any); /** * 全栈安全防爆网关 */ export class FullstackSafetyGateway { /** * 1. XSS 富文本清洗中间件 (专门防护 SSR 动态 HTML 渲染) */ public static sanitizeHtmlInput(dirtyHtml: string): string { return DOMPurify.sanitize(dirtyHtml, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'], ALLOWED_ATTR: ['href', 'target', 'title'], }); } /** * 2. SSRF 防护拦截器: 检查 URL 是否尝试访问内网 IP 或敏感域名 */ public static validateSafeUrl(inputUrl: string): { isSafe: boolean; reason?: string } { try { const parsed = new URL(inputUrl); // 仅允许 http 和 https 协议,禁止 file://, ftp://, gopher:// if (!['http:', 'https:'].includes(parsed.protocol)) { return { isSafe: false, reason: '仅支持 HTTP/HTTPS 协议' }; } const hostname = parsed.hostname; // 校验是否为 IP 地址 if (net.isIP(hostname)) { if (this.isPrivateIP(hostname)) { return { isSafe: false, reason: '🚨 拦截 SSRF 攻击: 禁止请求私有/内网 IP 地址!' }; } } // 禁止localhost与常见内网域名 if (['localhost', 'metadata.google.internal'].includes(hostname.toLowerCase())) { return { isSafe: false, reason: '🚨 拦截 SSRF 攻击: 禁止请求保留域名!' }; } return { isSafe: true }; } catch (e) { return { isSafe: false, reason: '非法 URL 格式' }; } } /** * 判断是否为私有/内网 IP */ private static isPrivateIP(ip: string): boolean { // 匹配 127.0.0.1, 10.x.x.x, 172.16.x.x - 172.31.x.x, 192.168.x.x, 169.254.x.x (Metadata) const privatePattern = /^(127\.0\.0\.1|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3})$/; return privatePattern.test(ip); } /** * 3. BOLA 对象级越权防护断言 */ public static assertResourceOwnership( currentUserId: string, resourceOwnerId: string ): void { if (currentUserId !== resourceOwnerId) { throw new Error('[Security Violation 403] 越权访问: 您无权对该资源进行读取或修改!'); } } } // ==================== 使用示例与测试 ==================== if (require.main === module) { // 测试 SSRF 防护 const dangerousUrls = [ 'http://127.0.0.1:8080/admin', 'http://169.254.169.254/latest/meta-data/', 'https://api.github.com/users', ]; console.log('🛡️ 正在进行 SSRF 安全过滤检测:'); dangerousUrls.forEach(url => { const check = FullstackSafetyGateway.validateSafeUrl(url); if (!check.isSafe) { console.error(`❌ [拦截成功] URL: ${url} -> 原因: ${check.reason}`); } else { console.log(`✅ [安全通过] URL: ${url}`); } }); // 测试 XSS 清洗 const dirtyScript = `<script>alert("XSS Vulnerability")</script><p>Hello World <a href="javascript:stealCookies()">Click Me</a></p>`; const cleanHtml = FullstackSafetyGateway.sanitizeHtmlInput(dirtyScript); console.log('\n🛡️ XSS 清洗前后对比:'); console.log('清洗前:', dirtyScript); console.log('清洗后:', cleanHtml); }

上线前逐项核对

校验分类核心检查项 (Checklist)确定性防范措施
密钥泄漏防护.env、客户端变量与构建产物中没有秘密公开变量允许清单、制品扫描和轮换预案
SSRF 风险控制所有服务端出站请求经过统一策略校验协议、解析地址、重定向和实际网络出口
XSS 防护搜索dangerouslySetInnerHTMLv-html与手工模板文本使用安全输出位置,确需 HTML 时清洗并及时更新依赖
对象级授权每个接收对象 ID 的接口按动作校验使用服务端身份与业务策略查询可访问对象,并测试拒绝路径
CORS 与 Header来源、凭据和响应头符合实际部署按调用方设置 CORS,CSP 作为附加层并在报告模式下验证
日志与错误响应和日志不包含令牌、内部地址或完整输入使用请求标识、错误阶段与脱敏摘要定位

用反例完成验收

安全验收不只跑正常请求。准备无权对象 ID、过期会话、富文本危险属性、主机名解析到受限地址和多次重定向等受控样本,确认请求在副作用前停止。测试在隔离环境进行,不访问真实内网资源,也不把生产凭据写进样例。

发现问题时保留配置版本、请求标识和修复后的回归结果。依赖、网络或授权模型变化后重跑相关场景。原型到上线的差别,不是多装一个安全包,而是每个外部入口都有明确的允许条件、拒绝行为和维护负责人。

http://www.cnnetsun.cn/news/4256796.html

相关文章:

  • 2026实测报告:毕业论文AI论文软件横向测评,千笔AI凭出色核心算法登顶
  • 蓝桥杯国赛单片机项目实战:状态机、定时器与模块化编程精解
  • 蓝桥杯单片机国赛深度解析:从定时器中断到DAC驱动的实战避坑指南
  • YOLO模型训练与优化实战:从数据可信度到部署落地
  • 蓝桥杯国赛JavaB组真题深度解析:从算法原理到实战技巧
  • 高光谱图像分类:Fermat距离与主动学习的半监督方案
  • 蓝桥杯国赛动态规划核心模型精讲:从LIS、背包到博弈DP实战
  • 低功耗双核BLE 5.2 MCU架构解析与选型实战指南
  • MATLAB GUI实现重力异常正演模拟:水平圆柱体模型交互式可视化
  • 虚警概率计算与ROC曲线实战:信号检测教学项目解析
  • 联想开天M99h G1t-D533 Win10驱动安装教程与常见问题排查
  • 基于强化学习的MPC参数自适应控制在车辆变道轨迹跟踪中的应用
  • 蓝桥杯Scratch国赛真题解析:从数学绘图到游戏逻辑的系统备考指南
  • 深度学习PyTorch实战:从理论到代码的完整指南与避坑技巧
  • 深入解析PCA与因子分析:从原理到实战的降维技术指南
  • C++面向对象编程实践:从校园信息管理系统看封装、继承与多态
  • 无人机编队纯方位无源定位:从数学建模到算法实现
  • AI情感陪伴产品技术拆解:从大模型到本地部署实战
  • 2026 研发管理平台选型指南:企业研发效能升级的落地路径
  • 多图生成3D场景:Transformer与神经渲染技术详解
  • cocos2d-x老项目解密实战:脚本还原与资源解包完整工具链
  • SpringBoot与微信小程序构建家政服务平台:毕业设计实战指南
  • 从论文到产品:AI影像模型落地与端侧部署实践
  • Python线性规划实战:从生产调度到资源优化,掌握PuLP与SciPy
  • STM32G431 ADC实战:从硬件过采样到DMA双缓冲的稳定数据采集方案
  • 程序化数据与补全监督:推理训练从堆答案到堆过程的关键实践
  • 用智能合约构建混合资产链上基金:代币化黄金、股票代币与数字资产的组合管理实践
  • STM32MP1异构双核开发:SoM+底板设计要点与OpenAMP通信实践
  • 为家人打造私人AI助手:模型选型、提示词与产品化实践
  • NTIRE 2026低光增强挑战赛:技术拆解与工程实战