search
百度安全實驗室:支付安全不能說的那些事兒

百度安全實驗室:支付安全不能說的那些事兒

引言

電子商務已經成為當今互聯網中重要的組成部分。同時「錢包」類服務成為了電子商務的關鍵組件。越來越多的電商服務通過「錢包」服務來進行支付。「錢包」提供的介面簡單易用,任何一個開發者都可以快速的將「錢包」服務供應商提供的SDK整合進自己的App中,提供App內的快速支付手段。目前國內最大的「錢包」類服務包括:支付寶錢包、微信錢包、百度錢包等,各有長處。

因此,支付過程的安全問題也成為了關鍵。如果錢包服務出現了安全漏洞,那麼很可能會影響到成千上萬的商家,數十億的現金流,後果往往非常嚴重。對於支付平台的安全研究自從其誕生之日起就開始了。經過數次血的教訓,幾大支付平台均修正了數個大大小小的漏洞,反覆改進設計和實現。現今的支付平台已經相當安全可靠。

本文對藉助支付平台進行的支付流程進行分析,對支付平台的安全進行討論。同時我們展示了商戶端和支付平台出現過的幾個嚴重安全漏洞。攻擊者通過這幾個安全漏洞可以達到修改金額、任意購買等效果,使得支付平台和商戶的利益收到巨大損失。本文中涉及的安全漏洞均已得到修正。

一、支付流程概述

目前市場上的大大小小的第三方支付平台有許多家,規模有大有小但是從整個支付流程上看這些支付平台的同質化程度很高。只有少數很大的支付平台做出了比較大的改動,進一步增強了安全性。這一節我們對支付平台普遍採用的支付協議做下介紹。

在支付流程中主要包括四個實體:商戶前端、商家伺服器、錢包模塊、以及錢包伺服器。商戶前端是用戶直接交互的部分,用戶通過操作商戶前端來購買商品等。這裡商戶前端不僅限於移動設備上安裝的App,也可以是商家提供的網站。商家伺服器是商戶的後端,提供相應的服務。錢包模塊是進行支付的「中介」,如支付寶、微信錢包、百度錢包等都有對應的支付模塊。

通常支付模塊可以是用戶App中的一個SDK,與商戶App一起安裝在用戶的手機上。支付模塊也可以以web的形式提供服務。錢包伺服器負責處理支付請求,並通知商家伺服器支付結果。錢包服務需要與錢包模塊交互以獲得訂單信息,同時需要與相關的機構(例如銀行)進行扣款處理,最後與商家服務和錢包模塊進行通訊以通知支付結果。因此整個支付過程是一個"四方通訊"的過程。一筆成功交易的後面通常包含四方之間數十次的複雜交互,任何一個環節的安全隱患都會擴大整個支付過程的攻擊面。多個安全隱患的疊加可能使得攻擊者可以進行訂單篡改等攻擊,使用戶、商戶、錢包服務的利益收到損失。

一個完整的經由支付平台的支付過程通常可以劃分為以下幾步:

1.商戶前端對錢包初始化

2.商戶前端與商戶服務交互,創建訂單

3.商戶前端經由錢包,對訂單進行支付

4.錢包與錢包伺服器通訊,錢包伺服器進行扣款,通知錢包和商戶伺服器支付結果。

5.商戶前端從錢包的支付返回中獲取結果,並和商戶伺服器進行確認。

6.支付完成,商戶獲得對應款項。

下面我們來介紹支付的四方通訊的詳細過程。

