4.1 SQL Injection


简介

这篇文章主要探讨SQL 注入原理、利用面、如何绕过代码过滤,而对于具体的代码暂不做过多探究,若感兴趣可以参阅 不同数据库的操作笔记 和 全面的技术细节

比赛中,通常没有 WAF,而在实际渗透中,目标通常都会安装 WAF 进行保护,而关于如何绕过 WAF 进行 SQL 注入,这就留到后面的 WAF 绕过章节。

什么是 SQL 注入?

sql 注入,直白点讲就是更改原本的 sql 语句含义。所以条件就显而易见:

  1. 程序的 SQL 语句中,有用户可控输入。即用户通过什么方式影响程序中的 SQL 语句。
  2. 对用户可控输入未严谨过滤。即为什么用户能更改 SQL 语句原意。
成功的 SQL 注入能干什么?
  • 修改程序逻辑。例如:改变验证逻辑,在登录过程中,可以跳过对密码检查。
  • 获取数据,修改数据。例如:可以获取网站存储的用户信息。修改表中金额等敏感数据。
  • 读取系统文件,写入系统文件。一般数据库管理系统都具有读写文件的能力。据此,有可能读取系统敏感例如 /etc/passwd ,或写入 shell 到系统上。
  • 执行系统命令。某些数据库具有执行命令的功能。
  • 与其他漏洞结合。例如利用 数据库管理系统发送请求功能来 ssrf。

等等

SQL 是什么?实际中采用它做什么?它的原理是什么?
  • sql 是 Structured Query Language 的缩写,是数据库管理系统用来操作数据的一种语言。

  • 像网站的注册、登录功能就会涉及向数据库中插入、查询等操作。

  • 程序通过编程语言提供的数据库 API + SQL 语句与数据库进行交互,进行数据的存取。

    简单点说,就是程序利用编程语言封装好的数据库 api 与数据库管理系统进行交互。

    在一开始,程序通过 tcp 连接到数据库,然后当执行 sql 命令时,会将命令通过建立起的 tcp 连接传给数据库管理系统,然后系统就执行接收到的命令。

实际渗透中存在的限制

首先是 WAF 防御。

其次是代码防御。

即使注入成功,也有可能当前数据库用户权限很低。

即使是获取了数据库 root 权限。也通常因为运行数据库管理软件的是普通用户,不一定能完全访问系统文件。

如何攻击?

三步走。检测是否存在注入点,绕过防御,然后进行利用。

经典工具 sqlmap 。

如何防御?

预处理语句 + WAF

通用思路

1. 判断是否存在注入点?

根据应用场景需要执行的功能猜想 SQL 语句是怎样的?

例如,在用户登录过程中,一般使用 select 语句进行检索。而用户注册功能中,使用 insert 向表中添加用户数据。

这里必须懂得 SQL 基础语句的含义,才能猜测功能。

常见有 Select、Updata、Insert、Delete。

2. 验证注入点是否存在?
  1. 当页面直接显示信息,union select 和 error 报错注入。
  2. 当页面不直接显示信息,那么就为盲注,此时也是有判断方法的。
    1. 利用 out-of-band 技术,最常见的是利用 dnslog ,但听说也有利用 icmp。但根本在于,数据库管理系统要具有能发送请求的功能(例如mysql 的load_file 函数)详见 Mysql oob 技术
    2. 利用推断,例如 bool 注入、与 time 注入

这块的方法其实也是获取数据的方式。

3. 绕过代码验证

见后文。再次强调一下,此处绕过的只是代码中常见有缺陷的防御手段,而针对 WAF 暂不作讨论。

4. 利用手法

其实,利用手法也参照 文章顶部参考文献的 1 和 4 。

另外,其实也可以通过大概研究一下 SQLMAP 的利用 payload 来学习利用手法。

绕过代码验证

针对代码的验证规则

如果代码只是进行简单的替换,则可以根据规则尝试,大小写绕过、双写关键字。

利用数据库管理系统特殊语法

例如,在需要字符串时,以下几种方法是几乎等效的。

select concat(char(0x67),char(0x75))
select 'users'=0x7573657273
SELECT char(114,111,111,116)
以上都可以表示字符串

在猜测表的列数时,也有多种方法

order by 4
select into @a,@b,@c
union select NULL,NULL

当空格被过滤

注释 
	select/**/*/**/from/**/users; 
	/*!select*//*!**//*!from*//*!users*/
	/*!50110 KEY_BLOCK_SIZE=1024 */
url编码
	%09 TAB 键(水平)%0a 新建一行 %0c 新的一页 %0d return 功能 %0b TAB 键(垂直)
括号
	select(a)from(yz)where(a=1)

更多数据库语法笔记,详见 4

利用字符集转换特性

宽字节注入通常是用来绕过转义符反斜杠 \ ,其原理是通过字符集转化,将多个字符视为一个字,从而 “吃掉” 转义符。

从原理上来讲,宽字节注入条件有两个:

  1. 可变长编码。这才可能发生吃掉一个字符的现象。
  2. 反斜杠 0x5c 要是变长编码中多字节编码中的非第一个的有效字符。

因为 Mysql 有多种编码格式,所以实际转化在哪步发生原理较为复杂,但实际攻击中只需要修改下 payload 尝试下即可。

其它

其它注入手法
二次注入

像上面 验证注入点是否存在 中说的注入手法都是属于直接注入,也就是说一次试探就可以知道结果,而二次注入需要注入恶意数据,和引用恶意数据两步。当注入恶意数据被程序进行正确过滤,而引用恶意数据时没有正确过滤时就会产生二次注入。其产生的原因是由于程序信任从数据库取出的数据。

例如 插入数据的sql

表中的内容

所以,当代码中引用这个数据时,没有进行过滤的话,就会产生漏洞。

解决办法也有两个,1. 在一次过滤前,双重转义。但这样等于会多引进几个字符。有可能发生长度截断。2. 在取出数据时进行过滤。

堆叠注入

在一次 sql 函数调用中执行多条 sql 语句。

需要调用特殊的函数或进行特殊设置。这个条件现实中一般较难满足。

例如 php 中使用 $mysqli->multi_querymysqli_multi_query 函数。

例如 java 中必须在连接数据库时指定允许堆叠查询 String dbUrl = "jdbc:mysql:///test?allowMultiQueries=true";

截断

比赛中较为常见,常出现于具有注册和登录功能,可以通过注册特殊用户名来改变登录验证逻辑。

假设程序检验是否以 admin 用户登录,但是只是通过检查 sql 语句 是否有 返回结果。那么利用以下数据库特性就能达到绕过的方式。

对于 mysql 来说 
'admin'='admin '
'AdMIn  '='admin'

例如,注册以下用户名

'AdMIn'  //大小写敏感
'admin ' //后加多余空格
'admin                                              a' 
//为预防编程语言去除末尾的空格在尾部添加a,而中间大量的空格为了利用用户名长度进行截断。

登录时,用户名 admin 而密码为新账户的密码。

预处理语句

预处理语句对关键符号进行转义的过程其实是由数据库系统实现的。详情可以抓一下 mysql 的数据包。

一般来讲,除非数据库编码存在缺陷可以使用宽字节注入,否则没有什么好的绕过方法。

注意某些语言具有模拟预处理功能,也就是说不是真正的预处理,例如 php 开关如下

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

这个特性主要跟在开发中 sql 查询效率方面有关。

SQLMAP

关于 sqlmap ,初学阶段基础使用会就行,帮助文档很详细,后期的tamper 脚本或者二次开发就等到基础了解差不多再进行。

以下是我目前在使用时遇到的几个坑,sqlmap 版本为 1.5.1.44#dev:

  1. 注意无法使用 --sql-query 执行 show grants 等非 select 语句,官方的解释是因为 这个东西不能嵌入存在注入的语句

    其实可以替代一下,show grants 数据其实来自下面这个表

    select * from information_schema.user_privileges;

  2. 当要指定 https 时,要主动指定 --force-ssl ,否则即使 url 为 https 也没用。

  3. 当 level > 3 ,才会识别头部中参数,否则 -p 指定了也用

WAF 绕过

预留,等链接到其它章节

ctf 中的 sql 注入

  1. [CISCN2021] 初赛 easy_sql

    考点: information_schema 被代码过滤,无列名注入。

    由于题目可以用 sqlmap 跑出三种类型注入点 error、bool、sleep。所以解法有两种:

    • 一种是通过 join 报错爆出列名,然后通过报错获取数据

      (SELECT * FROM (SELECT * FROM SOME_EXISTING_TABLE JOIN SOME_EXISTING_TABLE b) a)
      (SELECT * FROM (SELECT * FROM SOME_EXISTING_TABLE JOIN SOME_EXISTING_TABLE b USING (SOME_EXISTING_COLUMN)) a)
      
    • 另一种是通过 bool ,跳过列名爆破,直接逐步判断获取数据

      # 爆出列数,并且除数据位,其它几位不影响结果
      (select 1,2,3) > (select * from table)
      

    疑问:在无法访问 information_schema 的情况下,能否使用 select * from table ;

    可以,因为 information_schema 是一个投影,并不是真正存在的数据库。无法限制(revoke)正常 mysql 用户对此数据表的访问,见此 。

  2. [强网杯 2019] 随便注

    堆叠注入中的 sql 语句。sqlmap 探测必须要是用 select 等关键词。而当程序过滤了时,其就无法探测。

    当存在堆叠注入。可以通过堆叠得到表、列等信息。此时过滤 select 等,无法获取数据。

    1. 利用程序已写死的 sql 语句。

      selsect id,data from words where id =

      利用堆叠将目标表中的 flag 列列名修改为 id 或 data,再将目标表表名修改为 words 。从而利用已有的 sql 语句获取数据。

    2. 利用预处理过程。其可以从字符串中生成命令,而字符串可以替换。

      1';PREPARE hacker from concat(char(115,101,108,101,99,116), ' * from `1919810931114514` ');EXECUTE hacker;#
      
      PREPARE hacker from concat('``s``','``elect``', '` `* ``from` ``1919810931114514` ');EXECUTE hacker;#
      

相关