-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 - --- title: "Payment bypass on Twitch and Roblox" description: "I discovered a vulnerability in the payment service provider used by Twitch and Roblox that allowed me to make purchases for nearly free." publishDate: "25 Sep 2024" langs: [en] draft: false tags: [research, responsible, disclosure, payment, bypass, vulnerability, beg bounty] - --- **tl;dr**: I discovered a [vulnerability](#change-the-state) in the payment service provider [Xsolla](https://xsolla.com/) - 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](./img/logo.png) 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](https://hackerone.com/reports/1295844) 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
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](https://xsolla.com/) 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](./img/product.png) ![xsolla2](./img/xsolla2.png) 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](./img/json.png) Merchant app
{"fix_pid": 2449, ..., "fix_email": "a@a.com", "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](./img/fo3.png) ![as](./img/fo4.png) 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 `fix_email=a@a.com` 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=a@a.com` parameter effectively changed the `fix_email` value: ![as](./img/fo1.png) 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](./img/fo2.png) ![twitch_1](./img/twitch_1.png) 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](./img/purchase_2.png) 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](https://www.roblox.com/) 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](./img/roblox_premium.png) ![xsolla_roblox1](./img/xsolla_roblox1.png) 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](./img/roblox_cheap.png) ## Disclosure
| | Date of Metric |--| |-------|-----------|--| | **Jan 21st, 2024** | Developed payment bypass proof-of-concept on `twitch.tv` and `Roblox`; | | **Jan 21st, 2024** | Reported bug through `Roblox`'s bug bounty program; | | **Jan 21st, 2024** | `Roblox` diverted reporting to `Xsolla`s bug bounty program; | | **Jan 21st, 2024** | Reported bug to `Xsolla` using their bug bounty web form; | | **Jan 27th, 2024** | I verify that the vulnerability has been mitigated; | | **Jan 28th, 2024** | Still no response from `Xsolla` - I email them a support ticket asking for an update; | | **Jan 30th, 2024** | `Xsolla` responds and apologizes for the delay - indicating issues with a high throughput of tickets; | | **Jan 30th, 2024** | `Xsolla` rewards me with their max bounty: USD 2k; | | **Feb 7th, 2024** | `Roblox` thanks me for my submission and confirms the vulnerability has been mitigated - rewards me USD 2.5k; | | **Sep 25th, 2024** | I release this blog post. |
-----BEGIN PGP SIGNATURE----- iQHDBAEBCAAtFiEEUR60IXM9NmJXDRuqysNf5Hr0Z4cFAmbzNqwPHHY5QG91dGxv b2suY29tAAoJEMrDX+R69GeH2WwL/Aob6mRGkSD4rGIAEQst9hl9iivpPIT6P32T wwAGJC2nMMSp5TXIjoHCGmXr12W4E1EwYezDPkSTU2oOO6uakfzII2051yBnDpbg Ar+L9Zgm2xNs3RnCo7XNK/sR300LE4ujadxd5HWa4/SSjFzF441BbYnErQbAavDd rbFELZAoh2ldpG7BxG9tQ1lWdfqnDeE83qNPCHKCng61jJtf6Nq+vmDt3LJZfrKu F35AZxzgP6Fh6TOi7qboP7Vb7VWZD0Jm3hD3qoE4epIaXItVMDM3dDebNvH3RcT7 H0I9F9MAXs1FhGArRS9BUWVJBBgpxGmLXlXZbl1ykmZmN/H7kNHOMu7Y3+SFLVWp jizErhQkSzwydaWt9CwcHu4fo2hYCwxevsJPe4/2swscCsZJxZ2NneBO6iDrW965 aLJJQKyiHuifltHkVy29x+IiDZk/+aWZwhAWbMqPpo2oe+Cf0vZnGPfHyxDBFHoh xTdR3S1ZkGYNpHouE/556xhmAn+9Fw== =wWZU -----END PGP SIGNATURE-----