软件安全技术
上QQ阅读APP看书,第一时间看更新

4.3 SQL注入漏洞

在OWASP公布的Web应用安全十大威胁中,SQL注入漏洞一直名列榜首。本节首先介绍SQL注入漏洞的原理及利用方式,然后介绍该漏洞防护的基本措施,最后给出借助Web漏洞教学和演练的开源免费工具DVWA,在代码层对SQL注入漏洞原理及应用进行分析的案例。

4.3.1 漏洞原理及利用

1.漏洞原理

SQL注入漏洞是指,攻击者能够利用现有Web应用程序,将恶意的数据插入SQL查询中,提交到后台数据库引擎执行非授权操作。

SQL注入漏洞的主要危害如下。

●非法查询、修改或删除数据库资源。

●执行系统命令。

●获取承载主机操作系统和网络的访问权限。

SQL注入漏洞的风险要高于其他所有的漏洞,原因如下。

●SQL注入攻击具有广泛性。SQL注入利用的是SQL语法,这使得所有基于SQL语言标准的数据库软件,包括SQL Server、Oracle、MySQL、DB2和Informix等,以及与之连接的网络应用程序,包括Active/Java Server Pages、PHP或Perl等都面临该类攻击。当然各种软件都有自身的特点,实际的攻击代码可能不尽相同。此外,SQL注入攻击的原理相对简单,介绍注入漏洞和利用方法的教程等资源非常多。这些因素造成近年SQL注入攻击的数量一直居高不下。

●相较于其他漏洞,对于SQL注入漏洞的防范要困难。例如,要实施缓冲区溢出攻击,攻击者必须首先能绕过站点的防火墙,而SQL注入内容往往作为正常输入的一部分,能够通过防火墙的控制审查,访问数据库进而获得数据库所在服务器的访问权。

2.漏洞利用

就攻击技术的本质而言,SQL注入攻击利用的工具是SQL语法,针对的是应用程序开发者编程中的漏洞。当攻击者能操作数据,向应用程序中插入一些SQL语句时,SQL注入攻击就容易发生。

(1)注入点选择

在SQL注入攻击之前,首先要找到网站中各类与数据库形成交互的输入点。通常情况下,一个网站的输入点包括以下几项。

●表单提交,主要是POST请求,也包括GET请求。

●URL参数提交,主要是GET请求参数。

●Cookie参数提交。

●HTTP请求头部的一些可修改的值,如Referer、User_Agent等。

●一些边缘的输入点,如.mp3文件的一些文件信息等。

上面列举的几类输入点,只要任何一点存在过滤不严、过滤缺陷等问题,都有可能发生SQL注入攻击。

(2)数字型和字符型注入

按照注入的数据类型,可将SQL注入攻击分为数字型注入和字符型注入。

1)数字型注入。当输入的参数为整数时,即为数字型注入,如ID、年龄等。数字型注入是一种最简单的注入方式。

例如,对于URL地址http://www.test.com/view.jsp?id=1,可以猜测相应的SQL查询语句为:

测试是否存在数字型注入漏洞的方法如下。

●在参数id后输入“1'”,返回数据库报错信息。由此说明程序已经执行了如下SQL语句,但是因为“1'”非整数,导致SQL语句无法正常执行。

●在参数id后输入“1 and 1=1”,返回数据与原始请求无差异。这是因为对应的如下SQL语句正确,与输入“1”的查询结果相同。

●在参数id后输入“1 and 1=2”,因为对应的如下SQL语句虽然语法正确,但是“1=2”不成立,所以查询返回结果为空。

经过上述3个步骤可以判断该程序可能存在数字型SQL注入漏洞。

数字型漏洞经常存在于ASP、PHP等弱类型语言中,弱类型语言会自动推导变量类型,例如,若参数id=1,PHP会自动判断id为int型;若参数id=1 and 1=1,PHP则会自动判断id为string型。而对于Java、C#这些强类型语言,若试图把一个string型转换为int型,程序会抛出异常,无法继续执行,且这一步发生在SQL语言查询之前。所以,强类型语言很少存在数字型注入漏洞。

2)字符型注入。当输入的参数为字符串时,即为字符型注入,如姓名、密码等。字符型注入和数字型注入的区别在于:字符型注入需要闭合单引号,而数字型注入不需要。

例如,对于URL地址http://www.test.com/login.jsp,当输入用户名admin和密码admin123时,可以猜测相应的SQL查询语句为:

因为语法正确,同时数据库中存在用户名为admin、密码为admin123的用户,则登录成功,页面跳转。但是,若在用户名处输入“admin and 1=1”,则无法进行跳转,因为程序会将“admin and 1=1”当作一个整体来查询。SQL查询语句为:

虽然语法正确,但是不存在用户名为admin and 1=1的用户,所以查询失败,登录不成功。要想注入成功,则必须让字符串闭合。因此,若在用户名处输入“admin'and 1=1--”即可注入成功,此时查询语句变为了:

因为1=1恒真,且存在admin这个用户,所以会返回用户名为admin的所有信息。

由上可知,字符型注入的关键是如何闭合字符串及注释掉多余的代码。

说明:

●还有POST注入、Cookie注入等叫法,实际上这些注入方式是数字型和字符型注入的不同展现形式或注入的位置不同。例如POST注入,注入字段在POST数据中;Cookie注入,注入字段在Cookie数据中。

●本章所举语句示例仅为说明原理,兼用了ASP、PHP和JSP等开发语言。

●使用的数据库不同,SQL语句中的字符串连接符也不同,如SQL Server中的连接符为“+”,MySQL中的连接符为空格,Oracle中的连接符为“‖”。

●目前网络上的SQL注入漏洞大多是SQL盲注。SQL盲注与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。

●测试一个URL是否存在注入漏洞比较简单,而要获取数据、扩大权限,则要输入很复杂的SQL语句。此外,测试大批URL更是一件比较麻烦的事情。因此,SQL注入通常借助工具来完成。这些工具也可以用作渗透测试工具来帮助开发者或用户进行安全性检测。本书将在下一节“漏洞防护基本措施”中列举这些工具。

(3)通过Web端对数据库注入和直接访问数据库注入

按照注入的物理途径,可将SQL注入攻击分为通过Web端对数据库注入和直接访问数据库注入。

1)通过Web应用程序的用户对数据库进行连接并进行SQL注入攻击。在这种类型的SQL注入攻击中,攻击者多采用拼接语句的方法来改变查询的内容,获取该账号权限下的全部信息。

针对SQL操作的注入攻击(SQL Manipulation)是所有SQL注入攻击类型中最常见的一种。这种攻击的原理在于攻击者会试图在已经存在的SQL语句中通过集合运算符(SET Operator),比如UNION、INTERSECT或者MINUS来添加一些内容,在WHERE子句中使其功能产生变化。当然,还有可能存在许多其他的变化。最经典的SQL manipulation攻击就存在于登录验证过程中。

另一种代码注入攻击(Code Injection),就是尝试在已经存在的SQL语句中添加额外的SQL语句或者命令。例如,SQL Server中的EXECUTE语句经常会成为这种SQL注入攻击的目标。

2)直接访问数据库进行注入攻击,就是以数据库用户的身份直接连接数据库进行SQL注入攻击。

例如,函数调用注入(Function Call Injection)。因为在数据库函数或者自定义函数中存在某些漏洞,攻击者对问题函数进行SQL语句注入,从而使此函数可以执行非预期功能而达到攻击者的目的。不仅数据库中的函数可能存在这些漏洞,存储过程、触发器等也存在类似漏洞。这些函数调用可以被用来在数据库中生成数据或者系统调用。

再如,许多标准数据库函数都很容易受到缓冲区溢出攻击。缓冲区溢出漏洞危害很大,往往最终会造成攻击者直接控制数据库或数据库所在的操作系统。

一些高级的攻击往往先利用Web应用程序上的SQL注入漏洞,获取数据库和数据库所在服务器的基本信息,再利用数据库自身SQL注入漏洞对获取的数据库账号进行提权、越权等操作,以达到对数据库进行破坏或者获取敏感信息的目的。

说明:

本节主要介绍了通过Web端进行注入的基本方法,数据库的直接注入方法请参考本章末“拓展阅读”部分列出的参考资料。

4.3.2 漏洞防护的基本措施

本节介绍Web应用开发人员在编码和代码测试两个方面进行SQL注入漏洞防护的基本措施。

1.代码层漏洞防护

(1)基本措施

服务器端从客户端直接或间接获取数据的过程都是一次输入过程,无论直接或间接,默认情况下输入的数据都应该认为是不安全的。因此,解决SQL注入问题的关键是对所有可能来自用户输入的数据进行严格检查,对数据库配置使用最小权限原则。具体措施如下。

1)采用强类型语言,如Java、C#等强类型语言几乎可以完全忽略数字型注入。

