首页 / 帮助文档 / 后端开发语言代码安全编写规范

后端开发语言代码安全编写规范

后端代码的安全问题很少来自语言本身,更多来自开发者对语言特性、框架行为和运行环境的误判。代码安全编写规范要解决的不是“能不能跑通”,而是“跑通之后会不会被人利用”。下面直接按攻击面和语言特性拆解具体写法。

输入校验是所有安全问题的第一道闸门

后端语言无论强类型还是弱类型,都不能信任前端传来的任何数据。Java 中不要用字符串拼接构造 SQL,必须使用 PreparedStatement;Go 里注意区分 r.URL.Query() 和 r.PostFormValue() 的解析差异,避免参数走私;Python 的 Flask 框架中 request.args.get() 返回的永远是字符串,数值判断前必须显式转换。任何直接拿用户输入做文件路径、命令参数、正则表达式、反序列化来源的写法,都属于高危模式。

SQL 注入的根因是拼接,不是语言

很多人以为用了 ORM 就安全了,实际上 ORM 的原生 SQL 接口一样可以注入。规范应明确:业务代码中禁止出现字符串拼接 SQL,包括 ORDER BY、表名、列名等动态部分。如果必须动态排序,使用白名单映射,例如 Java 中定义一个枚举或 Map 把前端传入的 sort 字段映射到具体列名。Go 的 database/sql 占位符在 MySQL 下是 ?,PostgreSQL 是 $1,混用会直接报错,但更危险的是有人为了兼容而手动拼接。

// 错误示范:拼接排序字段
query := "SELECT * FROM users ORDER BY " + sortField

// 正确做法:白名单映射
sortMap := map[string]string{"id": "id", "name": "username", "time": "created_at"}
if col, ok := sortMap[sortField]; ok {
    query := "SELECT * FROM users ORDER BY " + col
}

命令执行和代码执行要默认禁止

Python 的 os.system()、subprocess.call(shell=True)、eval()、exec(),PHP 的 system()、passthru()、eval(),Node.js 的 child_process.exec() 都是高危函数。规范中应要求:业务代码中禁止使用这些函数处理用户可控输入;如果确实需要执行外部命令,必须使用参数数组形式,而不是字符串拼接,并且命令路径写死绝对路径。例如 Python 中 subprocess.run(["/usr/bin/convert", input_file, output_file]) 而不是 subprocess.run("convert " + input_file, shell=True)。

反序列化漏洞比想象中更普遍

Java 的 ObjectInputStream、Python 的 pickle、PHP 的 unserialize、Node.js 的 node-serialize 都存在反序列化攻击面。规范必须明确:禁止对不可信数据使用原生反序列化。Java 中如果必须使用,要配合 ObjectInputFilter 做类白名单;Python 中优先使用 json 而不是 pickle;PHP 7 以上可以用 allowed_classes 参数限制 unserialize 的类范围。Go 的 gob 虽然相对安全,但也不要解码不可信来源的数据。

文件上传和路径穿越要分开处理

文件上传漏洞不只是“上传了可执行文件”,还包括路径穿越、覆盖关键文件、解压炸弹。规范应要求:上传文件存储路径由服务端生成,文件名使用随机字符串或 UUID,保留原始扩展名时必须做白名单校验。解压用户上传的 zip 包前,必须检查每个条目的路径是否包含 ../ 或绝对路径。Java 中 ZipEntry 的 getName() 返回值要过滤,Go 的 archive/zip 同样要检查 filepath.Clean() 之后是否还在目标目录内。

身份认证和会话管理的硬性规则

密码存储必须使用自适应哈希算法,例如 bcrypt、scrypt 或 Argon2,禁止使用 MD5、SHA1、SHA256 直接存储密码。会话令牌必须使用密码学安全的随机数生成器,Java 中用 SecureRandom,Python 中用 secrets 模块,Node.js 中用 crypto.randomBytes,禁止使用 Math.random() 或时间戳。JWT 的签名算法要固定为 HS256 以上强度,禁止在代码中接受 alg=none,验证时必须显式指定算法列表。

越权漏洞要从数据归属上解决

水平越权是后端最常见的逻辑漏洞,典型场景是根据用户传入的 ID 直接查询数据。规范应要求:所有涉及资源访问的接口,必须校验资源归属关系。例如查询订单详情时,SQL 条件中必须同时包含 order_id 和 user_id;更新或删除操作同理。不要在 Controller 层查出来之后再判断归属,那样等于把数据先暴露给了攻击者。垂直越权则要求每个敏感接口都做角色校验,并且角色判断放在中间件或拦截器层,而不是每个方法里手动写。

日志记录不能泄露敏感信息

日志里打印请求参数、响应体、SQL 语句是常见做法,但必须脱敏。密码、令牌、身份证号、银行卡号、手机号、邮箱等字段在日志中必须掩码或完全移除。规范应要求:日志框架统一配置脱敏规则,例如 Logback 的 PatternLayout 配合自定义 Converter,或者使用过滤器在请求进入时就把敏感字段替换掉。同时日志中禁止记录完整的堆栈信息给客户端,服务端日志可以详细,但返回给前端的错误信息要模糊化。

依赖管理和版本更新是代码安全的一部分

很多后端漏洞来自第三方库,而不是自己写的代码。规范应要求:项目必须使用依赖锁定文件,例如 Java 的 pom.xml 配合 dependencyManagement、Node.js 的 package-lock.json、Go 的 go.sum、Python 的 requirements.txt 或 Pipfile.lock。定期运行依赖扫描工具,发现已知 CVE 漏洞的库必须升级或替换。禁止从非官方源下载依赖,内网环境要搭建私有镜像仓库。

错误处理和异常捕获不能吞掉安全问题

空 catch 块、except: pass、catch (Exception e) {} 这类写法会掩盖安全异常。规范应要求:所有异常必须记录日志,并且根据异常类型决定是否向上抛出或返回统一错误响应。不要将数据库异常、文件系统异常的具体信息返回给客户端,因为堆栈信息可能暴露表结构、文件路径、框架版本。Java 中可以用 @ControllerAdvice 统一处理,Python 的 Flask 用 errorhandler 装饰器,Node.js 的 Express 用错误中间件。

并发和竞态条件也会造成安全问题

扣减库存、发放优惠券、转账等场景如果先查后改,就会出现竞态条件。规范应要求:涉及金额、数量、状态变更的操作必须使用原子语句,例如 UPDATE ... SET stock = stock - 1 WHERE id = ? AND stock > 0,或者使用数据库行锁、乐观锁。Redis 场景下用 Lua 脚本保证原子性。不要依赖应用层的 synchronized 或互斥锁来解决分布式并发问题,那只能保护单机进程。

敏感配置和密钥管理要脱离代码仓库

数据库密码、API 密钥、加密盐值、证书私钥等敏感配置禁止硬编码在源码中。规范应要求:使用环境变量、配置中心或密钥管理服务来注入敏感配置。代码仓库中出现的 .env 文件、application.yml 中的明文密码、config.json 中的私钥都必须清理。同时注意不要把生产配置和测试配置混在一起,不同环境的密钥必须隔离。

跨语言通用规范的核心原则

不管用什么语言,后端代码安全规范都可以归纳为几条硬性原则:不信任输入、不拼接执行、不暴露细节、不存储明文、不忽略异常、不硬编码密钥。每一条都要落实到代码评审清单里,而不是停留在文档层面。安全规范最有效的形式是“禁止清单”加“正确示例”,让开发者在写代码时能直接对照。

代码评审中必须检查的安全项

提交代码前,评审人应逐项确认:是否有用户输入直接进入 SQL、命令、文件路径、反序列化、正则表达式;是否有密码明文存储或日志泄露;是否有越权访问未做归属校验;是否有竞态条件导致的数据不一致;是否有敏感配置硬编码;是否有依赖版本存在已知漏洞。这些问题一旦发现,必须阻断合并,而不是记录在案事后修复。