Wednesday, August 16, 2023

DEF CON 31 - Payments Village - Card Hacking Challenge Write-up

Payment Village Issued Credit Card
At DEF CON 31 this year the Payments Village put on a Card Hacking Challenge. You were provided with a working credit card, a point-of-sale terminal (physical, or as an Android APK), and the village was running a "bank" server at www.paymentvillageprocessing.com.

There were 3 main goals:

  1. Steal $100,000 from the card
  2. Commit other fraudulent operations
  3. Steal fifteen cards from the SoftPOS

Setup

With data flowing through 3 hops in this challenge, I had my choice of intercepting NFC traffic between the card and the POS, modifying the APK itself, intercepting traffic between the POS and the server, and/or directly attacking the server.

I chose to intercept (and modify) HTTP traffic between the POS client and bank server. First I setup the provided SoftPOS.apk on an Android phone (side loaded via adb). Then I setup the interception proxy using Burp Suite. 

SoftPOS main screen
Step-by-step:

  1. Phone 1: Setup WiFi tether to mobile network so all devices can talk to each other
  2. Laptop: Connect to WiFi tether on Phone 1
  3. Laptop: Listen with Burp Suite proxy on WiFi IP
  4. Laptop: Open up firewall to allow incoming connections
  5. Phone 2: Connect to WiFi tether
  6. Phone 2: Configure proxy on that WiFi connection
  7. Phone 2: Restart SoftPOS to pickup new proxy settings
  8. Phone 2: Run a payment through SoftPOS and it shows up in Burp Suite on laptop

Capture HTTP Traffic

With the HTTP proxy in place we can view what successful and unsuccessful payment attempts look like. A successful payment sends multiple fields as HTTP query args in a GET request.


These query parameters break down to:

amount
trans_type
9f15 (Merchant Category Code)
9f34 (Cardholder Verification Method Results - CVM)
9f36 (Application Transaction Counter)
9f26 (Transaction Cryptogram)
82 (Application Interchange Profile)
5a (Application Primary Account Number - PAN)
57 (Track2 Equivalent)
9f37 (Unpredictable Number)
5f2a (Transaction Currency)
9a (Date)
9f02 (EMV Amount Field)
type

Note: There are 2 different "type" fields and 2 different "amount" fields, which will be important later.

Here's a successful response:


💡 Thank you Payment Village organizers for keeping the traffic over HTTP so that I could learn about the EMV protocol and not spend half a day debugging TLS MitM issues!

Challenge 1 - Steal $100,000 from Card

Let's get hacking. We need to spend $100,000 so let's run a single transaction for that amount with the card on the POS.

Error: The POS app won't let us charge more than $10 at a time.

Ok, let's intercept the HTTP traffic of a successful $10 payment, then replay the payment packet to the server changing the amount from $10 to $100,000.

Error: The server won't let us replay packets.

Each request has an incrementing counter in field 9f36, so the server knows if a request is replayed. Let's increment that ourselves and replay it.

Error: Auth failure. We've changed a field that is signed by the cryptogram. i.e. the server can detect that the packet was tampered with.

We can recompute the cryptogram if we can extract some key material from the CHIP on the card. But we have a shortcut! The bank server, when receiving a bad cryptogram erroneously returns the expected cryptogram in the AC field of the error message.


We just have to make every request twice. Once with a failure to get the correct cryptogram, then a second time placing the returned cryptogram in our 9f26 field.

Now, replay the $10 transaction, but increment the counter, and update the cryptogram, and change $10 to $100,000 in the amount field.

Error: The server also detects this amount field being tampered with.

"We become safer than Visa or MasterCard! We do not allow random merchants to modify the final amount field from the amount, signed by the chip"

Let's try changing both the amount and 9f02 fields to $100,000 so that they match. 

Error: For that amount of money we need further verification from the client.

Our tampered amount is accepted, but there's additional verification required for that size of transaction.

This verification is done entirely between the card and the POS and is a simple boolean bit field when sent to the bank server. So we flip it to "verified". We claim that the POS verified the card pin by changing the 9f34 (Cardholder Verification Method Results - CVM) field from 1f0302 to 010302.

Changing 1f to 01 tells the server "Plaintext PIN verification performed by ICC". See the First Contact: New vulnerabilities in Contactless Payments whitepaper, page 18, for full details.

Error: Not enough money in the account to charge $100,000.

Ok, we can charge more than $10, but less than $100,000, let's try $10,000. 

Success$10,000 spent.

Now just repeat that 9 more times.

Success$100,000 spent.

Challenge 2 - Commit other fraudulent operations

While fuzzing various fields in the original request, an error is returned.

"Transaction type should be purchase or refund."

Alright, let's change "purchase" to "refund" in both type fields:

trans_type=EMV to trans_type=refund
and
type=purchase to type=refund

You must attempt a refund with a replay of a successful transaction (same ATC and amount) as we can only refund a charge that was made. But we can't refund immediately.

Error: Refund can't be issued at the moment

The server has some time delay before refunds are allowed. Wait ~1 hour and try again.

Success

Challenge 3 - Steal fifteen cards from the SoftPOS

Browsing to the directory base at www.paymentvillageprocessing.com, shows 3 other .php scripts.


Making a GET request on host_reset.php returns a MySQL input validation error indicating there's probably a SQL injection bug here that will allow us to dump stored card numbers. 

"Fatal error: Uncaught mysqli_sql_exception: Incorrect integer value: '' for column 'EUR_Trans' at row 1 in /var/www/www.paymentvillageprocessing.com/mysql.class.php:91 Stack trace: #0 /var/www/www.paymentvillageprocessing.com/mysql.class.php(91): mysqli_query() #1 /var/www/www.paymentvillageprocessing.com/host_reset.php(15): MySQL->execute() #2 {main} thrown in /var/www/www.paymentvillageprocessing.com/mysql.class.php on line 91"

But I'm at DEF CON and I'm sitting with a lot of nice, fun, hackers in the Payment Village AND I have an intercepting proxy on my personal POS. So I just walked around and asked folks nicely if I could scan their card, while my laptop grabbed their numbers, just like a real skimmer would. 18 folks said yes.

Skimmed payments captured over the WiFi

Success

PAN,Date
1016438914381100,230811
1016438914381118,230811
1016438914381126,230811
1016438914381134,230811
1016435898071137,230811
1016439201951142,230811
1016435898071079,230811
1016433175481186,230811
1016437680601097,230811
1016435539211100,230812
1016435656631130,230812
1016435898071137,230812
1016433175481038,230812
1016437112791045,230812
1016433175481053,230812
1016437112791060,230812
1016433175481186,230812
1016435656631098,230812

Gratitude

Thank you Leigh-Anne, Tim, and Ali for a realistic, engaging challenge. The whitepaper and write-ups from last year were very helpful to get started. And with no leaderboard, other hackers were really collaborative, and we could learn about payments together instead of competing.

See you again next year!

References