基於全流量許可權漏洞檢測技術

hoozheng發表於2020-07-08

一、背景

關於安全領域內漏洞的發現,技術手段非常多,工具也非常多,大致階段可分為事前、事中、事後來處理。事前大多采用SDL、白盒掃描等;事中、事後有NIDS及漏洞感知,甚至還有WAF來攔截惡意流量等。本文作者主要想嘗試通過流量掃描的方式,去發現更多的潛在漏洞。

本文將圍繞“許可權問題”這類漏洞展開討論,因為許可權一旦出問題,很可能導致大規模的敏感資訊洩漏,這樣的後果可能比一兩個跨站要嚴重的多。許可權問題是每個公司非常重要且很難處理好的一種漏洞,這類漏洞是和業務相關性很強的一種漏洞。對安全團隊來說,如果對業務不是足夠了解,就無法對許可權問題有一個很好的治理直到問題收斂。所以本文針對這些困難和問題,嘗試去循序漸進地解決網際網路公司許可權問題收斂,也不可能做到100%的檢測率,權當拋磚引玉。

許可權問題可分為以下幾類:

​ 1、未授權訪問問題。

​ 2、水平許可權問題。

​ 3、垂直許可權問題。

主要是對這三類進行掃描和檢測。

我們將把整個過程分為三個階段:

​ 1、資料清洗,清洗出需要的URL,並通過模型過濾那些不需要檢測的URL。

​ 2、掃描,對這些重點URL進行掃描嘗試是否存在越權訪問的行為。

​ 3、運營階段,通過運營進一步去除誤報的URL、確認漏洞的危害、是否有進一步利用的可能、以及其他相關介面是否還存在相同的漏洞,用來反哺修正掃描器。

二、漏洞檢測

許可權問題,顧名思義就是因為對使用者許可權控制不當導致的問題。為了便於檢測可以把它分為二個問題:1、未授權的問題。2、有授權的問題(水平、垂直)。其中對於使用者的操作又可分為增、刪、改、查,4個操作的識別。

先看下技術架構:

1592808102_5ef052a6aef72.png!small

技術架構

整個系統分為四層:

​ 流量清洗層:網際網路公司每日的流量高達幾百億條,我們不能對全部流量進行檢測,也沒必要。所以需要清洗出可能存在該類問題的URL並且去重,做到精準定位,這樣可以節約大量時間用於檢測。

​ 模型層:模型層主要過濾那些無法通過規則簡單過濾的干擾流量。

​ 掃描層:掃描層通過模型輸出的流量逐個進行掃描,並且檢測是否存在漏洞。

​ 運營層:最後一層主要是安全運營,逐個檢視被掃描器有可能存在的漏洞URL,並且可以去反推整個系統是否還有其他介面存在此類漏洞用於反哺掃描器。

0x01、收集流量

網際網路公司的每日流量幾乎都是海量資料,對每個流量都進行檢測速度太慢,也沒必要,並且全量的資料回放會混著非常多的干擾資料,這種資料本身就不需要做許可權控制,或本身就不存在許可權問題。這歸結于敏感資訊的識別,如果這部分內容屬於某一個人或某一個群體,被群體之外的人訪問了或編輯了,那就是有問題的,所以為了降低後續誤報帶來的影響和運營困難,我們前期先要對流量進行篩選,把那些重要的流量清洗出來再進行掃描。這樣做的優點很明顯,就是有的放矢;而缺點也很明顯,如果資料選擇面太窄就會有遺漏。所以在做資料收集時一定要根據業務不斷的迭代,增加敏感資料的維度。

1、流量清洗

流量清洗的主要目標是清洗出具備返回敏感資訊的API用於後續的檢測,當前清洗出了我們比較關心的敏感資訊,包含但不限於手機號、、郵箱、組織架構、訂單、密碼等含有敏感資料的URL作為檢測目標。

清洗邏輯這裡儘量多用UDF來判斷,具體邏輯就不再這裡贅述了,UDF函式如下:

程式碼塊

SQL

get_phone_number(response) as phone_num,
get_id_card(response) as id_card,
get_bank_card(response) as bank_card,
get_email(response) as email,
get_mark_number(response) as mark_number,

但是清洗出來的敏感資訊還需要做第一次誤報處理,例如提取出的手機號是包含在一串字串中的,

樣例1:19f3f34d44c135645909580e99ac

我們需要通過前後字元及上下文來判斷,這個是屬於真實的手機號、***等敏感資訊,還是某一個字串裡面的某一部分,如果是截斷的字串那就要作為非手機號過濾掉。

2、歸一化並取樣

由於流量資料非常大,每日幾百億的URL並且絕大部分都是重複的,沒必要做重複的掃描和檢測,所以這裡需要做2件事:1、歸一化。2、取樣。

首先需要做的是歸一化。

歸一化:歸一化的目的是為了合併同類URL做更好的取樣收錄。URL一般的構成形式如下:

程式碼塊

HTTP

https://www.x.com/abc/cdef?name=ali&id=1#top

其中:https - PROTOCOL ,www.x.com - DOMAIN,/abc/cdef - PATH,name=ali&id=1 - PARAM,#top - FRAGMENT

但絕大部分公司內,很多URL的PATH部分不會這麼規律,而是採取隨機字串的方式。

程式碼塊

HTTP

a.vip.x.com/cloud/x/y/19f3f34d44c0e99ac/e5f85c0875b5643dc37752554eec
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/e9b61adc14e12d071047d71b143b
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/4b0ed927c1454e0a2ced373a0863
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/fed8f52005cc8b4fe2a3d82728f8
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/59666a1b3d174c21ced72340c94d
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/aab104ff5ae8ca999ba9b01c7067
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/365ebe92ff1bc62e3158144a8fe5
a.vip.x.com/cloud/x/y/1c12c3cf727db5e24/c0894925b18cf1c3d71dc9f56945

其實上面這些都是訪問的一個資源,掃描器只需要對一個進行檢測就可以了,沒必要全量檢測,所以這類URL需要進行歸一化,進行取樣處理既減少了重複工作,又讓處理變得更簡單。歸一化後的URL如下:

程式碼塊

Plain Text

a.vip.x.com/cloud/x/y/{s}/{s}

這裡歸一化的演算法主要採用正則,合併URL路徑中含有序列碼、純數字、標籤、中文等URL,讓他們歸為一類:

程式碼塊

SQL

concat(domain, REGEXP_REPLACE(url_path,
'/([0-9A-Za-z_\\-\\*.@|,]{30,}|[a-zA-Z]+[0-9A-Za-z_\\-\\*.@|,]*[0-9]+|[0-9]+[0-9A-Za-z_\\-\\*.@|,]*[a-zA-Z]+|[0-9\\*.,\\-]+|[\\u4E00-\\u9FA5]+)','/{s}'))
as req_url_path

如果一家公司沒有一個非常統一的編碼標準,那麼他們的URL連結複雜程度,就遠遠不止上面這種型別。筆者遇到過各種千奇百怪的URL形式,有的URL裡面甚至包含中文,這都可能導致噪音。面對這一狀況,目前沒有一個很好的處理手段,只能遇到了就修改正則。

取樣:這裡取樣比較簡單的是同一型別URL每小時取一條資料,因為當前的檢測劃窗定的是1小時。通過SQL的row_number函式對歸一化後的URL連結每小時取樣一條,取樣過程中需要注意:過濾掉返回不成功的流量、掃描器的流量、異常的流量,因為這些流量可能會干擾你的掃描器,因為它本身就不是一個正常流量,在經過你的掃描器修改後,很可能得不到正確的結果。

程式碼塊

SQL

