2017年,入漏Equifax公司因一个未修补的原理SQL注入漏洞,泄露了1.43亿美国人的入漏社保号码、出生日期和家庭住址。原理这家公司市值蒸发超过60亿美元。入漏而攻击者做的原理事,本质上只是入漏往一个查询参数的URL后面加了一串单引号。
SQL注入的原理根源,是入漏程序把用户输入当成了代码来执行。开发者的原理本意是写一条查询语句:SELECT * FROM users WHERE username = '用户填写的名字'。但当用户在输入框里填的入漏不是一个名字,而是原理一段SQL代码时,比如填' OR '1'='1,入漏拼接后的原理查询就变成了SELECT * FROM users WHERE username = '' OR '1'='1'。这个条件永远为真,入漏数据库就把所有用户记录都吐出来了。
这就像你让门卫“只放行名单上的人”,结果来访者递上一张纸条写着“我是名单上的人,或者1等于1”,门卫还真就让他进去了。门卫没做错什么,他按规则办事——问题是规则本身没区分“数据”和“指令”。
漏洞的触发点通常藏在登录框、搜索栏、URL参数、Cookie值这些地方。攻击者先试探性地输入一个单引号,如果页面报错,就说明输入被直接拼进了SQL语句——有缝可钻。接下来就是不断调整payload,用注释符--或#截断后面的语句,用UNION SELECT把其他表的数据捞出来,甚至用INTO OUTFILE往服务器写webshell。整个过程像开锁,先试钥匙能不能转,能转就换各种工具撬。
我见过最离谱的一次,是某个老系统把整个数据库的连接字符串都写在配置里,注入点恰好能读取文件。攻击者没费什么劲,直接把配置拖走,然后连上数据库管理后台,把全库表结构导出来当纪念品。这已经不是技术问题,是懒——开发时图省事,把用户输入直接拼SQL,连参数化查询都懒得写。
防御手段其实不复杂。首选参数化查询(PreparedStatement),让SQL语句结构固定,用户输入只作为参数传递,数据库引擎知道哪些是代码、哪些是数据,从根本上杜绝拼接。其次是输入验证,对类型、长度、格式做白名单限制,比如数字字段只允许数字。再就是数据库权限最小化——就算被注入了,攻击者拿到的账号权限只够查一张无关紧要的表,翻不出大浪。
但现实是,很多老项目积重难返。代码里几十个文件都用了字符串拼接,重构成本高,测试不充分,一改就出bug。于是只能靠Web应用防火墙(WAF)拦在中间,用规则匹配可疑字符。这属于治标不治本——WAF能被绕过,就像给门卫配了张假证识别仪,但门卫自己眼花了照样放人。
SQL注入已经存在二十多年了,OWASP Top 10年年上榜。可每年依然有大量新系统栽在这上面。原因不外乎几个:赶工期时把安全放一边,框架自带的安全机制没开启,或者干脆是新手开发者根本不知道有这回事。技术本身不性感,但一个单引号能撬动几亿条数据,这事实本身就够让人警醒了。