Coindrawer is a “simple cryptocoin wallet and trading platform” that allows users to store and trade Bitcoin/Litecoin, as well as use merchant facilities to accept Bitcoin as payment for online and offline sales.
I am responsibly disclosing a type of replay attack to create multiple merchant orders with one (or zero) merchant payments that is exploitable at the time of disclosure. Coindrawer were notified of this issue 110 days ago as a submission to their Bug Bounty program. Coindrawer were informed of my intention to publicly disclose this issue 14 days ago. This disclosure is intended to allow Coindrawer users to take mitigating measures to help ensure the security of their merchant operations.
Update 1 May 2014 - Coindrawer resolved this issue on 26 April 2014. Coindrawer has not yet committed to honouring this submission under its bounty program.
Update 27 July 2014 - Coindrawer have shut down their service and have honoured this submission under their program. See more at Coindrawer Bug Bounty finale.
Estimated severity
Impact: Low (Provide misleading information, potentially trick a Merchant into trusting a fraudulent transaction)
Mitigations
This vulnerability was discovered and reported to Coindrawer 110 days ago. Before or after this time, it could have been discovered by others and secret knowledge and use of the vulnerability poses a threat to Coindrawer’s users and the security of their merchant operations. By publicly releasing these details, the playing field is leveled and users can take action to mitigate this risk.
Until Coindrawer fixes this particular issue, potential measures that can be taken to mitigate the risks presented by this issue include:
- Merchants should not trust callbacks or the Merchant dashboard to indicate successful orders, as they can be fraudulently created by payers without the Merchant receiving funds.
- Merchants should check the
receive_address
value sent to theirCallback URL
for supposed orders and verify that it is an address associated with their Coindrawer account. - Merchants should check that they receive a payment in their main screen of their Coindrawer dashboard around the times that corresponds to supposed orders listed in their Merchant dashboard.
Update 1 May 2014 - Coindrawer resolved this issue on 26 April 2014.
Coindrawer and their merchant facilities
Coindrawer is a “simple cryptocoin wallet and trading platform”. Coindrawer provides a digital wallet service to store Bitcoin and Litecoin, a trading platform to trade Bitcoin for Litecoin and vice versa, and merchant tools to accept Bitcoin for online and offline sales. Coindrawer offers a mechanism to buy and sell Bitcoin for USD, but this feature has been suspended at the time of writing citing “banking and regulatory challenges”. Coindrawer claims to be “architected to deliver the lowest cost, most secure Bitcoin transmissions for purchases as well as Bitcoin transfers”.
Coindrawer’s merchant facilities allow Merchants to create Payment Pages and Payment Buttons to accept Bitcoin payments in their checkout workflow. Customer payments are sent to the Merchant’s Coindrawer account. Merchants can specify a Callback URL
to which Coindrawer POSTs JSON messages to alert the Merchant to successful checkouts.
Replay Attack
Premise
When a purchaser begins the checkout process at a payment page, the merchant facility does the following:
- Generates a new Bitcoin address for the payment to be sent to. This address is then attributed to the Merchant’s Coindrawer account, and any Bitcoin sent to it is added to the Merchant’s Coindrawer balance.
- Returns a page to the purchaser, which:
- Shows the item name and item price in the Merchant’s chosen currency.
- Asks the purchaser to send Bitcoin to the value of the purchased item at the current Bitcoin rate to the generated Bitcoin address.
- Asks the purchaser to click
Confirm Payment
.
Once the purchaser clicks Confirm Payment
they are sent to the page /paycoin/
which repeatedly polls /paycoin/
with background POST requests as follows:
POST /paycoin/ HTTP/1.1
Host: www.coindrawer.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0 Iceweasel/28.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Referer: https://www.coindrawer.com/paycoin/
Content-Length: 131
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
bcaddr=18qjRZuGYy7WbsD2XAouVA5Aw9tzt5pmBD&merchant_id=<REDACTED>&btnhash=96ea497de815e5acc56d339cefa15316&pageaction=confajax
---
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
Date: Tue, 15 Apr 2014 10:45:21 GMT
Server: Google Frontend
Content-Length: 140
{"coin_address":"18qjRZuGYy7WbsD2XAouVA5Aw9tzt5pmBD","payment_status":"waiting","txnid":null,"cancel_url":"","success_url":"","amtpaid":"0"}
At this stage, a timeout is started, shown by a progress bar on the page. If the user succeeds in paying before the timeout expires, the return of the /paycoin/
POST changes to indicate:
payment_status
= “confirmed” (was “null”)txnid
= a transaction ID (was “null”)amtpaid
= amount of BTC sent to the Merchant (was “0”)
For example:
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
Date: Wed, 16 Apr 2014 13:17:58 GMT
Server: Google Frontend
Content-Length: 386
Expires: Wed, 16 Apr 2014 13:17:58 GMT
{"coin_address":"18qjRZuGYy7WbsD2XAouVA5Aw9tzt5pmBD","payment_status":"confirmed","txnid":"<REDACTED>","cancel_url":"","success_url":"","amtpaid":"0.003"}
Vulnerability
In the POSTs to /paycoin/
, the parameter coin_address
is not checked if it is the original coin_address
that belongs to the Merchant. It is possible to change it to a coin_address
that represents:
- A Bitcoin address known to the Coindrawer system (e.g. a user’s deposit address); and
- A Bitcoin address that has received Bitcoin equal to or greater than the checkout amount.
Even if the address that is used does not belong to the Coindrawer Merchant offering the sale, and even if it has been used to complete a checkout process previously, the following happens:
The background POSTs to /paycoin/
return with payment_status
, txnid
and amtpaid
as though the purchase is complete. The coin_address
that is returned becomes the attacker-supplied address. The page then updates using JavaScript to show a successful transaction.
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
Date: Thu, 17 Apr 2014 13:22:43 GMT
Server: Google Frontend
Content-Length: 208
Expires: Thu, 17 Apr 2014 13:22:43 GMT
{"coin_address":"1MkcmtobD2WHTHyZF1ngxYHxjBTv5KFgRA","payment_status":"confirmed","txnid":"<REDACTED>","cancel_url":"","success_url":"","amtpaid":"0.003"}
The Merchant’s Coindrawer account is updated to reflect the sale.
The Merchant receives a POST callback to their Callback URL
to indicate a successful purchase.
{"total_native": {"amount": "1.00", "currency_iso": "AUD"}, "total_btc": {"amount": "0.01", "currency_iso": "BTC"}, "receive_address": "1MkcmtobD2WHTHyZF1ngxYHxjBTv5KFgRA", "order": {"status": "completed", "created_at": "2014-04-17 13:22:43.384540", "id": "<REDACTED>"}, "transaction": {"confirmations": 3, "hash": "<REDACTED>"}}
It is possible to fraudulently complete the checkout process and provide misleading information to the Merchant that suggests the purchaser paid for the order. The Merchant’s account never receives the sale proceeds.
Disclosure Timeline
My communications with Coindrawer regarding this issue have been open and honest. They were made aware of my intention and reason for publicly disclosing the issue. As follows is the disclosure timeline. Coindrawer’s actions are in bold.
- 28 Dec 2013 - Submission of JSEC1051 (Replay attack, create multiple merchant orders with one payment)
- 30 Dec 2013 - Coindrawer acknowledges receipt of JSEC1051 submission
- 14 Feb 2014 - Coindrawer promises an update on issues including this one “by the end of the week”. This is the last I hear from Coindrawer for 2 months.
- 20 Feb 2014 - Request for an update. No response
- 27 Feb 2014 - Request for an update. No response.
- 4 Mar 2014 - Request for an update. No response.
- 20 Mar 2014 - Request for an update. No response.
- 21 Mar 2014 - Request for an update. No response.
- 27 Mar 2014 - Request for an update. No response.
- 3 April 2014 - Notification to Coindrawer of disclosure date (17 April 2014)
- 14 April 2014 - Mike Lucente, CTO of Coindrawer acknowledges disclosure date.
- 15 April 2014 - Discussion back and forth regarding disclosure and Coindrawer’s Bug Bounty program I remind Coindrawer that they can ask for an extension to the disclosure date if needed. No response.
- 17 April 2014 - Public disclosure of issue, which remains vulnerable.
- 26 April 2014 - Coindrawer resolves issue by totally removing merchant functionality from platform.
Please refer to the mitigation recommendations at the beginning of this disclosure to help mitigate the risk presented by this vulnerability.
I have a series of posts regarding Coindrawer:
- Coindrawer Bug Bounty experience
- JSEC1046 - Coindrawer Persistent DOM XSS Disclosure (Paycoin Feature)
- JSEC1051 - Coindrawer Payment Replay Disclosure, Create Multiple Merchant Orders
- JSEC1053 - Coindrawer Provide Arbitrary Exchange Rate Disclosure
- JSEC1065 - Coindrawer Non-persistent XSS Disclosure (Buy/sell Orders Feature, Cancel_order Param)
- Coindrawer Bug Bounty finale