圖1 支付過程的四方通訊

  • 1. 商戶前端與錢包模塊進行通訊,對錢包進行初始化。在這個過程中通常需要用戶登錄自己的錢包賬戶,進行必要的認證等操作。

  • 2. 錢包模塊初始化完成,返回到用戶App中。

  • 3. 商戶前端與商家伺服器通訊,建立訂單。帶有的參數通常包括用戶信息、需要購買的商品id、時間戳、商品金額、錢包類型等。

  • 4. 商家將訂單參數返回給商戶前端,帶有的參數通常包括:訂單號(order_no)、支付金額(total_fee)、支付結果通知地址(notify_url)、消息完整性簽名(sign)等。此時也有另一種實現,即虛線部分的4'。在4'中,商戶服務將大量參數直接通知給錢包服務,而只返回給用戶一個簡短的消息,包括一個錢包服務返回給商家服務的交易事務id。此後用戶只需通過錢包對這個交易事務id進行支付即可。

  • 5. 商戶前端對商戶返回的信息進行包裝,發送給錢包模塊。

  • 6. 錢包模塊與錢包服務進行交互,帶有的參數包括用戶的錢包session等信息,以及步驟4中返回的訂單信息等。

  • 7. 錢包服務在進行必要的扣款處理(如與銀行進行交互)和風控處理后,將支付結果以同步通知7(1)和非同步通知7(2)的方式分別通知錢包模塊和商戶服務。在支付結果通知中一般包括支付結果、支付金額、支付的訂單號、支付事務的流水號、商戶號,以及消息完整性簽名等。可選的參數包括商品信息、字符集/編碼方式、幣種信息等。商戶伺服器在獲得7(2)的非同步通知后,也需要驗證其消息的完整性,並對訂單狀態進行對應的更新(支付成功或者支付異常)。

  • 8. 錢包模塊接收到錢包服務返回的同步通知后,進行必要的完整性驗證,並將支付結果返回給商戶前端。

  • 9. 商戶前端在得到錢包模塊返回的消息,驗證其完整性。若獲得的是支付成功的消息,則需要向商戶伺服器發起請求,驗證支付結果(查賬)。商戶伺服器此時即可進行訂單支付完成後的處理。

在整個支付過程中,各個消息的完整性是最為關鍵的。如果消息完整性保護存在漏洞,攻擊者即可發起修改金額、修改訂單號、構造虛假訂單等攻擊。作為消息完整性保護的關鍵——簽名機制,是支付協議的核心之一。目前應用在第三方支付平台中的簽名機制可以分成兩種:基於非對稱密碼體制的簽名,和基於散列函數的簽名。基於非對稱密碼的簽名機制只在極少數支付平台上得到實現。而基於散列函數的簽名則基本被所有平台應用或曾經應用過。

在基於非對稱密碼體制的簽名機制中,每個商戶和支付平台都生成自己的一套公鑰-私鑰對,並互相告知對方自己的公鑰。在進行支付時,發送消息方使用自己的私鑰對消息(或消息的散列值)進行簽名,接受消息方使用對方(發送消息方)的公鑰進行驗簽。這個方法的安全性來自於非對稱密碼體制的安全性,例如RSA的大質數分解難度,或計算橢圓曲線離散對數的難度。在這個機制中,最關鍵的是支付平台的私鑰。攻擊者一旦獲得支付平台私鑰,就可以對任意消息進行簽名,從而欺騙商戶。其次是商戶的私鑰。攻擊者獲得商戶私鑰后,結合商戶App的其他漏洞,就可以進行各類攻擊。

基於散列函數的簽名機制安全性來自於哈希函數的不可逆性。在支付平台中此類簽名機制幾乎得到所有平台的使用。每個商戶與支付平台預先共享一個密鑰,以及協商一個hash函數(例如MD5或者SHA1)。在發送消息時,每個商戶活支付平台在消息中附加上與對方共享的這個密鑰,再對整體進行hash運算,得到簽名值,與原始消息合併作為最後的消息發送給對方。在接受消息時則將簽名值剝離,將剩下的部分與密鑰組合後進行hash運算,檢驗生成的散列值是否與發來的簽名值相符。在這個體制中,最關鍵的無疑就是商戶和支付平台預先共享的這個密鑰了。一旦這個密鑰泄露,攻擊者既可以模仿商戶給支付平台發信息,又可以模仿支付平台給商戶發信息,以進行各類欺騙和攻擊,危害無窮。在目前支付平台所採用的簽名機制中,以基於MD5的簽名最為常見。

此外,以上提到的私鑰泄露,其實等同於令商戶/支付平台對指定字元串進行簽名的能力。如果攻擊者可以在很少代價的情況下對指定的「畸形」/「惡意」串進行簽名的話,也相當於獲得了任意簽名的能力,從而以很小的代價發起攻擊。

