前言
最近我在整顿安全漏洞相干问题,筹备在公司做一次分享。恰好,这段时间团队发现了一个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大厂的前辈交换和学习。
集体公众号
集体微信