select *
from (
select *,
row_number() over (partition by req_url_path) as row_num
from (
select *,
concat(domain, REGEXP_REPLACE(url_path, '/([0-9A-Za-z_\\-\\*.@|,]{30,}|[a-zA-Z]+[0-9]+[0-9A-Za-z_\\-\\*.@|,]*|[0-9]+[a-zA-Z]+[0-9A-Za-z_\\-\\*.@|,]*|[0-9\\*.,\\-]+)','/{s}')) as req_url_path
from data.sec_ds_x_x_x_x_hh
where dt= 'yyyymmdd'
and hour = 'hour'
) t
) t1
where row_num = 1

3、基於提升樹的分類(GBDT)模型

上面通過歸一化、取樣、去重等手段鎖定了掃描器需要檢測的目標,並且也縮小了一定範圍,但我們這裡忽略了一個問題——並非全部手機號碼都是重要的,網際網路公司都是提供資訊的網站,很多賣家資訊等都是公開的資訊,其中就包括手機號,這在淘寶、京東等的網頁就能輕鬆獲取,這部分資訊如果作為敏感資訊來進行識別許可權問題,顯然是不合適的,所以需要採用一定方法過濾掉這些賣家息。先來看下息的一種形式如下:

公開賣家資料:

程式碼塊

JSON

{"a":200,"b":{"c":"*27816","d":"*1954900","e":"實木上下鋪木床成人高低床雙層床二層床子母床多功能兒童床上下床下單立減7000","f":"到店更多驚喜禮品等你拿",
"g":"https://*.x.com/app/x/x.html?y=*&x=*&z=0","h":true,"i":"實木傢俱"},"j":0}

真正的敏感手機號:(手機號、***這裡已做脫敏處理)

程式碼塊

JSON

