去年有个朋友把一段Python代码发给我,漏洞说他在GitHub上抄的检测爬虫脚本跑不起来。我扫了两眼,漏洞发现第37行有个`eval()`直接吃了用户输入。检测这不是漏洞跑不起来的问题,这是检测别人能在他机器上执行任意命令的问题。漏洞检测这件事,漏洞很多时候不是检测靠工具扫出来的,是漏洞靠人盯着代码看出不对劲。
静态分析是检测最容易上手的一类方法。原理不复杂:不运行程序,漏洞只分析源码或二进制。检测工具会把代码拆成语法树、漏洞控制流图、检测数据流图,漏洞然后按规则找可疑模式。比如你写了`strcpy(dst, src)`,而`src`来自网络,工具就会标记——它不管你是不是真的会被攻击,先报出来再说。这类工具像Fortify、Checkmarx,商业的贵得离谱,开源的Semgrep这几年倒是越来越好用。我自己常用Semgrep写自定义规则,比如匹配所有把`request.args`直接拼进SQL语句的地方。写规则的过程本身就是在梳理“我到底怕什么”。
但静态分析有个让人头疼的问题:误报太多。一个中型Java项目扫下来,几千条警告,真正能利用的可能不到二十条。安全工程师大部分时间花在关掉误报上,而不是修漏洞。所以有人转向动态分析——让程序跑起来,喂它异常输入,看它会不会崩、会不会泄露数据。模糊测试(fuzzing)就是典型代表。AFL、libFuzzer这些工具会不断变异输入,监控程序是否崩溃。2016年发现的ImageMagick一堆内存破坏漏洞,很多就是靠fuzzing砸出来的。动态分析的漏报率低一些,但覆盖率是硬伤——没执行到的代码路径,永远测不到。
还有一类方法经常被忽略:人工审计。工具再强,也理解不了业务逻辑。比如一个电商系统,攻击者把商品ID改成负数,下单后余额反而增加——这种漏洞没有固定模式,静态规则写不出来,fuzzing也很难恰好构造出那个请求。得靠人把业务流程走一遍,边看边想“这里如果乱填会怎样”。我见过一个真实案例:某后台的密码重置接口,验证码校验通过后,返回的token里直接包含了用户ID的明文。工具不会觉得这有问题,但人一看就知道,改个ID就能重置别人的密码。
实际工作中,这三种方法通常是混着用的。先用静态扫描快速过一遍代码库,把明显的坑填了;再对核心模块跑fuzzing,看有没有内存层面的问题;最后人工盯业务逻辑和权限校验。没有哪种方法能单独兜底。工具能帮你省时间,但判断“这个漏洞能不能被利用、危害多大”仍然依赖人的经验。我见过太多团队买了扫描器就以为万事大吉,结果扫描器没报的漏洞被人从线上打穿。漏洞检测不是买工具,是持续投入精力去理解系统到底怎么运转的。