SQL注入

#什么是SQL注入#

SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,比如很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。SQL注入攻击是黑客对数据库进行攻击的常用手段之一。

SQL注入的原理

  • SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。


  • 根据相关技术原理,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。


  • SQL注入的类型

    按照注入点类型来分

    (1) 数字型注入点

    在web端大概是http://xxx.com/news.php?id=1这种形式,其注入点id类型为数字,所以叫数字型注入点。这一类的SQL语句原型大概为select from 表名 where id=1。组合出来的SQL注入语句为`select from news where id=1 and 1=1`。

    (2) 字符型注入点

    在web端大概是http://xxx.com/news.php?name=admin这种形式,其注入点name类型为字符类型,所以叫字符型注入点。这一类的SQL语句原型大概为select * from 表名 where name=’admin’。注意多了引号,组合出来的SQL注入语句为select * from news where name=’admin’ and ‘1’=’1
    闭合单引号chr=’admin’ union select 1,2,3,4 and ‘1’=’1 chr=’admin’(闭合前面的单引号) union select 1,2,3,4 and ‘1’=’1

    (3) 搜索型注入点

    这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有”keyword=关键字”,有的不显示在链接地址里面,而是直接通过搜索框表单提交。此类注入点提交SQL语句,其原型大致为select * from 表名 where 字段like ‘%关键字%’
    组合出来的SQL注入语句为:select * from news where search like ‘%测试%’ and ‘%1%’=’%1%’

    按照数据提交的方式来分类

    (1) GET注入
    ———————————————————————–提交数据的方式是GET,注入点的位置在GET参数部分。比如有这样的一个链接http://xxx.com/news.php?id=1,id是注入点。
    原理:没有过滤GET请求中的参数信息中的非法字符。

    (2) POST注入

    使用POST方法提交数据,注入点位置在POST数据部分,常发生在表单中。
    原理:没有过滤掉POST请求中的参数信息中的非法字符。
    这里举一道南邮的例题,SQL注入1。
    这题直接查看源码
    截图
    因为POST请求中未对参数信息进行非法字符过滤,可以直接通过输入admin’) – adds(任意字符)来获取flag。

    (3) Cookie注入

    • HTTP请求的时候会带上客户端的Cookie,注入点存在Cookie当中的某个字段中。

    • 原理:cookie注入的形成有两个必须条件,条件1是程序对get和post方式提交的数据进行了过滤,但未对cookie提交的数据库进行过滤。在条件1的基础上还需要程序对提交数据获取方式是直接request(“xxx”)的方式,未指明使用request对象的具体方法进行获取。


    • 按照执行效果来分类

      (1) 基于布尔的盲注

      可以根据返回页面判断条件真假的注入。
      原理:基于布尔型SQL盲注即在SQL注入过程中,应用程序仅仅返回True(页面)和False(页面)。 这时,我们无法根据应用程序的返回页面得到我们需要的数据库信息。但是可以通过构造逻辑判断(比较大小)来得到我们需要的信息。
      此类注入常用到的三种函数:

      1. mid()函数

      mid(string,start,length)
      string(必需)规定要返回其中一部分的字符串。
      start(必需)规定开始位置(起始值为1)。
      length(可选)要返回的字符数。如果省略,则mid()函数返回剩余文本。

      1. substr()函数

      substr(string,start,length)
      string(必需)规定要返回其中一部分的字符串。
      start(必需)规定在字符串的何处开始。
      length(可选)规定被返回字符串的长度。
      应用:http://xxx.com/?id=1’ and ascii(substr((select database()),2,1))>100# (这里使用了ASCII判断)如果页面返回正常,则说明库的第二位大于ascii值大于100。

      1. left()函数

      left(string,length)
      string(必需)规定要返回其中一部分的字符串
      length(可选)规定被返回字符串的前length长度的字符。
      应用:http://xxx.com/?id=1’ and left(database(),1)<’t’# 如果页面返回正常,则说明库的首字母应该小于t

      (2)基于时间的盲注

      即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
      原理:在我们注入了SQL代码之后,存在以下两种情况:
      (1) 如果注入的SQL代码不影响后台(数据库)的正常功能执行,那么Web应用的页面显示正确(原始页面)。
      (2) 如果注入的SQL代码影响后台(数据库)的正常功能(产生了SQL注入),但是此时Web应用的页面依旧显示正常(原因是Web应用程序采取了“重定向”或“屏蔽”措施)。
      那么我们Web应用程序是否存在SQL注入?
      面对这种情况,前面的基于布尔的SQL盲注就很难发挥作用了。这时,我们一般采用基于web应用响应时间上的差异来判断是否存在SQL注入,即基于时间型SQL盲注。
      在基于时间型SQL盲注中,我们经常使用条件语句来判断我们的操作是否正确:
      即if语句/if()函数。
      在mysql中if语句的语法:
      IF expression THEN Statements; END IF;
      即如果某条件发生,那么执行语句一;否则,执行语句二。
      If()函数的语法如下:
      IF(expr1,expr2,expr3)
      即如果expr1的值为true,则返回expr2的值,如果expr1的值为false,则返回expr3的值
      此类注入主要用到的函数有两种:

      1. sleep()函数

      sleep()函数可使代码执行延迟若干秒。
      应用:http://xxx.com/?id=1’ and if(ascii(substr(database(),1,1)>116),1,sleep(5))#
      如果我们的查询语句为真,那么直接返回结果;如果我们的查询语句为假,那么晚过5秒之后返回页面。所以我们就根据返回页面的时间长短来判断我们的查询语句是否执行正确,即我们的出发点就回到了之前的基于布尔的SQL盲注,也就是构造查询语句来判断结果是否为真。

      (3)基于报错注入

      即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
      应用:select count(*),(concat(floor(rand()*2),(select version())))x from information_schema.tables group by x
      floor:取float的整数值
      rand:取0~1之间随机浮点值
      group by:为聚合函数,根据一个或多个列对结果集进行分组并有排序功能
      floor(rand()*2):rand为0~1,rand()*2为0~2,那么整个语句就是取0,1,2三个数字。
      也可写为:select count(*),(concat(floor(rand(0)*2),(select version())))x from information_schema.tables group by x
      其中rand()与rand(0)的区别为rand()生成的数据毫无规律,而rand(0)生成的数据则有规律可循 是:0110 0110

      (4)联合查询注入

      前提条件:页面上有显示位
      显示位的解释:在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端将数据展示在页面中,这个展示数据的位置就叫显示位。
      知道了在什么情况下使用联合注入之后,来看一看联合查询注入是如何实现的。

      1. 判断有无注入点
        (1) 在url后面加入and 1=1 --+执行,然后再写上and 1=2 --+执行,查看页面是否回显不同(回显不同说明这是一个整形注入),如果回显相同则可能存在字符注入。
        (2) 在参数后面加上一个’看页面回显是否相同
        (3) \为转义符
        (4) -1/+1回显上一个或下一个页面(用于整形判断)
        判断是整形注入还是字符型注入
        数字型注入与字符型注入的最大区别在于
        数字型不需要闭合,而字符型需要引号闭合。
        知道了整型还是字符型注入之后,使用order by 函数来判断站点中字段数目
        union的作用是将两个或多个select语句查询语句结果合并起来
      2. union必须由两条或两条以上的select语句组成,语句之间用关键字union分隔。
      3. union中的每个查询的列数必须相同。
      4. union会从查询结果集中自动去除了重复行
        使用group_concat()函数可以让查询获得的数据组成一行不显示
        例子:
        `select group_concat(SCHEMA_NAME) from information_schema.SCHEMATA
        count()`函数用于统计个数(类似于表的个数,数据库的个数等等)
        例子:
        `select count(SCHEMA_NAME) from information_schema.SCHEMATA
        concat()`函数可以将多个字符串拼接在一起
        介绍一些获取数据库中的所有数据名,表名,列名,字段名常用指令
        `select schema_name from information_schema.schemata(获取数据库名)`
        `select table_name from information_schema.tables (获取表名)`
        `select column_name from information_schema.columns(获取所以列名)`
        
        (5)堆叠注入(堆查询注入)

      原理:
      在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在;结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者 union all 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
      例如,用户输入:1;DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
      局限:
      并不是每一个环境学都可以执行,可能受到API或者数据库引擎不支持的限制。
      在MYSQL下使用堆叠注入查询数据: select * from 表名 where id=1;select 1,2,3

      SQL注入漏洞的修复

      (1) 接收提交的参数,把特殊符号过滤掉

      可以通过使用mysql_real_escape_string()函数对sql语句中的特殊字符进行转义。
      受影响的字符有:
      \x00 \n \r \ ‘ “ \x1a
      如果成功,则该函数返回被转义的字符串。如果失败,则返回false。

      (2) 采用sql语句预编译和绑定变量,防御sql注入。

      String sql = "select id, no from user where id=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); ps.executeQuery();
      采用PreparedStatement,就会将SQL语句:“select id,no from user where id=?”预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划。后面输入的参数,无论输入的是什么,都不会影响sql语句的语法结构,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。所以即使后面输入这些sql命令,也不会当成sql命令来执行了,因为这些sql命令的执行,必须先通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,绝对不可能作为sql命令来执行的,只会被当做字符串字面值参数。

      (3) 字符串替换

      使用str_replace()函数对字符串变量的内容进行字符替换。
      语法如下:str_replace(“需要替换的字段”,“替换为的字段”,”字符串内容”);
      例如:
      $id=$_GET[‘id’]; $id=str_replace(“and”,” ”,$id);
      这样一来,变量id字符串中的“and”被替换为空字符,可以理解为被删除了。
      而这种方式只替换了一个字符,sql中的执行字符可能需要替换十几个字符。所以需要使用数组的方式,将字符替换封装为一个函数,便于在任何时候调用它。
      例如:
      function fliter_sql($value){ $sql = array(“select”,”insert”,”updata”,”delete”); $sql_re=array(“ ”,” ”,” ”,” ”); return str_replace($sql,$sql_re,$value); }
      类似这样的进行一些字符的过滤。