热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SQL注入风险高,手写SQL须谨慎

我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!文章会收录在Java

我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!
文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面。


事情是这样子的,在10年以前,某个月黑风高夜的夜里,虽然这么说有点暴露年龄了,但无所谓,毕竟我也才18而已。我打开电脑,在浏览器中输入我们高中学校的网址,页面很熟悉,很简陋,也没什么设计感,不过学校的网站从来都是这种风格,直到今天依然是这样。

这次访问与以往有些不同,因为我的目的很明确。作为学校的一员,理应为学校做些贡献的,为学校官网找些安全漏洞也算贡献的一种吧(强烈的求生欲)。

之所以选择学校的官网,一来是因为熟悉,先从熟悉的东西下手,一定错不了。二来是因为之前使用网站的时候碰过到一些异常的页面,直接就是异常堆栈直接抛出来。正好那段时间对网络安全比较有兴趣,在研究SQL 注入的时候正好想到在学校官网上碰到的问题好像就存在 SQL 注入的风险。于是,顺理成章,我的第一个目标就锁定了学校官网。

问题就出在一个大概这样的搜索页面,真正的网站已经改版过好多次了,以前的页面找不到了。

当时我在上面随便输入了一些内容,里面含有单引号,然后一点击搜索,页面就直接出现了异常提示,类似于下面这样子的。别惊讶,当时很多网站都是这样的,异常是直接抛出来的,别说以前了,现在也不少。

于是我用那时候刚学会的皮毛知识,加上两个好用的工具,轻松拿到了数据库的数据,其中就包括了管理员的账号、密码,密码还是明文的,你说气人不。
然后通过后台管理员登录页面进入了管理员后台,当然了,只是进去看了看,什么都没碰,而且后台也没什么重要数据,顶多就是一些通知、新闻等数据。

我是怎么做到的呢,不说具体细节了,也确实没什么技术含量,而且时间太长也记不清了,后面就说一下 SQL 注入的原理和具体操作。


什么是 SQL 注入

SQL注入,是发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。

SQL 注入一般发生在用户交互场景中,比如需要用户自已输入信息的输入框,或者下拉选择选项的这种,如果不做好输入内容的过滤,就很可能发生 SQL 注入。

就拿这个登录界面来说,用户名和密码都是你要输入的内容,点击登录按钮之后,会把你输入的值传递到服务端,服务端再到数据库进行查询。

假设后端的查询语句是这样的,不要在乎这是什么语法,只是举个例子。

String sql = "select * from `user` where account={account} and password={password}";

正常的情况,比如 account 输入的是一个电话号码 13001980988,密码是 123456,那拼接出的 SQL 语句就是

String sql = "select * from `user` where account='13001980988' and password='123456'";

然后,服务端通过数据库连接执行这条语句:

select * from `user` where account='13001980988' and password='123456';

最后,数据库正常返回符合条件的记录,代码中再根据结果进行判断,执行后面的逻辑。


不正常的输入

SQL 注入就是通过不正常的输入来获取程序开发者意料之外的结果。

什么是不正常的输入呢?

比如我在用户名输入框中输入的内容是这样子的 :13001980988' or 1=1 --,密码输入框随便输入什么都无所谓,然后点击登录,传输给后端,后端拼接出来的结果就是这样的:

String sql = "select * from `user` where account='13001980988' or 1=1 --' and password='123456'";

然后,服务端通过数据库连接会执行下面这条语句:

select * from `user` where account='13001980988' or 1=1 --' and password='123456'

以 MySQL 为例, -- 是 MySQL 中的注释符号,上面的语句中 --后面的相当于是注释内容了,所以最后实际执行的 SQL 语句是这样的:

select * from `user` where account='13001980988' or 1=1

于是,SQL 注入就这么发生了,显然有了 or 1=1 这个条件,表中所有的记录都符合条件。如果在用户名输入框中输入的是 adminadministrator 等已知的后台管理员账号,那就可以用管理员账号直接登录系统了。

上面就是 SQL 注入的基本原理。


SQL 注入遍地都是的年代

在9、10年前,也就是在我小时候(对,这个词好,小时候)。那时候智能手机才刚刚出来,塞班系统还很贵,根本就买不起。用着功能机,30M的流量能用坚持一个月,聊天只靠 QQ 和 短信,微信才刚要问世,更别提什么 APP 了,根本就没有。那时候,PC Web 才是根正苗红的网络主宰,如果说要在网上干点儿什么的话,那必须要有一个配套的网站才可以。

