PortSwigger: All Business Logic Vulnerability Labs
In this post, I will cover all of the Business Logic labs located at PortSwigger Academy as well as providing some context regarding what business logic vulnerabilities are and how we can exploit them.
Business Logic Vulnerabilities
Business logic vulnerabilities are flaws in the design and implementation of an application that allows an attacker to elicit unintended behaviour. This potentially enables attackers to manipulate legitimate functionality to achieve a malicious goal.
These flaws are generally the result of failing to anticipate unusual application states that may occur and, consequently, failing to handle them safely.
The term "business logic" simply refers to the set of rules that define how the application operators. As these rules are not always directly related to a business, the associated vulnerabilities are also known as "application logic vulnerabilities", or simply "logic flaws"
Logic flaws are often invisible to people who are not explicitly looking for them as they typically won't be exposed by normal use of the application. However, an attacker may be able to exploit behavioural quirks by interacting with the application in ways that developers never intended.
One of the main purposes of business logic is to enforce the rules and constraints that were defined when designing the application or functionality. Broadly speaking, the business rules dictate how the application should react when a given scenario occurs. This includes preventing users from doing things that will have a negative impact on the business or that simply do not make sense.
Flaws in the logic can allow attackers to circumvent these rules. For example, they might be able to complete a transaction without going through the intended purchase workflow. In other cases, broken or non-existent validation of user-supplied data might allow users to make arbitrary changes to transaction-critical values or submit nonsensical input.
By passing unexpected values into server-side logic, an attacker can potentially induce the application to do something that it is not supposed to do.
Logic-based vulnerabilities can be extremely diverse and are often unique to the application and its specific functionality. Identifying them often requires a certain amount of human knowledge, such as an understanding of the business domain or what goals an attacker might have in a given context.
This makes them difficult to detect using automated vulnerability scanners. As a result, logic flaws are a great target for bug bounty hunters and manual testers in general.
How do they arise?
Business logic vulnerabilities often arise because the design and development teams make flawed assumptions about how users will interact with the application. These bad assumptions can lead to inadequate validation of user input.
For example, if the developers assume that users will pass data exclusively via a web browser, the app may rely entirely on weak client-side controls to validate input. These are easily bypassed by an attacker using an intercepting proxy.
Ultimately, this means that when an attacker deviates from the expected user behaviour, the app fails to take appropriate steps to prevent this and, subsequently, fails to handle the situation safely.
Logic flaws are particularly common in overly complicated systems that even the development team themselves do not fully understand. To avoid logic flaws, developers need to understand the app as a whole. This includes being aware of how different functions can be combined in unexpected ways.
Developers working on large code bases may not have an intimate understanding of how all areas of the app work. Someone working on one component could make flawed assumptions about how another component works and, as a result, inadvertently introduces serious logic flaws.
If the developers do not explicitly document any assumptions that are being made, it is easy for these kinds of vulnerabilities to creep into an application.
Impact of Business Logic Vulnerabilities
The impact of business logic vulnerabilities can, at times, be fairly trivial. It is a broad category and the impact is highly variable. However, any unintended behaviour can potentially lead to high-severity attacks if an attacker is able to manipulate the app in the right way.
For this reason, quirky logic should ideally be fixed even if you cannot work out how to exploit it yourself. There is always a risk that someone else will be able to.
Fundamentally, the impact of any logic flaw depends on what functionality it is related to. If the flaw is in the authentication mechanism, for example, this could have a serious impact on your overall security.
Attackers could potentially exploit this for privilege escalation, or to bypass authentication entirely, gaining access to sensitive data and functionality. This also exposes an increased attack surface for other exploits.
Flawed logic in financial transactions can obviously lead to massive losses for the business through stolen funds, fraud, and so on. You should also note that even though logic flaws may not allow an attacker to benefit directly, they could still allow a malicious party to damage the business.
Examples of Business Logic Vulnerabilities
The best way to understand business logic vulnerabilities is to look at real-world cases and learn from the mistakes that were made. Although individual instances of logic flaws differ hugely, they can share many common themes.
In particular, they can be loosely grouped based on the initial mistakes that introduced the vulnerability in the first place.
Examples of logic flaws include:
Excessive trust in client-side controls
Failing to handle unconventional input
Making flawed assumptions about user behaviour
Domain-specific flaws
Providing an encryption oracle
Excessive Trust in Client-Side Controls
A fundamentally flawed assumption is that users will only interact with the app via the provided web interface. This is especially dangerous because it leads to the further assumption that client-side validation will prevent users from supplying malicious input.
However, an attacker can simply use tools such as Burp Proxy to tamper with the data after it has been sent by the browser but before it is passed into the server-side logic. This effectively renders the client-side controls useless.
Accepting data at face value, without performing proper integrity checks and server-side validation, can allow an attacker to do all kinds of damage with relatively minimal effort. Exactly what they are able to achieve is dependent on the functionality and what it is doing with the controllable data.
In the right context, this kind of flaw can have devastating consequences for both business-related functionality and the security of the website itself
Failing to Handle Unconventional Input
One aim of the app logic is to restrict user input to values that adhere to the business rules. For example, the app may be designed to accept arbitrary values of a certain data type, but the logic determines whether or not this value is acceptable from the perspective of the business.
Many apps incorporate numeric limits into their logic. This might include limits designed to manage inventory, apply budgetary restrictions, trigger phases of the supply chain and so on.
We can take the simple example of an online shop. When ordering products, users typically specify the quantity that they want to order. Although any integer is theoretically a valid input, the business logic might prevent users from ordering more units than are currently in stock, for example.
To implement rules like this, developers need to anticipate all possible scenarios and incorporate ways to handle them into the app logic. In other words, they need to tell the app whether it should allow a given input and how it should react based on various conditions. If there is no explicit logic for handling a given case, this can lead to unexpected and potentially exploitable behaviour.
For example, a numeric data type might accept negative values. Depending on the related functionality, it may not make sense for the business logic to allow this. However, if the app does not perform adequate server-side validation and reject this input, an attacker may be able to pass in a negative value and induce unwanted behaviour.
Consider a funds transfer between two bank accounts. This functionality will almost certainly check whether the sender has sufficient funds before completing the transfer:
However, if the logic does not sufficiently prevent users from supplying a negative value in the amount parameter, this could be exploited by an attacker to both bypass the balance check and transfer funds in the "wrong" direction.
If the attacker sent -$1000 to the victim's account, this might result in them receiving $1000 from the victim instead. The logic would always evaluate that -1000 is less than the current balance and approve the transfer.
Simple logic flaws like this can be devastating if they occur in the right functionality. They are also easy to miss during both development and testing, especially given that such inputs may be blocked by client-side controls on the web interface.
When auditing an app, you should use tools such as Burp Proxy and Repeater to try submitting unconventional values. In particular, try input in ranges that legitimate users are unlikely to ever enter. This includes exceptionally high or exceptionally low numeric inputs and abnormally long strings for text-based fields.
You can even try unexpected data types. By observing the app's response, you should try and answer the following questions:
Are there any limits that are imposed on the data?
What happens when you reach those limits?
Is any transformation or normalization being performed on your input?
This may exposed weak input validation that allows you to manipulate the app in unusual ways. Keep in mind that if you find one form on the target website that fails to safely handle unconventional input, it is likely that other forms will have the same issues.
Making Flawed Assumptions About User Behaviour
One of the most common root causes of logic vulnerabilities is making flawed assumptions about user behaviour. This can lead to a wide range of issues where developers have not considered potentially dangerous scenarios that violate these assumptions.
Trusted Users Won't Always Remain Trustworthy
Apps may appear to be secure because they implement seemingly robust measures to enforce the business rules. Unfortunately, some apps make the mistake of assuming that, having passed these strict controls initially, the user and their data can be trusted indefinitely.
This can result in relatively lax enforcement of the same controls from that point on. If business rules and security measures are not applied consistently throughout the app, this can lead to potentially dangerous loopholes that may be exploited by an attacker.
Users won't always supply mandatory input
One misconception is that users will always supply values for mandatory input fields. Browsers may prevent ordinary users from submitting a form without a required input, but as we know, attackers can tamper with parameters in transit. This even extends to removing parameters entirely.
This is a particular issue in cases where multiple functions are implemented within the same serer-side script. In this case, the presence or absence of a particular parameter may determine which code is executed. Removing parameter values may allow an attacker to access code paths that are supposed to be out of reach.
When probing for logic flaws, you should try removing each parameter in turn and observing what effect this has on the response. You should make sure to:
Only remove one parameter at a time to ensure all relevant code paths are reached
Try deleting the name of the parameter as well as the value. The server will typically handle both cases indifferently
Follow multi-stage processes through to completion. Sometimes, tampering with a parameter in one step will have an effect on another step further along the workflow.
This applies to both URL and POST parameters, but do not forget to check the cookies too. This simple process can reveal some bizarre app behaviour that may be exploitable.
Users won't always follow the intended sequence
Many transactions rely on predefined workflows consisting of a sequence of steps. The web interface will typically guide users through this process, taking them to the next step of the workflow each time they complete the current one.
However, attackers won't necessarily adhere to this intended sequence. Failing to account for this possiblity can lead to dangerous flaws that may be relatively simple to exploit.
For example, many website that implement 2FA require users to login on one page before entering a verification code on a separate page. Assuming that users will always follow this process through to completion and, as a result, not verifying that they do, may allow attackers to bypass 2FA entirely.
Making assumptions about the sequence of events can lead to a wide range of issues even within the same worfklow or functionality. Using tools like Burp Proxy and Repeater, once an attacker has sent a request, they can replay it at will and use forced browsing to perform any interactions with the server in any order they want.
This allows them to complete different actions while the app is in an unexpected state.
To identify these kinds of flaws, you should use forced browsing to submit requests in an unintended sequence. For example, you might skip certain steps, access a single step more than once, return to earlier steps and so on. Take note of how different steps are accessed.
Although you often just submit a GET or POST request to a specific URL, sometimes you can access steps by submitting different sets of parameters to the same URL. As with all logic flaws, try to identify what assumptions the developers have made and where the attack surface lies. You can then look for ways of violating these assumptions.
Note that this kind of testing wil often cause exceptions because expected variables have null or uninitialized values. Arriving at a location in a partly defined or inconsistent state is also likely to cause the app to complain.
In this case, be sure to pay close attention to any error messages or debug information that you encounter. These can be a valuable source of information disclosure, which can help you fine-tune your attack and understand key details about the back-end behaviour.
Domain-Specific Flaws
In many cases, you will encounter logic flaws that are specific to the business domain or the purpose of the site.
The discounting functionality of online shops is classic attack surface when hunting for logic flaws. This can be a potential gold mine for an attacker, with all kindsd of basic logic flaws occuring int eh way discounts are applied.
For example, consider an online shop that offers a 10% discount on orders over $1000. This could be vulnerable to abuse if the business logic fails to check whether the order was changed after the discount is applied.
In this case, an attacker could simply add items to their cart until they hit the $1000 threshold, then remove the items they do not want before placing the order. They would then receive the discount on their oder even though it no longer satisfies the intended criteria.
You should pay particular attention to any situation where prices or other sensitive values are adjusted based on criteria determine by user actions. Try to understand what algorithms the app users to make these adjustments and at what point these adjustments are made.
This often involves manipulating the app so that it is in a state where the applied adjustments do not correspond to the original criteria intended by the developers.
To identify these vulnerabilities, you need to think carefully about what objectives an attacker might have and try to find different ways of achieving this using the provided functionality. This may require a certain level of domain-specific knowledge in order to understand what might be advantageous in a given context.
To use a simple example, you need to understand social media to understand the benefits of forcing a large number of users to follow you.
Without this knowledge of the domain, you may dismiss dangerous behavior because you simply aren't aware of its potential knock-on effects. Likewise, you may struggle to join the dots and notice how two functions can be combined in a harmful way.
For simplicity, the examples used in this topic are specific to a domain that all users will already be familiar with, namely an online shop. However, whether you're bug bounty hunting, pentesting, or even just a developer trying to write more secure code, you may at some point encounter applications from less familiar domains.
In this case, you should read as much documentation as possible and, where available, talk to subject-matter experts from the domain to get their insight. This may sound like a lot of work, but the more obscure the domain is, the more likely other testers will have missed plenty of bugs.
Providing an Encryption Oracle
Dangerous scenarios can occur when user-controllable input is encrypted and the resulting ciphertext is then made available to the user in some way. This kind of input is sometimes known as an "encryption oracle". An attacker can use this input to encrypt arbitrary data using the correct algorithm and asymmetric key.
This becomes dangerous when there are other user-controllable inputs in the app that expect data encrypted with the same algorithm. In this case, an attacker could potentially use the encryption oracle to generate valid, encrypted input and then pass it into other sensitive functions.
This issue can be compounded if there is another user-controllable input on the site that provides the reverse function. This would enable the attacker to decrypt other data to identify the expected structure. This saves them some of the work involved in creating their malicious data but is not necessarily required to craft a successful exploit.
The severity of an encryption oracle depends on what functionality also uses the same algorithm as the oracle.
How to Prevent Business Logic Vulnerabilities
In short, the keys to preventing business logic vulnerabilities are to:
Make sure developers and testers understand the domain that the app servers
Avoid making implicit assumptions about user behaviour or the behaviour of other parts of the app
You should identify what assumptions you have made about the server-side state and implement the necessary logic to verify that these assumptions are met. This includes making sure that the value of any input is sensible before proceeding.
It is also important to make sure that both developers and testers are able to fully understand these assumptions and how the app is supposed to react in different scenarios. This can help the team to spot logic flaws as early as possible. To facilitate this, the dev team should adhere to the following best practices wherever possible:
Maintain clear design documents and data flows for all transactions and workflows, noting any assumptions that are made at each stage
Write code as clearly as possible. If it is difficult to understand what is supposed to happen, it will be difficult to spot any logic flaws. Ideally, well written code should not need documentaton to understand it. In unavoidable complex cases, producing clear documentation is crucial to ensure that other devs and testers know what assumptions are being made and exactly what the expected behaviour is.
Note any references to other code that uses each component. Think about any side-effects of these dependencies if a malicious part were to manipulate them in an unusual way.
Due to the relatively unique nature of many logic flaws, it is easy to brush them off as a one-time mistake due to human error and move on. However, as we've demonstrated, these flaws are often the result of bad practices in the initial phases of building the application.
Analyzing why a logic flaw existed in the first place, and how it was missed by the team, can help you to spot weaknesses in your processes. By making minor adjustments, you can increase the likelihood that similar flaws will be cut off at the source or caught earlier in the development process.
Lab 1 - Excessive trust in client-side controls
Once we start the lab and navigate to the home page, we can see simple looking shop:
If we click on the “View details” button for any product, it will take us to that product’s page, providing a more detailed description of the product, alongside two important options we are interested in - the quantity number and the option to “Add to cart” as we can see below:
As said before in many posts on web app pentesting, we want to map out the application, figure out how it works and what functionality it has before trying to break it. With that said, let’s add the leather jacket to basket and see what happens:
As we can see, it simply adds the product name with the quantity we selected to our shopping basket. It also includes an option to remove the product, as well as the option to enter a coupon code. Finally, at the bottom of the page, we have the button that allows us to actually place the order.
All of these functions would be in scope for testing in a real-world scenario, but let’s continue with this lab.
At the top of our shopping cart page, we can see that we have a $100 store credit that we can use to buy products. Knowing that our end goal is to purchase this leather jacket, we have to find a way to lower its price somehow. But how?
First, let’s see what happens when we try and place the order - maybe the store is nice and doesn’t check our balance:
Sadly not. The shop requires sufficient funds - who would have thought! Our next step is to analyse what these requests look like in Burp Suite. As we were mapping out the application and playing around, Burp Suite was running in the background, intercepting all these requests for us.
One request stands out to us - the POST request to /cart which is when we hit the “Add to cart” button. Can you tell why it’s interesting to us? Take a look below:
We can see that this POST request includes 4 parameters being sent in the request body:
productId - just the ID of the product we added to cart
redir - likely something to do with the redirection that occurs
quantity - how many of that item we want to have in our basket
price - the price of the item
Immediately, when we see the price being sent through the request body of a POST request, alarm bells should be ringing! Why is the price being sent on the client side? The most obvious test would be to change this value and see if we can potentially get it for free!
However, before we do that, let’s observe what a normal response looks like so we can determine if, when we perform our attack, it works normally. Once we send the POST request to /cart, we get a 302 Found response:
If we follow this redirection, it simply takes us back to the home page using a simple GET request. With this functionality enumerate, let’s simply try setting the price to a ridiculously low value like $0.50 (since the value is in pennies/cents):
With this set, let’s send the request over using Repeater and observe what happens:
We can see that it still does the 302 Found response. Let’s follow it to make sure no error occurs during the redirection:
And we can see, the redirection products a 200 OK response, likely indicating that no error occurred.
This is a good indicator that our attack worked. To see if it worked, we can navigate back to our shopping cart in the browser and see if our item was added at a discounted price:
Now, we can try placing our order and see if it goes through:
It does and we were able to order the leather jacket for only $0.50 - what a bargain! This completes this lab and we can move on to the next.
Lab 2 - High-level logic vulnerability
As before, we can start the lab and then access it. Once we do, we are greeted with the same looking shop:
In the last lab, we had $100 store credit sitting in our shopping cart, so let’s see if we have the same here:
We do - just a good thing to note. Next, let’s click onto a product and see what the product page looks like:
It looks just the same as before - we have a more detailed description of the product, as well as a quantity field and a button to add it to our cart. Again, we want to test all the functionality of this site, so let’s add this item to our cart:
Once we do, we can see it appears in our cart, with its price set to $1337.00. With the previous lab, we were able to see that the POST request that sent the product into our cart contained the price in the request and was able to be modified by the client. Let’s check and see if anything has changed:
It has! The developer has learned their lesson and has not included the price parameter being sent over in the POST request body. So, how do we change the price of this product or get it at a lower price?
Woah! Before jumping into it, we need to once again observe its normal functionality and see what happens. Let’s send this request on using Repeater and view the response:
As we can see, we get the same 302 Found response. Let’s follow the redirection and see where it takes us:
The redirection sends a GET request to the product page that we just added to our cart, essentially taking us back to the same page. With this knowledge, we can now start playing around with things and seeing if we can break it.
Since the quantity is sent in this request, let’s try modifying it and seeing what happens. For example, let’s set the quantity to 100:
Then, let’s send it on:
As before, we get the same 302 Found response - this is a good indicator. Since we are only modifying the quantity up to 100, it shouldn’t really produce an error unless there is a limit on how many we can order. Let’s check our cart and see if we have 100 jackets in it:
We do! So we know that we can manipulate the quantity field in the POST request body. As a hacker, we should be thinking “How do we break this? Did the developer not think about a scenario that a hacker might"?”
One of these scenarios we can try is sending a negative number. What happens if we set the quantity to negative 1?
Once configured, let’s send it on and see what happens:
Again, we get the same 302 Found response likely meaning no error occurred. Let’s navigate to our cart and observe what happened:
Aha! So, we can see if we set the quantity to a negative number, the price also becomes negative - great! Let’s see if we can get this jacket for negative money and hopefully they will send us money instead by trying to place this order:
No luck. The application tells us that the total price cannot be less than zero - makes sense right? Maybe there is a way where we can make the price positive but less than our store credit, so we can still get it for a discounted price.
If we were to only use the jacket, it would be impossible as the value would either be negative (not allowed), 0 (also not allowed) or positive ($1337) which we don’t have enough money for.
To fix this, we can try and use another product to balance the prices out and make it fall within that $0-$100 range that we want. For example, we can take the WTF adult party game product:
Since this price is $58.48, we can do some quick maths and work out that we need to send negative 22 (-22) of this product to get a total price of -$1286.56. So, let’s intercept this request and set the quantity to -22:
Once set, we can forward the request and view our cart to make sure it went through successfully:
It did! Our cart total is now $1286.56. Now, since the jacket is $1337, we can simply add 1 jacket to the cart to make the price fall between that range we want where we are able to purchase it with our store credit.
Doing some quick maths, if we add 1 jacket at a price of $1337, to our cart total right now of $-1286.56, we will get a value of $50.44 which we will be able to purchase. Let’s see if our maths is correct:
It is! We can now successfully order 1 jacket at the “technically full price” and order negative 22 of the adult party game to make our balance a lot lower than it should be. With this, we can get the congratulatory banner dropping down and we can complete the lab:
Lab 3 - Inconsistent security controls
Again, we can access the lab and are greeted with the “We Like To Shop” homepage:
On your own, map the application - click around, view products, check the source code etc. Once done, we can notice a new link in the top right corner that allows us to register a new account:
An important thing we see is it tells us that if we work for “DontWannaCry”, we can use the associated email domain. It’s important to note that staff or employees likely have access to additional or hidden functionality from normal users so it’s a good thing to note.
Sadly, we do not have access to any email in that domain, but we can register an account anyways. PortSwigger is nice enough to provide us an email client as seen below that we can use:
With this email in hand, we can simply register an account. For this, I simply used the username attacker and the password attacker, while providing the email given to us:
Once we hit the Register button, we can see the following message:
It tells us that a registration link was sent to our email. Checking our email inbox, we can see the following message we received:
It provides us a link with a temporary registration token. We may be able to fuzz or brute force this token for another user, but that is out of scope for these labs. Moving on, if we click the link, we can see the following:
Our account is successfully registered. With this, we can login to the web app and see what is available in our account page:
In our account page, we only have one function - changing our email. At this point, alarm bells should be ringing. Remember what we saw on the register page? We saw the following message:
This tells us that there is likely some additional functionality provided to employees of this company if they use their email. Now, we know that if we register an account, we have to click on a link sent to an email.
If we provide an email from the domain, we cannot get access to it to click the link and register the account. But, what if we change our email in our account page? Does it require us to verify this new email? Let’s test it by setting our email to “attacker@dontwannacry.com”:
Then, let’s hit Update email and see what happens:
It seems to work! And we didn’t even need to verify that the email was valid or that we owned it - bad developers. Now, the web app likely sees us as an internal employee for the DontWannaCry company.
Most importantly, if we look around the web app again for anything that changed, there is a red flag in the top right that screams “LOOK AT ME!”:
We have an admin panel that was not there before. Let’s see what is in it:
Now we have the option to delete users. For this lab, our end goal was to delete the Carlos user so let’s click on the delete link for Carlos and complete the lab by deleting him from existence:
Lab 4 - Flawed enforcement of business rules
Once we start the lab and navigate to the home page, we immediately see that new customers are provided a coupon code - NEWCUST5:
This is interesting and we can keep this in our back pocket for now. Exploring the page further, there is an option at the bottom of the page to sign up to a newsletter. Typically, companies will offer a discount or something for people who sign up:
If we sign up by using a dummy email such as attacker@attacker.com, it then pops up a message telling us that we can use another coupon code SIGNUP30:
If we login with the provided credentials (wiener:peter), we can see that we once again have $100 of store credit:
Knowing that our end goal is to purchase the leather jacket, let’s try adding it to our cart:
As we can see, this time it adds it at the full price and analysing the request, there is no way we can change this as it is likely done server side. However, we do have two coupon codes! Let’s try applying the first coupon code for new customers and see what happens:
As we can see, it provides us with a $5 discount. A nice gift, but not nearly enough to purchase the jacket. Most web apps will only allow us to enter one coupon code at a time, so let’s try and enter the second discount code and see what happens:
To our surprise, it works and it takes off $401.10 for us which is 30% of the price of the product ($1337). A much better discount, but we still cannot afford it. As a quick test, we can see if the web app allows us to use the same discount code multiple times by entering the SIGNUP30 code again:
Sadly, it tells us that the coupon has already applied. As a test, let’s try with the first coupon again:
It actually works and we are able to use that coupon multiple times. Maybe the back end is seeing what coupon code was applied last and disallowing it from being used. However, it might not be checking if it has been used before in an alternating fashion.
To test this, we can try entering the SIGNUP30 code again and see if that works with our alternating theory:
It does, so it seems like we can alternate these coupons and continue discounting the product over and over again. To exploit this, we can keep alternating codes until the total price hits $0:
Now that the product costs $0, we can easily order it with our $100 store credit and complete the lab by exploiting coupon codes:
Lab 5 - Low-level Logic Flaw
First, we spawn the lab and access the normal home page:
Once there, we can view the details of the leather jacket and see we have the ability to add it to our cart:
Once we highlight the quantity list, some arrows appear on the right hand side:
There are multiple ways to test this, but we can see what the limit is of how many we can add to our cart. You could do this manually or using Burp Suite, but if we hold down the up arrow, we can see below that it stops at 99:
With this, let’s add 99 leather jackets to our cart:
Now obviously we don’t have enough credit to place this order, but maybe we can break the application somehow. Let’s take a look at the add to cart request in Burp Suite:
We can see that it provides 3 parameters in the body - productId, redir and quantity. Since we know that the quantity limit is 99 in the web app itself, let’s try modifying this value to a value over 99 such as 101 and see what happens in the response:
As we can see, it tells us that it’s an invalid parameter. Now that we know we can’t do that, let’s try and observe normal functionality by sending a POST request with quantity of 99:
We can see above that it works as normal and simply provides a 302 response. If we check our cart, the request went through using Burp (I cleared the cart before so that’s why it’s only 99 and not 198):
Let’s try sending the same request again - does it work and allow us to add more than 99 of a single item?
It does! We now have 198 leather jackets in our basket, meaning we can exceed the limit put in place by the web app by simply sending it multiple times. Now, for this lab, we need to understand something.
Generally, an integer is stored as a 32 bit value in the memory. This means the maximum number that can be stored is 2^32 equal to 4,294,967,296 for unsigned integer or −2,147,483,648 and 2,147,483,647 for signed integer.
If we add 1 to the 4,294,967,295 which is the highest number allowed for a variable the result is zero and if we add 1 to 2,147,483,648 we got a negative number. This happens because the compiler is not able to handle what is beyond permitted value.
So what? Why does that matter here? Well, let’s send the POST /cart request to Intruder and find out:
Once in Intruder, let’s set the payload type to Null payloads and make it continue indefinitely. This will constantly send this request and won’t stop until we make it stop:
After starting the attack, we can wait and constantly refresh the page. At some point, when it hits a certain value, the total turns from a positive value to a negative value. As you can see below, with 87,417 jackets in our cart, the value became -11,972,489:
Does this mean if we place this order the company will give us $11 million dollars? Sadly not. Before we place an order, it must be a number greater than 0. To fix this, we can try sending 323 paylaods, with each request adding 99 items to the cart at a value of $1337 each.
I know, I hate maths too, but after this attack runs our cart’s total will be $64,060.96 as it will reach the threshold for positive numbers, turn negative and then start going back to 0:
Before sending this payload we need to throttle it. If you have Burp Professional, we need to set a custom resource pool to make sure that only 1 concurrent request is being sent at a time:
Now, we can let this run and view the total going up, turning negative and coming back down as seen below:
Now that our total is $64060.96, we can add a certain amount of jackets that makes it the smallest negative value possible. If each jacket is $1337 dollars and we have a value of $64060, we can work out that if we add 47 jackets, it will come to a price of $62,839, meaning the final total will be $64060 - $62839 which would equal $1221:
Let’s modify the Request and change the quantity tom 47 and send it along:
Once sent, we can see that the total value becomes $-1221.96:
If we add 1 more leather jacket, that value would become positive, but too positive. Since we only have $100 credit, if we add another jacket, the total price would become $115 which is over our limit. To fix this, we can find a cheaper product such as the Baby Minding Shoes (at least for me) that have a price of $94.83:
Again, some more maths. But if we add 13 of these shoes at a price of $94.83, we would get a total of $1232.79:
If we add that to the negative value we currently have, we would get a value of $10.83, which seems like a bargain for thousands of leather jackets. Let’s set the quantity to 13 in the web app and send it forward:
Now, let’s view the cart and we can see that the total is indeed just $10.83:
So, now we can just place our order right? Well, I was stupid and didn’t sign in before this attack and so it tells me I need to login to continue:
Ooops! If you were smarter and logged in, you could place the order and complete the lab. However, I had to do the same attack again after logging in and ending up with the same value:
This time, we can place the order successfully and complete the lab:
Lab 6 - Inconsistent Handling of Exceptional Input
As usual, the first step is to start the lab and access it:
Once we do, it seems to be a simple shopping site. After mapping the application, we can navigate to the Register link and see if we can register:
As we can see, there is an interesting message at the top telling us that if we work for DontWannaCry, we can use the company email address. Since we don’t know a work email that we have compromised, we can put it in our back pocket for now.
Before continuing, we can see if there is any hidden directories by checking for a robots.txt file:
Not found sadly. Another one we can try and search for is sitemap.xml which could provide the same information:
Another thing we can try is simply manually trying to access an admin panel located at /admin (this is a good step to try regardless of if it exists or not):
Boom! We see that an admin interface does in fact exist but is only available to users who work for the company - interesting. Now, we REALLY want to exploit this web app and try and get an account with a dontwannacry.com email address.
So, the first step is to determine how it operates normally. To do that, we can simply try registering an account:
Once entered, we can hit the Register button and hopefully create an account. However, once we do, we are presented with the following message:
It seems like we need to provide a valid email address to get the registration link. How do we do that? Luckily for us, PortSwigger is kind enough to provide an email client for us:
Let’s try registering again, but this time providing the valid email that we have access to:
Then, we can check that email and see that we do indeed get a registration link:
If we click it, the following happens:
Success! We now have a valid account that we can use for testing. If we navigate to the account page, we can see that it displays our username and email address:
One thing we can try is essentially fuzzing the email to see if there is a character limit where the email gets truncated at. A good value to try at first is 256 characters (i.e. 2^16).
A common issue is different systems using different lengths for the same string. If we can use a string that the web application considers as a “dontwannacry” email while the mail server at the same time uses the full string then we can get the confirmation email.
First, we need to find out if there might be a truncation anywhere. To do this, we can use an online random string generator with the normal character set and set the length to 256:
Once we have this long string, we can paste it into the email field and append the attacker email at the end of it (@exploit-randomstring.web-security-academy.net):
Once we send this across, a new registration link appears in our email client:
If we click it, we can see we successfully register a new account with the username of “hacker2”:
Now, if we go to the My Account page, we can see if our email gets truncated due to the length:
And we can see it does as we cannot see the ending of the email - it gets cut off before the “@” symbol even appears. The question is, at what character does it get truncated at? If we look above, we can see that the last character is a capital Q.
Looking below at our original string, we can see the same full string, but it has a lowercase “m”:
Since we know this is a 256 character string, we know that the cutoff point is likely at 255 characters and anything after it gets truncated:
Now, we want to add the “@dontwannacry.com” domain somewhere in the string. First, we must determine how many characters it is - we can use an online word counter and see that it is 17 characters long:
Now, considering that the length is 17 characters long, we can generate a new random string being 239 characters long:
Once we have this string, we can start our email with the 239 character string, then add the dontwannacry domain, and finally add the “.exploit-random-string” domain to actually receive the email:
With the dontwannacry domain added at the end, the entire length becomes 256 characters:
Once we hit the Register button, it tells us to check our email again:
Checking our email, we successfully see the email was sent and we can click it to register our new account:
Now, if we go to our My Account page, we can see what our email is:
And, like a human, I made an error. I made the random string 1 character too long so the email gets cut off at “dontwannacry.co” instead of “dontwannacry.com” meaning we don’t get access to the email.
Why did I include this error in this writeup you may ask? I wanted to show that everyone can make mistakes sometimes and learning from your errors is a key part of succeeding in this field. On the bright side, we can do the exploit again, properly this time and get more practice in.
If you followed along and made the same error I did, re-do the email registration part again, this time taking out 1 character from the previous string so that the email becomes valid.
Once done, you should end up with an email that ends in “dontwannacry.com” as seen below:
Now, we have access to the admin panel. If we go to the admin panel, we have the ability to delete all the users:
To complete this lab, we simply delete the Carlos user and the banner drops down, indicating we have successfully completed the lab:
Lab 7 - Weak Isolation on Dual-Use Endpoint
Once we start and access the lab, we can see a simple blog site:
We can log in with the provided credentials (wiener:peter) and navigate to our account page and see what’s happening:
We can see some functionality on this page. For example, we can update our email and see that it gets reflected back onto the page:
However, more interestingly is the password update functionality. Here, we can provide the current password and then the new password twice:
Once we do, we get a message saying our password changed successfully:
Now that we know the normal functionality of the website, we can view the request in Burp Suite and start tampering around. If we take a look at the password change request sent, we can see it was sent to the /my-account/change-password URL and provides 4 parameters in the body of the request:
We can see it requires our username, the current password, and the new password twice. Immediately, you might be thinking, what if we just change the username to another user, such as administrator? Good thinking! Let’s try it:
Now, if we send this request on, let’s look and see what happens:
It returns a 302 Response - seems like a good thing, like it worked right? Let’s follow the redirection and see if anything else happens:
It simply takes us back to the login page. Interesting as we observed the normal functionality was to get a message saying it succeeded. However, let’s try anyway to log in as the administrator with the new password of “password”:
As expected, it failed meaning our attack likely failed. Ok, so we know just changing the username does not work - this is good for them, bad for us. We also saw a current-password field. It seems like we are required to know the current password of the user before we can change it.
However, what happens if we play around with this parameter? For example, let’s try simply deleting the value inside it:
Let’s send it on and see if anything changes:
Sadly, it seems we get the same response as before but you never know. Let’s try logging in again with the password “admin” as seen above:
Again, no luck. So we tried changing the username, we tried removing the current password, but what if we remove that parameter entirely and don’t even send the current password in the request:
Now, let’s send it forward and see what happens:
Again, seems like no luck. Let’s try it anyways:
Hmmm…. maybe we are missing something. At this point, we can try changing the username back to wiener and still remove the current password parameter:
Let’s see if that works:
Again, no luck and if we try logging in with the changed password, nothing changed:
What’s happening? it’s hard to say, but maybe it has to do with using Repeater and not actually intercepting the request on its way to the server. Let’s test this theory by turning intercept on:
Once on, let’s send a change password request and catch it:
Once captured, let’s try changing the username to administrator and removing the current password parameter as we did before:
Now, this time, what do we see in the browser when we forward it:
Interesting! It seems like we changed the password successfully. Let’s try logging in as the administrator user with the password you specified:
Boom! We are successfully logged in as administrator and were able to change his password without providing the current password. From here, we can navigate to the admin panel and see we have the option to delete the Carlos user:
Once we delete Carlos, we complete the lab:
Lab 8 - Insufficient Workflow Validation
As always, let’s start the lab and see what we are working with:
We see the same simple shop we’ve seen many times before. Let’s login using the provided credentials and see what’s up:
Here, we can see the option to update our email and we also see that we have $100 in store credit that we can use to purchase items. Let’s observe other functionality. When we click View Details, we can see the option to specify the quantity and a button to add the item to our cart:
Once we add the item to the cart, we can see it simply adds it, specifying the price and the quantity and then displays the total:
Since this item is extremely cheap, let’s place the order and see what happens:
It simply tells us that our order is on the way and shows what we ordered. Now that we’ve observed the functionality in Burp Suite, we can start playing around. If we look at the request that adds the item to the cart, we can see it provides the parameters:
If we look at the request to checkout, it is a simple request with no parameters in the body:
However, if we look at the GET request for order confirmation, there is a parameter in the URL titled “order-confirmed” which is set to TRUE:
Very interesting! Now, let’s try adding the leather jacket to our cart:
As we can see, we don’t have anywhere near the funds to complete this order. Once we hit the “Place order” button, a message appears telling us that we do not have enough store credit for this purchase:
If we view the request in Burp Suite, we can see that the GET request does not include the order-confirmed parameter, but instead includes the “err” parameter set to “INSUFFICIENT_FUNDS”:
Remember in the earlier request we saw an order-confirmed request, if we look at the body of that request, there is nothing there. It doesn’t check our cart, our funds, what items we’re ordering, anything - maybe we can manipulate this?
If we grab this request and send it to Repeater from the previous order and forward it one, we see a 200 OK response:
Now, if we go to the render view, we can actually see the message that tells us our order is on its way meaning we bypassed the likely funds check and were able to order items that we cannot purchase legitimately:
And with this, we complete the lab!
Lab 9 - Authentication Bypass via Flawed State Machine
As always, we start the lab and are greeted by the familiar shopping site:
If we attempt to login with the credentials, we are greeted with the following page telling us to select a role:
Now, it defaults to the “User” role but let’s see what other roles are available in the drop down menu - maybe there is an administrator role?
Unfortunately not, but we do see another role - Content Author. This likely does not have admin privileges, but let’s select that role anyway and submit it:
Once we log in and navigate to the My Account page, we can see that we have the option to change our email, but nothing that really stands out. If we try and navigate to the /admin page, we are greeted with the message telling us that it is only available to admins - not us:
Now, we can look at the requests in Burp Suite. I’ve highlighted the key ones below - being the POST /login request, the GET /role-selector request and the POST /role-selector request.
Below is the initial POST /login request that simply sends the username and password to the web app:
Then, once the login is successful, the web app performs a GET request to /role-selector which is the drop down menu page we saw earlier:
Then finally, once we select the role we want and send it forward, it sends a POST request specifying that role in the body of the POST request inside a parameter called “role”:
So, what now? Well, first we can try and set the role to administrator after interception and see if that works. Who knows, maybe there is a hidden administrator role only available to certain users? To do this, we can login again and turn intercept on and send the POST /role-selector requests:
Once intercepted, we can try modifying the role parameter value to “administrator”:
Once we forward it one, we get taken back to the home page, but we do not see any sort of admin panel in place at the top right:
it seems like it did not work. However, one thing we can try is simply dropping the role-selector request. Maybe there is a default value that the role is set to (admin?) and if we drop the page to select that role, it gives us the default role.
Let’s try it by first intercepting the GET request for the role-selector:
After intercepting, let’s hit the Drop button and not allow that request to go through. Once we do that, Burp Suite gives us an error stating that the request was dropped by the user:
However, if we simply navigate back to the home page, we see an interesting new link on the top right:
We have access to the admin panel! It seems like the default role given is the administrator or admin role and we can get this role by simply dropping the request for the role-selector page and navigating back to the home page ourselves.
From here, we can access the admin panel, delete the Carlos user and complete the lab successfully:
Lab 10 - Infinite Money Logic Flaw
As always, let’s start the lab and we are greeted with the simple shop again:
Next, we can start to map out the application. Once done, we can login with the provided account and navigate to the My Account page to see what we are working with:
We see the same functionality to change the email, but there is an additional box allowing us to redeem a gift card code - interesting! Let’s keep that in our back pocket and take a look around. If we go back to the main page and scroll all the way down to the bottom, there is an option to sign up for a newsletter:
Let’s sign up with a random email and see what happens:
Immediately, we are greeted with the message telling us that we can use a coupon code “SIGNUP30” at checkout. Judging by the name, we can assume that we get 30% off a product. It’s not a big enough discount to purchase the jacket outright, so let’s delve further in.
We saw an option to redeem a gift card, meaning there is likely somewhere we can purchase gift cards. Usually, stores don’t allow discount codes to be applied to gift cards since it is free money, but let’s see. If we look around the main page, we can see a product titled “Gift Card”. Let’s look at its details:
There is an option to add it to the cart, so let’s add it. Once added, let’s try and use the coupon code we received earlier and see if it works - SIGNUP30:
It does! Here, we can see that the normal price of the Gift card is $10, but with our coupon applied it comes down to $7. Now, let’s see if we can complete this order:
We can and our order was completed. Additionally, we can see the web app tells us we bough the following gift cards and provides the code to redeem it. If it is redeemable for the full price ($10) and we only paid $7, then we should get $3 free dollars. Let’s navigate to our account page and redeem the code:
Bingo! We now have more money than what we started with. This is exploitable if we can redeem the code more than once. So, let’s try again and do the same steps. First, we add the Gift Card to our cart and use the SIGNUP30 code again:
Seems to accept the code more than once. Let’s confirm by placing another gift card order:
And we successfully get a second code below the first (positioning is important for later). Let’s go back to the account page and try redeeming this:
Perfect! We get another $3 free dollars. Since we have confirmed that we can use this coupon code more than once and apply it to a gift card, let’s start exploiting this to earn free money. In order to do this effectively, we need to identify the entire process for purchasing the gift card, getting the code and then redeeming it.
Looking closely at the requests, we are able to determine that 5 key requests are part of this process:
First, we simply send a POST request to /cart that adds the gift card to our basket. Then, we send another POST request to /cart/coupon that redeems the SIGNUP30 coupon code, allowing us to get 30% off. Thirdly, we send a POST request to /cart/checkout that starts the process of completing our transaction.
The fourth request sends a GET request to /cart/order-confirmation with a parameter telling the web app our order was confirmed. Finally, we send another POST request to /gift-card that sends the coupon code and redeems it for us, adding money to our account.
To automate this, we can use a Macro. First, we go into the Session Handling Rules tab and click Edit. Once there, we specify the URL scope to include all URLs:
Then, we go to the rule editor and click Add and then “Run a macro”:
Once in the editor, we need to select which requests are part of this macro. Since I highlighted all 5 requests, they are the only ones that show up. These are the requests we need to complete this process. Select them in order, starting from POST /cart and ending with POST /gift-card:
Once added, they should appear in the correct order:
We need to edit the 4th request to tell it where to grab the coupon code from. To do this, let’s select request #4 and hit the “Configure item” button on the right:
Inside here, we can name it whatever we want (I named it gift-card). Then, we need to highlight the coupon code in the request by simply clicking and dragging:
This will fill out the information for us using regex. Once done, it should appear in the parameter handling menu:
Next, we need to configure request #5 to do something similiar:
For request #5, we need to tell it to use a certain parameter which is the parameter we just specified in request 4. To do this, we simply tell it to grab the gift-card parameter from Response 4:
Once everything is configured, we can hit the “Test macro” button in the bottom right and it should work:
We can see that the last request has a new gift-card parameter. To check it worked successfully, we can go back to the web app and check our store credit:
It worked! Now, we have $109 dollars. Everything is setup and we can start generating free money. To do this, we can use Intruder (unless you want to it manually, then feel free). First, we send a simply GET request for the home page over to Intruder:
Then, we are not setting any positions to insert payloads. Instead, let’s navigate over to the payloads tab and select the payload type of “Null payloads” and tell it to send 500 payloads:
This will send 500 of these GET / requests, but it will also send 500 of the macros we sent - therefore redeeming 500 gift card codes, giving us enough money to buy the jacket. However, before firing it off, we must set the concurrent requests to 1, otherwise the macro may bug out and not work correctly:
Once we start the attack, we can keep refreshing the web app and see our money going up:
After the attack completes, let’s look at our store credit and notice we now have over $1600 dollars:
With this, we can now add the leather jacket to our cart and purchase it using our free money. Additionally, to rub salt in the wound, we can also apply that same SIGNUP30 code and get over $400 dollars over, giving us even more free money:
Once we place the order, we successfully complete the lab:
Lab 11 - Authentication Bypass via Encryption Oracle
Let’s start the lab and see what we are dealing with:
After looking around the app, nothing stands out immediately. Next, we can try logging in with the provided credentials (wiener:peter):
As we can see, we also have the option to stay logged in which we can check. Once we log in successfully, we are taken to the My Account page:
Let’s keep that stay logged in box in the back of our minds and continue mapping out the app’s functionality. If we look at a blog post, we can see the ability to leave a comment which could be vulnerable:
Seeing this, let’s post a simple comment to test its normal functionality:
Once we post the comment, we can see a message telling us that our comment was submitted:
Once we go back to the blog, we can see our comment appears on the site:
Now, let’s try using an invalid email format and see what happens:
If we send that comment, an error message appears at the top telling us that an invalid email address was used and the value that we submitted:
With this in mind, let’s check these requests in Burp Suite. If we look at the POST request to /post/comment, we can see that the response includes a “notification” inside the Set-Cookie header which looks like something has been encoded:
With a name like “notification”, this might be our error message that we saw at the top of the screen. Looking at the other request, we can see that error message in the HTML source code alongside the notification being sent in the request with the same value:
Looking at the requests again, we can see a cookie set for stay-logged-in with a random value:
And in the GET request, we see it also includes that stay-logged-in cookie:
Now, if our theory is correct and the notification value is simply the error message encoded, let’s try entering a different value into the notification parameter - maybe our stay-logged-in value? Replace the value of notification with the stay-logged-in cookie and forward it:
Interestingly, it decodes it for us and we can now see that the value of stay-logged-in was the username (wiener) and a random string of numbers. What is this random string? Well, it’s not our password or a hash. Immediately, I am thinking it is Epoch time, which is essentially a long string of numbers to tell the date and time.
To confirm this, we can go to an Epoch converter and enter the value:
And we can see it converts to a valid date and time format (you can see I did this lab on 21st March). Ok, so we know that we can use the notification parameter to decode certain things. Our next step is to try and become the administrator.
Knowing what we know with the stay-logged-in parameter, we can try changing the email in the POST /post/comment request to “administrator” and the Epoch value that we got earlier (you can use the same one):
When we send that one, we get the same response that sets an encoded notification for us. If we look at the next request it does, we can see that it produces that same error:
From here, we can grab that encoded value and decode it as URL encoding in Burp Decoder:
This decodes to Base64 (by the == at the end). So next, we decode it again from Base64 and are left with the raw bytes. Now, once we have the raw bytes, we can delete the first 23 bytes. But why you might ask? Well, if we know that this is the full error “Invalid email address: administrator:16793999729831”, then we want to delete the start of it so we are only left with the administrator and the epoch time value.
In order to do that, we need to delete the first 23 characters. If we look at a character counter online and enter the start of this, we can see that it does equal 23 characters with each character being a byte:
With that sorted, we must reverse our decoding steps so we need to first Base64 encode it and then URL encode it to get the massive string seen below:
Once we have this string, we can paste this string into the "notification” parameter as seen below and view the response:
Interestingly, we get an internal server error so something has happened. If we scroll down into the response, we can see a message stating that the input length must be a multiple of 16:
With this mind, we need to pad the “Invalid email address: " prefix with enough bytes so that the number of bytes we remove is a multiple of 16. Since the number of bytes right now is 23, we must add 9 characters, therefore we simply add 9 x’s to the front of administrator in the email field:
Once done, we perform the same steps again. First, we send the request forward and grab the notification value:
Then, we follow the same steps - decode it as URL:
Then, we decode it as Base64, remove the first 32 bytes (this would be removing “Invalid email address: xxxxxxxxx” which is 32 characters long). Once removed, we simply re-encode with BAse64 and URL:
Once we have the final value, we grab a request to the home page and capture the stay-logged-in value:
Then, we simply paste in our encoded value we just got from Decoder and delete the session header (since the session is mapped to wiener):
And we send it on. Here, we can see in the response that an Admin panel actually appears, indicating that our attack worked and we have impersonated the administrator user using the stay-logged-in cookie:
Looking at the render in Burp Suite, we can see this is true:
From here, we can open the response in our browser:
Once in and we have access to the admin panel, we can simply delete the Carlos user and complete the lab: