sql注入攻击的原理

```html sql注入攻击的入攻原理

一个登录框,你输入 ' OR '1'='1,原理密码随便填,入攻啪,原理进去了。入攻这不是原理什么魔法,是入攻数据库把你输入的东西当成了命令的一部分。

SQL注入的原理本质就一句话:用户输入被拼接进了SQL语句,而数据库分不清哪部分是入攻代码、哪部分是原理数据。 听起来简单,入攻但坑极深。原理

拿个最经典的入攻例子。假设有个PHP页面这样写:

$sql = "SELECT * FROM users WHERE username = '" . $_GET['user'] . "' AND password = '" . $_GET['pass'] . "'";

正常访问 ?原理user=admin&pass=123,拼出来的入攻SQL是:

SELECT * FROM users WHERE username = 'admin' AND password = '123'

没问题。但如果你把 user 改成 admin' --,注意那个单引号和两个减号。拼出来变成:

SELECT * FROM users WHERE username = 'admin' --' AND password = '任意'

-- 在SQL里是注释符,后面的密码判断直接被干掉了。数据库只看到用户名是admin,密码条件没了。你进去了。这就是最原始的注入,靠闭合引号加注释。

但现实里没这么简单。很多站点会过滤单引号,或者用 addslashes 转义。这时候数字型注入就来了。比如 ?id=1 这种,SQL里没引号包裹:

SELECT * FROM articles WHERE id = 1

你传 ?id=1 UNION SELECT username, password FROM users,连引号都不用。数据库执行两条查询,把用户表的内容拼到文章列表里显示出来。我第一次在DVWA靶场里看到admin的密码哈希直接弹在页面上时,后背发凉——就这么几行输入,整个库的门就开了。

还有一种更隐蔽的:盲注。页面不显示数据,也不报错,但根据你构造的条件真假,页面会有细微差别。比如布尔盲注,你猜数据库名字第一个字母是不是's':

?id=1 AND SUBSTRING(database(),1,1)='s'

页面正常,说明猜对了;页面异常,猜错了。一个字符一个字符地跑,用脚本自动化,几分钟就能把整个库拖下来。时间盲注更绝,连页面差异都没有,靠 SLEEP(5) 让数据库卡五秒,看响应时间来判断。你根本看不出攻击在进行。

为什么这种攻击活了二十多年还没死透?我自己的观察是,很多开发者对“输入”的理解太窄了。他们觉得表单提交的是数据,但忘了HTTP头、Cookie、甚至文件名都能塞进SQL。我见过一个案例,网站把 User-Agent 存进数据库做访问统计,结果攻击者把注入语句写在UA里,照样打穿。防不胜防。

更麻烦的是,ORM框架也不是万能药。有人用Hibernate的HQL拼接,以为安全,结果 order by 后面跟了个用户可控的字段名,直接注入。因为排序字段没法参数化,只能拼字符串。这种边角料地方,往往是漏洞的温床。

说个我实际遇到的。某后台的导出功能,URL长这样:/export?table=orders&format=csv。开发者觉得table参数是内部写死的,没过滤。结果攻击者传 table=orders; DROP TABLE users--,数据库直接删表。你问他为什么不做白名单,他说“我以为前端会校验”。前端校验?抓个包就绕过了。

所以防御的核心就两条:参数化查询,让数据和代码彻底分离;最小权限,数据库账号别用sa,只给必要的表权限。参数化查询不是转义,转义总有漏网之鱼,比如宽字节注入(GBK编码下 %df%27 吃掉反斜杠)。参数化是预编译,SQL结构先定死,输入只占位,数据库根本不把它当代码解析。

但现实很骨感。我见过太多老系统,代码里几百个拼接点,改造成本高得吓人。运维又不敢随便升级数据库驱动。于是WAF成了遮羞布,可WAF规则永远滞后,一个 /*!50000union*/ 就能绕过。说到底,SQL注入是设计缺陷,不是配置问题。

最后提一嘴,别觉得只有查询会中招。INSERT、UPDATE、DELETE 一样能注入。比如注册时用户名填 admin', 'x', 'y')--,可能直接往用户表插一条管理员记录。攻击面比你想象的大得多。写代码时但凡看到字符串拼接SQL,心里就该拉响警报——这行代码迟早出事。


个人经验:在代码审计里,搜 execute(、query(、raw( 这些关键词,比看一百篇安全规范都管用。

```