{"x":2,"a":0,"b":"預設","c":"","d":false,"e":2,"f":"","g":"130****7844","h":"","i":0.0,"j":"**0832173740073","k":""},"l":null,"m":null,"n":null,
"o":"2020-03-17 08:20"},{"p":"***783755538501","q":"***3813620001","r":"2020-03-18 08:25","s":"2020-03-18 12:58","t":"D7126","u":"ZHQ","v":"*海",
"w":"ZWQ","x":"*西","y":264.0,"z":"2020-03-16 23:50:25","aa":"300","ab":"出票成功","ac":"260","ad":"xxx票務","ae":"紙質票","af":"E3W5343313","ag":
"**票務—1號","ah":[{"ai":"****3759745069","aj":"*886119","ak":"陳*","al":"B","am":"**","an":"E*****","ao":264.0,"ap":"1","aq":
"成人票","ar":"14","as":"二等座","au":264.0,"av":"14","aw":"二等座","ax":"","ay":"4","az":"5D號","ba":null,"bb":"**5343313"}]

所以我們需要做的就是,過濾掉第一類賣家資料,留下第二類敏感資料做檢測。

首先簡單介紹下GBDT(Gradient boosting Decision Tree)梯度提升決策樹,它的主要思想是採用加法模型的方式不斷減小訓練過程產生的殘差來達到將資料分類或者回歸的演算法,它的基學習器採用提升樹。提升樹模型可以表現為決策樹的加法模型,

1592808161_5ef052e167325.png!small

其中T(x;Θm)表示決策樹,Θm表示樹的引數,M為樹的個數。

他的訓練過程大致是先構建一個迴歸決策樹,然後用提升的思想擬合上一個模型的殘差,結果由訓練出來的多棵決策樹的結果累加起來產生。這是一種由多個弱分類器構建而成的分類演算法是一種典型的整合學習演算法(Ensemble)。

1592808139_5ef052cb6b3dd.png!small

圖2

(1)特徵工程

俗話說特徵決定模型逼近上限的程度,根據需求從業務中提取了40多個特徵,由於篇幅過長,在這裡只能做一個歸類,大致分為{訪問量,訪問行為,引數型別,返回型別,敏感資訊佔比,特定資訊佔比,請求成功率}共40多個特徵用於分類器的學習。當前的專案中訓練集採用了10000條資料,手工+規則進行標註和修正,其中正樣本3400多條,負樣本6500多條,正負比例大約是1:2。

這裡1、3標為敏感資料、2、4標為非敏感資料(賣家***息),通過以下特徵我們建立第一棵Tree,

Features 1 構建Tree 1

請求URL 訪問量 引數型別 返回型別 敏感資訊佔比 特定資訊佔比 請求成功率 label
1 *.x.com/x/y/z 37 21 1 0.9459459459459459 1.0 1 1
2 *.x.com/x/{s}/y/z/b 25 17 1 0.84 0.51 0.9 0
3 *.x.com/x/y/z/c 8 4 1 1 1.0 1 1
4 *.x.com/z/y/z/d 9 6 1 0.3 0.2 1 0

1592808189_5ef052fd5ad74.png!small

根據Tree1 預測結果計算殘差,獲得一個殘差表。

Feature 2 殘差表

請求URL 訪問量 引數型別 返回型別 敏感資訊佔比 特定資訊佔比 請求成功率 label
1 *.x.com/x/y/z 11 16 0 0.7 0.5 1.0 1
2 *.x.com/x/{s}/y/z/b 16 8 1 0.64 0.0 0.8 0
3 *.x.com/x/y/z/c 13 4 1 0.5 0.0 0.5 1
4 *.x.com/z/y/z/d 3 1 1 0.2 0.0 0.1 0

根據殘差構建Tree2,以此類推

1592808240_5ef05330e9521.png!small

直到達到訓練指標便結束訓練。最後對所有樹進行線性加法計算。

1592808267_5ef0534baba6d.png!small

(2)模型評估

模型評估可以用最簡單的方式,這裡採用的是精度(precision)和召回率(recall)來評估模型。這裡選擇另外一天的全量資料作為驗證集,一共大約有1000多條資料,還是手工標註好正負樣本集,過模型後分別統計精度、召回情況。Precision = TP / (TP + FP)、Recall = TP / (TP + FN)。其中TP(true positive)為真正例,FP(false positive)為假正例,FN(false negtive)假反例。從實驗來看,

Precision = 421/ (421+ 43) = 0.907 Recall = 421/ (421 + 11) = 0.97

也就是在另外一天的資料表現來看,精度能做到每日的URL是0.9左右,召回率能做到0.97,在這個基礎上我們需要去看下哪些漏掉了、什麼原因漏掉了,經過對特徵重要性進一步分析,模型應該是把很多訂單類的文字識別為了***息,主要原因是訂單的特徵和公開的特徵非常像,裡面都有類似shopid、sellerid、http,也存在固定電話等。在這種情況下需要新增一個專門標註訂單的特徵項isOrder,如果看到這個欄位為1,就自動標註為非賣家資訊,再去訓練該模型,最後的結果確實也提升了一些recall,但還是不盡人意。

這種情況下,就需要另外的手段來彌補不足,我們專門從流量裡清洗出了帶有訂單標誌的流量,單獨進行檢測。這樣做既不會增加工作量,也能很好地彌補模型的不足。

最後的效果是,在模型預測前,每日會有3000多條報警記錄需要人工去看,而經過模型過濾後每日告警減少到100多條,不過感覺還是有優化的空間,最好的做法是把很多無法識別或識別錯的,用規則過濾掉,儘量控制誤報同時降低漏報。

0x02、掃描是否存在越權

1、漏洞掃描

漏洞掃描,主要是基於模型輸出的API去主動掃描和發現該API是否存在漏洞的情況,這是一個主動發現的過程,它和傳統的漏洞感知、NIDS的差別在於,它在不被攻擊的情況下也能發現基礎漏洞的存在。這裡對於許可權的掃描主要是通過Java的http介面重新訪問該URL,類似某些公司的迴音牆,然後根據response來校驗是否獲取到了敏感資訊,來確定是否有漏洞存在。掃描器支援多種引擎,這裡選擇Chrome和http兩種引擎,主要是為了解決js跳轉等問題,不同的引擎優缺點不太一樣,要根據適合的場景來選擇。目前可以支援的漏洞型別如下:

具有許可權問題的URL

​ JSONP

​ URL重定向

​ 非預期檔案讀取

​ 線上資料庫異常連結

​ 敏感檔案下載等等

先看下掃描器的框架,如下:

1592808360_5ef053a81b9fa.png!small

圖3 漏洞掃描框架

掃描器在掃描許可權問題的時候需要具備如下能力:

1、登入態的設定能力,沒有登入態連基本的許可權都沒有,所以這裡必須設定。

2、多引擎的能力,不同引擎有不同的優缺點需要切換使用。

3、多執行緒能力,多執行緒去執行才能提高檢測效率。

4、漏洞的檢測能力。

(1)掃描查詢是否存在越權

介面的訪問形式多種多樣,本文就以某一種形式來討論,例如遇到以下型別的URL

程式碼塊

Java

https://x.y.com/a/b/getOrderDetail?orderNo=11000603698171

從上面的介面可以看到,這是一個查詢訂單的介面,很顯然上面的模型會把它預測為是敏感資訊,接下來資料來到掃描器這一層,掃描器就要對他進行重放一次,看是否能拿到之前的response資訊,在重放之前我們先要設定下登入態,如果單純地去渲染可能沒辦法達到一個很好的效果,這裡需要給掃描工具建立一些登入態,能夠進入系統內部去呼叫他們的介面能力。

程式碼塊

Java

private static Map<String, String> headers = new HashMap<>();
static {
// 初始化header
headers.put("Referer", Constant.REFERER);
headers.put("Host", "1.x.com");
headers.put("X-Requested-With", "XMLHttpRequest");
headers.put("User-Agent", Constant.UA);
headers.put("Cookie", Constant.COOKIE_1 + Constant.COOKIE_2 + Constant.COOKIE_3 + Constant.COOKIE_4);
}

有了上面的header可能還不夠,在適當的時候需要去替換URL裡面的各種引數,例如token資訊等等,所以還需要判斷這個介面的鑑權是在哪裡做的,token的校驗有的是通過url傳入的,那我們需要通過替換過後再進行重放,否則還是用的老登入態會導致誤報。接下來就需要對介面做各種嘗試來判斷是否具有許可權問題或其他漏洞。這個過程主要是通過事先做好的URL工具類訪問下,訪問的主要介面如下:

程式碼塊

Java

String response = HttpUtils.get(url, null, headers,3000, 3000, "UTF-8");

返回值如下

程式碼塊

Java

{"data":{"orderNo":"11000603698171","price":"19.80","quantity":1,"originalPrice":"23.80","stock":0,"remainStock":0,"dailyStock":0,
"dailyRemainStock":0,"salesVolume":0,"startTime":null,"status":0,"offlineTime":null,"productId":613196357,"skuCode":""}],"consignee":
{"buyerNickName":"甜美xxxx","name":"xxx","phone":"11111111","address":"*********xxxxxxx","zipCode":""},"userRemark":""}}

