◆刘存普 胡 勇
Web应用的SQL注入防范研究
◆刘存普 胡 勇
(四川大学电子信息学院 四川 610065)
SQL注入严重威胁Web应用的安全。本文介绍SQL注入的基本原理,分析SQL注入的基本过程以及几种常见的方法,并从多方面提出防御SQL注入攻击的安全措施。
网络信息安全;SQL注入;防御措施
网络安全是信息化的重要基石。网络信息涉及国家的军事、政府、经济等领域及公民的个人信息,其中许多信息是敏感信息,甚至是机密信息,而层出不穷的各种安全漏洞和不断变化的攻击手段,使得网络和信息安全面临艰巨挑战。
目前,针对Web应用程序和数据库的攻击已成为网络安全隐患的主要方面,其具有技术门槛低、可绕过安全设备、攻击回报高等特点。据开放Web应用程序安全项目(Open Web Application Security Project,OWASP)近些年的统计中,注入问题仍然是影响网络安全最严重的漏洞[1]。在许多安全论坛,SQL注入攻击也是热点话题之一,对SQL注入问题的研究非常重要。
SQL是操作数据库数据的结构化查询语言,网页的应用数据和后台数据库中的数据进行交互时会采用SQL。而SQL注入是将Web页面的原URL、表单域或数据包输入的参数,修改拼接成SQL语句,传递给Web服务器,进而传给数据库服务器以执行数据库命令。如Web应用程序的开发人员对用户所输入的数据或cookie等内容不进行过滤或验证(即存在注入点)就直接传输给数据库,就可能导致拼接的SQL被执行,获取对数据库的信息以及提权,发生SQL注入攻击。
SQL注入攻击的一般过程如图1所示。
图1 SQL注入过程
2.1 寻找注入点
下面是一种经典的通过报错判断是否存在SQL注入点的方法:
①http://www.xxx.com/xxx.asp?id=1
②http://www.xxx.com/xxx.asp?id=1 and 1=1
③http://www.xxx.com/xxx.asp?id=1 and 1=2
返回结果如果如下就可以基本确定这个网站存在SQL注入点。
①正常显示。
②正常显示,与①相同。
③页面报错。
2.2 获取后台数据库的信息
不同的数据库注入的方式略有不同。在注入参数后添加一个空格,再写如下代码,根据页面的报错返回信息,就能猜测出网站的数据库类型。
and user>0
user是MS SQL Server的一个固定函数,将user与0相比较,页面会显示错误信息:将nvarchar值“xxx”转换数据类型为int的列时发生语法错误。而Access数据库不存在user,不会产生上述错误信息。另外,下面的两个语句选其中一句执行就能猜测到数据库是MS SQL Server还是Access。因为MS SQL Server中有固定的表sysobject,①返回正常。而Access中的固定表是msysobject,②返回正常。
①and(select count(*)from sysobject)>0
②and(select count(*)from msysobject)>0
在MYSQL中,可以用@@version或是version()来返回当前的版本信息。但同样MSSQL中,也能用@@version返回版本信息。③④返回都是正确时,则是MYSQL。如果③返回错误,④返回正确时,则是MSSQL。
③and version()>0
④and @@version>0
在MSSQL中可以使用函数substring,使用⑤返回成功。ORACLE则只能使用函数substr,使用⑥返回成功。
⑤substring('abc',1,1)=a
⑥substr('abc',1,1)=a
ORACLE还能使用语句banner FROM v$version和banner FROM v$version WHERE rownum=1来返回数据库服务器版本信息。
2.3 读取数据库表的信息
获取数据库表信息包括猜解表名、字段名。
(1)猜表名
可利用语句:and exists(select * from 表名)猜测表名。如果页面返回是正常的,就证明所猜测的表名正确;如果页面返回不正常,那么就证明所猜测的表名不正确,换表名继续执行上述语句,一直到返回正常页面而猜到正确的表名。这里可以用工具组成库表字典进行猜解。如系统常用的存储用户的表名有admin、manage、user、member等。
(2)猜字段
假设user是正确的表名,可利用语句:and exists(select 字段名 from user)猜测字段名。过程与猜解表名相同。常用的字段名有username、password、user、pass、name、pass、pwd、usr、psd等。
(3)查询管理员用户名和口令
①联合查询注入
http://www.xxx.com/xxx.asp?id=1 order by n
这里的n是任意自然数。如果输入n,返回正常的页面,而输入n+1,页面显示不正常,则该网页上的字段数为n+1。这里假设字段数为10,表名为admin。运用联合查询:
http://www.xxx.com/xxx.asp?id=1 and 1=2 union select 1,2,3,4,5,6,7,8,9,10 from admin
网页会返回几个数字(数字在字段数之内),而这些数字就是显示内容的字段,如图2所示,显示位为4,5,7。这时可以将上述所提交4,7分别替换为字段名如username,password如图3所示,可能查询到管理员的用户名和口令。
图2页面字段数显示
图3替换数字后得到的用户名及口令
②ASCII码暴力破解
有些注入点不支持联合查询,用union select网页会报错,得不到字段的内容[2]。这时就要用ASCII码暴力破解。语句:and (select top 1 len(字段名)from 表名)>n,其中n为自然数。当输入n,返回正常的网页,而替换n为n+1,页面报错,则这个字段的内容长度为n+1。再用语句:and(select top 1 asc(mid(字段名,m,1))from 表名)>k,其中m、k也是自然数(n>=m)。分析该语句,最里边的mid(字段名,m,1)函数,1表示截取字段的字符长度为1,m表示从第m个字符开始截取。函数asc()是将函数mid中截取的字符转换成ASCII码。函数top 1,表示返回的第一条记录,上述语句中最右边的“>k”,是将转换的ASCII码值与k比较。经过多次调整k的值,最终得到截取字符的ASCII码。
③Access偏移注入
有些网站能够得到表名,但是由于字段名过于生僻,难以猜解。如果这个网站所用的数据库类型是Access,这时就可以用Access偏移注入。如一个网站通过order by的方法得到字段数为47,表名为admin,其中有个字段为id。用上述的联合查询法没有得到显示位,用union select 1,* from admin。如果返回错误,则继续用union select 1,2,* from admin。以此类推,一直到页面返回正常为止。这里假设到33返回正常页面。这里的*就代表了14(47-33=14)个字段。
union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,* from(admin as a inner join admin as b on a.id=b.id)
这里(admin as a inner join admin as b on a.id=b.id)是admin表自连接,这样from 后面的表就会成为字段数加倍的表。*(14*2=28)代表的字段就会拓宽,就加大了显示用户名和密码在可显示位置的几率。如果还没显示出来就在19(47-14*2=19)后面加个a.id,还没显示再加b.id。
union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,a.id,b.id,* from(admin as a inner join admin as b on a.id=b.id)。如果还没显示出来就继续加个c.id,因为又多了一个c.id所以*(14*3=42)所代表的字段数也要加倍。
union select 1,2,3,4,5,a.id,b.id,c.id,* from((admin as a inner join admin as b on a.id=b.id)inner join admin as c on a.id=c.id)。如图4所示。
图4 偏移注入
3.1 敏感字符直接过滤
使用过滤敏感字符的方法对用户提交信息进行检查。SQL注入攻击的敏感字符有:“and”“or”“union”“order”“cmd”“update”“’” “//”“create”“--”等。针对上列的敏感字符,设置相应的字符处理函数。这种过滤方法容易漏掉某些字符,容易绕过,已渐渐被淘汰。
3.2 SQL语句预编译和绑定变量[4]
不将用户提交的内容直接嵌入到数据库执行命令的语句中,而是通过参数进行传递,能有效地防御SQL注入。如:
String sql = "select id,no from user where id=?";
PreparedStatementps = conn.prepareStatement(sql);
ps.setInt(1,id);
ps.executeQuery();
代码中的PreparedStatement,会把SQL语句:"select id,no from user where id=?" 先编译好,也就是SQL的引擎会先进行语法分析,产生语法树,生成执行计划。输入的内容不能造成该SQL语句的命令变化,即使后来提交类似SQL的语句,也不会被当作是SQL语句来执行命令,而只会被当作字符串。
还有种类似的方法,比较用户输入提交前后动态SQL语句的语法结构是否一致来检测SQL注入攻击[3]。
3.3 检查参数类型
SQL语句预编译不能在所有情况下使用,在一些情况下必需得使用字符串拼接的方法。所以应该严格检测参数的类型,也可以使用一些安全性高的函数,用来防止SQL注入攻击。
例如 String sql = "select id,no from user where id=" + id;
在收到使用者输入的数据时,检测字段“id”的内容是否是整数类型,如果不是则一律报错或者弹窗警告。在复杂的情况下还能采用正则表达式来检测数据是否合法。这样也能防御SQL注入攻击。下例就是强制转换所输入数据中特殊字符的代码:
MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
name = ESAPI.encoder().encodeForSQL(codec,name);
String sql = "select id,no from user where name=" + name;
函数“ESAPI.encoder().encodeForSQL(codec,name)”可以把“name”中的一些特殊字符进行转译,这样SQL引擎就不会把“name”中的特殊字符当成SQL命令来进行语法编译了。
3.4 权限管理
删除sa用户,新建一个权限为sa的用户,用户名和口令要有较高的复杂性,以防暴力破解。针对web应用,新建一个web连接用户,去掉所有服务器角色,在用户映射中加入此用户要操作的数据库db_owner和db_public,并另加必须的权限(如insert/delete/select/update等)。
一般的SQL注入都是从网站的前台网页寻找漏洞,可针对前台操作和后台操作分别建立不同的数据库操作用户。只赋予用户需要的最低权限。后台用户的权限也只能是当前数据库的权限。千万不能使用root级的账户连接数据库。
3.5 SQL注入防御模型[5-8]
由于针对SQL注入的攻击方式多种多样,没有单独的一种SQL注入防御技术能够有效的预防所有的SQL注入攻击。所以需要将各种防御方法联合起来运用,共同建立一个防御模型才能更加有效地防御SQL注入攻击。
本文对SQL注入的几种方法进行比较详细的讲解。同时从屏蔽关键字、语句预编译、参数类型、用户权限进行对SQL注入的防御。但是这里并没有对如何防止绕过防火墙的SQL注入进行研究,还需要进一步的探索。网站开发与网站管理才是最重要的防御环节,管理人员与开发人员切不可大意马虎。
[1]OWASPFoundation.OWASPTopTenProject[EB/OL].https://www.owasp.org/index.php/Category:OWASP_Top_Ten_ Project,2015.
[2]陈炜.基于手工SQL注入的Web渗透测试技术研究[D].中北大学,2015.
[3]田玉杰,赵泽茂,王丽君等.基于分类的SQL注入攻击双层防御模型研究[J].信息网络安全,2015.
[4]周敬利,王晓峰,余胜生等.一种新的饭SQL注入策略的研究与实现[J].计算机科学,2006.
[5]田宇杰,赵泽茂,张海川等.二阶SQL注入攻击防御模型[J].信息网络安全,2014.
[6]李元鹏.SQL注入扫描分析工具的实现与攻击防范技术研究[D].北京交通大学,2014.
[7]赵阳,郭玉翠.新型SQL注入攻击的研究与防范[J].计算机系统运用,2016.
[8]沈寿忠.基于网络爬虫的SQL注入与XSS漏洞挖掘[D].西安电子科技大学,2009.