2)尽可能避免使用拼接的动态SQL语句,所有的查询语句都使用数据库提供的参数化查询接口。参数化的语句使用参数而不是将用户输入变量嵌入到SQL语句中。

3)在服务器端验证用户输入的值和类型是否符合程序的预期要求,一般应验证以下内容。

●检查字段是否为空,要求其长度大于零,不包括行距和后面的空格。

●检查字段数据类型是否符合预期要求,如所有HTTP请求参数或Cookie值的类型都应是字符串;在PHP中使用is_numeric()、ctype_digit()等函数判断数据类型。

●检查输入字段长度是否符合预期要求。

●根据功能需求定义的允许选项来验证用户输入的数据。

●检查用户输入是否与功能需求定义的模式匹配。例如,使用以下正则表达式:^[a-zA-Z0-9]*$验证UserName字段是否符合“仅允许字母数字字符,且不区分大小写”。

4)在服务器端对用户输入进行过滤。针对非法的HTML字符和关键字,可以编写函数对其进行检查或过滤。

●需要检查或过滤的特殊字符至少应包含:、&、;、$、%、@、'、"、,、\、+、*、\'(反斜杠转义单引号)、\"(反斜杠转义引号)、<>(尖括号)、()(小括号)、CR(回车符,ASCII0x0d)或LF(换行,ASCII0x0a)。

●需要检查或过滤的关键字至少应包含(不区分大小写):and、exec、insert、select、delete、update、count、chr、mid、master、truncate、char、declare、backup或script。

5)避免网站显示SQL错误信息,如类型错误、字段不匹配等,防止攻击者利用这些错误信息进行一些判断。

6)加固应用程序服务器和数据库,利用最低权限账户与数据库连接。具体做法列举如下。

●配置可信任的IP接入和访问,如IPSec,控制哪些机器能够与数据库服务器通信。

●从数据库服务器上移除所有的示例脚本和应用程序。

●不应使用sa、dba或admin等具备数据库DBA权限的账户,为每一个应用程序的数据库的连接账户使用一个专用的最低权限账户。如果应用程序仅需要读取访问,应将数据库的访问限制为只读。

●应用程序尽量使用存储过程,利用存储过程,将数据访问抽象化,使用户不能直接访问表或视图。存储过程是在大型数据库系统中,为了完成特定功能或经常使用的一组SQL语句集,经编译后存储在数据库中。存储过程具有较高的安全性,可以防止SQL注入。不过,如果编写不当,依然有SQL注入的风险。

●应从生产数据库中移除未用的存储过程。

●将对应用程序的访问仅授权给用户创建的存储过程。

●禁止应用程序访问不必要的系统存储过程。

(2)安全规范

Web安全开发中应当遵循安全规范,如OWASP企业安全应用程序接口(The OWASP Enterprise Security API,OWASP ESAPI)。这是一个已经被许多软件企业和组织应用的,免费、开源的网页应用程序安全控件库,它使程序员能够更容易地写出更低风险的程序。ESAPI接口库被用于使程序员更容易地在现有程序中引入安全因素。ESAPI库也可以作为新程序开发的基础。除了语言方面的差异,所有的OWASP ESAPI版本都具有以下相同的基本设计结构。

●都有一整套安全控件接口。例如,这些安全接口中定义了发送给不同安全控件的参数类型。

●每个安全控件都有一个参考实现。这些实现不是基于特定组织或者特定程序的。例如,基于字符串的输入验证。

●程序开发者可以有选择地实现自己的安全控件接口。可能这些接口类中的应用逻辑是由用户的组织开发的或者为用户公司定制的,如企业认证。

●为使该项目尽可能易于传播并使更多人能够自由使用,该项目的源代码使用了BSD许可证。该项目的文档使用了知识共享署名许可证,开发者可以随意使用、修改ESAPI,甚至将它包含在商业产品中。

2.使用专业的漏洞扫描工具进行安全性测试

应当在Web应用程序开发过程的所有阶段实施代码的安全检查,在开发和部署Web应用的前后,应利用专业的漏洞扫描工具对网站进行安全性测试,对检测出的SQL注入漏洞及时进行修补。

常见的SQL注入漏洞扫描工具如下。