互联网还没有发展的这么成熟,用的技术也比较原始,绝大多数的网站是用 PHP 写的,还有很多用 ASP 。可能有些同学都不知道 ASP 是什么,它虽然也是微软的,但是却不是 ASP.NET。数据库很多用的是 MySQL ,还有一部分用的是更原始的 Access,可能又触到某些同学的盲区了,这不怪你没见识,只怪你太年轻。

一些小公司啊、学校啊、政府部门网站啊、各种论坛啊等等,各种五花八门的网站。不像现在这样,无论你用 PHP、Java 还是 Python,都有很多成熟的开发框架供你选择,成熟的框架必然会减少漏洞和降低被攻击的风险。但那时候没有这么多框架供选择,就比如很多学校会选择用 ASP + Access 组合的架构来开发自己的学校官网、教务管理系统等,功能上比较简单,但是全靠手工去写,就说 SQL 查询吧,从建立数据库连接到拼接 SQL 语句,再到执行查询处理查询结果,全都要自己实现,并没有什么 ORM 框架、数据库连接池供选择,由此就带来了 SQL 注入的风险。

而且建网站,如果不想开发的话,有很多 CMS 框架,尤其 PHP 的很多,现在依旧使用广泛的有 WordPress,当时国内的有 Discuz、DEDECMS 等一批傻瓜建站的 CMS 系统,由于代码都是开源的,而且 WordPress 还支持插件,所以会有很多相关的漏洞爆出来,尤其在多年以前,有了漏洞,想拿下一个网站真是太容易了,即使漏洞已经公布并有了解决方案,但依然有好多网站不及时修补和升级。现在在 Google 中搜索相关的 SQL注入关键词,有很多相关介绍。

说了这么多,这不都是 PHP 的代码吗?嘘,只是碰巧而已,说明 PHP 市场大呀,毕竟PHP是最好的开发语言。那 Java 中就没有了吗,当然有啊。


MyBatis 中的 SQL注入风险

最近在看一些代码,Spring Boot + MyBatis 的,偶然发现一个模糊查询的方法的 SQL 语句中用到了 like '%${keyword}%'这样的查询条件,这一看就有 SQL 注入漏洞。
大家可能都了解,MyBatis 是可以解决 SQL 注入的问题的。一般我们在使用 MyBatis 的时候都会把 SQL 语句单独的放到 xml 文件中,在 SQL 语句中支持两种格式的参数占位符,一种是 #{parameter},另一种是 ${parameter},在这两种参数占位符中,#{parameter}是安全的,不存在SQL注入漏洞,而 ${parameter}是存在 SQL 注入漏洞的。


安全的占位符格式

#{parameter} 这种占位符会在 MySQL中进行预编译,所以你观察到 MyBatis 打印出来的日志是这样的:

select * from `user` where account=? and password=?

其实在框架底层,是 JDBC 中的 PreparedStatement 类在起作用,PreparedStatement 是我们很熟悉的 Statement 的子类,它的对象包含了编译好的SQL语句。这种预编译的方式不仅能提高安全性,而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。

不知道你有没有写过直接用 JDBC 操作数据库的代码,反正大学老师就告诉我们要用占位符去做数据库查询,而不是拼接 SQL 字符串,因为用占位符的方式安全。其实,MyBatis 的预编译模式的底层实现就可以理解为下面这样的。

Connection conn = getConn();//获得连接
String sql = "select * from `user` where account=? and password=?"; //执行sql前会预编译号该条语句
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "13001980988");
pstmt.setString(2, "123456");
ResultSet rs=pstmt.executeUpdate();

不安全的占位符格式

${parameter} 这种格式是不进行预编译的,也就相当于字符串的拼接,存在 SQL注入的问题,如果非要用这种方式,那需要在程序中对参数进行安全性校验。强烈建议在使用 MyBatis 的过程中不要使用 ${parameter}这种格式的占位符,而要使用 #{parameter}这种格式。


来一波SQL注入

我模拟了一个 SQL 注入的场景,很简单,就是一个模糊查询的接口,根据用户输入的关键词查询。

数据库中有两张表,分别为用户表(user)和 新闻表(news)。

用户表:

新闻表:

NewsMapper 类:

public interface NewsMapper {List<News> selectNewsLikeTitle(@Param("keyword") String keyword);
}

实际的 SQL 语句,注意,正是用到了 ${keyword},才有了 SQL 注入的问题。

<select id="selectNewsLikeTitle" resultType="org.kite.purely.mybatis.entity.News">select * from news where title like &#39;%${keyword}%&#39;;
select>

然后我写了一个控制台,接收输入的参数,传给 selectNewsLikeTitle,以便来尝试 SQL 注入。

代码已上传至 github,链接放在了文末,需要的同学自取


正常的输入

新闻表就三条数据,我以 Docker 作为关键词,正常情况下,应该是这样的返回结果:

蓝色是我输入的关键词,后面跟着查询语句。一个标准的模糊查询语句,最后输出的结果也没问题。

select * from news where title like &#39;%Docker%&#39;;

SQL 注入

如果使用者都这么守规矩就好了,但是真实情况往往并不是这样的,有些人就是喜欢躲在阴暗的角落放着冷箭,大部分情况下是有利可图,极少部分干脆就只是为了满足变态心理。


1、查询所有记录

有同学已经看出来了,输入空值不就是查询所有吗?对的,没错,但真实情况下,前端或者 Controller、Service 层会做拦截,不允许查询所有。

正常逻辑不允许,但是 SQL 注入就可以。我输入下面这样的条件参数,看看会出现什么结果呢?

Docker&#39; or 1=1 or 1=&#39;

结果出来了,三条数据全部查询出来了。

因为构造的 SQL 语句已经完全变味儿了,SQL 语句是这样的,由于条件 or 1=1 的加持,导致任何记录都符合条件。

select * from news where title like &#39;%Docker&#39; or 1=1 or 1=&#39;%&#39;;

通过添加&#39;来保证条件中字符串前后单引号的闭合。

还可以是这样的条件

Docker&#39; or 1=1 --
或者
Docker&#39; or 1=1 #

因为--#都是 MySQL 中的注释符号,用它们来注释掉关键注入后面的部分,最后构造出来的 SQL 语句是:

select * from news where title like &#39;%Docker&#39; or 1=1 -- %&#39;;

select * from news where title like &#39;%Docker&#39; or 1=1 #%&#39;;

所以,最后有效的部分就是注释符号前面的部分,自然,查询出来的就是所有的记录。

select * from news where title like &#39;%Docker&#39; or 1=1

这种情况,其实保密数据没有什么泄漏,但是,它可能会拖垮数据库,抛开 Redis 缓存什么的不谈,假设仅有 MySQL 这一层,假设数据库中有几万条、几十万条数据,黑客不断制造这样的模糊查询,你的数据库服务马上就会挂掉。


2、联查其他表,危险行为

把数据库拖垮已经很不爽了,但是更严重的,是获取数据。

我想要通过这条查询语句把 user表的数据也套出来,你看着是不是就有点儿意思了。怎么办呢,通过 union就可以。

前提是我已经知道有 user 表的存在了,别问怎么知道的,反正是已经知道了,而且黑客有很多办法能猜到。

我构造这样的参数:

Docker&#39; union select * from `user` -- 注释掉后面的内容

执行一下,出现这样的提示:

构造出来的 SQL 没有问题,就是我们想要的。

select * from news where title like &#39;%Docker&#39; union select * from `user` -- 注释掉后面的内容%&#39;;

但是这用户体验很好,给出了具体的异常。体验好是对于攻击者而言的,如果每次异常都把原始异常信息抛出,那能给攻击者省不少事儿,就像下面这个异常。

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 &#39;%Docker&#39; union select 1,2 from `user`;

要构造这样的语句,需要输入的参数是:

Docker&#39; union select 1,2 from user #

因为 news 表只有两列,所以上面的参数可以成功执行。


盲注

大多数网站都不会将异常信息直接返回的,当攻击者拿不到即时的异常反馈时,就像是合着眼睛去猜,这种情况就叫做盲注。盲注是需要极大的耐心、高超的技术以及丰富的经验的,所以说黑客真不是好当的。


当试探出 news 表的列数后,再去配合它筛选 user 表的列就可以了,user 表的列名其实也是靠各种猜测的,比如常规的命名 name、account、phone、mobile、password 等,或者根据你返回给前端的属性对应着猜,比如返回的用户名是 userName,那你数据库中的字段就很有可能是 user_name。

