一文搞懂SQL注入攻击

1. 前言

随着互联网的发展和普及,网络安全问题越来越突出,网络在为用户提供越来越多服务的同时,也要面对各类越来越复杂的恶意攻击。SQL注入(SQL Injection)攻击是其中最普遍的安全隐患之一,它利用应用程序对用户输入数据的信任,将恶意SQL代码注入到应用程序中,从而执行攻击者的操作。这种攻击可以导致敏感信息泄露、数据损坏或删除及系统瘫痪,给企业和个人带来巨大损失。因此,如何防范SQL注入攻击成为了网络安全领域的一个重要议题。

在国外,SQL注入最早出现在1999年;在我国大约出现在2002年。2015年,约翰·卡特尔(John McAfee)的网站遭到了SQL注入攻击,导致黑客获取了网站上全部的注册用户信息。2016年,Yahoo公司披露了两起大规模数据泄露事件,其中涉及的一次就是通过SQL注入攻击获得的。2017年,Equifax公司遭到了一次严重的数据泄露事件,泄露了超过1.43亿条用户记录。调查人员发现,攻击者使用了SQL注入攻击。

可见,SQL注入攻击破坏敏感数据的完整性和可用性,给网络用户和企业造成了巨大的生活困扰和经济损失。

2. SQL注入简介

(1)SQL语言
Structured Query Language(简称SQL)是—种结构化查询语言,一种数据库文本语言。SQL用于同关系数据库进行交互,能够执行对数据库的查询、获取数据库的信息、向数据库插入新的纪录、删除及更新数据库中的记录。

SQL有很多种类,但大多都基于ANSI标准SQL-92。SQL执行的单位是一个“query”,该“query”可以是一系列语句集合,返回一个结果集。SQL语句可以修改数据库结构(使用数据定义语言DDL)和操作数据库内容(使用数据操作语言DML)。SQL语言本身造成了SQL注入漏洞,给SQL注入攻击带来了可能性。

(2)SQL注入
SQL注入攻击是一种利用应用程序漏洞的攻击方式。攻击者通过向应用程序发送构造的恶意SQL语句,欺骗应用程序执行这些SQL语句,如果Web应用没有适当的验证用户输入的信息,攻击者就有可能改变后台执行的SQL语句的结构,获取相应结果。攻击者可以利用SQL注入漏洞获取数据库中的敏感信息,修改或删除数据库中的数据,或者完全控制Web服务器。

比如基于表单登录功能的应用程序,通过执行一个简单的SQL查询来确认每次登录,以下是这个查询的一个典型实例:

SELECT * FROM users WHERE username=’alice’ and password=’secret’

这个查询要求数据库检查用户表中的每一行,提取username值为alice并且password值为secret的记录。如果返回一条用户记录,该用户即可成功登录。

攻击者可注入用户名或密码字段来修改程序执行的查询,一般是输入双连字符(--)注释掉其余部分或类似’ or ‘1’ =‘1用引号包含的字符串数据来“平衡引号”,来破坏查询的逻辑。比如攻击者可以通过提交用户名为alice’--alice’ or ‘1’=‘1,密码为任意,应用程序将执行以下查询:

SELECT * FROM users WHERE username=’alice’--‘’ and password=’any’

SELECT * FROM users WHERE username=’ alice’ or1=1and password=any

这两条查询均等同于

SELECT * FROM users WHERE username=’alice’

从而避开了密码检查,成功登录。

因此SQL注入漏洞本质上是针对程序员编程中的漏洞,利用SQL的语法在应用程序与数据库交互的SQL语句中插入精心编制的额外的SQL语句,从而对数据库进行非法查询和修改。由于程序运行SQL语句时的权限与当前该组建(例如,数据库服务器、Web应用服务器)的权限相同,而这些组件一般的运行权限都很高,而且经常是以管理员的权限运行,所以攻击者可能获得数据库的完全控制,并执行系统命令。

3. SQL注入步骤

SQL注入攻击有多种类型,包括基于错误的注入、联合查询注入、堆叠查询注入等。其中,基于错误的注入是最常见的类型。攻击者通过发送包含恶意SQL语句的输入数据来触发应用程序中的错误,从而获取有关数据库结构和内容的信息。

(1)发现漏洞
可以用经典的1=1,1=2测试法测试SQL注入是否存在。

