🕵️ Hacking a Webshop – don’t fall for this common trap!

Hacking a Webshop – a crappy one, the "Crapshop"
Hacking a Webshop – a crappy one, the „Crapshop“

Hacking a Webshop

So you are interested in hacking a webshop, or in software development security in general?

I welcome you to todays post on application security and how to use or protect against this special case.

In this example you can simply follow along by just downloading and executing the example code.

Especially when i first started programming, I found a lot of code like the one you will see.

Easy cart items just being added without enough checking or protection against some malicious boi.

I’m still working on this article, so more content will come in a few hours 🙂

Here comes the big thingeven today some shops will still be programmed like that..

So hacking a webshop like this will still be possible for some of those badly programmed ones.

On my journey i continiously asked myself the „why“ behind that problem.

Most of the time, you will potentially find the same answer as i already did.

We need to consider the different aspects, or the different roles here:

  • the customer, ordering the software
  • a developer actually creating that piece of application
  • the users using that software

How bad software is created – the roles

Hacking a webshop - Customer and Developer role
Hacking a webshop – Customer and Developer role

When thinking about that bad software which is actually vulnerable to a hacker, we have to consider the involved roles.

As i already mentioned above, there are 3 roles involved, but one isn’t really responsible in the process itself, so..

For sure, this also depends on the type of project and if theres something like a special beta phase, etc.

Let’s have a basic look at those roles and see what we can like expect or take into consideration.

🧍 An end user

How developers imagine the end user
How developers imagine the end user

The end user can like report some problems he found, but for security thingys, this mostly won’t be the case.

You can’t expect a normal user to actually tell you about your security.

He won’t get in touch in a (any) way, where he could really understand or judge that thing.

Obviously it isn’t his responsibility either, so forget about that as quick as possible!

You can be glad, if an end user actually takes the time to report a problem he faced.

This is great, because you then have the nice chance to actually fix that.

End users just want one thing, or well we could say 3 things – use your god damn (sorry) app as:

  • easy
  • intuitive
  • and bug free

as possible – full stop!

So if that happens to you, be glad your customer did that and give him some treat!

In the past i pretty much contacted every company where i faced some problem by email.

Some of them were nice and gave like some sort of bonus, some of them were ignorant.

There even were some companies, where they just got pissed and argued, but well..

Don’t be that last kind of company!

💸 The (devs) customer

The developers customer
The developers customer

Now we are getting to the first interesting role as this one probably made this whole situation start.

There is one guy or even a whole company with a plan of a software (hopefully they have a plan..).

They may want to outsource that software due to missing time inside the original team.

It could also be another type of person – like a private one – which has an idea.

In a typical scenario, the problem we are discussing here is mostly faced, when hiring a (maybe) unexperienced freelancer.

As I have seen it in my past a lot as well, this also counts for like small agencies.

Even if most persons think, that about everything is the developers fault, theres more to consider.

Especially he customer has a chance to control this process and the outcoming quality as well.

Why/How? Well, let me do one quick example:

If you want a ferrari and you want to „vroom, vroom“, then you will have to pay for that.

The problem here with „little“ customers is, that they commonly want that big „Amazony“ application.

All the possibilities and strengths and more, but they only want to pay for like a basic „add, edit, delete, list“-thingy.

Furthermore this also sometimes applies to „bigger“ companies, as they want to save money as well..

Obviously that won’t work..

👨‍💻 Developer (developing firm)

Software Developer working - Hacking a webshop
Software Developer working – Hacking a webshop

The last role is likely to be the most responsible one, as important and fundamental tasks lie on its shoulders.

However most projects start with the actual contact from the developers to the customer.

This can be done by like some freelancing platform, creating the basic frame“ around that project.

You can believe me when i say, that at this point there can already be so many things going wrong.

I’m talking about budget, milestones, communication between the different parties, the final deadline and more..

But i think that’s part of another big story, so I’ll just continue..

Going over the experience of the working team, next to the experience and tools of the individual developer.

The vulnerable spot that you will see in a moment, is mostly created by inexperienced devs.

For sure, such big mistakes can also be created through being close to a deadline and whatsoever reason.

I mean this won’t be any excuse, because:

  • either you have that budget you need to accomplish the work
  • or you’ve told the client, that this is not realistic and therfore won’t work

