安全开发原则与编码规范
字数 2453 2025-08-20 18:17:02

安全开发原则与编码规范

第一章 前言

软件安全开发生命周期(SDL,Security Development Lifecycle)是一个旨在帮助开发人员构建更安全软件的开发过程,其核心理念是将安全考虑集成在软件开发的每一个阶段:需求分析、设计、编码、测试和维护,以减少漏洞并提高系统安全性。

第二章 安全编码原则

2.1 上线前通用安全开发要求

  1. 使用经测试和可信的平台/框架代码开发应用程序,并保持对所依赖的框架更新
  2. log4j等第三方组件应当升级至最高版本
  3. 避免于页面中包含技术性注释语句、功能说明、个人信息或解释
  4. 代码上线前应删除测试内容(测试页面、测试代码等)
  5. 删除默认部署页面,禁止留存SVN/Git相关文件、备份文件
  6. 客户端应用(如移动APP)应使用混淆、签名、加固等措施
  7. 禁止使用phpMyAdmin、Struts 1/2、Fastjson组件
  8. 禁止特殊组件(德鲁伊、Actuator、nacos等)对外网暴露
  9. 禁止使用应用组件缺省账号和密码
  10. 禁止swagger等接口文档对外开放

2.2 输入验证

  1. 必须在后台服务完成数据校验
  2. 判断输入是否符合预期的数据类型、长度、数据范围
  3. 采用白名单形式进行输入校验
  4. 对常见危险字符(<>'"%|;&/\)结合业务场景过滤
  5. 从框架层面进行全局处理,避免前后处理不一致
  6. 内部服务传递的数据也应进行校验

2.3 身份验证与会话管理

  1. 避免在URL中传递会话标识
  2. 控制用户登录鉴权的Cookie应设置HttpOnly属性
  3. 实现全站HTTPS后,Cookie应设置secure属性
  4. 设置会话令牌有效期(建议公网系统不超过30分钟)
  5. 用户注销时立即清理当前用户会话
  6. 执行关键操作前应再次验证用户身份
  7. 禁止userid、roleid等关键身份鉴别参数被外部控制

2.4 注册/登录/忘记密码

2.4.1 自行实现功能安全要点

  1. 管理平台禁止自行实现注册功能
  2. 2B业务系统注册需配有审核流程
  3. 注册接口应使用短信或邮箱验证码
  4. 限制用户名合法字符和长度;密码需满足复杂度要求
  5. 登录失败时不应返回详细提示
  6. 登录验证码每校验过一次应立即失效
  7. 后台管理页面需记录成功登录用户名和IP、时间
  8. 非常登录IP应进行多因素二次验证
  9. 密码输入界面不应以明文显示
  10. 校验手机号/邮箱和验证码的关联性

2.4.2 敏感/核心业务必须采用登录态隔离

  1. 敏感业务需采用登录态隔离方案,通过统一登录平台下发业务私有凭证

2.5 访问控制

  1. 游客身份不应使用预埋账号
  2. 接口返回数据需遵循最小化原则
  3. 限制登录尝试频率和次数
  4. 新注册用户遵循权限最小化原则
  5. 短信验证码应采用防爆破机制
  6. 防止参数置空后进行全量数据查询
  7. 用户个人页面或功能需严格权限控制
  8. 确保只有授权用户才能访问敏感数据

2.6 传输安全与加密

  1. 增删改操作使用POST方法提交
  2. 所有Web页面和HTTP API接口必须通过HTTPS(TLS 1.2+)
  3. 算法选择:
    • 对称加密:AES-128+
    • 非对称加密:RSA-2048+、DSA-2048+、ECC-256+
    • 哈希算法:SHA-2(SHA-256)或SHA-3

2.7 数据保护

  1. 不在客户端明文保存敏感信息
  2. 个人隐私敏感信息需加密存储并脱敏显示
  3. ID参数不能是数序数字(使用16位以上随机编码)
  4. 涉及用户隐私、高价值信息的接口需做频率控制

2.8 文件上传、下载

  1. 建议搭建本地文件服务器
  2. 文件上传前需验证用户身份
  3. 以白名单形式校验限制上传文件类型
  4. 验证文件包头(content-type)信息是否匹配
  5. 限制解压后的文件数量和总大小
  6. 文件禁止保存在Web容器内
  7. 第三方存储服务需检查权限配置
  8. 禁止直传OSS存储,禁止密钥写入客户端
  9. 对上传文件进行安全重命名(16位以上)
  10. 验证下载文件名防止路径遍历
  11. 不返回文件保存的绝对路径
  12. 验证文件真实路径是否在允许范围内
  13. 及时关闭字节/字符流

2.9 支付逻辑

  1. 支付接口需严格校验入参:
    • 禁止用户修改订单金额
    • 禁止用户修改支付状态
    • 防止优惠券并发领取和重用
    • 优惠券id需采用安全随机算法生成(16位以上)
    • 判断优惠券归属
    • 服务端需对数量和金额设定合理上限
    • 防止金额四舍五入
    • 禁止商品数量为非整数
    • 禁止频繁注销注册重置体验日期
    • 禁止并发提现

2.10 异常处理与日志审计

  1. 精确捕获异常并恰当处理
  2. 不将系统异常信息直接反馈给用户
  3. 创建默认错误页面,使用通用错误消息
  4. 不在日志中保存敏感信息
  5. 对日志中的特殊元素进行过滤和验证
  6. 留存相关网络日志不少于六个月

2.11 编码相关

  1. 及时删除废弃的冗余代码
  2. 不应存在永真和永假代码
  3. 禁止将敏感信息写死在代码中
  4. 禁止使用默认或易猜解的密钥
  5. 禁止危险的方法或函数public
  6. 遵守正确的行为次序
  7. 防止边界操作发生越界
  8. 防止遗漏边界值检查
  9. 防止除零错误

第三章 安全编码示例

3.1 命令注入

错误示范

String command = "ping " + args[0]; // 直接拼接用户输入
Process process = Runtime.getRuntime().exec(command);

修复

private static String sanitizeInput(String input) {
    return input.replaceAll("[;|&<>{}]", "");
}
String command = "ping " + sanitizeInput(args[0]);

3.2 Cookie验证

错误示范

Cookie[] cookies = request.getCookies();
String userRole = null;
for (Cookie c : cookies) {
    if (c.getName().equals("role")) {
        userRole = c.getValue(); // 直接从cookie读取角色
    }
}

修复

// 存储
HttpSession session = request.getSession();
session.setAttribute("userId", "12345");
session.setAttribute("role", "admin");

// 获取
String role = (String) session.getAttribute("role");

3.3 内存溢出

错误示范

public List<ThirdPaySlave> selectByPayNoThird(String payNoThird) {
    return thirdPaySlaveMapper.selectByPayNoThird(payNoThird); // 未判空
}

修复

if (StringUtils.isBlank(payNoThird)) {
    return new ArrayList<>(); // 参数判空
}

3.4 整数溢出

错误示范

int a = 2147483647;
int b = 1;
int sum = a + b; // 溢出

修复

long a = 2147483647L;
long b = 1;
long sum = a + b;

3.5 除零错误

错误示范

int a = 10;
int b = 0;
int result = a / b; // 除零异常

修复

try {
    result = a / b;
} catch (ArithmeticException e) {
    System.out.println("Error: Division by zero");
}

3.6 边界值检查

错误示范

if (a < b) {
    // ...
} else if (a > b) {
    // ... // 遗漏相等情况
}

修复

if (a < b) {
    // ...
} else if (a > b) {
    // ...
} else {
    // 处理相等情况
}

3.7 随机数生成

错误示范

Random random = new Random();
for (int i = 0; i < 16; i++) {
    sb.append(random.nextInt(2)); // 仅生成0或1
}

修复

SecureRandom random = new SecureRandom();
byte[] bytes = new byte[16];
random.nextBytes(bytes);
for (byte b : bytes) {
    sb.append(Integer.toHexString((b & 0xFF) | 0x100).substring(1,3));
}

3.8 信息泄露

错误示范

catch (IOException e) {
    e.printStackTrace();
    System.out.println("数据库密码:" + dbPassword); // 打印敏感信息
}

修复

catch (IOException e) {
    logger.error("账号密码逻辑处理异常", e); // 使用日志记录
}

3.9 并发控制

错误示范

public void increment() {
    count++; // 非线程安全
}

修复

public synchronized void increment() {
    count++;
}
// 或
private static Object lock = new Object();
synchronized (lock) {
    count++;
}

3.10 资源释放

错误示范

FileInputStream fis = new FileInputStream("file.txt");
// 使用后未关闭

修复

try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 使用资源
} // 自动关闭

3.11 危险方法暴露

错误示范

public void removeDatabase(String name) {
    stmt.execute("DROP DATABASE " + name); // 危险方法公开
}

修复

private void removeDatabase(String name) { // 改为私有
    // ...
}

3.12 操作顺序

错误示范

File file = new File("file.txt");
FileReader reader = new FileReader(file); // 先读取
if (file.canRead()) { // 后检查权限
    // ...
}

修复

if (file.canRead()) { // 先检查权限
    FileReader reader = new FileReader(file); // 后读取
    // ...
}

3.13 压缩炸弹防护

错误示范

if (file.length() > MAX_SIZE) {
    // ...
} else {
    // 直接解压 // 未检查解压后大小
}

修复

try (ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
        if (entry.getSize() > MAX_SIZE) {
            continue; // 检查每个条目大小
        }
        // 处理
    }
}

3.14 数组越界

错误示范

int[] arr = new int[5];
for (int i = 0; i < 6; i++) { // 可能越界
    arr[i] = i;
}

修复

for (int i = 0; i < arr.length; i++) { // 使用length属性
    arr[i] = i;
}

3.15 会话失效

错误示范

request.getSession().setMaxInactiveInterval(-1); // 会话永不过期

修复

request.getSession().setMaxInactiveInterval(30 * 60); // 30分钟过期