如果能訪問成功,這說明這類介面是有問題的,存在水平查詢許可權問題,反之則不存在越權問題。

當然這還遠遠不夠,單個的訪問效率是非常低的,每日可能有好幾十萬的連結需要回放單執行緒,這樣是沒辦法滿足我們的需求的,所以掃描器需要採用多執行緒的方式,用100個甚至更多的執行緒來同時執行。

程式碼塊

Java

/**
* 多執行緒執行
* @param urls
*/

public static void execute(List<VulBase> urls) {
for (VulBase vulBase : urls) {
futureList.add(executorService.submit(new ProcessThread(vulBase)));
}
for (Future<Result> resultFuture : futureList) {
try {
Result result = resultFuture.get();
if(result.getSuccess() == true) {
System.out.println(result.getSuccess() + "," + result.getMsg());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
futureList.clear();
}

這樣同時執行100個任務。效率提升會非常明顯,原來1萬條URL需要3小時左右的回放時間,採用多執行緒後只需要5分鐘。這裡也可以根據機器效能,適當調整自己的執行緒數。

(2)掃描修改類介面是否存在越權

絕大部分增刪改的動作都是POST請求,這裡同樣需要過濾掉那些無效的POST請求,以免產生大量誤報,真正的難點是要找出增刪改過資料庫的POST請求,這個過程比較困難,從表面是無法識別的,我們一般把流量中記錄的traceid和db的traceid進行一個關聯,如果關聯上就說明在這次訪問中增刪改過資料庫,然後就需要構造訪問包的方式去訪問系統,這個過程也比較危險,因為你很有可能刪除掉了非常重要的資訊,所以在這裡需要控制好登入態就顯得非常重要了,否則很可能刪除或修改了別人的資料導致線上故障。程式碼如下:

程式碼塊

Java

String response = HttpUtils.post(url, body, headers,3000, 3000, "UTF-8");

如果能通過自己的登入態去POST這個請求並且改變了別人資料庫裡的內容,那可能就存在問題了。

0x03、運營

檢測結果出來後還有一個比較重要的工作就是運營,我們通過安全運營可以去除一些掃描器的誤報,並且還可以發現該介面的一些其他問題,或者進一步被利用的可能,比如是否可以被遍歷,還可以橫向思考是否同系統還有其他介面也存在這類問題,用來發現更多的流量裡面沒有的URL,因為有些URL非常重要,但是他一天也沒幾個人訪問,甚至沒有訪問,這種就只能通過運營的能力來發現,有點類似根據掃描結果來做一個有指導的SDL。還是從上面的URL來看,

程式碼塊

Java

https://x.y.com/a/b/getOrderDetail?orderNo=11000603698171

如果他存在許可權的問題,接下來運營還需要確認是否可以通過orderNo來遍歷全部的訂單資訊,如果可以那這個漏洞的危害就變得非常大了,還可以排查出y.com這個域名是否存在其他的重要介面,大概率也會存在問題,從而達到橫向、縱向的許可權梳理,儘量全的覆蓋全域的系統和URL。

0x04、小結

許可權掃描中最難的問題,就是我們對業務的無法理解導致大量誤報,最終導致的結果就是不可運營性,這其中的誤報包括:

​ 1、返回資訊的不確定(是否是敏感資訊)

​ 2、對資料庫的修改是否是合法操作

筆者主要是通過限定返回資訊來縮小敏感資訊的範圍並配合模型和規則去除誤報和無用的返回資訊。這裡取樣起到了一個非常重要的作用,對於全量的資料我們沒必要全部進行校驗,只需要對同一類介面進行校驗就夠了,這樣可以大大降低引擎的壓力同時也能提升效率減少誤報。

三、結語

許可權問題治理、發現、檢測對於每一家公司都是非常困難的,困難點主要源於我們對業務的不理解。我們最好在事前、事中、事後體系化去解決這類問題,沒有銀彈。事前可以通過架構層,統一開發框架,統一編碼規範,結合白盒掃描等等方式,事後的解決辦法主要是從具備敏感資產的這個點進入,並做好許可權的動態配置和校驗,從而達到檢測許可權漏洞的能力。其中最主要的是我們要具備每一個系統的許可權動態配置能力,這樣才能進入到系統對URL進行掃描。時間倉促本文作為漏洞掃描系統的一個功能和大家做一個技術上的探討和分析,盲人摸象而已,實際許可權問題的實踐遠比想象複雜,後續有機會再做進一步交流。

附錄:

https://www.researchgate.net/publication/221023992_A_pattern_tree-based_approach_to_learning_URL_normalization_rules

https://en..org/wiki/Gradient_boosting#Gradient_tree_boosting*********

https://yq.aliyun.com/articles/673308

相關文章