強化串連智付寶(Pay2go)交易的安全性
.作者:jollen/
.日期:March 31, 2016 2:14 PM
這幾天因為專案需要,開始將線上支付的金流平臺,從 Paypal 轉移到 Pay2go...
本文章採用 Markdown 語法撰寫(why?),若無法完整閱讀全文,請點擊這裡。
前言
這幾天因為專案需要,開始將線上支付的金流平臺,從 Paypal 轉移到 Pay2go。要串接 Pay2go 金流平台是一個很簡單的工作,API 文件也很完整。在實作前端與後端的過程中,發現有一些應該要注意的事項,紀錄如下。
關於 MPG 參數
由於 Pay2go 是以 Form Post(HTML5 的 <form></form>)的方式提交付款請求,所以必須使用 <input> 來紀錄交易參數,如圖一。

圖一: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();
將幾個主要的交易參數、 hashKey 與 hashIV 連接成 Query String 形式後,再用 SHA256 編碼。CheckCode 看起來是為了檢查交易參數的正確性。不過:
- 將
CheckCode放在 Form 的欄位裡,有安全上的疑慮 - 最好能改用 REST API 的做法,由 Backend 直接向 Pay2go 的 API 送出一個自定的 HTTP headers,來傳送
CheckCode,但 Pay2go 目前不支援這樣的做法
另外,有些前端網頁,直接將 hashKey 與 hashIV 也紀錄在 Form 裡面,也是非常危險的做法。如圖二(為避免不必要的問題,僅做截圖,恕不註明出處)。

圖二:曝露在外的 hashKey 與 hashIV
類似的安全議題,可以在研發或工程階段,就進行嚴格檢視。不過,把交易機制、平台架構與 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 為上述回傳資料
詳細的交易狀態,則是紀錄在 jsonData 的 Result 裡,再將這個欄位轉換為 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: '-' }
這個回傳結果也是頗耐人尋味:
CheckCode放在這裡面,不但有安全上的疑慮,必要性也值得探討CheckCode的目的,如果是為了 Pay2go 與店家,自行驗證交易資料使用(檢查交易參數是否被竄改),或許可以在各自的後台運算即可,是否以明文傳送,有討論空間
回傳的結果,在交易使用的 Form 裡面幾乎都可以取得。因此向店家發出一個「模擬交易成功」的請求並不是太困難。
強化方案
消極作為:
-
不要在 Form 裡面傳送
NotifyURL參數,避免用心人士,向店家發出「模擬交易成功」的通知。可以在 Pay2go 的管理介面設定NotifyURL,減少NotifyURL曝露的機會。 -
不要在 Form 裡面放入
hashKey與hashIV。 -
店家的
NotifyURL實作,可以考慮移除「自動出貨」、「自動付款銷核」等流程,取消部份自動化,改採手工銷核。 -
店家的
NotifyURL實作,可以考慮加入更嚴謹的檢查,例如查看 HTTP 的Origin:,或是Referer:等檔頭,或是使用 DNS 反查詢等機制。不過因為 HTTP headers 也是很容易變造,所以根本之道還是 Pay2go 的後台能強化相關機制。
最後,展示如何用 curl 進行「模擬交易成功」。首先,在店家的 Form 裡面取得 MerchantID、CheckValue、MerchantOrderNo 與 Amt 四個參數。
接著,將以上參數帶入以下的內容模板:
{
"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 方式,可以消除主要的安全疑慮。
Reports and Discussion
- Bug reports of this article: https://github.com/jollen/blog/issues/18
Tags: