文献标识码: A
文章编号: 0258-7998(2013)04-0137-04
SQL注入攻击是一种常见的互联网攻击手段,它具有易操作、强隐蔽、高危害和难检测等特点。由于SQL注入攻击的对象是Web服务器后台的数据库,而数据库往往存放重要的用户数据或业务数据,因而SQL注入攻击的后果影响非常严重。特别对银行、证券、电信、移动、政府以及电子商务企业等后台数据库。一旦遭受SQL注入而导致数据纂改或机密数据丢失,在经济角度和社会角度都将产生恶劣的影响。因此检测和防范SQL注入是软件安全领域的重要课题,具有极高的研究价值和意义。
常见的Web应用系统架构如图1所示,其中包括Web服务器及后台数据库。在Web服务器上部署Web服务,用户使用浏览器通过Http或Https协议与Web服务器进行交互。常称之为B/S架构。
在该架构中,Web服务器向用户提供各种应用服务,这些服务都涉及到与数据库的交互,即伴执行SQL语句。如果Web应用系统设计或实现不合理,将可能导致用户对数据库执行的SQL语句偏离系统设计的预期,从而引发安全事故。
Java程序运行在JVM中,因为具有与平台无关的特点,这种完全面向对象开发的语言广泛应用在Web系统的设计中。针对Java源代码的SQL注入攻击检测进行研究,对于提高Web应用系统的安全性具有重要意义。
本文介绍了SQL注入原理、目前的检测方法以及Java代码对数据库操作的三种代码结构。针对Java执行SQL语句的特点,重点提出一种基于抽象语法树进行SQL执行路径检测的SQL注入检测算法;在Web 应用程序发布前进行深入静态分析和检查,从而提高Web应用系统的安全性。算例分析和在实际Web系统中的应用结果表明,本文提出的SQL注入检测算法具有高检测率和低误报率。
1 相关研究工作
SQL注入漏洞最早在2000年左右被提出,其后学者们对SQL注入的检测和防护展开了一系列的研究。
典型的一种SQL注入例子如下:
(1)用户登录Web应用系统时,需要进行身份认证,主要输入用户名和密码两个变量v_usr和v_pwd;
(2)Web系统执行合法性检查的SQL语句为:“Select * from users where username=‘”+v_usr+“’and password=‘”+ v_pwd+“’”,如果用户登录时用户取为‘admin’ or 1=1--,那么合法性检查的SQL语句等效于Select * from users where username=‘ admin’ or 1=1;
显然,用户名取‘admin’ or 1=1--时,无论密码输入多少,都可以登录系统。
SQL注入的检测和防护方式目前主要有两大类,一是系统上线前检测,也称为静态检测;二是系统在线运行防御,也称为动态检测。
动态检测方式是一种黑盒检测方法,对上线的系统进行SQL漏洞扫描,编制SQL注入攻击脚本对Web系统进行试探,通过检查Http的回应报文内容来判断是否发生SQL注入攻击,从而确定是否存在SQL注入漏洞。AppScan等工具可执行此类安全检测。
静态检测方法是一种白盒检测方法[1],通过静态语法解析查找Web 应用代码中可能引发SQL注入的环节,在Web应用发布前检查代码质量。Fotify等工具可执行此类安全检测。参考文献[2-3]给出一种动态生成SQL语句进行类型正确性检查的方法来检测是否存在SQL注入,该方法的的缺点在于只能检测句法结构或语句类型出现异常的SQL注入问题。参考文献[4]提出一种自动推理机的方法对添加了输入值后的SQL语句进行检查,该方法的缺点在于只能检测出重言式的SQL注入攻击。此外,还有一些学者采用机器学习算法对SQL注入漏洞进行检测[5],这一类检测方法的检测率和误报率往往依赖于训练集的大小。
2 Java源代码静态扫描分析思路
程序静态分析指在不执行程序的情况下,通过自动扫描代码发现隐含的程序隐患[6],具备执行速度快、效率高等优点。对源代码进行静态分析时借鉴编译技术的词法分析和语法分析。源代码进行静态分析扫描原理如图2所示。
Java程序与数据库之间的交互主要通过Java数据库连接JDBC进行。其数据库操作涉及与数据库建立连接、发送SQL指令、处理返回结果、断开数据库连接4个步骤。
具体实现上主要涉及的Java类有Connection类、DriverManager类、Statement类、ResultSet类、PreparedStatement类和callableStatement类。在代码实现上往往有3种实现方式:
(1) 常规方式
//函数 DB_SQL(SQL语句)
Connection con=DriverManager. getConnection(参数URL,参数用户名,参数密码);
Statement stat = con.createStatement();
ResultSet rs = stat.executeQuery("SQL语句");(或者executeUpdate /execute /executeBatch)
//此处为rs的处理代码
rs.close(); stat.close(); con.close();
Java采用常规方式访问数据库,每次都需要进行建立连接,再执行SQL语句,最后释放连接。由于建立连接过程一般比较耗时(一般需要几十毫秒至几百毫秒之间),而且Statement对象每次执行SQL语句的解析和编译也比较耗时,如果涉及如下循环使用情况,则代码的执行效率将会比较低下。为此,对于需要频繁建立数据库连接的情况,可以采用连接池方式进行优化;对于需要频繁执行相似SQL语句的情况,则可采用预编译方式进行优化。
while(N次)
{
DB_SQL(SQL语句);
}
(2) 预编译方式
采用常规方式执行SQL语句时,DBMS需要对SQL语句进行解析和编译;而采用预编译方式时,DBMS只在第一次对SQL语句进行解析和编译。相比前者,后者更难注入SQL。原因在于,PreparedStatement对象执行SQL语句前,需要设置“?”号对应的变量,例如ps.setInt(1,变量v1)、ps.setLong(2,变量v2) 表示SQL语句中第一个“?”号是int型、值为“变量v1”;这样在安全性上相当于增加了一个类型匹配。
Connection con=DriverManager.getConnection(参数URL,参数用户名,参数密码);
while(N次)
{
PreparedStatement ps = con.prepareStatement(带若干“?”号的SQL语句);
ps.setXX(“?”的序号, 变量); //XX包括int double等
ResultSet rs = ps.executeQuery();(或者executeUpdate /execute /executeBatch)
//此处为rs的处理代码
rs.close();
}
ps.close(); con.close();
(3) 连接池方式
连接池(Connection Pool)是设计模式中资源池(Resource Pool)的一种具体应用,主要解决资源频繁分配和释放所带来的性能问题。其基本思想是预先建立一个数据库连接池,在该连接池中预先存放一定数量的连接,当需要建立数据库连接时,并非新建一个数据库连接,而是从连接池中取出一个连接对象,然后对该连接对象进行参数设置和使用,使用完毕后,并不释放该连接,而是将连接对象的参数复位并回收。
采用连接池的应用开发方式与常规方式的代码结构类似。区别是常规方式的连接对象在需要时才从堆中动态分配空间与数据库建立连接,使用完毕后即断开连接,等待Java的GC垃圾回收器发现该对象不再需要后,将回收连接对象的内存空间;而采用连接池方式,则是在连接池中预先建立一个若干数据的连接对象,需要使用时才从连接池中取出一个连接对象(不是临时创建,因此效率高),使用完毕后,并不断开连接,而是将该连接对象归还给对象池。通过一个“借用”和“归还”的方式使得可以高效地对付频繁的数据库访问操作。目前常用的连接池主要有C3PO、BoneCP、DBCP、Proxool等。
综上所述,无论采用哪一种数据库操作方式,Java源代码中可能出现SQL注入漏洞的代码执行过程中,一定经过executeQuery、executeUpdate、execute 或executeBatch函数。本文提出的SQL检测算法,正是通过定位这些函数并以此为分析SQL注入隐患的切入点,跟踪这些函数的参数或参数表达式。如果这些参数表达式最终能追溯到某个特定的对象(如HttpServletRequest),则可以肯定这条路径将可能存在SQL注入隐患。
3 Java源代码SQL注入检测算法
抽象语法树AST(Abstract Syntax Tree)是程序源代码的抽象语法结构的树状表现形式,树上的每个节点表示源代码中的一种结构,AST的好处在于不依赖于具体的方法和语言细节。对于源代码的文法分析,首先进行词法分析,将源代码中所有字符串从前至后逐个字符进行扫描,并对每个“单词”进行标识。这些“单词”主要包括Java语言中的关键字、标识符、运算符和分隔符等。然后进行语法分析,将这些识别出来的“单词”序列分解成各类Java语法单位。
根据源代码静态分析扫描原理,结合Java 1.6的文法定义,本文提出的SQL注入检测算法通过对源代码的词法和语法分析,生成相应的抽象语法树,定义规则,根据规则遍历抽象语法树。
本文提出的检测算法实现步骤如下:
(1) 遍历抽象语法树,寻找METHOD_DECL结点中节点名为executeQuery或executeUpdate或execute或executeBatch的所有节点,并将它们保存在哈希表keyNode中。
(2) 找keyNode中的每一个节点,确认是否是正确的结点。首先得到keyNode节点的前继表达式,并得到前继表达式的返回值类型。若前继表达式的返回值类型为Java.sql.Statement,则可确认此结点,并转到下一步;若返回值类型不是Java.sql.Statement,则转步骤(2),检测下一个节点;
(3) 确认结点,取得第一个参数作为路径结点,分析并跟踪路径结点的数据流。具体为:分析并跟踪路径结点表达式为变量表达式;分析并跟踪路径结点表达式为方法表达式;跟踪退出的准则描述如下:①当所有跟踪变量均到达常量定值,则此路径不会产生SQL注入漏洞,转步骤(2);②跟踪到开始API定义的方法结点,转步骤(4)。
(4) 记录跟踪到的SQL注入的侵入路径,转步骤(2),检测下一个节点。若keyNode中所有结点都已检测完成,则算法终止。
4 算法实验
为了验证本文提出的算法的有效性,以一个Java程序为实验算例进行分析和说明,实验算例代码如图3所示。
在实验的Java源代码中,可能产生SQL注入的开始API和结束API(即Java源代码SQL注入检测规则),具体如表1所示。
根据SQL注入检测规则,利用本文所提出的检测算法对图3所示的Java源代码进行扫描分析,产生的抽象语法树如图4所示,可发现SQL注入漏洞,并报告相应的路径。
本文提出的SQL注入检测算法已经成功应用于本单位信息系统的Java源代码静态检测中。在新上线运行的某Web系统(约10万行代码)静态分析中,检测时间仅为5 s,检测准确识别率高达90%,而误报率仅有10%;通过实验和实际系统的测试和验证,本文提出的Java源代码SQL注入静态分析算法具有很好的应用效果和前景。
参考文献
[1] WILLIAM G J, VIEGAS H J, ORSO A. A classification of SQL injection attacks and countermeasures[C]. Proc. of International Symposium on Secure Software Engineering.2006.
[2] GOULD C, SU Z, DEVANBU P. JDBC checker: a static analysis tool for SQL/JDBC applications[C]. Proceeding of the 26th International conference on Software Engineering(ICSE). Washington D C: IEEE computer Society, 2004.
[3] GOULD C, SU Z, DEVANBU P. Static checking of dynamically generated queries in database applications[C].Proceedings of 26th International Conference on Software Engineering, 2004.
[4] WASSERMANN G, SU Z. An analysis framework for security in Web applications[C]. Proceedings of the FSE Work shop on Specification and Verification of Component Based System, 2004.
[5] HUANG Y W, HUANG S K, LIN T P, et al. Web application security assessment by fault injection and behavior monitoring[C]. Porceeding of the 11th International World Wide Conferecne, 2002.
[6] 张卓.SQL注入攻击技术及防范措施研究[D].上海:上海交通大学,2007.