SQL注入盘点专栏

12,223 阅读7分钟

MedusaSorcerer的博客


注:该文章内容过多, 将持续更新, 并署明更新日期
2019年12月15日 15:00:00

专栏目录

专栏详情

前言

作为一个开发人员, 必不可少的需要了解 SQL 语句, 所以就阅读了一本关于SQL注入的书籍 《SQL注入攻击与防御 第二版 Justin Clarke著》, 之后想用更加简单的方式让大家认识并防御SQL注入, 因此写下本篇博客文章, 分享给大家。

来源

SQL(Structured Query Language) 注入是一种极具破坏力的漏洞, 自从SQL数据库开始连接WEB应用的时候, SQL注入就开始存在了, 首次发现SQL注入的是 Rain Forest Puppy 并引入了公众视野。 1998年圣诞节, Rain Forest Puppy在为Phrack(一本由黑客开创且面向黑客的电子杂志)上撰写了一篇名为 NT Web Technology Vulnerabilities 的文章, 2000年还发布了一篇 How I Hacked PacketStorm 的文章, 讲述了如何用SQL注入来攻击一个WEB应用, 自此, SQL注入备受关注。

原理

在应用程序向后端传递SQL数据查询时, 如果为攻击者提供了影响该查询的能力, 就会引发SQL注入, 此时攻击者通过影响传递给数据库的内容来修改SQL自身的语法和功能, 并且影响SQL所支持数据库和操作系统的功能和灵活性, 对于任何不可信源获取输入代码的方式来构造动态SQL语句, 都很有可能收到SQL的注入攻击。
注入可纳取的方式:

  • 动态构建查询SQL语句
    • 转义字符处理不恰当:如Oracle中空格/双竖线/逗号等
    • 类型处理不恰当:传递注入文件路径或者写入Webshell
    • 查询语句组装不恰当:多次组装查询语句已失去原有查询数据库的功能
    • 错误处理不恰当:详细的错误信息传递给请求客户端
    • 多个提交处理不恰当
  • 不恰当的数据配置

在预防你的数据免受注入的威胁的时候, 对输入数据进行预判和过滤将很有用, 将数据配置中权限分离, 并且将原有配置的用户进行相关配置或者密码重设, 会得到更好的预防, 在用户配置中, 很多应用系统采用的是一个用户就已经拥有 SELECT | UPDATE | INSERT | DELETE | EXECUTE 的权限, 这将很危险。

注入方式盘点

字符串内联注入

在我们对一个SQL的API进行注入'的时候如果返回的信息状态是错误信息, 那么这个API极度容易遭受注入的风险, 如用户登陆的界面若是以下SQL检索语句:

SELECT * FROM TableName WHERE username = '{UserName}' AND password = '{PassWord}' ;

此时注入'的时候查询语句将会是错误语法, 并不能给服务器识别