3.16 SQL注入

错误示范

@Select("SELECT * FROM users WHERE username = '${username}'") // 使用${}
User getUser(@Param("username") String username);

修复

@Select("SELECT * FROM users WHERE username = #{username}") // 使用#{}
User getUser(@Param("username") String username);

3.17 XSS防护

错误示范

model.addAttribute("username", username); // 直接输出未过滤

修复

String safeUsername = Jsoup.clean(username, Safelist.basic());
model.addAttribute("username", safeUsername);

全局过滤器示例

public class XssFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        String safeInput = Jsoup.clean(request.getParameter("input"), Safelist.basic());
        request.setAttribute("input", safeInput);
        chain.doFilter(request, response);
    }
}

3.18 路径遍历

错误示范

String path = "/safe_dir/" + inputPath;
File f = new File(path); // 可能包含../
f.delete();

修复

Path path = Paths.get("/safe_dir/", inputPath).normalize(); // 规范化路径
if (path.startsWith("/safe_dir/")) {
    Files.delete(path);
}

3.19 文件上传

错误示范

Part filePart = request.getPart("file");
filePart.write("uploads/" + filePart.getSubmittedFileName()); // 无任何校验

修复

// 1. 校验文件类型、大小和后缀名
if (!ALLOWED_TYPE.equals(filePart.getContentType()) 
    || filePart.getSize() > MAX_SIZE
    || !ALLOWED_EXTENSIONS.contains(getExtension(fileName))) {
    return;
}

// 2. 重命名文件
String newName = System.currentTimeMillis() + "_" + random.nextInt(1000) + ext;
filePart.write("uploads/" + newName);

3.20 OGNL注入

错误示范

String expression = request.getParameter("exp"); // 用户可控
Object value = Ognl.getValue(expression, context, root); // 直接执行

修复

String expression = request.getParameter("exp");
if (hasSpecialChars(expression)) { // 检查特殊字符
    return;
}
Object value = Ognl.getValue(expression, context, root);

public boolean hasSpecialChars(String input) {
    Pattern p = Pattern.compile("[`~!@#$%^&*()+=|{}':;'\\\
$$
\\\
$$
<>/?]");
    return p.matcher(input).find();
}

3.21 URL重定向

错误示范

String url = request.getParameter("url");
response.sendRedirect(url); // 直接重定向

修复