值得注意的是,基於MD5的簽名機制並不僅限於"支付協議"中使用,在相當多類型的通信中均得到大量應用。而MD5如果不嚴格限定輸入並使用正確的模式將會是相當脆弱的,我們可以構造通用的簽名碰撞攻擊,將在後繼文章中進行介紹。

同時,支付結果的同步、非同步通知則是最容易受到攻擊的點。支付結果的同步通知可以在端上被攻擊者篡改(例如使用代理或者Xposed)。對於非同步通知,由於非同步通知經常缺乏可靠的對發送者的身份鑒定,因此攻擊者可以自行構造非同步通知來通知商戶服務已支付成功,從而完成攻擊。此外,非同步通知的地址往往是可變的,以參數的形式傳遞給支付平台。攻擊者一旦獲得了修改非同步通知地址的能力,也會對支付過程的安全性造成威脅。

二、簽名機制

1、基於MD5的消息完整性簽名機制

在目前國內大部分支付平台以及諸如anySDK平台等平台的介面中均使用或曾使用基於MD5的消息完整性簽名機制。該機制主要用戶保證圖1中四方通訊時消息傳遞的完整性。

基於MD5的消息完整性簽名機制如圖2所示。該方法的關鍵在於:商家和錢包服務之間共享一個簽名密鑰。該簽名密鑰參與到每個簽名生成以及簽名驗證過程中。該密鑰不能泄露,一旦泄露則會造成極大安全隱患。攻擊者可以藉助泄露的密鑰來偽造消息,修改訂單,發送支付成功消息等。

在簽名過程中,簽名方將待簽名的原請求中的key-value對按照key的字母序進行排序,然後連接在一起。這個連接可以使用『&』組合,也可以不使用『&』。再將簽名密鑰附帶在組合的結尾,生成「待簽字元串」。有的簽名方案使用『&key=』來連接key,有的則直接附加key在末尾,區別不大。然後使用MD5演算法生成待簽字元串的散列值作為簽名。最後將該散列值作為一個域附加在原請求中,得到最終的請求。

驗簽過程和簽名過程是基本相同的。首先從最終請求中分離出簽名域,再將需要驗簽的部分按照key的順序排列並重新組合,附加上簽名密鑰,生成簽名過程中的「待簽字元串」,最後計算其MD5值,判斷其與最終請求中所帶的散列值是否相符。

2、基於非對稱密碼體制的簽名機制

應用這一類簽名機制的平台較少,其支付過程可參見圖3。

以RSA為例。商戶生成一對RSA公私鑰對,錢包服務生成一對RSA公私鑰對。雙方把各自的公鑰(金色鑰匙)發給對方。

對消息進行簽名的過程和基於MD5的過程類似,也是首先將請求按key-value對排序,再使用RSA-SHA1演算法(先SHA1再變形再RSA)和對方的RSA公鑰生成簽名,最後將簽名附在原請求中形成完整的請求。 驗簽過程使用自己的RSA私鑰進行驗簽,具體過程不表。

3、待簽字元串的生成

以上兩類簽名機制均依賴於「待簽字元串」的生成。在待簽字元串的生成過程中有以下四個主要問題:

  • 參數值為空的情況。在某些平台中,參數值為空的參數在待簽字元串中被忽略。在某些平台中則不被忽略。

  • 參數值的編碼問題。在某些平台中,參數值編碼后(編碼方式也有不同)進入待簽字元串。在某些平台中則在解碼後進入待簽字元串。

  • 特殊字元問題。由於待簽字元串使用&和=作為元字元,因此參數中存在的&和=等字元會影響待簽字元串的結構。不同平台對特殊字元的處理也不同。

  • 進入待簽字元串的參數選擇。某些平台中,所有參數均進入待簽字元串中參與簽名生成。而某些平台中只有指定參數才會進入待簽字元串中參與運算。

待簽字元串的種種性質導致了其「二義性」的出現。在某些情況下,同一個待簽字元串可以等價於兩個不同請求。例如這個待簽字元串

a=A&b=B&c=C&d=D

可以由一個包含四個key-value對的原請求

{"a":"A", "b":"B", "c":"C", "d":"D"}

生成。在某些情況下,還可以由以下包含五個key-value對的原請求生成

{"a":"A", "b":"B", "c":"C", "d":"D", "junk":"JUNK"}

