-----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: 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 appMerchant app1 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-----