String url = request.getParameter("url");
String host = new URL(url).getHost(); // 提取host
if (ALLOWED_DOMAINS.contains(host)) { // 白名单校验
    response.sendRedirect(url);
}
安全开发原则与编码规范 第一章 前言 软件安全开发生命周期(SDL,Security Development Lifecycle)是一个旨在帮助开发人员构建更安全软件的开发过程,其核心理念是将安全考虑集成在软件开发的每一个阶段:需求分析、设计、编码、测试和维护,以减少漏洞并提高系统安全性。 第二章 安全编码原则 2.1 上线前通用安全开发要求 使用经测试和可信的平台/框架代码开发应用程序,并保持对所依赖的框架更新 log4j等第三方组件应当升级至最高版本 避免于页面中包含技术性注释语句、功能说明、个人信息或解释 代码上线前应删除测试内容(测试页面、测试代码等) 删除默认部署页面,禁止留存SVN/Git相关文件、备份文件 客户端应用(如移动APP)应使用混淆、签名、加固等措施 禁止使用phpMyAdmin、Struts 1/2、Fastjson组件 禁止特殊组件(德鲁伊、Actuator、nacos等)对外网暴露 禁止使用应用组件缺省账号和密码 禁止swagger等接口文档对外开放 2.2 输入验证 必须在后台服务完成数据校验 判断输入是否符合预期的数据类型、长度、数据范围 采用白名单形式进行输入校验 对常见危险字符( <>'"%|;&/\)结合业务场景过滤 从框架层面进行全局处理,避免前后处理不一致 内部服务传递的数据也应进行校验 2.3 身份验证与会话管理 避免在URL中传递会话标识 控制用户登录鉴权的Cookie应设置HttpOnly属性 实现全站HTTPS后,Cookie应设置secure属性 设置会话令牌有效期(建议公网系统不超过30分钟) 用户注销时立即清理当前用户会话 执行关键操作前应再次验证用户身份 禁止userid、roleid等关键身份鉴别参数被外部控制 2.4 注册/登录/忘记密码 2.4.1 自行实现功能安全要点 管理平台禁止自行实现注册功能 2B业务系统注册需配有审核流程 注册接口应使用短信或邮箱验证码 限制用户名合法字符和长度;密码需满足复杂度要求 登录失败时不应返回详细提示 登录验证码每校验过一次应立即失效 后台管理页面需记录成功登录用户名和IP、时间 非常登录IP应进行多因素二次验证 密码输入界面不应以明文显示 校验手机号/邮箱和验证码的关联性 2.4.2 敏感/核心业务必须采用登录态隔离 敏感业务需采用登录态隔离方案,通过统一登录平台下发业务私有凭证 2.5 访问控制 游客身份不应使用预埋账号 接口返回数据需遵循最小化原则 限制登录尝试频率和次数 新注册用户遵循权限最小化原则 短信验证码应采用防爆破机制 防止参数置空后进行全量数据查询 用户个人页面或功能需严格权限控制 确保只有授权用户才能访问敏感数据 2.6 传输安全与加密 增删改操作使用POST方法提交 所有Web页面和HTTP API接口必须通过HTTPS(TLS 1.2+) 算法选择: 对称加密:AES-128+ 非对称加密:RSA-2048+、DSA-2048+、ECC-256+ 哈希算法:SHA-2(SHA-256)或SHA-3 2.7 数据保护 不在客户端明文保存敏感信息 个人隐私敏感信息需加密存储并脱敏显示 ID参数不能是数序数字(使用16位以上随机编码) 涉及用户隐私、高价值信息的接口需做频率控制 2.8 文件上传、下载 建议搭建本地文件服务器 文件上传前需验证用户身份 以白名单形式校验限制上传文件类型 验证文件包头(content-type)信息是否匹配 限制解压后的文件数量和总大小 文件禁止保存在Web容器内 第三方存储服务需检查权限配置 禁止直传OSS存储,禁止密钥写入客户端 对上传文件进行安全重命名(16位以上) 验证下载文件名防止路径遍历 不返回文件保存的绝对路径 验证文件真实路径是否在允许范围内 及时关闭字节/字符流 2.9 支付逻辑 支付接口需严格校验入参: 禁止用户修改订单金额 禁止用户修改支付状态 防止优惠券并发领取和重用 优惠券id需采用安全随机算法生成(16位以上) 判断优惠券归属 服务端需对数量和金额设定合理上限 防止金额四舍五入 禁止商品数量为非整数 禁止频繁注销注册重置体验日期 禁止并发提现 2.10 异常处理与日志审计 精确捕获异常并恰当处理 不将系统异常信息直接反馈给用户 创建默认错误页面,使用通用错误消息 不在日志中保存敏感信息 对日志中的特殊元素进行过滤和验证 留存相关网络日志不少于六个月 2.11 编码相关 及时删除废弃的冗余代码 不应存在永真和永假代码 禁止将敏感信息写死在代码中 禁止使用默认或易猜解的密钥 禁止危险的方法或函数public 遵守正确的行为次序 防止边界操作发生越界 防止遗漏边界值检查 防止除零错误 第三章 安全编码示例 3.1 命令注入 错误示范 : 修复 : 3.2 Cookie验证 错误示范 : 修复 : 3.3 内存溢出 错误示范 : 修复 : 3.4 整数溢出 错误示范 : 修复 : 3.5 除零错误 错误示范 : 修复 : 3.6 边界值检查 错误示范 : 修复 : 3.7 随机数生成 错误示范 : 修复 : 3.8 信息泄露 错误示范 : 修复 : 3.9 并发控制 错误示范 : 修复 : 3.10 资源释放 错误示范 : 修复 : 3.11 危险方法暴露 错误示范 : 修复 : 3.12 操作顺序 错误示范 : 修复 : 3.13 压缩炸弹防护 错误示范 : 修复 : 3.14 数组越界 错误示范 : 修复 : 3.15 会话失效 错误示范 : 修复 : 3.16 SQL注入 错误示范 : 修复 : 3.17 XSS防护 错误示范 : 修复 : 全局过滤器示例 : 3.18 路径遍历 错误示范 : 修复 : 3.19 文件上传 错误示范 : 修复 : 3.20 OGNL注入 错误示范 : 修复 : 3.21 URL重定向 错误示范 : 修复 :