或者,在另一些情況下,可以由以下只包含三個key-value對的原請求生成

{"a":"A&b=B", "c":"C", "d":"D"}

這些變形依賴於「待簽字元串」的生成方法。攻擊者可以通過構造畸形請求來生成具有相同「待簽字元串」的請求,從而繞過簽名驗證限制。

三、支付協議的安全漏洞

由以上分析,第三方支付過程的安全嚴重依賴於以下三點:

密鑰的安全管理

支付平台簽名演算法的正確實現

商戶對支付協議的正確使用

然而在生產環境中每個環節都有可能出錯,引起嚴重的安全隱患。

1、密鑰泄露

這是最常見的一種安全漏洞。密鑰泄漏並不是一種罕見的情況,不少app在開發時,將密鑰硬編碼在app代碼中(用於本地實現簽名計算)。消息的完整性依賴於簽名的計算,密鑰泄漏后消息將無法保證未被篡改或偽造。但對稱密鑰和不對稱密鑰泄漏后的利用和危害有所區別。

圖4展示了最常見的情況。MD5簽名密鑰編碼在用戶App中造成密鑰泄露。在對相當多的app進行逆向工程后我們發現,有部分app直接照搬一些樣例代碼,導致key被直接明文編碼到程序中,非常容易提取。還有一部分app作者使用了一些變形手段,例如將key拆成奇數位、偶數位分別存儲,或使用特定常數進行異或存儲。這些簡單變形在熟練的攻擊者面前是徒勞的。

由於商戶和支付平台共享密鑰,密鑰泄漏后,攻擊者既可以冒充商戶向支付平台發送訂單消息,又可以冒充支付平台向商戶發送支付結果。當然,後者更加直接(如圖4)。

例如,若攻擊者準備購買一件商品,其訂單消息為

notify_url=http://seller.com/notify&out_trade_no=12345&seller=alice&total_fee=100&sign=XXX,

攻擊者可以首先通過修改notify_url到攻擊者掌控的地址,如http://attacker.com/,提交請求:

notify_url=http://attacker.com/notify&out_trade_no=12345&seller=alice&total_fee=100&sign=XXX

來獲得notify_url的結構。再偽造以下消息簽名后發送給商戶,偽造非同步通知,實現免費購物。

target_url: http://seller.com/notify

post_data: put_trade_no=12345&seller=alice&total_fee=100&trade_status=SUCCESS&sign=XXX.

商戶收到消息后驗證簽名正確,所有參數均正確,將完成攻擊者的訂單。而事實上,攻擊者並未進行過任何支付。

另一方面,基於非對稱密碼體制的簽名方案中,私鑰泄露後攻擊者也可以進行攻擊。但是仍依賴於其他的邏輯漏洞。攻擊者只能獲取商戶的私鑰,而支付平台的私鑰往往被妥善保護無法獲得。因此,攻擊者無法冒充支付平台向商戶發送支付成功的消息,而只能冒充商戶向支付平台偽造訂單或者篡改訂單,修改支付金額。

如圖5所示,若攻擊者準備購買一件商品,其訂單消息為

notify_url=http://seller.com/notify&out_trade_no=12345&seller=alice&total_fee=100&sign=XXX

攻擊者修改金額,使用私鑰重新簽名,並提交支付訂單

notify_url=http://seller.com/notify&out_trade_no=12345&seller=alice&total_fee=1&sign=XXX

成功支付1元后,商戶會收到支付結果消息

out_trade_no=12345&seller=alice&total_fee=1&trade_status=SUCCESS&sign=XXX

商戶進行消息的驗證,會發現簽名正確,商戶號正確,訂單12345支付成功。若商戶沒有驗證支付金額與訂單是否匹配,將完成攻擊者的訂單。從而攻擊者以1元購買了100元的商品。在許多App中,曾出現過只驗證簽名和訂單id的情況,沒有驗證實付金額,因此可以通過這種金額篡改進行攻擊。

為了防禦這樣的攻擊,商家一定要修改app和服務端的設計,使得簽名全部在服務端進行。網上充斥著大量可以直接照搬的富含漏洞的樣例代碼,一定不要簡單修改這些代碼就直接接入支付平台。此外,每筆交易均要進行查賬,驗證錢真的得到了支付,才可以標記訂單為成功支付。