以http://www.test.com/profile.do?id=113为例,使用以下测试方案:

  • http://www.test.com/profile.do?id=113’
  • http://www.test.com/profile.do?id=113 and 1=1
  • http://www.test.com/profile.do?id=113 and 1=2

(2)信息收集

确认系统表是否存在,主要利用Oracle数据库中以下系统表:

  • all_tables:存放当前ID和其他用户的所有表
  • user_tables:存放当前用户所有表
  • user_tab_columns:存放当前用户表的所有列

测试以下URL:

  • http://www.test.com/profile.do?id=113’and 0<>(select count(*) from all_tables) and '1'='1
  • http://www.test.com/profile.do?id=113’and 0<>(select count(*) from user_tables)and '1'='1
  • http://www.test.com/profile.do?id=113’and 0<>(select count(*) from user_tab_ columns) and '1'='1

如果以上页面都能正确返回,说明存在猜测的系统表。

(3)攻击Web系统(猜解用户名和密码)

首先查找当前用户是否有敏感列名:

'and 0<>(select count(*) from user_tab_columns where column_name like '%25PASS%25') and '1'='1

正常返回说明存在类似PASS的字段,猜解该字段对应的表名。先确定表名的长度(不断改变length(table_name)后的数值):

'and 0<>(select count(*) from user_tables where length(table_name)>8 and table_ name like'%25PASS%25') and '1'='1

利用ASCII二分法猜解表名(不断改变substr函数的参数及比较值):

'and  (ascii(substr((select table_name from user_tab_columns where column_name like '%PASS%' And Rownum<=1),1,1))>64) and '1'='1

猜字段同样先确定长度,然后利用ASCII二分法猜解字段名:

'and 0<>(select count(*) from user_tables where table_name=’T_SYSUSER’and length (column_ name) >8  and column_name like '%PASS%') and '1'='1
'and  (ascii(substr((select column_name from user_tab_columns where table_name =’T_SYSUSER’ and column_name like '%25PASS%25'),1,1))>64) and '1'='1

得到用户表的USERNAMEPASSWD两列的名称后,依然是利用ASCII码猜解法来一步一步确定USERNAMEPASSWD的值,不再详述。

(4)获取管理员权限

要想获取对系统的完全控制,还要有系统的管理员权限。Oracle包含许多可在数据库管理员权限下运行的内置存储过程,并发现在这些存储过程中存在SQL注入漏洞。2006年7月补丁发布之前,存在于默认包SYS.DBMS_ EXPORT_EXTERSION.GET_DOMAIN_INDEX_TABLES中的缺陷就是一个典型的示例。攻击者可以利用这个缺陷,在易受攻击的字段中注入GRANT DBA TO PUBLIC查询(需要换成char形式)来提升权限。

这种类型的攻击可通过利用Web程序中的SQL注入漏洞,在易受攻击的参数中输入函数来实现。许多其他类型的缺陷也影响到Oracle的内置组件。一个示例是CTXSYS.DRILOAD.VALIDATE_STMT函数。这个函数的目的是检查一个指定的字符串中是否包含一个有效的SQL语句。早期Oracle版本中,在确定被提交的语句的过程中,这个函数实际执行了该语句。这意味着任何用户只需向这个函数提交一个语句,就能够作为数据库管理员执行该语句。例如:

exec CTXSYS.DRILOAD.VALIDATE_STMT(GRANT DBA TO PUBLIC)

除这些漏洞外,Oracle还含有大量默认功能,这些功能可被低权限用户访问,并可用于执行各种敏感操作,建立网络连接或访问文件系统。比如,可利用UTL_HTTP 包里面的 request函数构造注射,来建立用户,并赋予DBA权限。

4. 防范SQL注入

(1)使用参数化查询或存储过程
参数化查询分两个步骤建立一个包含用户输入的SQL语句:

  • 应用程序制定查询结构,为用户输入的每个数据预留占位符;
  • 应用程序制定每个占位符的内容。
    在第二步中指定的专门设计的数据无法破坏在第一步中指定的查询结构。由于查询结构已经确定,且相关API 对任何类型的占位符数据进行安全处理,因此它总被解释为数据,而非语句结构的一部分。

Java提供以下API,允许应用程序创建一个预先编译的SQL语句,并以可靠且类型安全的方式指定它的参数占位符的值:

 java.sql.Connection.prepareStatement
 java.sql.PrepareStatement.*

使用如下:

String username = request.getParameter("j_username");
String passwd = request.getParameter("j_password");
String query = "select * from t_sysuser where username=? and passwd=?";
PreparedStatement stmt = con.prepareStatement(query);
stmt.setString(1, username);
stmt.setString(1, passwd);
ResultSet rs=stmt.executeQuery();

如果用户提交的用户名为alice’ or 1=1--,密码为any,生成的查询等同于:

select * from t_sysuser where username= ‘alice’ ‘or 1=1--’ and passwd=’any’

由此可见使用参数化查询可以有效防止SQL注入,应在每一个数据库查询中使用参数化查询。如果仅注意用户直接提交的输入,二阶SQL注入攻击就很容易被忽略因为已经被处理的数据被认为是可信的。另外参数占位符不能用于指定查询中表和列的名称,如果应用程序需要根据用户提交的数据在SQL查询中指定这些数据行,需要使用一份由已知可靠的值组成的“白名单”(即数据库中表和列的名称),并拒绝任何与这份名单上数据不匹配的输入项。或者对用户输入实施严格的确认机制。

使用存储过程的原理与参数化查询类似,传入的参数在最后被执行的SQL语句中会被数据库服务器软件进行处理,使得输入的字符串会被当作一个单纯的文本参数参与到SQL查询中。不论是攻击者输入什么参数都不会改变设计者编写的SQL语句的语义,也就防止了SQL注入。另外由于存储过程是预编译的,对复杂的SQL语句,效率有很大程度上的提升,还可以进一步减少查询时客户端发送到服务端的数据包。

(2)用户输入检测
对输入进行检测一般是在客户端和服务器端利用正则表达式来匹配SQL的特殊字符及其等值的十六进制形式,包括单引号(’)、双重破折号(–)、SELECT、UNION等查询关键字,然后对其进行替换或者过滤。但是在所有的接受输入的程序部分都严格的执行检测却很难,而且会产生误报。

(3)SQL语法分析
SQL注入根源在于对于用户提交的请求没有足够的验证机制,正常的用户输入是具有逻辑整体含义的简单结构,而实施 SQL 注入的字符串一定是包含 SQL语法片段的复合结构。 SQL 注入攻击本质上是希望后台数据库的 SQL 解释程序按照不同的方式解释组装好的 SQL 语句,如果用户输入的字符串不包含 SQL 语法片段,显然无法达到这个目的。

静态代码分析中能从语法、语义上理解程序行为,直接分析被测程序特征,寻找可能导致错误的异常。因此可以在服务器端采用一种类似于静态代码分析的SQL 语法预分析策略来防止SQL注入。首先将 SQL 注入分类,并对其进行词法分析和语法分析,抽象出各类注入的语法结构,生成语法分析树;能够利用该语法分析树通过机器学习的方法来得到预想的SQL注入的集合,并从该集合中生成新的语法分析树;然后将用户提交的输入预先组装成完整的 SQL 语句,对该语句进行语法分析,如果发现具有 SQL 注入特征的语法结构,则判定为 SQL 注入攻击。判定的唯一依据是抽象的语法结串,而非具体的特征字符串,避免了传统的特征字符串匹配策略固有的高识别率和低误判率之间的矛盾。

由于SQL注入利用的是正常的服务端口,在各类Web应用中已不能通过使用防火墙,SSL等网络层保护技术来阻止应用层攻击,也就是说安全边界扩展到了系统源代码。因此,必须通过防范软件代码中存在的安全漏洞,从而保障系统的安全。

(4)其他
通过在程序中对口令等敏感信息加密,(一般采用MD5函数),另外在原来的加密的基础上可以增加一些非常规的方式,即在MD5加密的基础上附带一些值 如密文=MD5 (MD5 (明文)+系统时间);编码时注意屏蔽网页上显示的异常与出错信息,并对源代码进行安全审查及软件安全测试。

安全配置服务器,包括目录最小化权限设置:给静态网页目录和动态网页目录分别设置不同权限,尽量不给写目录权限;修改或者去掉 Web 服务器上默认的一些危险命令,例如ftp、cmd等 需要时再复制到相应目录;正确配置iptables及Oracle的sqlnet.ora文件,通过IP地址限制对于数据库访问。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>