SQL注入在英文中称为SQL Injection,是黑客对Web数据库进行攻击的常用手段之一。在这种攻击方式中,恶意代码被插入到查询字符串中,然后将该字符串传递到数据库服务器进行执行,根据数据库返回的结果,获得某些数据并发起进一步攻击,甚至获取管理员帐号密码、窃取或者篡改系统数据。
为了让读者了解SQL注入,首先我们举一个简单的例子。
首先,数据库中有一个表格:
USERS
ACCOUNT(主键) | PASSWORD | UNAME |
… |
|
|
… |
|
|
有一个登录页面,输入用户的账号、密码,查询数据库,进行登录,为了将问题简化,我们仅仅将其SQL打印出来供大家分析。
login.jsp
- <%@ page language="java" import="java.util.*" pageEncoding="gb2312"%>
- 欢迎登录
- <form action="loginResult.jsp" method="post">
- 请您输入账号:
- <input name="account" type="text">
- <BR>
- 请您输入密码:
- <input name="password" type="password">
- <input type="submit" value="登录">
- </form>
运行,效果如下:
![]() |
在文本框内输入查询信息,提交,能够到达loginResult.jsp显示登录结果。loginResult.jsp代码如下:
loginResult.jsp
- <%@ page language="java" import="java.util.*" pageEncoding="gb2312"%>
- <%
- //获取账号密码
- String account = request.getParameter("account");
- String password = request.getParameter("password");
- if(account!=null)
- {
- //验证账号密码
- String sql = "SELECT * FROM USERS WHERE ACCOUNT='"
- + account
- + "' AND PASSWORD='"
- + password
- + "'";
- out.println("数据库执行语句:<BR>" + sql);
- }
- %>
运行login.jsp,输入正常数据(如guokehua,guokehua):
![]() |
![]() |
熟悉SQL的读者可以看到,该结果没有任何问题,数据库将对该输入进行验证,看能否返回结果,如果有,表示登录成功,否则表示登录失败。
但是该程序有漏洞。比如,客户输入账号为:" aa' OR 1=1 --",密码随便输入,如"aa":
![]() |
![]() |
该程序中,SQL语句为:
- SELECT * FROM USERS WHERE ACCOUNT='aa' OR 11=1 --' AND PASSWORD='aa'
其中,--表示注释,因此,真正运行的SQL语句是:
- SELECT * FROM USERS WHERE ACCOUNT='aa' OR 11=1
此处,"1=1"永真,所以该语句将返回USERS表中的所有记录。网站受到了SQL注入的攻击。
另一种方法是使用通配符进行注入。比如,有一个页面,可以对学生的姓名(STUNAME)从STUDENTS表中进行模糊查询:同样,为了将问题简化,我们仅仅将其SQL打印出来供大家分析。
query.jsp
- <%@ page language="java" import="java.util.*"
pageEncoding="gb2312"%>- 欢迎查询
- <form action="queryResult.jsp" method="post">
- 请您输入学生姓名的模糊资料:
- <input name="stuname" type="text">
- <input type="submit" value="查询">
- </form>
运行,效果如下:
![]() |
在文本框内输入查询信息,提交,能够到达queryResult.jsp显示登录结果。queryResult.jsp代码如下:
queryResult.jsp
- <%@ page language="java" import="java.util.*" pageEncoding="gb2312"%>
- <%
- //获取姓名
- String stuname = request.getParameter("stuname");
- if(stuname!=null&&!stuname.equals(""))
- {
- String sql = "SELECT * FROM STUDENTS WHERE STUNAME LIKE '%"
- + stuname
- + "%'";
- out.println("数据库执行语句:<BR>" + sql);
- }
- else
- {
- out.println("输入不正确!");
- }
- %>
运行query.jsp,输入正常数据(如guokehua),提交,效果为:
![]() |
![]() |
说明该程序不允许无条件的模糊查询。
但是该程序也有漏洞。比如,客户输入:" %'--":
![]() |
![]() |
- SELECT * FROM STUDENTS WHERE STUNAME LIKE '%%'
该语句中,也会将STUDENTS中所有的内容显示出来。相当于程序又允许了无条件的模糊查询。
更有甚者,可以在文本框内输入:" %';DELETE FROM STUDENTS --":
查询显示的结果为:
![]() |
这样,就可以删除表STUDENTS中所有的内容。
提示
该攻击中,数据库中表名STUDENTS可以通过猜测的方法得到,如果猜测不准确,那就没办法攻击了。
还有一种方法是直接通过DELETE语句进行注入,如有下面语句:
- String sql = "DELETE FROM BOOKS WHERE BOOKNAME='"
- + bookName
- + "'";
其中,bookName为输入的变量。
如果bookName以正常数据输入,如输入Java,语句为:
- DELETE FROM BOOKS WHERE BOOKNAME='Java'
正常,但是如果给bookName的值为:Java' OR 1=1 --,语句变为:
- DELETE FROM BOOKS WHERE BOOKNAME=' Java' OR 11=1 --'
实际执行的语句为:
- DELETE FROM BOOKS WHERE BOOKNAME=' Java' OR 11=1
可以将表中所有内容删除。
不仅仅SELECT语句会被注入,UPDATE语句也会被注入,如有下面表格:
- CREATE TABLE CUSTOMER (
- ACCOUNT VARCHAR(25),
- PASSWORD VARCHAR(25),
- CNAME VARCHAR(25),
- MONEY INT
- )
有一条语句,为:
- String sql = "UPDATE CUSTOMER SET CNAME ='"
- + cname
- + "' WHERE ACCOUNT='"
- + account
- + "'";
其中,cname和account为输入的变量。
如果cname输入和account以正常数据输入,如输入"guokehua"," 0001",语句为:
- UPDATE CUSTOMER SET CNAME ='guokehua' WHERE ACCOUNT='0001'
正常,但是如果给cname的值为:" guokehua', PASSWORD='111' --",account随便输入,如"111",语句变为:
- UPDATE CUSTOMER SET
- CNAME ='guokehua', PASSWORD='111' --' WHERE ACCOUNT='111'
就将所有记录的CNAME改为guokehua,密码改为111。
同样是上面那张表,如有INSERT语句:
- String sql = "INSERT INTO CUSTOMER VALUES('"
- + account +"', '"
- + password +"', '"
- +cname +"', 0) "
如果account、password和cname以正常数据输入,如输入"0001","akdj","guokehua",语句为:
- INSERT INTO CUSTOMER
- VALUES('0001', 'akdj', 'guokehua',0)
正常,但是如果给cname的值为:" guokehua', 1000) --",其他值和前面一样,语句变为:
很明显,注册一个账号,默认的MONEY字段变成了1000。
- INSERT INTO CUSTOMER
- VALUES('0001', 'akdj', ' guokehua', 1000) --',0)
SQL注入攻击的主要危害包括:
非法读取、篡改、添加、删除数据库中的数据;
盗取用户的各类敏感信息,获取利益;
通过修改数据库来修改网页上的内容;
私自添加或删除账号;
注入木马;等等。
由于SQL 注入攻击一般利用的是利用的是SQL 语法,这使得于所有基于SQL 语言标准的数据库软件,如SQL Server,Oracle,MySQL, DB2等都有可能受到攻击,并且攻击的发生和Web编程语言本身也无关,如ASP、JSP、PHP,在理论上都无法完全幸免。
SQL 注入攻击的危险是比较大的。很多其他的攻击,如DoS等,可能通过防火墙等手段进行阻拦,但是而对于SQL 注入攻击,由于注入访问是通过正常用户端进行的,所以普通防火墙对此不会发出警示,一般只能通过程序来控制,而SQL攻击一般可以直接访问数据库进而甚至能够获得数据库所在的服务器的访问权,因此,危害相当严重。
以上问题的解决方法有很多种,比较常见的有:
1:将输入中的单引号变成双引号。这种方法经常用于解决数据库输入问题,同时也是一种对于数据库安全问题的补救措施。
例如代码:
- String sql = "SELECT * FROM T_CUSTOMER
WHERE NAME='" + name + "'";
当用户输入"Guokehua' OR 1=1 --"时,首先利用程序将里面的'(单引号)换成"(双引号),于是,输入就变成了:"Guokehua" OR 1=1 --",SQL代码变成了:
- String sql = "SELECT * FROM T_CUSTOMER WHERE
NAME=' Guokehua" OR 11=1 --'"
很显然,该代码不符合SQL语法。
如果是正常输入呢?正常情况下,用户输入"Guokehua",程序将其中的'换成",当然,这里面没有单引号,结果仍是Guokehua,SQL为:
- String sql = "SELECT * FROM T_CUSTOMER
WHERE NAME='Guokehua'";
这是一句正常的SQL
不过,有时候,攻击者可以将单引号隐藏掉。比如,用"char(0x27) "表示单引号。所以,该方法并不是解决所有问题的方法。
2:使用存储过程。
比如上面的例子,可以将查询功能写在存储过程prcGetCustomer内,调用存储过程的方法为:
- String sql = "exec prcGetCustomer'" +name + "'";
当攻击者输入"Guokehua' or 1=1 --"时,SQL命令变为:
- exec prcGetCustomer 'Guokehua' or 11=1 --'
显然无法通过存储过程的编译。
注意,千万不要将存储过程定义为用户输入的SQL语句。如:
- CREATE PROCEDURE prcTest @input varchar(256)
- AS
- exec(@input)
从安全角度讲,这是一个最危险的错误。
提示
实际上,用存储过程也不能完全防范本节出现的问题,有兴趣的读者可以设计另一个攻击方法。安全本身就是在攻防间进行的博弈,这也没有什么好奇怪的。
3:认真对表单输入进行校验,从查询变量中滤去尽可能多的可疑字符。
可以利用一些手段,测试输入字符串变量的内容,定义一个格式为只接受的格式,只有此种格式下的数据才能被接受,拒绝其他输入的内容,如二进制数据、转义序列和注释字符等。另外,还可以对用户输入的字符串变量进行类型、长度、格式和范围来进行验证并过滤,也有助于防治SQL注入攻击。
4:在程序中,组织SQL语句时,应该尽量将用户输入的字符串以参数的形式来进行包装,而不是直接嵌入SQL语言。
由于很多SQL注入都是把用户输入和原始的SQL语言嵌套组成查询语句来完成攻击,而参数不能被嵌套进入SQL查询语言,因此,该种措施可以在某种程度上防止SQL注入。不过,在不同的语言和产品里面,做法稍有不同,如:
如果使用Java系列,可以使用PreparedStatement代替Statement;
SQL Server数据库中可以使用存储过程,结合Parameters集合;Parameters集合提供了长度验证和类型检查的功能,Parameters集合内的内容将被视为字符值而不是可执行代码。
.net中,SQL语句可以用参数来包装;等等。
5:严格区分数据库访问权限。
在权限设计中,对于应用软件的使用者,一定要严格限制权限,没有必要给他们数据库对象的建立、删除等权限。这样,即使在收到SQL注入攻击时,有一些对数据库危害较大的工作,如DROP TABLE语句,也不会被执行,可以最大限度的减少注入式攻击对数据库带来的危害。
6:多层架构下的防治策略。
在多层环境下,用户输入数据的校验与数据库的查询被分离成多个层次。此时,应该采用以下方式来进行验证:
用户输入的所有数据,都需要进行验证,通过验证才能进入下一层;此过程与数据库分离的;
没有通过验证的数据,应该被数据库拒绝,并向上一层报告错误信息。
7:对于数据库敏感的、重要的数据,不要以明文显示,要进行加密。关于加密的方法,读者可以参考后面的章节。
8:对数据库查询中的出错信息进行屏蔽,尽量减少攻击者根据数据库的查询出错信息来猜测数据库特征的可能。
9:由于SQL注入有时伴随着猜测,因此,如果发现一个IP不断进行登录或者短时间内不断进行查询,可以自动拒绝他的登陆;也可以建立攻击者IP地址备案机制,对曾经的攻击者IP进行备案,发现此IP,直接拒绝。
10:可以使用专业的漏洞扫描工具来寻找可能被攻击的漏洞。