一旦出現這樣的秘鑰泄漏商家將面臨嚴峻的安全風險,支付平台也將面臨嚴重的連帶品牌危機。發生這樣的危機時,如果簡單的替換秘鑰將會直接導致老App客戶端無法進行交易,如果不替換則將面臨嚴峻的支付風險。目前臨時緩解方案是依靠商家服務後台與錢包服務後台的增強校驗和風控來探測和抵抗攻擊。

2、簽名演算法實現錯誤

簽名泄露隻影響個別自己實現錯誤的商家,而支付平台的漏洞則會影響千萬商家。在這一節我們討論我們提交給兩個國內著名支付平台的平台漏洞,他們均已得到修復。

案例一

第三方支付平台A採用了對稱密鑰的設計,並提供了服務端SDK供商家集成。服務端SDK提供了API驗證簽名是否正確。

在PHP和C#的SDK實現中,當簽名欄位不存在時,SDK會直接返回簽名正確,這就導致了攻擊者可以直接冒充支付平台向商戶發送偽造的支付結果消息並通過簽名認證。

以PHP版代碼為例

public function CheckSign

{

if(!$this->IsSignSet){

returntrue;

}

$sign = $this->MakeSign;

if($this->GetSign == $sign){

}

throw newException("簽名錯誤!");

}

public function IsSignSet

{

return array_key_exists('sign', $this->values);

}

檢查簽名時,首先會利用函數 IsSignSet判斷簽名是否存在。若簽名不存在,直接認為簽名正確。

由於該支付平台要求商戶伺服器將訂單(包括通知URL)發送給支付伺服器獲取一個ID,隨後商戶應用將ID傳遞給支付客戶端調起支付界面,在實際攻擊中, 攻擊者還需要從其他渠道獲取通知URL(例如路徑猜測、URL硬編碼或存在網路請求中等)才可偽造支付結果。

案例二

支付平台B採用了非對稱密鑰的設計,每個商戶有自己的一套公鑰私鑰,但支付平台的公鑰私鑰僅一套,即所有商戶使用同一個公鑰驗證來自支付平台的消息。

除此以外,訂單消息和支付結果消息中包含欄位body描述商品信息,且訂單消息和對應支付結果消息中的body一致。

如果攻擊者希望免費(低價)購物,應該如何進行呢?回顧上文中關於待簽字元串二義性的討論,待簽字元串為形如:

key1=value1&key2=value2&key3=value3

的格式。&和=作為了連接符號。

如果某一個參數值(value)中包含&和=符號,待簽字元串和原始的參數集合就可能不再是一對一,即存在多組參數集合對應同一組待簽字元串。

例如:參數集合

{"key1":"value1","key2":"value2&key3=fake_value&zend_key=a", "key3":"value3"}

的待簽字元串為

key1=value1&key2=value2&key3=fake_value&zend_key=a&key3=value3

考慮另一個參數集合

{"key1":"value1","key2":"value2", "key3":"fake_value","zend_key":"a&key3=value3"}

的待簽字元串同為

兩組集合的待簽字元串一樣,但key3的值不同。攻擊者若知道其中一組的簽名,便知道另一組參數在相同密鑰下的簽名了。

有了這個發現,如何在實際中利用並實現免費(低價)購物呢? 攻擊者需要「騙一個畸形訂單支付成功的簽名」!

首先,攻擊者需要擁有一個商戶evil的私鑰(可自行註冊或利用已泄漏密鑰)

現在攻擊者準備向商戶alice購買一件商品,正常的訂單消息參數為

{"body":"商品A","notify_url":"http://seller.com/notify","out_trade_no":"12345","seller":"alice", "total_fee":"100","sign":"XXX"}

若訂單支付成功,支付結果的參數應為

{"body":"商品A","out_trade_no":"12345", "seller":"alice", "total_fee":"100", "trade_status":"SUCCESS","sign":"YYY"},

因此,攻擊者的目標是向http://seller.com/notify發送一個支付完成的消息,並且包含

{"body":"商品A", "out_trade_no":"12345", "seller":"alice", "total_fee":"100", "trade_status":"SUCCESS"}

這些參數和正確的簽名。