假设我猜 user 表有 phone 这个字段,那我构造的参数就可以是下面这样的:

Docker&#39; union all select 1,phone from user # 注释掉后面的内容 ,来确定news表有两个字段

最后执行下来,结果是这样的,四条 user 记录全出来了。

或者我还确定 user 表中有 password 列,那就可以直接把 password 再取出来了,如果 password 再是明文的,那就热闹了。

有了用户名和密码,是不是就有点危险了。


3、更危险的高权限

还有更危险的呢,假设你程序中数据库连接用到的账号是高权限的,比如 root 账号,有好多中小应用都这么用,别惊讶。

发散思维想一想,这时候能干嘛?

由于权限够高,那意味着可以执行任何合法的 SQL 语句了。在 MySQL 中有数据库 information_schema,它存储着当前数据库实例中的很多重要信息,而且其中的表结构都是公开透明的,那这样一来呢,我们就可以通过这个数据库掌握当前数据库服务的几乎所有内容了。

不仅如此,高权限用户还能通过 MySQL 的读写文件功能实现更多的功能,比如配合 webshell 上传木马,获取服务器的控制权,从而实现脱裤(拖库)。

当然了,说的轻松,实现起来就困难了,不过只要有漏洞,就会被利用。


一些工具

俗话说,工欲善其事,必先利其器。漏洞哪儿那么容易挖,很多有价值的漏洞确实是厉害的黑客手工挖出来的。

为了方便的挖掘常见漏洞及利用漏洞,有很多网络安全专家开放出来的工具,可以让我等小白简单上手。比如我上学时候用到的「啊D注入工具」。可以用来扫码注入点、SQL注入检测、管理入口检测等。

还有比较专业的 SQL 注入工具「SQLMap」,它是一款命令行工具。SQLMap 提供了丰富的命令来帮我们发现漏洞、利用漏洞。

img

另外,如果你想学习 SQL 注入的一些基础,可以直接整个靶场来玩玩儿。比如这个 Pikachu 就不错。

https://github.com/zhuifengshaonianhanlu/pikachu


总结

本文并不是为了教各位如何完成 SQL注入,毕竟,我也没这个实力。当然,制造漏洞的实力还是有的。

只是想说,在写代码的时候一定要注意,稍有不慎就可能写出有漏洞的 SQL,所以,尽量用成熟框架的标准写法,不要图省事自己拼 SQL。

不光是 SQL 这部分,其他的涉及到用户交互的地方都要注入,比如用户表单,有时候可能产生 xss 漏洞,还有文件上传的部分,别用户上传了木马都不知道怎么回事。

愿你的代码没有 bug。虽然这是不可能的。

文中的测试代码已放至仓库:https://github.com/huzhicheng/SQL_Injection,有需要的同学自取。



这位英俊潇洒的少年,如果觉得还不错的话,给个三连可好!

公众号「古时的风筝」,Java 开发者,全栈工程师,bug 杀手,擅长解决问题。
一个兼具深度与广度的程序员鼓励师,本打算写诗却写起了代码的田园码农!坚持原创干货输出,你可选择现在就关注我,或者看看历史文章再关注也不迟。长按二维码关注,跟我一起变优秀!


推荐阅读
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • GAMETECH腾讯云游戏行业技术沙龙成都站圆满落幕
    11月13日,由腾讯云主办、游戏茶馆协办的2020年首场GAME-TECH腾讯云游戏行业技术沙龙在成都圆满落幕。本次沙龙邀请了腾讯云游戏行业解决方案总监宋永周、腾讯云游戏行业高级解决方案架构师曾梓恩、腾讯云游戏行业高级产品架构师郑晓曦、腾讯云游戏行业高级解决方案架构师温球良和天美L1(王者荣耀)服务器技术副总监杨光,为参会同行们带来了干货满满的技术建议。本文介绍了腾讯云游戏云的优势和为不同游戏研运场景提供的服务。腾讯云在中国游戏云服务市场领跑,成为众多游戏开发者的合作伙伴。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文介绍了腾讯最近开源的BERT推理模型TurboTransformers,该模型在推理速度上比PyTorch快1~4倍。TurboTransformers采用了分层设计的思想,通过简化问题和加速开发,实现了快速推理能力。同时,文章还探讨了PyTorch在中间层延迟和深度神经网络中存在的问题,并提出了合并计算的解决方案。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
author-avatar
西北孤狼2502911947
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有