求重放:從黑盒分析到構(gòu)造合法請(qǐng)求全記錄)
前陣子拿到一套很有意思的安全賽題代號(hào)叫“wsgsig secdd-challenge”。這套題目表面上是給某出行平臺(tái)的內(nèi)部安全挑戰(zhàn)賽準(zhǔn)備的實(shí)際上拆開(kāi)來(lái)看它把真實(shí)業(yè)務(wù)里最常見(jiàn)的三類問(wèn)題——簽名算法被逆向、加密參數(shù)被繞過(guò)、請(qǐng)求重放——全都揉到了一起。我花了兩天時(shí)間完整走了一遍從黑盒分析到最終構(gòu)造合法請(qǐng)求拿結(jié)果的流程踩了不少坑也沉淀出一些通用打法。這篇文章就把整個(gè)拆解過(guò)程、關(guān)鍵原理、實(shí)操步驟和排查經(jīng)驗(yàn)完整記錄下來(lái)希望能給正在研究接口安全、簽名校驗(yàn)或者準(zhǔn)備類似CTF/實(shí)戰(zhàn)演練的同學(xué)一些參考。1. 賽題概況與整體思路拆解1.1 賽題背景與核心需求先說(shuō)這個(gè)題目的結(jié)構(gòu)。整套環(huán)境模擬的是一個(gè)帶有簽名校驗(yàn)機(jī)制的Web服務(wù)客戶端向服務(wù)端發(fā)起請(qǐng)求時(shí)需要攜帶一組由特定算法計(jì)算出的簽名參數(shù)服務(wù)端收到請(qǐng)求后會(huì)重新計(jì)算簽名做比對(duì)不一致就直接拒絕。題目名稱里“wsgsig”可以理解為某種Web服務(wù)簽名機(jī)制的代號(hào)“secdd”則指向安全防護(hù)與加解密相關(guān)的場(chǎng)景。這種設(shè)計(jì)在真實(shí)業(yè)務(wù)里太常見(jiàn)了?,F(xiàn)在但凡有點(diǎn)規(guī)模的系統(tǒng)對(duì)外暴露的接口基本都不會(huì)裸奔——要么有簽名校驗(yàn)要么有加密傳輸要么兩者都有。簽名的作用是保證請(qǐng)求參數(shù)在傳輸過(guò)程中沒(méi)有被篡改加密的作用是保證數(shù)據(jù)內(nèi)容不會(huì)直接被第三方讀取。這套題目等于把這兩個(gè)問(wèn)題塞進(jìn)了一個(gè)可實(shí)操的環(huán)境里讓我能在“合法請(qǐng)求長(zhǎng)什么樣、簽名怎么算的、加密怎么解開(kāi)的”這幾個(gè)問(wèn)題上反復(fù)驗(yàn)證自己的判斷。我在拿到題面之后的第一反應(yīng)不是直接去看代碼或者跑掃描器而是先想清楚這題目究竟想考什么。答案其實(shí)很明確簽名機(jī)制的分析能力和構(gòu)造合法請(qǐng)求的能力。前者考驗(yàn)?zāi)隳懿荒軓暮诤谢蛘邘Р糠中畔⒌幕液协h(huán)境中還原出簽名的生成邏輯后者考驗(yàn)?zāi)隳懿荒馨迅倪^(guò)的參數(shù)、新的時(shí)間戳、正確的簽名組合成一次能被服務(wù)端接受的請(qǐng)求。從攻防兩端的角度來(lái)看這套能力在真實(shí)業(yè)務(wù)里就是做接口安全測(cè)試的底層基本功。1.2 為什么這類簽名校驗(yàn)題目值得花時(shí)間深挖我遇到過(guò)不少人覺(jué)得簽名校驗(yàn)就是加個(gè)密、算個(gè)哈希沒(méi)什么技術(shù)含量。但真正落到實(shí)操層面你會(huì)發(fā)現(xiàn)它牽扯到的問(wèn)題遠(yuǎn)比想象中復(fù)雜。首先是簽名不等于加密。簽名解決的是“參數(shù)是否被篡改”的問(wèn)題加密解決的是“內(nèi)容是否被偷看”的問(wèn)題。很多人在分析時(shí)會(huì)在這兩個(gè)概念上繞暈導(dǎo)致走了彎路。比如有些題目表面上給你一堆密文實(shí)際上簽名用的只是拼接后的一段明文參數(shù)加密只是用來(lái)傳輸對(duì)稱密鑰的殼。其次是簽名算法的可還原性。業(yè)界常用的簽名方案無(wú)外乎幾種MD5家族、HMAC-SHA系列、以及基于非對(duì)稱加密的數(shù)字簽名。每種方案的逆向難度不同。MD5拼接參數(shù)的簽名很容易通過(guò)輸入輸出對(duì)比猜出規(guī)則HMAC要是有固定的密鑰還行如果密鑰是從服務(wù)端動(dòng)態(tài)下發(fā)的就得先解決取密鑰的問(wèn)題RSA/ECDSA這類非對(duì)稱簽名則會(huì)直接給逆向者出難題。這套題目把幾種可能性都覆蓋了一部分正好用來(lái)系統(tǒng)練習(xí)。再就是時(shí)間戳與隨機(jī)數(shù)的對(duì)抗。為了防止重放攻擊簽名中基本都會(huì)包含時(shí)間戳甚至隨機(jī)數(shù)。這意味著你就算完全逆向出了簽名算法如果請(qǐng)求里的時(shí)間戳是舊值簽名照樣過(guò)不了。這個(gè)細(xì)節(jié)卡住了很多半途而廢的人也是后面我要重點(diǎn)講的部分。1.3 整體解題思路地圖我自己的解題流程分成五個(gè)階段這張圖即使換一道類似題目也完全通用環(huán)境與信息收集部署好本地環(huán)境確認(rèn)目標(biāo)地址、接口列表、請(qǐng)求樣例拿到一份含簽名的合法請(qǐng)求作為分析樣本。定位校驗(yàn)邏輯通過(guò)修改單個(gè)參數(shù)觀察響應(yīng)差異找出哪個(gè)部分是簽名校驗(yàn)、哪個(gè)部分是加密字段、哪個(gè)部分是防重放的時(shí)間戳。逆向簽名過(guò)程結(jié)合已知參數(shù)樣本和響應(yīng)反饋推斷簽名生成規(guī)則。必要時(shí)抓取加密JS、反編譯二進(jìn)制或者用輸入輸出對(duì)做差分分析。構(gòu)造合法請(qǐng)求按還原出的算法用新時(shí)間戳和新參數(shù)重新計(jì)算簽名組裝請(qǐng)求并驗(yàn)證通過(guò)。總結(jié)與自動(dòng)化寫(xiě)個(gè)腳本把整個(gè)簽名計(jì)算和請(qǐng)求發(fā)送流程固化下來(lái)方便后續(xù)批量測(cè)試和復(fù)用。后續(xù)的章節(jié)我會(huì)按這個(gè)順序展開(kāi)重點(diǎn)講每一步“為什么要這么做”以及“怎么判斷自己的方向是對(duì)的”。2. 核心機(jī)制解析與前置知識(shí)儲(chǔ)備2.1 簽名算法的常見(jiàn)結(jié)構(gòu)與識(shí)別技巧在拆解這套題的過(guò)程中我對(duì)接口簽名機(jī)制的整體形態(tài)做了系統(tǒng)梳理。大多數(shù)簽名邏輯都可以抽象成三層結(jié)構(gòu)第一層是參與簽名的參數(shù)集合。有些實(shí)現(xiàn)會(huì)把所有請(qǐng)求參數(shù)都拼進(jìn)去有些只選擇除簽名本身以外的固定幾項(xiàng)。判斷哪些參數(shù)參與簽名最樸素的方法是把某個(gè)參數(shù)的值改掉看簽名是否還通過(guò)。如果通過(guò)說(shuō)明該參數(shù)沒(méi)有參與簽名如果不通過(guò)說(shuō)明在簽名集合內(nèi)。第二層是參數(shù)的組織方式。常見(jiàn)的組織方式包括按字典序排序后拼接a1b2c3直接按固定順序拼接成字符串1|2|3拼接后再加上鹽值或密鑰secretKey queryString嵌套JSON序列化后參與計(jì)算第三層是散列或加密算法。這是最容易通過(guò)特征判斷的部分——MD5是32位十六進(jìn)制、SHA1是40位十六進(jìn)制、SHA256是64位十六進(jìn)制、HMAC在輸出長(zhǎng)度之外沒(méi)有明顯特征但可以通過(guò)源碼或行為推斷、AES則有填充和初始化向量的問(wèn)題。如果你想快速判斷算法就把同一組參數(shù)多提交幾次觀察簽名長(zhǎng)度和字符集。64位且為十六進(jìn)制字符大概率是SHA25632位則大概率是MD5但也存在SHA256截?cái)嗷蛘咦远x散列的可能。實(shí)踐中最穩(wěn)妥的方式還是拿到足夠多的輸入輸出對(duì)直接拿常見(jiàn)算法套算對(duì)得上就是它。2.2 防重放機(jī)制時(shí)間戳、nonce與服務(wù)端校驗(yàn)簽名本身防的是篡改但如果沒(méi)有防重放措施攻擊者拿到一個(gè)合法請(qǐng)求后可以無(wú)限次重復(fù)提交。所以絕大多數(shù)簽名方案都會(huì)加入時(shí)間戳或一次性隨機(jī)數(shù)nonce。時(shí)間戳的校驗(yàn)邏輯一般是“當(dāng)前時(shí)間戳 - 請(qǐng)求時(shí)間戳”的絕對(duì)值不能超過(guò)某個(gè)閾值比如300秒。有些嚴(yán)格的服務(wù)會(huì)直接把這個(gè)閾值設(shè)定為60秒甚至更短這種情況下構(gòu)造請(qǐng)求時(shí)必須確保時(shí)間偏差在允許范圍內(nèi)。如果你在本地測(cè)試系統(tǒng)時(shí)間和目標(biāo)服務(wù)器時(shí)間相差很大一定要先同步時(shí)間。nonce的校驗(yàn)邏輯一般是服務(wù)端維護(hù)最近一段時(shí)間的nonce集合使用過(guò)的nonce如果再次出現(xiàn)就拒絕。這種情況比時(shí)間戳難處理一些因?yàn)槟阈枰WC每次請(qǐng)求的nonce是全新的。好在很多實(shí)現(xiàn)里nonce可以直接用UUID或者隨機(jī)字符串代替。在這套題目里我觀察到時(shí)間戳和nonce的配對(duì)方式是比較典型的簽名中拼接時(shí)間戳請(qǐng)求參數(shù)中附帶時(shí)間戳服務(wù)端先校驗(yàn)時(shí)間戳是否新鮮再校驗(yàn)簽名是否匹配。也就是說(shuō)直接拿舊請(qǐng)求改簽名的地方是行不通的必須同步更新簽名里的時(shí)間戳、請(qǐng)求參數(shù)里的時(shí)間戳再重新計(jì)算整個(gè)簽名。2.3 加密字段的定位與處理思路題目名稱中的“secdd”后半段指向加解密場(chǎng)景。在真實(shí)的簽名機(jī)制里有些系統(tǒng)會(huì)把敏感字段加密后再參與簽名或者對(duì)傳輸內(nèi)容做整體加密。這類題目的分析順序我建議是先解密再定位明文參與簽名的部分最后重新計(jì)算簽名。有個(gè)原則必須記住密文的可識(shí)別性。如果密文是Base64編碼的直接解碼就能看到二進(jìn)制結(jié)構(gòu)如果密文是十六進(jìn)制字符串長(zhǎng)度往往是明文的整數(shù)倍尤其AES這類分組加密算法。分析時(shí)需要關(guān)注的不是密文本身而是解密之后得到的明文結(jié)構(gòu)——參與簽名的到底是解密后的明文、還是加密前的原始請(qǐng)求體這兩個(gè)是完全不同的路徑。我在這套題里碰到的情況比較直接請(qǐng)求體中的某個(gè)字段是經(jīng)過(guò)加密的加密后的結(jié)果也參與到了簽名計(jì)算中。這種做法看起來(lái)很安全——改動(dòng)一個(gè)字符都會(huì)導(dǎo)致簽名失效。但反過(guò)來(lái)想只要逆向出加密邏輯和簽名邏輯構(gòu)造合法請(qǐng)求反而是順理成章的事。3. 實(shí)操全過(guò)程記錄從黑盒分析到請(qǐng)求偽造3.1 環(huán)境部署與初始信息搜集我習(xí)慣在本地跑一套獨(dú)立環(huán)境來(lái)復(fù)現(xiàn)這類題目這樣既能隨意斷點(diǎn)調(diào)試又不會(huì)因?yàn)榉?wù)端策略變化而影響分析。題目提供的環(huán)境包含一個(gè)后端服務(wù)、一個(gè)模擬客戶端客戶端會(huì)向服務(wù)端發(fā)起帶簽名和加密參數(shù)的請(qǐng)求。部署完成后第一步不是急著提交亂七八糟的payload而是先抓一個(gè)完整請(qǐng)求出來(lái)。操作方式很簡(jiǎn)單在客戶端點(diǎn)擊某個(gè)功能按鈕同時(shí)用一個(gè)HTTP抓包工具記錄請(qǐng)求內(nèi)容。抓到之后把請(qǐng)求頭、請(qǐng)求體原樣保存一份作為所有分析的基準(zhǔn)樣本。這個(gè)基準(zhǔn)樣本里包含了接口路徑比如/api/order/create和/api/user/info請(qǐng)求頭里的固定字段和動(dòng)態(tài)字段請(qǐng)求體中的明文參數(shù)、密文字段和簽名字段拿到基準(zhǔn)樣本后我對(duì)請(qǐng)求體做了一個(gè)快速拆分。以其中一個(gè)典型請(qǐng)求為例它大致長(zhǎng)這樣{ app_id: test_app, timestamp: 1712332800, nonce: a1b2c3d4e5f6a7b8c9d0e1f2, data: U2FsdGVkX1..., sign: 9f2c4b7e6d1a3f8c0b2e4d6a8f1c3e5b }這里timestamp和nonce很直觀data是一串看起來(lái)像Base64的密文sign是64位十六進(jìn)制字符串——和SHA256的輸出長(zhǎng)度完全吻合。我先做了兩個(gè)測(cè)試。第一個(gè)測(cè)試是把timestamp改成一個(gè)明顯過(guò)期的時(shí)間結(jié)果顯示簽名校驗(yàn)錯(cuò)誤說(shuō)明時(shí)間戳參與校驗(yàn)。第二個(gè)測(cè)試是把data中的任意一個(gè)字符改掉同樣簽名校驗(yàn)錯(cuò)誤說(shuō)明data參與了簽名計(jì)算。這個(gè)結(jié)果直接確認(rèn)了我對(duì)參數(shù)集合的猜測(cè)app_id、timestamp、nonce、data四個(gè)字段全部在簽名覆蓋范圍內(nèi)。3.2 定位簽名校驗(yàn)點(diǎn)與區(qū)分主校驗(yàn)和輔助校驗(yàn)很多系統(tǒng)會(huì)有兩層左右的校驗(yàn)邏輯一層是“格式校驗(yàn)”一層是“簽名校驗(yàn)”。格式校驗(yàn)做的事情包括參數(shù)是否齊全、字段格式是否正確、時(shí)間戳是否過(guò)期簽名校驗(yàn)做的事情則是重新計(jì)算簽名并與請(qǐng)求中的sign字段做比對(duì)。這兩層校驗(yàn)失敗的響應(yīng)往往不同可以利用響應(yīng)差異來(lái)區(qū)分當(dāng)前遇到的是哪一層。我提交了一個(gè)時(shí)間戳過(guò)期的請(qǐng)求服務(wù)端返回的錯(cuò)誤信息是“timestamp expired”。這說(shuō)明時(shí)間戳校驗(yàn)發(fā)生在簽名校驗(yàn)之前或者時(shí)間戳被單獨(dú)檢查。接著我把timestamp設(shè)置為當(dāng)前時(shí)間把nonce保持不變其他字段也保持不變?cè)俅翁峤贿@時(shí)候錯(cuò)誤信息變成了“sign invalid”。這個(gè)結(jié)果說(shuō)明簽名比對(duì)是在時(shí)間戳通過(guò)之后才進(jìn)行的。這個(gè)發(fā)現(xiàn)很重要意味著構(gòu)造請(qǐng)求時(shí)必須先確保時(shí)間戳在有效窗口內(nèi)再去處理簽名問(wèn)題。還有一個(gè)小細(xì)節(jié)值得提有些系統(tǒng)的時(shí)間戳校驗(yàn)不會(huì)告訴你“過(guò)期”還是“非法”而是把時(shí)間校驗(yàn)也塞進(jìn)簽名校驗(yàn)的異常信息里導(dǎo)致你分不清到底是時(shí)間問(wèn)題還是簽名問(wèn)題。解決的辦法就是用“控制變量法”——先把時(shí)間戳設(shè)置為當(dāng)前時(shí)間附近的值排除時(shí)間因素再排除其他干擾項(xiàng)。3.3 逆向簽名生成過(guò)程輸入輸出對(duì)與源碼定位簽名算法是整個(gè)題目的核心。我在初識(shí)樣本中看到的簽名是64位十六進(jìn)制最像SHA256?,F(xiàn)在要驗(yàn)證的就是一件事sign SHA256(某種拼接規(guī)則 data值)到底成不成立。驗(yàn)證步驟是這樣的從基準(zhǔn)樣本中拿到app_id、timestamp、nonce、data以及sign。嘗試幾種常見(jiàn)拼接方式比如app_id timestamp nonce datadata nonce timestamp app_idapp_idxxxtimestampxxxnoncexxxdataxxx按字典序?qū)γ糠N拼接結(jié)果計(jì)算SHA256哈希與目標(biāo)簽名做比對(duì)。前幾種常規(guī)嘗試都失敗了。這就說(shuō)明要么拼接規(guī)則里帶了額外鹽值secret_key要么算法不是SHA256要么拼接格式里有隱藏的分隔符。這時(shí)我決定去客戶端的代碼里找線索。我打開(kāi)了模擬客戶端對(duì)應(yīng)的前端資源目錄掃描了半天看到一個(gè)與簽名相關(guān)的腳本模塊。這個(gè)模塊里定義了一個(gè)名為generateSignature的函數(shù)內(nèi)部實(shí)現(xiàn)是這樣的function generateSignature(params, secretKey) { var keys Object.keys(params).sort(); var parts keys.map(function(k) { return k params[k]; }); var queryString parts.join(); var raw queryString key secretKey; return sha256(raw).hex(); }這套邏輯非常清晰參數(shù)按鍵的字母序排列拼成a1b2這種標(biāo)準(zhǔn)查詢字符串格式最后在結(jié)尾拼上key密鑰整體做SHA256轉(zhuǎn)十六進(jìn)制輸出。這說(shuō)明我之前的失敗原因不是拼接規(guī)則錯(cuò)而是缺了鹽值。只要補(bǔ)上密鑰就能算出正確簽名。但密鑰怎么來(lái)的這里藏了一個(gè)常見(jiàn)設(shè)計(jì)漏洞——客戶端在初始化時(shí)從配置接口獲取了這個(gè)密鑰而腳本中有一段代碼把密鑰掛載到了全局對(duì)象上。我返回到腳本上下文里搜索secretKey的賦值位置很快找到了值sk_live_test_2024_x9k2。這個(gè)操作讓我想到了真實(shí)業(yè)務(wù)里經(jīng)常出現(xiàn)的問(wèn)題密鑰寫(xiě)在JavaScript前端代碼里、寫(xiě)在Android APK里、寫(xiě)在配置文件中沒(méi)做混淆。理論上客戶端要計(jì)算簽名必然會(huì)在某個(gè)環(huán)節(jié)接觸到密鑰。密鑰放在客戶端讓計(jì)算變得可行同時(shí)也讓逆向變得可行。3.4 解密data字段一類典型的對(duì)稱加密處理解決了簽名算法另一塊硬骨頭是data字段的解密。data的密文以U2FsdGVkX1開(kāi)頭這個(gè)前綴我印象很深——這是CryptoJS在AES加密后附帶Salt信息的特征。具體的格式是Salted__這8個(gè)字節(jié)加8字節(jié)Salt后面才是真正的密文。這意味著data字段很可能是用了OpenSSL兼容的AES-128-CBC或AES-256-CBC加密密鑰可以從某個(gè)固定的地方獲取。我在腳本里繼續(xù)搜索與加密相關(guān)的函數(shù)調(diào)用發(fā)現(xiàn)了一個(gè)encryptData函數(shù)核心邏輯是用AES-CBC模式加密密鑰是32字節(jié)的固定字符串初始化向量則是前16字節(jié)。關(guān)鍵代碼大概是var key CryptoJS.enc.Utf8.parse(32byteFixedKeyStringForAES); var iv CryptoJS.enc.Utf8.parse(16byteFixedIVString); var encrypted CryptoJS.AES.encrypt(plaintext, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });拿到這段實(shí)現(xiàn)后解密就只是個(gè)順手的操作了。我用Python的pycryptodome庫(kù)按同樣的參數(shù)做了解密把data字段還原成了明文。解密后我看到的是一個(gè)JSON對(duì)象里面包含真實(shí)的業(yè)務(wù)參數(shù)比如用戶名、訂單編號(hào)、金額等。這里有個(gè)非常關(guān)鍵的認(rèn)知加密的作用是防止業(yè)務(wù)參數(shù)被直接讀取但不改變簽名計(jì)算的方式。簽名計(jì)算的是加密后的data值不是解密后的明文。如果我構(gòu)造請(qǐng)求時(shí)先改了業(yè)務(wù)參數(shù)再用明文去參與簽名一定會(huì)失敗。正確的順序是構(gòu)造業(yè)務(wù)參數(shù)明文。用固定密鑰加密得到新的密文。用新密文作為data字段參與簽名計(jì)算。提交完整請(qǐng)求。很多人在這里會(huì)犯一個(gè)迷糊明明解密時(shí)用的是密鑰A為什么加密時(shí)也可以用密鑰A答案是這就是對(duì)稱加密的特性同一個(gè)密鑰既可以加密也可以解密前提是你在本地完全復(fù)刻了對(duì)端的加密邏輯。3.5 構(gòu)造合法請(qǐng)求參數(shù)更新、簽名重算與請(qǐng)求重放簽名算法、密鑰、加密方式全部確認(rèn)之后構(gòu)造一個(gè)完全合法的請(qǐng)求就變成了流水線作業(yè)。我用Python寫(xiě)了完整的構(gòu)造腳本大致流程如下import hashlib import time import uuid import base64 import json from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad SECRET_KEY sk_live_test_2024_x9k2 AES_KEY b32byteFixedKeyStringForAES AES_IV b16byteFixedIVString def aes_encrypt(plaintext): cipher AES.new(AES_KEY, AES.MODE_CBC, AES_IV) padded pad(plaintext.encode(utf-8), AES.block_size) encrypted cipher.encrypt(padded) return base64.b64encode(encrypted).decode(utf-8) def gen_sign(params): keys sorted(params.keys()) parts [f{k}{params[k]} for k in keys] raw .join(parts) key SECRET_KEY return hashlib.sha256(raw.encode(utf-8)).hexdigest() def build_request(): timestamp int(time.time()) nonce uuid.uuid4().hex business_data { user_id: u_10086, order_id: order_20240318_001, amount: 99.00, currency: CNY } data_enc aes_encrypt(json.dumps(business_data, separators(,, :))) params { app_id: test_app, timestamp: str(timestamp), nonce: nonce, data: data_enc } params[sign] gen_sign(params) return params if __name__ __main__: print(json.dumps(build_request(), indent2))腳本里的幾個(gè)關(guān)鍵點(diǎn)說(shuō)明一下時(shí)間戳必須是當(dāng)前Unix時(shí)間戳的字符串形式不能帶毫秒。nonce每次都要重新生成不能復(fù)用舊值。data的加密結(jié)果始終在變因?yàn)锳ES-CBC模式下相同的明文在固定的IV下雖然會(huì)得到相同的密文但你仍然要保證每次加密用的明文是你當(dāng)前構(gòu)造的參數(shù)版本。簽名計(jì)算的輸入是params字典中除了sign字段之外的所有字段做字典序排序后的查詢字符串。腳本跑出來(lái)之后我直接把生成的請(qǐng)求提交到服務(wù)端返回的狀態(tài)碼是200響應(yīng)體里包含了正常的業(yè)務(wù)數(shù)據(jù)。這意味著整個(gè)逆向與重放過(guò)程完全成功——我手里現(xiàn)在握著一張能隨時(shí)訪問(wèn)該接口的“合法通行證”。這個(gè)結(jié)果在實(shí)戰(zhàn)里就是最典型的業(yè)務(wù)漏洞場(chǎng)景掌握了簽名規(guī)則的人可以直接構(gòu)造任意參數(shù)提交給服務(wù)端服務(wù)端會(huì)認(rèn)賬。如果接口本身沒(méi)有額外的風(fēng)控策略比如IP限頻、設(shè)備指紋、用戶行為分析這個(gè)漏洞就會(huì)被無(wú)限放大。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 簽名長(zhǎng)度正確但始終校驗(yàn)失敗這是我第一次嘗試套公式時(shí)遇到的最大障礙。拿到64位十六進(jìn)制字符串自然就想到SHA256但直接拼接參數(shù)老是驗(yàn)不過(guò)。后來(lái)檢查源碼才發(fā)現(xiàn)密鑰參與拼接的格式是keyxxx不是常見(jiàn)的secret_keyxxx或者saltxxx。這類問(wèn)題在真實(shí)環(huán)境中最難查因?yàn)槟愀静恢利}值的名字和位置。排查方法不要用猜的去找客戶端里生成簽名的實(shí)際代碼。如果代碼是混淆過(guò)的就先做格式化搜關(guān)鍵詞比如sha256、hmac、sign、md5、encrypt這類高頻詞。如果只有抓包數(shù)據(jù)沒(méi)有代碼就嘗試暴力枚舉拼接方式把參數(shù)順序、分隔符、鹽值名、鹽值位置全部作為變量去碰撞。4.2 時(shí)間戳不同步導(dǎo)致反復(fù)“過(guò)期”我在第一次構(gòu)造完新請(qǐng)求時(shí)本地系統(tǒng)時(shí)間沒(méi)有問(wèn)題但提交后依然提示時(shí)間戳錯(cuò)誤。仔細(xì)排查后發(fā)現(xiàn)本機(jī)時(shí)間差目標(biāo)服務(wù)器5分鐘左右超過(guò)了服務(wù)端設(shè)置的容差窗口。這種情況下你除非能把本機(jī)時(shí)間校準(zhǔn)否則無(wú)論簽名算得多正確都會(huì)失敗。經(jīng)驗(yàn)是調(diào)研階段就先看一下目標(biāo)返回的響應(yīng)頭里的Date字段或者任何時(shí)間相關(guān)字段以目標(biāo)服務(wù)器的時(shí)間為基準(zhǔn)來(lái)生成時(shí)間戳。不要默認(rèn)“我的電腦時(shí)間就是準(zhǔn)的”容器環(huán)境里差幾分鐘很正常。4.3 同一段明文加密結(jié)果不穩(wěn)定有朋友會(huì)問(wèn)如果密鑰和IV都固定為什么每次生成的密文不一樣有可能的原因是服務(wù)端在密文中帶了隨機(jī)Salt比如CryptoJS的Salted__前綴格式。這類加密方式下即使明文和密鑰都相同生成的密文也不一樣因?yàn)槊看渭用芏紩?huì)生成一個(gè)新的隨機(jī)Salt。判斷的方法就是看密文開(kāi)頭有沒(méi)有Salted__特征字節(jié)。如果你在逆向時(shí)忽略了Salt的存在直接用固定IV去解密結(jié)果必然失敗。正確做法是從密文中提取Salt按OpenSSL的推導(dǎo)方式計(jì)算出真實(shí)的密鑰和IV再解密。4.4 請(qǐng)求體解碼陷阱URL編碼與Base64變體在抓包和重放時(shí)我一直注意到請(qǐng)求體里data字段的Base64字符串里會(huì)出現(xiàn)和/字符這在標(biāo)準(zhǔn)的Base64編碼里正常。但有的時(shí)候后端接口期望URL安全的Base64變體把替換為-、把/替換為_(kāi)如果直接拿標(biāo)準(zhǔn)Base64去提交服務(wù)端解析密文會(huì)出現(xiàn)填充錯(cuò)誤報(bào)“wrong final block length”之類的異常。處理方式很簡(jiǎn)單發(fā)送請(qǐng)求前把標(biāo)準(zhǔn)Base64轉(zhuǎn)換為URL安全的Base64或者請(qǐng)求時(shí)用表單/JSON體而不是URL參數(shù)傳遞。JSON請(qǐng)求體的話一般不需要額外轉(zhuǎn)換但如果是GET請(qǐng)求的參數(shù)拼接就必需要做URL編碼。4.5 客戶端腳本調(diào)試受阻有些環(huán)境里客戶端代碼做了重度混淆函數(shù)名、變量名全是單字母可讀性極差。我的建議是不要試圖讀懂所有邏輯而是只關(guān)注輸入輸出接口。運(yùn)行腳本后在生成簽名的函數(shù)前后各打印一次關(guān)鍵變量的值用動(dòng)態(tài)調(diào)試代替靜態(tài)分析。比對(duì)著正常請(qǐng)求和篡改后請(qǐng)求的輸出變化來(lái)定位關(guān)鍵函數(shù)比一個(gè)變量一個(gè)變量地去猜快得多。4.6 請(qǐng)求重放被限頻或者風(fēng)控?cái)r截這道題本身可能沒(méi)有風(fēng)控但實(shí)戰(zhàn)接口十有八九會(huì)有。如果你發(fā)現(xiàn)自己構(gòu)造的請(qǐng)求明明簽名正確卻被直接拒絕或者返回驗(yàn)證碼那大概率是觸發(fā)了其他維度的規(guī)則。常見(jiàn)的有IP頻率限制、設(shè)備標(biāo)識(shí)校驗(yàn)、請(qǐng)求方指紋校驗(yàn)。這時(shí)候別硬懟頻率先把正常用戶的行為曲線復(fù)制下來(lái)把請(qǐng)求節(jié)奏降到接近真人的水平或者尋找可以復(fù)用設(shè)備標(biāo)識(shí)的條件。5. 實(shí)操心得與避坑總結(jié)整套題解下來(lái)我最深的感覺(jué)是安全測(cè)試的價(jià)值不來(lái)自“發(fā)現(xiàn)了一個(gè)簽名是SHA256”而是來(lái)自“能完整地把一個(gè)合法請(qǐng)求從頭到尾構(gòu)造出來(lái)”。前者只是靜態(tài)知識(shí)后者才是在真實(shí)攻防里用得上的動(dòng)態(tài)能力。有幾個(gè)習(xí)慣建議所有做這塊的人刻意培養(yǎng)每拿到一個(gè)請(qǐng)求樣本先拆字段不要急著爆破。把固定參數(shù)、動(dòng)態(tài)參數(shù)、簽名參數(shù)、加密字段劃分清楚問(wèn)題就解決了一半。所有推測(cè)都要用實(shí)驗(yàn)驗(yàn)證。懷疑時(shí)間戳過(guò)期就改時(shí)間懷疑密鑰寫(xiě)在前端就搜索源碼懷疑算法是SHA256就寫(xiě)腳本算一遍??障氩粫?huì)出結(jié)果動(dòng)手才會(huì)。構(gòu)造請(qǐng)求腳本盡量做到自動(dòng)化。簽名、加密、時(shí)間戳、nonce、請(qǐng)求發(fā)送、結(jié)果校驗(yàn)全部寫(xiě)在同一個(gè)腳本里后續(xù)做批量測(cè)試或者參數(shù)變異時(shí)會(huì)省下大量重復(fù)勞動(dòng)。這套“wsgsig secdd-challenge”做完之后我又順手把同類型的題目練了幾個(gè)發(fā)現(xiàn)整個(gè)流程基本是通用的。差別主要在加密算法的類型和密鑰的位置上但分析路徑始終一致。最后再分享一個(gè)小技巧不管題目多復(fù)雜先從抓包拿到一個(gè)“確定合法的請(qǐng)求”開(kāi)始。這個(gè)樣本就是你所有分析的錨點(diǎn)。沒(méi)有它你會(huì)陷入“不知道正確答案長(zhǎng)什么樣”的泥潭有了它后續(xù)每一步驗(yàn)證都有明確參照。做接口安全分析錨點(diǎn)思維比任何技巧都重要。