前言

最近我在整顿安全漏洞相干问题,筹备在公司做一次分享。恰好,这段时间团队发现了一个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注入为何会导致数据库连贯过多?

我上面用一张图,给大家解释一下:

  1. 攻击者sql注入了相似这样的参数:-1;锁表语句--
  2. 其中;后面的查问语句先执行了。
  3. 因为--前面的语句会被正文,接下来只会执行锁表语句,把表锁住。
  4. 失常业务申请从数据库连接池胜利获取连贯后,须要操作表的时候,尝试获取表锁,但始终获取不到,直到超时。留神,这里可能会累计大量的数据库连贯被占用,没有及时偿还。
  5. 数据库连接池不够用,没有闲暇连贯。
  6. 新的业务申请从数据库连接池获取不到连贯,报数据库连贯过多异样。

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注入的危险。

那么这种状况该怎办呢?

  1. 本人写个util工具过滤掉所有的注入关键字,动静计算时调用该工具。
  2. 如果数据源用的阿里的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. 应用其余伎俩解决

对于不能应用预编译传参时,要么开启druidfilter防火墙,要么本人写代码逻辑过滤掉所有可能的注入关键字。

最初说一句(求关注,别白嫖我)

如果这篇文章对您有所帮忙,或者有所启发的话,帮忙扫描下发二维码关注一下,您的反对是我保持写作最大的能源。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、工夫治理有超赞的粉丝福利,另外回复:加群,能够跟很多BAT大厂的前辈交换和学习。

 集体公众号

 集体微信