●SQLMap(http://sqlmap.sourceforge.net):一款被称为SQL注入第一神器的开放源代码工具,可以自动探测和利用SQL注入漏洞及接管数据库服务器的过程。SQLMap支持多种数据库、多种类型和模式的注入。

●Pangolin(中文译名“穿山甲”):一款目前国内使用率很高的SQL注入测试软件。它能够通过一系列简单的操作,完成从检测注入到控制目标。

说明:

本书在第9章将介绍更多关于Web应用安全检测的技术和应用。

请读者完成本章思考与实践第15题,学习使用SQL注入漏洞检测工具。

【案例4-1】SQL注入漏洞源代码层分析

DVWA(Damn Vulnerable Web Application)是一款用PHP+MySQL编写的,用于Web漏洞教学和演练的开源免费工具。著名的渗透测试平台Kali中也已经集成了该工具。

DVWA目前的版本共有10个漏洞模块,分别是Brute Force(暴力破解)、Command In jection(命令行注入)、CSRF(跨站请求伪造)、File Inclusion(文件包含)、File Upload(文件上传)、Insecure CAPTCHA(不安全的验证码)、SQL Injection(SQL注入)、SQL Injection(Blind)(SQL盲注)和XSS(Reflected)(反射型跨站脚本)和XSS(Stored)(存储型跨站脚本)。

DVWA中将代码分为4种安全级别:Low、Medium、High和Impossible。试下载并安装DVWA,通过设置不同安全级别的代码,了解并分析SQL注入漏洞的原理及利用方法。

【案例4-1思考与分析】

1.DVWA安装配置

(1)Web服务器配置

如果还没有配置Web服务器,可以下载并安装XAMPP。XAMPP是一个易于安装且包含MySQL、PHP和Perl的Apache发行版。XAMPP非常容易安装和使用:只需下载(https://www.apachefriends.org/zh_cn/index.html)、解压缩并启动即可。

本书实验下载的版本为XAMPP for Windows v5.6.24。安装成功后,单击Apache服务和MySQL服务一行中Actions栏中的Start按钮,开启相应服务,如图4-3所示。

(2)安装配置DVWA

1)下载安装压缩包。从官方网站http://www.dvwa.co.uk下载DVWA,也可以从DVWA在Github上的资源链接https://github.com/ethicalhack3r/DVWA下载,这上面的资源描述和安装指导更加详细。

2)解压到站点根目录。解压缩DVWA安装包到XAMPP安装目录中,具体为xampp\ht-docs,可将解压后的文件夹名称改为dvwa。注意,如果是用其他软件配置的Web服务,就将解压包放在站点根目录www目录下。

3)创建数据库。打开浏览器,在地址栏中输入http://localhost/dvwa/setup.php,其中的dvwa为解压后修改的文件夹名。如图4-4所示,进入DataBase Setup界面,单击Create/Reset Database按钮。

图4-3 XAMPP安装配置

图4-4 DVWA数据库配置

如果出现“Could not connect to the database-please check the config file.”的错误信息,则打开以下目录中的文件“xampp\htdocs\config\config.inc.php”,将$_DVWA[ 'db_password']='p@ssw0rd';中的密码部分替换成所设置的MySQL root用户的密码(此处为空,直接把'p@ ssw0rd'改为''就行了,即密码为空),再重新创建数据库即可,如图4-5所示。

4)登录。重新在DataBase Setup界面中单击Create/Reset Database按钮,将顺利进入登录界面,默认的用户名和密码分别为admin和password。如图4-6所示,进入DVWA主界面。

图4-5 在数据库配置文件中修改登录密码

图4-6 DVWA主界面

2.SQL手工注入分析

现实攻击场景下,攻击者是无法看到后端代码的,所以下面的手工注入步骤是建立在无法看到源代码的基础上。SQL手工注入(非盲注)的基本步骤如下。

1)判断是否存在注入,注入是字符型还是数字型。

2)猜解SQL查询语句中的字段数及字段顺序。

3)获取当前数据库名。

4)获取数据库中的表名。

5)获取表中的字段名。

6)下载数据。

下面分别设置不同安全级别的代码,学习并了解SQL注入漏洞的原理及利用。

(1)low安全级别

1)判断是否存在注入,注入是字符型还是数字型。

输入:1,查询成功,返回结果如图4-7所示。

输入:1'and '1'='2,查询失败,返回结果为空,如图4-8所示。

图4-7 输入“1”时的返回结果

图4-8 输入“1'and '1'='2”时的返回结果为空

输入:1'or'1'='1,查询成功,返回多个结果,如图4-9所示。

从上述3次尝试可知,此处存在字符型注入漏洞。

2)猜解SQL查询语句中的字段数。

