如何编写一份可落地的 Web 安全开发规范

很多团队都写过安全规范,但大多数最终沦为 Wiki 里无人翻阅的文档。问题不在于规范本身“不对”,而在于它离日常开发太远——条款笼统、缺乏示例、没有优先级。一份可落地的 Web 安全开发规范,应该让开发者在写代码时能直接对照,在 Code Review 时能直接引用。本文从 XSS 防御、SQL 注入防护、服务器加固三个维度展开,给出一份可操作的规范框架。

一、XSS 的常见方式与前端防御

XSS(跨站脚本攻击)的本质是:攻击者让恶意脚本在受害者的浏览器中执行。常见方式主要有三类:

反射型 XSS:恶意脚本作为请求参数传入,服务器未经处理直接拼接到 HTML 中返回。典型场景是搜索框——用户搜索的内容被原样显示在页面上。

存储型 XSS:恶意脚本被存入数据库,所有访问该页面的用户都会中招。评论区、用户昵称、留言板是最常见入口。这类 XSS 危害最大,因为无需诱导点击即可批量攻击。

DOM 型 XSS:不经过服务器,前端 JavaScript 直接从 URL 或用户输入中取值并插入 DOM。例如 document.getElementById('output').innerHTML = location.hash.slice(1),攻击者构造 #<img src=x onerror=alert(1)> 即可触发。

前端防御规范

原则一:输出编码,而非输入过滤。 输入过滤容易绕过,输出时根据上下文进行编码才是可靠做法。HTML 上下文使用 HTML 实体编码(<&lt;),JavaScript 上下文使用 JS 编码,URL 上下文使用 encodeURIComponent

原则二:优先使用安全 API。 规范中应明确列出:

  • 禁止使用 innerHTMLouterHTMLdocument.write 插入不可信内容
  • 改用 textContentinnerText 设置文本
  • 必须插入 HTML 时,使用 DOMPurify 等成熟库进行净化

原则三:CSP 作为纵深防御。 配置 Content-Security-Policy 响应头,限制脚本来源,禁止 unsafe-inline。即使编码出现疏漏,CSP 也能阻断大部分攻击。

规范条款示例:

所有用户可控数据在插入 DOM 前,必须经过上下文对应的编码处理。禁止直接使用 innerHTML 赋值用户输入。React/Vue 等框架默认转义,但 dangerouslySetInnerHTMLv-html 需经安全评审。

二、MySQL 防止 SQL 注入的几种写法

SQL 注入的根源是:用户输入被当作 SQL 代码执行。防御的核心思路只有一个——让数据和代码分离。

推荐写法(按优先级排序)

1. 参数化查询(Prepared Statement)——首选

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ? AND status = ?');
$stmt->execute([$email, $status]);
PreparedStatement ps = conn.prepareStatement(
    "SELECT * FROM orders WHERE user_id = ? AND created_at > ?");
ps.setInt(1, userId);
ps.setTimestamp(2, since);

参数化查询将 SQL 模板与数据分别发送给数据库,数据库先编译模板再填入数据,数据永远不会被解析为 SQL 语法。这是最可靠的防御方式,规范中应要求所有涉及用户输入的查询必须使用参数化查询

2. ORM 框架的安全用法

MyBatis 中,#{} 是预编译占位符,${} 是字符串拼接。规范应明确:

MyBatis Mapper 中禁止使用 ${} 拼接用户输入。动态表名、列名等无法参数化的场景,必须使用白名单校验。

<!-- 禁止 -->
SELECT * FROM ${tableName} WHERE id = ${id}
<!-- 推荐 -->
SELECT * FROM users WHERE id = #{id}

3. 存储过程

存储过程本身不自动防注入,关键在于内部是否使用参数化。如果存储过程内部拼接 SQL,同样存在注入风险。

4. 输入校验作为辅助

类型校验(如 ID 必须是整数)、长度限制、白名单过滤可以作为辅助手段,但不能替代参数化查询。

规范条款示例

禁止任何形式的 SQL 字符串拼接。所有数据库查询必须使用参数化查询或 ORM 安全 API。动态表名/列名必须通过白名单映射,禁止直接拼接。代码审查中发现 ${} 拼接用户输入,一律打回。

三、Linux 服务器安全加固清单

服务器是 Web 应用的运行基座,加固规范应覆盖以下层面:

账户与权限

  • 禁用 root 远程登录,使用普通用户 + sudo
  • 删除或锁定不必要的系统账户(如 games、ftp)
  • 设置强密码策略,或强制使用 SSH 密钥登录
  • 定期审计 authorized_keys 文件

SSH 加固

  • 修改默认 22 端口(降低自动化扫描噪音)
  • 禁用密码认证:PasswordAuthentication no
  • 限制登录用户:AllowUsers deploy ops
  • 配置 fail2ban 自动封禁暴力破解 IP

网络与防火墙

  • 仅开放必要端口(80、443、SSH 自定义端口)
  • 使用 iptables/nftables 或云安全组限制来源 IP
  • 数据库端口(3306、6379)禁止对公网暴露

系统更新与补丁

  • 开启自动安全更新:unattended-upgrades(Debian/Ubuntu)或 dnf-automatic(RHEL 系)
  • 定期检查内核版本,及时重启应用补丁

日志与监控

  • 开启 auditd 或至少确保 auth.log、syslog 正常记录
  • 配置日志轮转,避免磁盘写满
  • 对异常登录、提权操作设置告警

服务最小化

  • 卸载不需要的软件包和服务
  • 使用 systemd 限制服务权限(NoNewPrivilegesProtectSystem
  • Web 服务以非 root 用户运行

规范条款示例:

所有生产服务器必须完成基线加固:禁用 root SSH 登录、仅开放必要端口、开启自动安全更新、配置 fail2ban。新服务器上线前需通过加固检查脚本,未通过不得接入生产环境。

四、让规范真正落地的三个关键

第一,每条规范都要有“可检测性”。 “注意防范 XSS”无法执行,“禁止使用 innerHTML 插入用户输入”可以在 Code Review 中逐条检查。规范条款应尽量写成可判断真伪的陈述句。

第二,配套工具链。 规范不能只靠人自觉。SAST 工具(如 Semgrep、SonarQube)可以自动检测 SQL 拼接和 XSS 风险;依赖扫描(Dependabot、Trivy)可以发现已知漏洞;服务器基线检查脚本可以定期验证加固状态。规范中应注明哪些条款由工具强制、哪些由人工审查。

第三,建立例外流程。 总有场景无法完全遵守规范。与其让开发者偷偷绕过,不如建立例外申请机制:说明原因、评估风险、记录在案、定期复审。这样规范才有弹性,也才能被真正尊重。

一份好的安全开发规范,不是写给别人看的文档,而是团队日常工作的检查清单。从这三类高频问题入手,把条款写具体、把工具配到位、把流程跑通,规范才能真正落地。

未经允许不得转载:任鹏个人博客 » 如何编写一份可落地的 Web 安全开发规范

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