Jumping in the code of hacking a webshop

Hacking a webshop – Thumbnail
Hacking a webshop – Thumbnail

Before we actually start talking about the code behind, I would recommend downloading the code.

To do so, go to the bottom of the page and download that „crapshop.zip“ file, or just the link here!

Extract the containing project folder to a location of your desire and we are like ready to go.

Prolog

Keep in mind that our focus in this blog post is to actually understand and hack the crappy webshop itself.

So I pretty much won’t use time on explaining how to setup the Slim web application (API), or the concepts.

I will concentrate on the specific web api route and the process of adding a product to our cart.

Please also keep in mind, that there are so many other things which could be improved.

I just tried to come up with a quick, usable example, nothing more, so..

Prerequisites

Before you are able to start or execute the code, you should have some tools ready:

Installing dependencies with composer

In the first step you need to use the composer package manager to install dependencies.

You can do so by opening the project folder in like a Visual Studio Code terminal, or the normal console.

Then go ahead and execute the following command to install the dependencies:

composer install

Starting the app

After everything is installed successfully you can start the app using the native PHP server.

To run that native PHP server you can execute this command here:

php -S localhost:8000 public/index.php

It will basically start the server at the „public/index.php“ location, with „localhost“ as host and listening port 8000.

After that, visit „https://localhost:8000“ in your favourite browser and you will see the webshop starting page:

Hacking a webshop starting page
Hacking a webshop starting page

A little product search page

After you have started the server, you can now navigate to the product search page.

Do so by clicking the „Products“-link and you will see a basic page with listed products.

Just for fun I’ve implemented a little search functionality, but this isn’t really necessary here.

Let’s see what happens, if you click on that „add to cart“-button on the listing page:

Clicking the add to cart button inside our hacky webshop example
Clicking the add to cart button inside our hacky webshop example

As you can see there is a POST request being executed to our backend at „/api/cart/add-product“.

The clientside code

Let’s take a look at the code which triggered the request (you can find it in „public/js/page_products.js“):

document.addEventListener('click', function (e) {
  if (e.target.classList.contains('btnAddToCart')) {
    addProductToCart(e.target);
  }
});

function addProductToCart(btnAddToCart) {
  var id = parseInt(btnAddToCart.dataset.id);
  var price = parseFloat(btnAddToCart.dataset.price);
  var xhr = new XMLHttpRequest();
  xhr.open("POST", '/api/cart/add-product', true);
  xhr.setRequestHeader('Content-Type', 'application/json');
  // xhr.onload = function () {
  //   location.reload();
  // };
  xhr.send(JSON.stringify({
    id,
    price,
  }));
}

There isn’t much more going on than registering that click-callback on the document and reacting to that.

If an element with our „.btnAddToCart„-class is clicked, we respond to that by executing the „addProductToCart“-function.

The function itself will pull the id and the price from the corresponding „data“-attribute.

After that, it will send a new „XMLHttpRequest“ to the server and that’s it.

If you want to refresh the page after doing so, you can uncomment the lines with „onload“.

I’ve omitted it on purpose as this will mostly clear the network history and you won’t be able to see the request.

The dangerous web api endpoint

Dangerous web api - hacking a webshop
Dangerous web api – hacking a webshop

After explaining the client code which has been executed above, we will now focus on the server side.

You may have already guessed the problem in our case, but let’s dive deeper anyways.

Go ahead, step into your code editor and open the „src/Controller/CartController.php„-file.

When you have the file ready, focus on the „addProduct“-function, which represents the corresponding endpoint.

The code explained

In the first two lines, we will pull the json contents sent by javascript:

    $json = $request->getBody();
    $data = json_decode($json, true);

Then we’re checking, if the needed data – product id and price – have been provided.

If those are not present, we will respond with a nice 400 Http response status, which means „bad request“.

Additionally there will be some code identifying what the actual problem was:

    if (!array_key_exists('id', $data)) {
      return $response
        ->withJson([ 'code' => 'ID_MISSING' ], 400);
    }

    if (!array_key_exists('price', $data)) {
      return $response
        ->withJson([ 'code' => 'PRICE_MISSING' ], 400);
    }

Next we are fetching and parsing the id, to check if it’s kinda correct, so like above „0“.

Sure this could be done better, but this is enough for this purpose..

    $id = intval($data['id']);
    if ($id <= 0) {
      return $response
        ->withJson([ 'code' => 'INVALID_ID' ], 400);
    }

After that, we are fetching the product information from our small „ProductService“-class.

This enables us checking, if that product is actually a product from our „database“.

    $product = $this->productService->getProductById($id);
    if (!$product) {
      return $response
        ->withJson([ 'code' => 'PRODUCT_NOT_FOUND' ], 404);
    }

NOW the important and dangerous part starts, which is accepting the price from the client side.

Later I’ll sum up how to correctly work with that price and what you should also take into consideration.

I mean I added the small thingy, that we are at least checking, that the price can’t be negative, so „it’s something“.

    $price = floatval($data['price']);
    if ($price <= 0) {
      return $response
        ->withJson([ 'code' => 'INVALID_PRICE' ], 400);
    }

Then we are doing some basic cart stuff like fetching the cart from the session (which could also be done by database).

We are also checking if the product has already been added to that cart.

If it has been already added, we just increment this quantity for now, if not, it’s added in general.

    $cart = $this->session->get('cart') ?? [];

    $alreadyExists = null;
    foreach ($cart as $key => $value) {
      $item = $cart[$key];
      if ($item['id'] === $id && $item['price'] === $price) {
        $alreadyExists = $key;
        break;
      }
    }

    if ($alreadyExists !== null) {
      $cart[$alreadyExists]['quantity'] = $cart[$alreadyExists]['quantity'] + 1;
    } else {
      $cart[] = [
        'id' => $id,
        'title' => $product['title'],
        'thumbnail' => $product['thumbnail'],
        'quantity' => 1,
        'price' => $price,
      ];
    }

In the last step we are just setting the new value for our cart inside the session and we are returning a response to the client.

    $this->session->set('cart', $cart);

    return $response;

Manipulating that request – hacking a webshop

Manipulating the request attacking the api
Manipulating the request attacking the api

It’s now time to get to the final point of creating our own version of that request.

To work on that we need to analyse the request being sent and provide our own data.

Then we need to re-send that request with our own data being accepted by the server.

The easiest way to do so is by opening that network tab of the webbrowser console, again.

Opening the browsers developer tools

Visit the product list page once again and open the browser console with F12.

Go to the network tab and then execute the code by clicking that „add to cart„-button.

Now you could inspect the request itself by just left clicking it:

Inspecting the add product api request
Inspecting the add product api request

We already know which data is send, therefore we don’t really need to inspect the „payload„-tab to get an idea.

Now the easiest way to test a custom request is, to right click that „add-product“ on the left, which opens a context menu.

There you can select the „Copy“ and „Copy as fetch“ items – to copy the fetch instructions into your clipboard.

Copying the web api request as fetch
Copying the web api request as fetch

Now, in the final step, you can then copy the clipboard contents to your „Console„-tab.

There you can actually make your custom changes like changing the product id which you want to put in your cart.

The most important thing here is, we can change the price, too!

Executing with manipulated request data

So go ahead and do something like this here:

Changing the request body creating our request
Changing the request body creating our request

I changed the product id to „3“ and the price to something low like „5“.

Hit enter on your keyboard while the console is focused and the request will be executed.

After that you can visit the cart and you will see the expensive item now as a very cheap one!

In the end, this is how our final cart screen looks like:

Final cart screen with manipulated price
Final cart screen with manipulated price

Conclusion – Hacking a webshop

So at the end of this interesting blog post, i think we can agree on one thing: Never trust the client side!!

For normal users, the client side may be the bright side of life, but not for us as developers!

Next to many other things which could’ve been improved or being implemented at all, theres the dangerous main thing.

The product price gets send by the client and trusted by the server, which is (in mortal combat language) „fatality“!

Instead of this, you should actually have some sort of dataset in your database, to pull the price at request.

The client saying: „I want that product in my cart“ is not a problem at all.

Next thing you should keep in mind when designing/creating such a type of thing also has to do with like pricing.

Even if you pull that price from the database at the moment the customer puts it into the cart, think about changes.

When some guy using like a CRM to change that price while a customer has the product inside its cart, that could also be a problem.

Downloads

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.