Loading

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. 利用手法

其实,利用手法也参照 文章顶部参考文献的 14

另外,其实也可以通过大概研究一下 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 尝试下即可。

image-20210305105621448

其它

其它注入手法

二次注入

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

img

例如 插入数据的sql

image-20210305122403449

表中的内容

image-20210305122427467

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

解决办法也有两个,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 而密码为新账户的密码。

image-20210306103142914

image-20210306103155528

image-20210306103257790

预处理语句

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

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

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

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

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

Mybatis

MyBatis框架底层已经实现了对SQL注入的防御,但存在使用不当的情况下,仍然存在SQL注入的风险。

Mybatis中 ${ } 是字符串替换,在处理时会将 sql 中的 ${ } 替换为变量的值,传入的数据不会加两边加上单引号

#{ } 是预编译处理,在处理时会将 sql 中的 #{ } 替换为?,然后调用 PreparedStatement 的 set 方法来赋值,传入字符串后,会在值两边加上单引号

order by 注入

此漏洞出现频率较高,由于直接使用 #{} 会将对象转成字符串,形成 order by "user" desc 造成错误,因此很多研发会为了方便采用 ${} 来解决,而 order by的语句也会默认使用 ${q},从而造成SQL注入。

@RequestMapping("/mybatis/vul/order")
public List<User> orderBy(String field, String sort) {
    return userMapper.orderBy(field, sort);
}

// mapper.xml语句
<select id="orderBy" resultType="com.best.hello.entity.User">
    select * from users order by ${field} ${sort}
</select>

安全代码:排序映射,缺失是代码量会增加。如果想减少代码量,需要在Java层面做映射,设置一个字段/表名数组,仅允许用户传入索引值,这样保证传入的字段或者表名都在白名单里面。

in语句

和上面的order by大概相同,直接使用 #{} 会爆错,直接使用 ${} 又存在 SQL 注入漏洞。

漏洞代码

Select * from news where id in (${ids})
Select * from news where id in (#{ids})

安全代码:使用foreach 进行遍历

id in
<foreach collection="ids" item="item" open="("separatosr="," close=")">
	#{ids}
</foreach>

like 模糊搜索

Mybatis 也可以把SQL语句用注解方式写到Mapper接口文件中。在模糊搜索时,直接使用 %#{q}% 会报错,部分开发为了方便直接改成 %${q}% 从而造成注入。

@Select("select * from users where user like '%${q}%'")
List<User> search(String q);

安全代码

@Select("select * from users where user like concat('%',#{q},'%')")
List<User> search(String q)

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;#
      
posted @ 2021-03-07 13:53  沉云  阅读(189)  评论(0编辑  收藏  举报