前言
最近我在整顿安全漏洞相干问题,筹备在公司做一次分享。恰好,这段时间团队发现了一个 sql 注入破绽:在一个公共的分页性能中,排序字段作为入参,前端页面能够自定义。在分页 sql 的 mybatis
mapper.xml
中,order by
字段前面应用 $ 符号动静接管计算后的排序参数,这样能够实现动静排序的性能。
然而,如果入参传入:
id; select 1 --
最终执行的 sql 会变成:
select * from test1 order by id; select 1 -- limit 1,20
--
会把前面的 limit
语句正文掉,导致分页条件生效,返回了所有数据。攻击者能够通过这个破绽一次性获取所有数据。
动静排序这个性能本来的想法是好的,然而却有sql 注入的危险
。值得庆幸的是,这次咱们及时发现了问题,并且及时解决了,没有造成什么损失。
然而,几年前在老东家的时候,就没那么侥幸了。
一次 sql 注入间接把咱们领取服务搞挂了。
1. 还原事故现场
有一天经营小姐姐跑过来跟我说,有很多用户领取不了。这个领取服务是一个老零碎,转手了 3 集体了,始终很稳固没有出过啥问题。
我二话不说开始定位问题了,先看服务器日志,发现了很多报数据库连贯过多的异样。因为领取性能太重要了,过后为了保障领取性能疾速复原,先找运维把领取服务 2 个节点重启了。
5 分钟后临时复原了失常。
我再持续定位起因,据我过后的教训判断个别呈现数据库连贯过多,可能是因为连贯 忘了敞开
导致。然而认真排查代码没有发现问题,咱们过后用的数据库连接池,它会主动回收闲暇连贯的,排除了这种可能
。
过了会儿,又有一个节点呈现了数据库连贯过多的问题。
但此时,还没查到起因,逼于无奈,只能让运维再重启服务,不过这次把数据库 最大连接数调大
了,默认是 100,咱们过后设置的 500,前面调成了 1000。(其实当初大部分公司会将这个参数设置成1000
)
应用命令:
set GLOBAL max_connections=500;
能及时失效,不须要重启 mysql 服务。
这次给我争取了更多的工夫,找 dba 帮忙一起排查起因。
他应用 show processlist;
命令查看以后线程执行状况:
还能够查看以后的连贯状态帮忙辨认出有问题的查问语句。
- id 线程 id
- User 执行 sql 的账号
- Host 执行 sql 的数据库的 ip 和端号
- db 数据库名称
- Command 执行命令,包含:Daemon、Query、Sleep 等。
- Time 执行 sql 所耗费的工夫
- State 执行状态
- info 执行信息,外面可能蕴含 sql 信息。
果然,发现了一条不寻常的查问 sql,执行了差不多 1 个小时还没有执行完。
dba 把那条 sql 复制进去,发给我了。而后kill -9
杀掉了那条执行耗时十分长的 sql 线程。
前面,数据库连贯过多的问题就没再呈现了。
我拿到那条 sql 仔细分析了一下,发现一条订单查问语句被攻击者注入了很长的一段 sql,必定是高手写的,有些语法我都没见过。
但能够确认无误,被人 sql 注入了。
通过那条 sql 中的信息,我很快找到了相干代码,查问数据时入参居然用的 Statment
,而非PrepareStatement
预编译机制。
晓得起因就好解决了,将查问数据的中央改成 preparestatement
预编译机制后问题得以最终解决。
2. 为什么会导致数据库连贯过多?
我置信很多同学看到这里,都会有一个疑难:sql 注入为何会导致数据库连贯过多?
我上面用一张图,给大家解释一下:
- 攻击者 sql 注入了相似这样的参数:
-1; 锁表语句 --
。 - 其中
;
后面的查问语句先执行了。 - 因为
--
前面的语句会被正文,接下来只会执行锁表语句,把表锁住。 - 失常业务申请从数据库连接池胜利获取连贯后,须要操作表的时候,尝试获取表锁,但始终获取不到,直到超时。留神,这里可能会累计大量的数据库连贯被占用,没有及时偿还。
- 数据库连接池不够用,没有闲暇连贯。
- 新的业务申请从数据库连接池获取不到连贯,报数据库连贯过多异样。
sql 注入导致数据库连贯过多问题,最基本的起因是长时间锁表。
3. 预编译为什么能防 sql 注入?
preparestatement
预编译机制会在 sql 语句执行前,对其进行语法分析、编译和优化,其中参数地位应用占位符 ?
代替了。
当真正运行时,传过来的参数会被看作是一个纯文本,不会从新编译,不会被当做 sql 指令。
这样,即便入参传入 sql 注入指令如:
id; select 1 --
最终执行的 sql 会变成:
select * from test1 order by 'id; select 1 --' limit 1,20
这样就不会呈现 sql 注入问题了。
4. 预编译就肯定平安?
不晓得你在查问数据时有没有用过 like 语句,比方:查问名字中带有“苏”字的用户,就可能会用相似这样的语句查问:
select * from user where name like '% 苏 %';
失常状况下是没有问题的。
但有些场景下要求传入的条件是必填的,比方:name 是必填的,如果注入了:%
,最初执行的 sql 会变成这样的:
select * from user where name like '%%%';
这种状况预编译机制是失常通过的,但 sql 的执行后果不会返回蕴含 %
的用户,而是返回了所有用户。
name 字段必填变得没啥用了,攻击者同样能够获取用户表所有数据。
为什么会呈现这个问题呢?
%
在 mysql 中是关键字,如果应用like '%%%'
,该 like 条件会生效。
如何解决呢?
须要对 %
进行本义:/%
。
本义后的 sql 变成:
select * from user where name like '%/%%';
只会返回蕴含 %
的用户。
5. 有些非凡的场景怎么办?
在 java 中如果应用 mybatis
作为长久化框架,在 mapper.xml
文件中,如果入参应用 #
传值,会应用预编译机制。
个别咱们是这样用的:
<sql id="query">
select * from user
<where>
name = #{name}
</where>
</sql>
绝大多数状况下,激励大家应用 #
这种形式传参,更平安,效率更高。
然而有时有些非凡状况,比方:
<sql id="orderBy">
order by ${sortString}
</sql>
sortString 字段的内容是一个办法中动静计算出来的,这种状况是没法用 #
,代替$
的,这样程序会报错。
应用 $
的状况就有 sql 注入的危险。
那么这种状况该怎办呢?
- 本人写个 util 工具过滤掉所有的注入关键字,动静计算时调用该工具。
- 如果数据源用的阿里的 druid 的话,能够开启 filter 中的 wall(防火墙),它蕴含了避免 sql 注入的性能。然而有个问题,就是它默认不容许多语句同时操作,对批量更新操作也会拦挡,这就须要咱们自定义 filter 了。
6. 表信息是如何泄露的?
有些仔细的同学,可能会提出一个问题:在下面锁表的例子中,攻击者是如何拿到表信息的?
办法 1:盲猜
就是攻击者依据常识猜想可能存在的表名称。
假如咱们有这样的查问条件:
select * from t_order where id = ${id};
传入参数:-1;select * from user
最终执行 sql 变成:
select * from t_order where id = -1;select * from user;
如果该 sql 有数据返回,阐明 user 表存在,被猜中了。
倡议表名不要起得过于简略,能够带上适当的前缀,比方:t_user。这样能够减少盲猜的难度。
办法 2:通过零碎表
其实 mysql 有些零碎表,能够查到咱们自定义的数据库和表的信息。
假如咱们还是以这条 sql 为例:
select code,name from t_order where id = ${id};
第一步,获取数据库和账号名。
传参为:-1 union select database(),user()#
最终执行 sql 变成:
select code,name from t_order where id = -1 union select database(),user()#
会返回以后 数据库名称:sue
和 账号名称:root@localhost
。
第二步,获取表名。
传参改成:-1 union select table_name,table_schema from information_schema.tables where table_schema='sue'#
最终执行 sql 变成:
select code,name from t_order where id = -1 union select table_name,table_schema from information_schema.tables where table_schema='sue'#
会返回数据库 sue
上面所有表名。
7.sql 注入到底有哪些危害?
1. 外围数据泄露
大部分攻击者的目标是为了赚钱,说白了就是获取到有价值的信息拿出去卖钱,比方:用户账号、明码、手机号、身份证信息、银行卡号、地址等敏感信息。
他们能够注入相似这样的语句:
-1; select * from user;--
就能轻松把用户表中所有信息都获取到。
所以,倡议大家对这些敏感信息加密存储,能够应用 AES
对称加密。
2. 删库跑路
也不乏有些攻击者不按常理出牌,sql 注入后间接把零碎的表或者数据库都删了。
他们能够注入相似这样的语句:
-1; delete from user;--
以上语句会删掉 user 表中所有数据。
-1; drop database test;--
以上语句会把整个 test 数据库所有内容都删掉。
失常状况下,咱们须要控制线上账号的权限,只容许 DML(data manipulation language)数据操纵语言语句,包含:select、update、insert、delete 等。
不容许 DDL(data definition language)数据库定义语言语句,蕴含:create、alter、drop 等。
也不容许 DCL(Data Control Language)数据库管制语言语句,蕴含:grant,deny,revoke 等。
DDL 和 DCL 语句只有 dba 的管理员账号能力操作。
顺便提一句:如果被删表或删库了,其实还有补救措施,就是从备份文件中复原,可能只会失落大量实时的数据,所以肯定有备份机制。
3. 把零碎搞挂
有些攻击者甚至能够间接把咱们的服务搞挂了,在老东家的时候就是这种状况。
他们能够注入相似这样的语句:
-1; 锁表语句;--
把表长时间锁住后,可能会导致数据库连贯耗尽。
这时,咱们须要对数据库线程做监控,如果某条 sql 执行工夫太长,要邮件预警。此外,正当设置数据库连贯的超时工夫,也能略微缓解一下这类问题。
从下面三个方面,能看出 sql 注入问题的危害真的挺大的,咱们肯定要防止该类问题的产生,不要存着幸运的心理。如果遇到一些不按常理出票的攻击者,一旦被攻打了,你可能会损失惨重。
8. 如何避免 sql 注入?
1. 应用预编译机制
尽量用预编译机制,少用字符串拼接的形式传参,它是 sql 注入问题的本源。
2. 要对特殊字符本义
有些特殊字符,比方:%
作为 like
语句中的参数时,要对其进行本义解决。
3. 要捕捉异样
须要对所有的异常情况进行捕捉,切记接口间接返回异样信息,因为有些异样信息中蕴含了 sql 信息,包含:库名,表名,字段名等。攻击者拿着这些信息,就能通过 sql 注入得心应手的攻打你的数据库了。目前比拟支流的做法是,有个专门的网关服务,它对立裸露对外接口。用户申请接口时先通过它,再由它将申请转发给业务服务。这样做的益处是:能对立封装返回数据的返回体,并且如果出现异常,能返回对立的异样信息,暗藏敏感信息。此外还能做限流和权限管制。
4. 应用代码检测工具
应用 sqlMap 等代码检测工具,它能检测 sql 注入破绽。
5. 要有监控
须要对数据库 sql 的执行状况进行监控,有异常情况,及时邮件或短信揭示。
6. 数据库账号需管制权限
对生产环境的数据库建设独自的账号,只调配 DML
相干权限,且不能拜访零碎表。切勿在程序中间接应用管理员账号。
7. 代码 review
建设代码 review 机制,能找出局部暗藏的问题,晋升代码品质。
8. 应用其余伎俩解决
对于不能应用预编译传参时,要么开启 druid
的filter
防火墙,要么本人写代码逻辑过滤掉所有可能的注入关键字。
最初说一句(求关注,别白嫖我)
如果这篇文章对您有所帮忙,或者有所启发的话,帮忙扫描下发二维码关注一下,您的反对是我保持写作最大的能源。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、工夫治理有超赞的粉丝福利,另外回复:加群,能够跟很多 BAT 大厂的前辈交换和学习。
集体公众号
集体微信