SELECT * FROM TableName WHERE username = ''' AND password = '' ;

既然'没有被转义, 那么可以将这样的语句进行注入到上面的SQL查询中:

SELECT * FROM TableName WHERE username = '' OR '1' = '1'  AND password = '' OR '1' = '1';

而对你来说你的输入仅仅是对Username输入了' OR '1' = '1, 相同的Password输入了' OR '1' = '1来满足这条查询语句, 在很多的查询语句中可以用这个方式进行注入, 而达到获取全部数据的目的。

测试字符串变种SQL语句预期结果
'错误语法, 返回错误信息
1' or '1' = '11') or ('1' = '1恒等于真, 返回所有数据
value' or '1' = '2value') or ('1' = '空条件, 返回原值相同的数据
1' or 'ab' = 'a' + 'b1') or ('ab' = 'a' + 'bSQL Server字符拼接, 返回所有数据
1' or 'ab' = 'a' 'b1') or ('ab' = 'a' 'bMySQL字符拼接, 返回所有数据
1' or 'ab' = 'a' || 'b1') or ('ab' = 'a' || 'bOracle字符拼接, 返回所有数据
数字值内联注入

很多时候我们检索一条数据的详情的时候会用到唯一标识ID, 当然有的可能用的是UUID, 这种UUID可以忽略这种方式的注入, UUID在数据库是以字符串的形式存储着, 在对于数字ID的类型检索内容详情的时候用到的SQL查询例如以下:

SELECT * FROM TableName WHERE id = {ID} ;

在你输入已有ID的时候将会返回已有ID下的数据详情, 对于很多详情数据将会很有用, 那根据我们已有的 字符串内联注入 的方式, 我们将采取以下的方式进行注入测试:

SELECT * FROM TableName WHERE id = 1 or 1 = 1 ;

在你对SQL的API输入1 or 1 = 1就可以达到获取所有数据详情的目的, 在注入中, 我们最简单的注入方式都是以恒等于真的条件进行注入的方式最为常见和切最为简单。

测试字符串变种SQL语句预期结果
'错误语法, 返回错误信息
1 + 13 - 1返回操作结果相同的数据
value + 0返回与原先结果相同的数据
1 or 1 = 11) or (1 = 1恒等于真, 返回所有数据
value or 1 = 2value) or (1 = 2空条件, 返回原值相同的数据
1 and 1 = 21) and (1 = 2恒等于假, 返回空数据
1 or 'ab' = 'a' + 'b'1) or ('ab' = 'a' + 'b'SQL Server字符拼接, 返回所有数据
1 or 'ab' = 'a' 'b'1) or ('ab' = 'a' 'b'MySQL字符拼接, 返回所有数据
1 or 'ab' = 'a' || 'b'1) or ('ab' = 'a' || 'b'Oracle字符拼接, 返回所有数据
终止式SQL注入

在对SQL注入的时候, 我们可以尝试通过注释语法进行SQL注入:

数据库类型 注释语法 描述
SQL Server
Oracle
PostgreSQL
-- (双连字符) 用于单行注释
/* */ 用于多行注释
MySQL -- (双连字符) 用于单行注释
要求第二个字符跟一个空格或控制字符
# 用于单行注释
/* */ 用于多行注释

如一个验证表单, 正常情况下键入一个合法的 UsernamePassword 对所登陆的用户进行验证, 若是我们采用注入代码并终止该查询语句, 就可以达到注入的目的:

SELECT * FROM TableName WHERE username='admin'/*' AND password='*/'';

我们注入的时候仅仅是对 Username 字段提交了 admin'/*Password 字段提交了 */' 就达到了注入的目的, 最后实际查询的SQL仅仅是:

SELECT * FROM TableName WHERE username='admin' '';

当然, 你不必仅仅是局限于查询的SQL语句, 我们也可以自己在更新/新增/删除的操作中插入想要的注入语句, 最后达到我们想要的目的, 这总执行多个SQL语句的方式, 也是利用注释的语法实现的。

时间延时注入

在大多数的网站上并没有SQL执行的报错信息返回, 所以我们难以确认是否存在漏洞, 对于这种情况, 我们可以向数据库注入时间延迟, 并查看服务器的相应是否也是延时的状态, 在执行SQL Server的时候仅仅需要增加:

WAITFOR DELAY 'hours:minutes:seconds'

如:

SELECT * FROM TableName WHERE id=1314; WAITFOR LEDAY '0:0:5'; -- 

相同的, 你在Oracle PL/SQL里面使用下面的语句, 也可以达到延时注入的方式:

BEGIN DBMS_LOCK.SLEEP(5); END;

虽然 DBMS_LOCK.SLEEP() 可以让过程休眠指定秒数, 但是这个函数不能直接注入子查询中, 并且需要数据库管理员才可以使用 DBSM_LOCK 包, 所以我们还有更好的办法, 如:

SELECT * FROM TableName WHERE id=1314 or 1=DBSM_PIPE.RECEIVE_MESSAGE('RDS', 5)

DBSM_PIPE.RECEIVE_MESSAGE() 函数将会为从 RDS 管道返回的数据等待 5 秒, 默认情况下, 也允许 public 权限下执行这个包的函数。
在PostgreSQL 8.2版本或8.2版本以上的版本, 可以用 PG_SLEEP() 来执行你的注入:

SELECT * FROM TableName WHERE id=1314; SELECT pg_sleep(10); --

这几种方式都可以实现我们的延时注入获取服务是否存在注入漏洞的方法。

周末愉快