SQL注入长期稳居OWASP漏洞排行榜前列,在政府类网站的安全测评和等保整改中,几乎是每检必出的问题。这类漏洞的可怕之处在于,攻击者不需要任何特殊工具,只要在URL参数、搜索框或登录表单中构造恶意输入,就可能绕过身份验证、拖取整库数据,甚至通过数据库执行系统命令。由于政府网站往往承载大量公民个人信息,一旦被注入攻击得手,影响面和社会危害都远超普通站点。本文将结合实际整改经验,从漏洞定位、代码修复、国密加密改造到访问控制加固,给出一套可落地的完整方案。

一、SQL注入漏洞的成因与注入点定位
SQL注入的根本原因是程序将用户输入直接拼接到SQL语句中,数据库无法区分哪些是开发者的原始意图、哪些是攻击者注入的指令。以一个典型的登录验证为例,很多老系统至今仍在使用字符串拼接的方式构造查询。
// 存在注入风险的写法 String sql = "SELECT * FROM sys_user WHERE username='" + username + "' AND password='" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);
如果攻击者在用户名输入框中填入' or '1'='1' -- ,拼接后的SQL会变成恒真条件,直接绕过登录校验进入后台。更严重的情况是,攻击者利用union select逐字段探测列数,进而拖出用户表、配置表中的全部敏感数据。
定位注入点时,可以借助自动化扫描工具先做全站爬取和检测,但扫描结果只能作为参考,最终必须人工审计代码确认。重点排查以下几类位置:一是所有接收GET、POST参数后直接进入SQL语句的接口;二是排序字段、表名等无法参数化的动态拼接位置;二是模糊搜索中的like条件拼接;四是存储过程内部的动态SQL。建议在代码仓库中全局搜索Statement、createQuery(、execute(以及" + 这类拼接特征,逐条确认。
二、参数化查询与输入校验:彻底修复注入点
修复SQL注入的标准答案是参数化查询(也叫预编译语句)。其原理是先把SQL语句的骨架发送给数据库完成语法解析,参数值在后续阶段以独立数据形式传输,数据库不会把参数内容再当作SQL指令解析,无论攻击者输入什么都不可能改变语句结构。
// 修复后的参数化查询写法 String sql = "SELECT user_id, real_name, role FROM sys_user WHERE username = ? AND password_hash = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, sm3Hash(password + salt)); ResultSet rs = ps.executeQuery();
有两类场景无法直接参数化,需要单独处理。第一类是排序字段和分页表名这类标识符,正确做法是建立白名单映射,例如前端传入sort=createtime时,后端在Map中查找对应的真实列名,查不到就直接拒绝请求,绝不把原始值拼进SQL。第二类是like模糊查询,要先把用户输入中的通配符转义,再将整个表达式作为参数传入,而不是在Java侧拼接百分号到SQL字符串里。
// 排序字段白名单校验
private static final Map<String, String> SORT_MAP = new HashMap<>();
static {
SORT_MAP.put("createtime", "create_time");
SORT_MAP.put("updatetime", "update_time");
SORT_MAP.put("username", "username");
}
String orderColumn = SORT_MAP.get(sortParam);
if (orderColumn == null) {
throw new IllegalArgumentException("非法的排序字段");
}
String sql = "SELECT * FROM sys_user ORDER BY " + orderColumn + " LIMIT ?, ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, offset);
ps.setInt(2, pageSize);除参数化之外,还应补充多层防御:在入口层对参数做类型和格式校验,例如身份证号只允许18位数字加末位校验字符,手机号必须匹配^1[3-9]\d{9}$;部署WAF拦截明显的注入特征;数据库层面删除多语句执行权限,并为应用分配专用低权限账号,即使被注入也难以执行load_file、into outfile等危险操作。
三、引入国密算法:敏感数据的加密存储与传输
修复注入漏洞只解决了第一层问题,按照等保和密码应用安全性评估的要求,政府系统还需对敏感数据实施密码保护,防止拖库后数据明文泄露。国密算法体系中,SM3用于口令摘要,SM4用于数据加解密,SM2用于密钥交换和数字签名,三者配合可以覆盖存储和传输两个环节。
口令存储必须放弃MD5和SHA-1这类已被破解的算法,改用SM3加盐迭代摘要。盐值建议每个用户独立生成并随机存储,迭代次数不低于一万次,以对抗彩虹表和GPU暴力破解。下面以Java配合BouncyCastle为例给出实现。
// SM3加盐迭代口令摘要
public static String sm3HashWithSalt(String password, byte[] salt) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SM3", "BC");
byte[] input = new byte[salt.length + password.getBytes().length];
System.arraycopy(salt, 0, input, 0, salt.length);
System.arraycopy(password.getBytes(), 0, input, salt.length, password.getBytes().length);
byte[] result = digest.digest(input);
for (int i = 0; i < 10000; i++) {
result = digest.digest(result);
}
return Base64.getEncoder().encodeToString(result);
}对于身份证号、手机号、家庭住址等字段,入库前用SM4加密,查询展示时解密。这里有个容易被忽视的坑:加密后的字段无法直接做等值查询和like检索。推荐的做法是增加一个SM3摘要列作为索引列用于精确查询,原文密文列只用于回显解密,这样既满足了加密要求,又不牺牲查询性能。密钥本身必须托管在专用密码机或密钥管理服务中,严禁硬编码在代码或配置文件里,密钥轮换周期建议不超过一年。
传输层面,将站点全站升级到HTTPS,并在国密改造场景中启用支持SM2套件的国密TLS证书,浏览器到服务器之间的所有报文都经过加密通道,防止中间人窃听和篡改。对于与上级政务平台的数据交换接口,报文体额外用SM4加密、SM2签名,实现应用层的双重保障。
四、访问控制加固:把权限关进笼子里
很多政府网站被攻破,并不是因为算法不够强,而是访问控制形同虚设。常见问题包括:登录后所有接口不校验角色,改一下URL中的ID就能越权查看他人材料;后台管理入口暴露在公网;会话标识不失效导致可以被重放。访问控制加固需要从认证、授权、会话三个层面同时入手。
认证层面,强制口令复杂度策略(长度不低于八位且包含大小写字母、数字、特殊字符中的三类),连续五次登录失败锁定账户十五分钟,关键系统叠加短信验证码或国密UKey做双因素认证。授权层面采用RBAC模型,将权限精确到按钮和接口级别,并在服务端统一拦截校验,绝不能依赖前端隐藏按钮来控制权限。所有涉及数据读取的接口都要校验资源归属,即当前用户是否对该条数据有操作权,防止水平越权。
// 越权校验示例:确认数据归属
Long currentUserId = SessionContext.getUserId();
ApplyRecord record = applyDao.findById(recordId);
if (record == null || !record.getUserId().equals(currentUserId)) {
// 无权访问他人数据,返回统一的403响应并记录审计日志
auditLog.warn("越权访问尝试 userId={} recordId={}", currentUserId, recordId);
return Response.forbidden();
}会话管理方面,登录成功后必须重新生成会话标识,防止会话固定攻击;会话空闲三十分钟自动过期;推出登录后服务端立即销毁会话而非仅清除前端Cookie。后台管理入口建议限制为政务外网或VPN访问,公网只保留面向公众的服务页面,从网络层面直接缩小攻击面。此外开启全量操作审计日志,记录谁在什么时间访问了什么数据,日志保留六个月以上,为事后溯源提供依据。
五、整改验证与持续运营
修复完成并不等于整改结束,必须通过复测验证效果。先用sqlmap等工具对原有注入点重新扫描,确认全部返回无害结果;再组织渗透测试,重点尝试越权访问、注入变体绕过和加密算法强度验证。整改报告要保留漏洞修复前后的对比证据,包括扫描报告、代码变更记录和复测截图,以备等保测评检查。
安全不是一次性工程,建议建立季度代码安全审计机制,在新上线功能中把参数化查询、输入校验、权限校验作为强制代码规范,通过代码评审和自动化SonarQube扫描双重把关。同时订阅CVE漏洞通告,及时升级框架和依赖组件,让SQL修复、国密加密、访问控制这套组合拳真正形成长期有效的防护体系。