乐趣区

关于java:卧槽sql注入竟然把我们的系统搞挂了

前言

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

 集体公众号

 集体微信

退出移动版