输入:1'or1=1 order by1 #,查询成功,返回结果如图4-10所示。

输入:1'or1=1 order by2 #,查询成功,返回结果如图4-11所示。

图4-9 输入“1'or'1'='1”时的返回结果

图4-10 查询第一个字段结果

图4-11 查询第二个字段结果

输入:1'or1=1 order by3 #,查询失败。说明执行的SQL查询语句中只有两个字段,即图中的First name和Surname。

这里也可以通过输入:

来猜解字段数。该命令还能显示字段顺序,返回结果如图4-12所示。

3)获取当前数据库名。输入命令如下。

返回结果如图4-13所示,说明当前的数据库为dvwa。

图4-12 猜测字段数

图4-13 获取当前数据库名

4)获取数据库中表的名称。输入命令如下。

返回结果如图4-14所示,说明数据库dvwa中共有两个表:guestbook与users。

5)获取表中的字段名。输入命令如下。

返回结果如图4-15所示,表中有user_id、first_name、user和password等字段。

6)尝试获取user及password。输入命令如下。

返回结果如图4-16所示。

图4-14 获取数据库中表名

图4-15 获取表中字段名

图4-16 返回user和password字段内容

7)尝试破解口令。口令共32位,猜测其为md5哈希值,借助提供md5哈希值查询网站http://pmd5.com,可以很快解析出密码如下。

●5f4dcc3b5aa765d61d8327deb882cf99——password。

●e99a18c428cb38d5f260853678922e03——abc123。

●8d3533d75ae2c3966d7e0d4fcc69216b——charley。

●0d107d09f5bbe40cade3de5c71e9e9b7——letmein。

●5f4dcc3b5aa765d61d8327deb882cf99——password。

查看低安全级别时的服务器端代码如下。

可以发现,程序对来自客户端的参数id没有进行任何检查与过滤,存在明显的SQL注入漏洞。

(2)medium安全级别

中等安全级别的服务器端源代码如下。

可以看到,这段代码利用mysql_real_escape_string函数对特殊符号进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。同时可以发现,SQL语句也变成了数字型。

不过,由于实际情况下无法获知服务器端代码,因此,继续进行注入尝试。虽然前端使用了下拉选择菜单进行输入限制,但依然可以通过BurpSuite等工具修改参数,提交恶意构造的查询参数。

1)判断是否存在注入,注入是字符型还是数字型。

使用BurpSuite抓包,更改参数id为:1'or1=1 #,如图4-17所示。

图4-17 BurpSuite工具抓包更改参数

返回出错信息,如图4-18所示。

图4-18 返回出错信息

抓包更改参数id为:1 or 1=1 #,查询成功。

说明存在数字型注入。由于是数字型注入,服务器端的mysql_real_escape_string函数就形同虚设了,因为数字型注入并不需要借助引号。

2)猜测SQL查询语句中的字段数。

抓包更改参数id为:1 order by 2 #,查询成功。

抓包更改参数id为:1 order by 3 #,返回出错信息。

说明执行的SQL查询语句中只有两个字段,即First name和Surname。

接下来,确定显示的字段顺序、获取当前数据库名、获取数据库中的表名、获取表中的字段名及获取字段信息的操作基本与low安全级别的一样,均可通过BurpSuite修改参数重新提交完成。

(3)high安全级别

高安全级别的服务器端源代码如下。

可以看到,与medium级别的代码相比,high级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。虽然添加了LIMIT 1,但是可以通过#将其注释掉。由于手工注入的过程与low级别基本一样,直接最后一步演示下载数据。输入如下命令。

返回结果如图4-19所示。

图4-19 返回user和password字段内容

需要特别提到的是,high级别的查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入。因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。

(4)impossible安全级别

最高安全级别的服务器端源代码如下。

可以看到,impossible级别的代码采用了PDO技术,划清了代码与数据的界限,能有效防御SQL注入,同时只有返回的查询结果数量为1时,才会成功输出,这样就有效预防了“脱裤”。此外,Anti-CSRFtoken机制的加入进一步提高了安全性。

知识拓展:PDO(PHP Database Object,PHP数据库对象)

PDO为PHP访问数据库定义了一个轻量级的、一致性的接口,它提供了一个数据访问抽象层,这样,无论使用什么数据库,都可以通过一致的函数执行查询和获取数据。

应用了PDO后,项目换数据库时,相关数据库的代码和函数库都不需要修改。此外,还能提高相同SQL模板查询性能,阻止SQL注入。