jollen / blog

Jollen's Blog
http://www.jollen.org/blog
66 stars 4 forks source link

強化串連智付寶(Pay2go)交易的安全性 #18

Closed jollen closed 8 years ago

jollen commented 8 years ago

這幾天因為專案需要,開始將線上支付的金流平臺,從 Paypal 轉移到 Pay2go。在實作前端與後端的過程中,發現有一些應該要注意的事項。

關於 MPG 參數

由於 Pay2go 是以 Form Post(HTML5 的 <form></form>)的方式提交付款請求,所以必須使用 <input> 來紀錄交易參數,如圖一。

2016-03-31 11 57 06 圖一:MPG API

一個稱為 CheckValue 的參數,編碼規則如下:

var data = 'HashKey=' + hashKey +
          '&Amt=' + amt +
          '&MerchantID=' + merchantID +
          '&MerchantOrderNo=' + merchantOrderNo +
          '&TimeStamp=' + timeStamp +
          '&Version=1.2' +
          '&HashIV=' + hashIV;
// 使用 SHA256 壓碼後轉大寫
var checkValue = sha256(data).toLocaleUpperCase();

將幾個主要的交易參數、 hashKeyhashIV 連接成 Query String 形式後,再用 SHA256 編碼。CheckCode 看起來是為了檢查交易參數的正確性。不過:

另外,有些前端網頁,直接將 hashKeyhashIV 也紀錄在 Form 裡面,也是非常危險的做法。如圖二(為避免不必要的問題,僅做截圖,無註明出處)。

2016-03-31 12 04 17 圖二:曝露在外的 hashKeyhashIV

類似的安全議題,可以在研發或工程階段,就進行嚴格檢視。不過,把交易機制、平台架構與 SDK 做到非常嚴謹,應該是金流平臺的基本責任。

一個安全且嚴謹的交易平臺,也要針對各種「使用不當」的案例,而造成的安全疑慮,進行完整測試。

關於 NotifyURL

NotifyURL 是 Pay2go 用來通知店家後台交易結果的 URL。Pay2go 在訂單完成交易後,會以 HTTP POST 方法,呼叫 NotifyURL。店家的後台則是要實作這個 API,並進行自已的訂單處理流程。

以下是一個 Pay2go 模擬交易,所回送的 POST 資料:

{ JSONData: '{"Status":"SUCCESS","Message":"\\u4ed8\\u6b3e\\u6210\\u529f","Result":"{\\"MerchantID\\":\\"31745140\\",\\"Amt\\":992,\\"TradeNo\\":\\"16033018180593725\\",\\"MerchantOrderNo\\":\\"14593335802368129\\",\\"RespondType\\":\\"JSON\\",\\"CheckCode\\":\\"4E2B2FB0A54CCFECE9616742FE9025EFBBE8FABBC6562268C11D39DFD6E03E9C\\",\\"IP\\":\\"36.231.213.27\\",\\"EscrowBank\\":\\"KGI\\",\\"PaymentType\\":\\"WEBATM\\",\\"PayTime\\":\\"2016-03-30 18:18:05\\",\\"PayerAccount5Code\\":\\"-\\",\\"PayBankCode\\":\\"-\\"}"}' }

這份資料會以 JSON 格式,經由 POST 方法,由 Pay2go 後台傳送到店家的 NotifyURL。將這筆資料的 JSONData 轉換為 JSON 物件:

var jsonData = JSON.parse(postData.JSONData);  // postData 為上述回傳資料

詳細的交易狀態,則是紀錄在 jsonDataResult 裡,再將這個欄位轉換為 JSON 物件:

var result = JSON.parse(jsonData.Result);

終於得到以下內容(疑問:為什麼不直接回傳一個 Stringify 過的 JSON 物件就好?):

{ MerchantID: '31745140',
  Amt: 992,
  TradeNo: '16033018180593725',
  MerchantOrderNo: '14593335802368129',
  RespondType: 'JSON',
  CheckCode: '4E2B2FB0A54CCFECE9616742FE9025EFBBE8FABBC6562268C11D39DFD6E03E9C',
  IP: '36.231.213.27',
  EscrowBank: 'KGI',
  PaymentType: 'WEBATM',
  PayTime: '2016-03-30 18:18:05',
  PayerAccount5Code: '-',
  PayBankCode: '-' }

這個回傳結果也是頗耐人尋味:

回傳的結果,在交易使用的 Form 裡面幾乎都可以取得。因此向店家發出一個「模擬交易成功」的 NotifyURL 並不是太困難。

強化方案

  1. 不要在 Form 裡面傳送 NotifyURL 參數,避免用心人士,向店家發出「模擬交易成功」的通知。可以在 Pay2go 的管理介面設定 NotifyURL,減少 NotifyURL 曝露的機會。
  2. 不要在 Form 裡面放入 hashKeyhashIV
  3. 後台的 NotifyURL 可以考慮移除「自動出貨」、「自動付款銷核」等流程,取消部份自動化,改採手工銷核。
  4. 後台的 NotifyURL 實作,可以考慮加入更嚴謹的檢查,例如查看 HTTP 的 Origin:,或是 Refer: 等檔頭。不過因為 HTTP headers 也是很容易變造,所以根本之道還是 Pay2go 的後台能強化相關機制。

最後,展示如何用 curl 進行「模擬交易成功」。首先,在店家的 Form 裡面取得 MerchantIDCheckValueMerchantOrderNoAmt 四個參數。

接著,將以上參數帶入以下的內容模板:

{
"JSONData": "{\"Status\":\"SUCCESS\",\"Message\":\"\\u4ed8\\u6b3e\\u6210\\u529f\",\"Result\":\"{\\\"MerchantID\\\":\\\"31745140\\\",\\\"Amt\\\":992,\\\"TradeNo\\\":\\\"16033018180593725\\\",\\\"MerchantOrderNo\\\":\\\"14593258232469307\\\",\\\"RespondType\\\":\\\"JSON\\\",\\\"CheckCode\\\":\\\"4E2B2FB0A54CCFECE9616742FE9025EFBBE8FABBC6562268C11D39DFD6E03E9C\\\",\\\"IP\\\":\\\"36.231.213.27\\\",\\\"EscrowBank\\\":\\\"KGI\\\",\\\"PaymentType\\\":\\\"WEBATM\\\",\\\"PayTime\\\":\\\"2016-03-30 18:18:05\\\",\\\"PayerAccount5Code\\\":\\\"-\\\",\\\"PayBankCode\\\":\\\"-\\\"}\"}"
}

將上述內容儲存為 test.json。最後利用 curl 向店家的 NotifyURL 發出 POST 請求:

curl -X POST -d @test.json --header "Content-Type: application/json" http://localhost:80/pay2go/notify

從這個「模擬交易」的測試可以知道,把自已的 NotifyURL 藏好是一個很重要的工作。提供 REST API 來替代 Form Post 方式,可以消除主要的安全疑慮。