skip to content
one.0day.works

Payment bypass on Twitch and Roblox

/ 24 min read

tl;dr: I discovered a vulnerability in the payment service provider Xsolla - used by Twitch, Roblox, and many others - that allowed for nearly free subscriptions/payments. This flaw was reported and fixed through Roblox and Xsolla’s bug programs.

Introduction

I was watching someone do a CTF live on Twitch and after watching a couple of ads I noticed the “Twitch Turbo” feature where for X dollars a month you can skip all ads. There’s also a monthly streamer subscription where for X amount of $ a month you support the streamer and gain emotes/recognition/loneliness/etc.

Yes this cover image was made with Openai - the prompt I used is on my Github

For mere curiosity - and because I love looking at payment flows and payment logic bypasses - I decided to look at the payment processing system being used.

Novel Payment Bypasses

Generally speaking, payment logic bypasses happen in some of the following instances:

  • Attacker can change the payment value of the current purchase state - throughout the payment flow - and perform a successful transaction marking the state as completed:

Checkout button clicked
Checkout button…
item=01&price=2.0&signature=82..b4
item=01&price=2.0&signature=82..b4
Javascript function that generates payment data  signature
Javascript function that genera…
Checkout form (Payment Gateway)
Checkout form (Paymen…
--
JS breakpoint
JS breakpoint
Tamper with signature generation
Tamper with signature generation
1
1
price changed but signature still valid
price changed but signature still v…
Client-side signature generation
Client-side signature generation
---

To avoid data tampering, most modern payment flows perform cryptographic signatures of the payment data. When a POST request gets made toward the payment provider, a signature of these values is also sent - along with the payment values and IDs. Ideally, this signature would be previously calculated server-side after all the parameters have been properly validated, and the server has deemed the payment state valid and untampered with.

There are however instances where that’s not the case - and this signature gets generated client-side, which can be easily intercepted and tampered with by an attacker through javascript breakpoints 1.

  • Attacker completes the purchase of a cheaper item and uses its transaction ID to confirm a much more expensive purchase state. This can happen when there are no purchase-state <-> transaction-state correction checks in the backend.

Payment GatewayMerchant app
Checkout button clicked
Checkout button…
Lack of payment data correlation
Lack of payment data correlation
payment_token
payment_token
item=01&price=20.0&signature=82..b4
item=01&price=20.0&signature=82..b4
Payment form
Payment form
purchase_type=cc&card=x&amount=2.0
purchase_type=cc&card=x&amount=2.0
payment_token has a successful transaction
payment_token has a suc…
1
1
price intercepted and changed
price intercepte…
Price intercepted and changedPrice intercepted and changed-
Make payment
Make payment
callback
callback
Merchant app
Merchant app
Payment Gateway
Payment Gateway
2
2

When the user gets redirected to the payment gateway, a payment token 1 is sent. If the gateway does not correlate this token to the payment purchase values, then the user can change the payment value and the Merchant app will only validate if a successful transaction occurred 2.

This can also be performed by establishing two payment states in parallel, one with a cheaper item and the other with a more expensive item. The attacker completes the purchase of a cheaper item and uses the callback payment_token on the more expensive item - marking that purchase as complete.

  • Attacker manipulates parameter (i.e. shipping price) quantities to subtract values from the total amount.

Checkout button clicked
Checkout button…
Value to pay: 1
Value to pay: 1
item=01&price=20&shipping_price=-19
item=01&price=20&shipping_price=-19
Server performs unsafe total price calculation
Server performs unsafe total pri…
1
1
shipping price intercepted and changed
shipping price intercepted and chang…
Price arithmetic manipulation
Price arithmetic manipulation
Price changed but signature still validPrice changed but signature still valid-
In this case, the attacker takes advantage of the server-side total calculation that is performed given a certain input that has not been properly validated (i.e. shipping price 1).

Payment Gateway

Nowadays most websites outsource payment processing through Payment Gateways (i.e. Stripe, Paypal, etc). This was also the case with Twitch - where the payment processing platform Xsolla was found to be in use.

I clicked on the “Twitch Turbo” (the subscription feature that allows you to skip ads) and multiple payment options showed up in the Xsolla’s iframe.

product xsolla2

The following payment flow was analyzed when observing the HTTP requests belonging to the credit card option.

I noticed that the very first requests after clicking on the Credit Card payment method were initializing the payment session through /paystation2/api/directpayment?pid=2449 - where the payment session ID was sent in user_session_id - and returning a huge piece of JSON response:

Json response

Merchant app
{“fix_pid”: 2449, …, “fix_email”: ”[email protected]”, “fix_projectAmount”:“11.99”}
{“fix_pid”: 2449, …, “fix_email”: “a@…
/paystation2/api/directpayment?pid=0
/paystation2/api/directpayment?pid=0
1
1
user_session_id=82..b4
user_session_id=82..b4

Some of these JSON values included the subscription price of 11.99 in fix_projectAmount, fix_projectAmount_stored, sum - as well as other payment information such as receipt email 1 in fix_email.

The usage of the fixed wording made me interested in these parameters since it usually represents immutable values associated to the current payment session. Ideally, these values would already be in an immutable state since the payment provider has already assimilated item ID’s (i.e. Twitch Turbo) to the current purchase session ID.

The following interesting parameters were also found in this JSON response:

as as

These values seemingly represent the immutable variables across the payment state. Most of these variables were also found to be existent - and already assigned - within the JSON blob.

Change the state

In an attempt to change the current email address, the parameter [email protected] was appended to the POST request, but no change in the payment state occurred.

After some enumeration - and taking into account the list of fixed variables enumerated previously - the [email protected] parameter effectively changed the fix_email value:

as

This showcased the ability to change the values of the current payment state. Especially seemingly fixed values such as email.

I then attempted to modify all the variables containing the value ”11.99”. After going through all the variable changes, I found that the projectAmount variable was the only one that would allow a price change without triggering any errors in the payment form.

as twitch_1

After filling in the Credit Card information and performing the 1€ purchase, a receipt was generated and a Twitch callback redirect triggered the Twitch Turbo activation (!)

purchase_2

After further analysis of the associated API requests, I figured out that this behavior only occurs in the subscription model payments. This is because Xsolla uses another payment flow for instant purchases with far less variables.

More free stuff

While still trying to figure out if this bug only occurs in the Twitch Xsolla project, I figured I’d try other services that use Xsolla with subscription-based plans.

After some research, the game Roblox was found to be one of them. Apparently, there’s a thing called “Roblox Premium” that allows parents to give their kids a monthly Robux (the Roblox currency) allowance by setting up a subscription-based service:

roblox_premium xsolla_roblox1

As it turns out this product was also vulnerable to this type of bug, making it then possible to purchase “Roblox Premium 450” for 0.37€:

roblox_cheap

Disclosure

Date of Metric
Jan 21st, 2024Developed payment bypass proof-of-concept on twitch.tv and Roblox;
Jan 21st, 2024Reported bug through Roblox’s bug bounty program;
Jan 21st, 2024Roblox diverted reporting to Xsollas bug bounty program;
Jan 21st, 2024Reported bug to Xsolla using their bug bounty web form;
Jan 27th, 2024I verify that the vulnerability has been mitigated;
Jan 28th, 2024Still no response from Xsolla - I email them a support ticket asking for an update;
Jan 30th, 2024Xsolla responds and apologizes for the delay - indicating issues with a high throughput of tickets;
Jan 30th, 2024Xsolla rewards me with their max bounty: USD 2k;
Feb 7th, 2024Roblox thanks me for my submission and confirms the vulnerability has been mitigated - rewards me USD 2.5k;
Sep 25th, 2024I release this blog post.