這樣一條消息會經過支付平台的簽名,攻擊者無法直接偽造。但是前面提到,所有商戶使用同一個公鑰驗證來自支付平台的消息,也就是說支付平台發給商戶evil的消息若轉發給商戶alice,簽名可以通過驗證,僅僅是商戶號等參數值不正確。因此,攻擊者可以考慮利用evil的支付結果來偽造給alice的支付結果。

攻擊者首先發給支付平台一個屬於商戶Evil的訂單消息

{「body」:「商品A&out_trade_no=12345&seller=alice&total_fee=100&trade_status=SUCCESS&z=」,「notify_url」:「http://evil.com/notify」, 「out_trade_no」:」12345」,「seller」:「evil」, 「total_fee」:「1」, 「sign」:「XXXX」},

支付1元后,http://evil.com/notify會收到支付結果,並且經過了支付平台簽名。

{「body」:「商品A&out_trade_no=12345&seller=alice&total_fee=100&trade_status=SUCCESS&z=」, 「out_trade_no」:」12345」, 「seller」:「evil」, 「total_fee」:「1」,「trade_status」:「SUCCESS」, 「sign」:「signed by payment platform」},

攻擊者可以將其變換為

{「body」:「商品A」,「out_trade_no」:「12345」,「seller」:「alice」, 「total_fee」:「100」, 「trade_status」:「SUCCESS」,「z」:「&out_trade_no=12345&seller=evil&total_fee=1&trade_status=SUCCESS」, 「sign」:「signed by payment platform」}

然後發送給商戶Alice的URL http://seller.com/notify。這些數據具有正確的簽名和期望的商家號、訂單號、支付金額,將會通過alice驗證,從而alice會通過攻擊者的訂單。

攻擊者因此實現了免費(低價)購物。

3、商戶驗證支付結果時存在邏輯錯誤

1)簽名驗證

儘管支付平台提供了服務端SDK,商戶後端在實現邏輯時可能並未使用SDK。那麼,商戶應正確實現簽名的驗證邏輯,避免相關邏輯錯誤,如簽名不存在時通過簽名驗證。

2)總金額、商戶號的驗證

商戶在收到支付結果通知,驗證完簽名后,還應正確處理支付結果中的相關參數。支付金額、商戶號都應該被驗證。

當支付金額未被驗證,攻擊者可以支付較低的費用實現購物。支付金額不一致可能是訂單消息在簽名之前金額被篡改或是攻擊者偽造了訂單消息(參考不對稱密鑰泄漏)。

若商戶號未被驗證,攻擊者可以考慮復用另一商戶的支付結果,對當前商戶進行攻擊。攻擊發生的條件在於發給不同商戶的支付結果使用了同樣的私鑰簽名,同時攻擊者註冊了自己商戶。攻擊者生成一個訂單,支付給自己的商戶后,將支付結果復用。

若商戶號和總金額均未被驗證,攻擊者還可以再次考慮復用另一商戶的支付結果對當前商戶進行攻擊。雖然復用支付結果仍要求發給不同商戶的支付結果使用了同樣的私鑰簽名,但攻擊者不再需要註冊自己的商戶,而是利用已泄漏的商戶密鑰。實現攻擊時,攻擊者生成一個費用較低的訂單,完成支付后復用支付結果。

3)只在客戶端驗證

在用戶完成支付后,支付平台往往既會非同步通知商戶伺服器支付結果,又會同步通知商戶客戶端支付結果以向用戶展示。

然而,支付結果的驗證應在服務端完成。僅在客戶端驗證將容易受到攻擊。攻擊者可以直接修改客戶端邏輯實現免費購物。

總結

本文總結了常見支付平台的支付過程的機制,探討了其中存在和可能存在的問題,並通過實例展現了商戶端和支付平台出現的安全問題。除了本文中探討的問題,還有許許多多安全隱患在近年得到不斷修正。總的來說現在的網上支付還是比較可靠的。在下篇中我們繼續對基於MD5的消息簽名機制進行討論,發掘更多的安全問題。

熱門推薦

本文由 一點資訊 提供 原文連結

一點資訊
寫了5860316篇文章,獲得23271次喜歡
留言回覆
回覆
精彩推薦