Never Winter Bank

Prompt

You have been hired to run a pentest on this bank's old out of date system. Can you find the vulnerability in the code and drain this user's account?

Walk-Through

This challenge requires you to identify a vulnerability in the web application that allows you to drain another user’s account, implying some sort of logic error is involved.

image

To start, let’s inspect the web server for any interesting files or behavior. Try to access /robots.txt to see if there are any files that the web developer do not want search engines to index. This is usually a good sign of something interesting to probe.

Add robots.txt to the end of the URL and click enter to go to the page.
Add robots.txt to the end of the URL and click enter to go to the page.

Here’s what the /robots.txt file shows:

Disallow: *
/dev/rel.js
/robots.txt content. This prevents bots from crawling any path that has “/dev.rel.js” in the URL

Try to access a page with dev/rel.js in the URL to see what is contained in that file.

image

Here’s the output of that .js file:

// TODO auditor says something is wrong with this code....
if (parseInt(amount) < account.amount) {
  if ((account.amount - parseInt(amount)) < account.minimum) {
    return res.status(400).send('Error: Account is not allowed to have a balance lower than 10');
  }
  var transferAmount = parseInt(amount, 10);
  account.amount -= transferAmount;

}
/dev/rel.js content

You don’ t need to understand what the code is doing to notice something wrong with this code.

The thing that stands out the most is the use of parseInt, specifically that its usage is inconsistent. The first two times that parseInt is used, it is called only with one argument.

image

but the third time that parseInt is used, it is called with two arguments.

image

Referring to the parseInt() documentation, we can see that there are two different function prototypes: parseInt(string) and parseInt(string, radix).

In the above code snippet, parseInt(string, radix) uses 10 as the radix, meaning that the parsing function will treat the string parameter (or the value entered for “amount”) as a base 10 integer, or decimal number.

However, the parseInt(string) does not specify any base system to use so the input is unclear. In older versions of JavaScript runtimes, there is a vulnerability that exists such that you can actually pass in confusing strings into parseInt.

Let’s visualize this here:

console.log(parseInt('01000')); // 1. returns 512

console.log(parseInt('01000', 10)); // 2. return 1000

The above code snippet shows that if we do not include a specific radix, the parseInt function will treat strings that start with 0 as an octal (base 8) string. This means that it’ll parse the string "01000" as octal and output the decimal value for that number.

Octal 01000 == Decimal 512
Octal 01000 == Decimal 512

This leads to different data values for the same input string depending on if you specify a radix base or not. This is precisely the vulnerability that we will take advantage of.

In the original /dev/rel.js code snippet, we can see that the conditionals (if statements) that check if you have enough balance is using parseInt without a radix.

At the end of the program, the transfer uses parseInt with a specific base 10 radix. Since the string is parsed initially as an octal number, we can successfully pass the check and transfer more money than is in the account.

For example, if we use "01000" as the string, that means the code will only check if we have $512 to cover the transaction, but it will transfer $1000 in the end. However, “01000” is not the only value that can be entered to get the flag.

To get the flag, the value entered (in octal) must be less than or equal to the equivalent of 990 (in decimal). This is because of the error line in the script that says that the minimum in the account must be at least 10. The value entered (in octal) also needs to end up being interpreted as at least 1000 in decimal as well.

image
“00999” does not yield the flag.
“00999” does not yield the flag.

Keep in mind, the input value is evaluated before the transfer is made. Therefore, any value in octal between "01000" and "01736" (octal for a decimal value of 990) will yield the flag.

image
Part of the flag is redacted. The flag is randomly generated for each user.
Part of the flag is redacted. The flag is randomly generated for each user.

Useful resources for this challenge:

  • parseInt() Documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
  • Radix: https://en.wikipedia.org/wiki/Radix

Questions

1. What is the path of the leaked file?

Look in the /robots.txt endpoint for the leaked file path

2. What is the flag?

Exploit the text to number conversion by submitting 01000 as the amount of money to transfer. This will transfer more money than the account has and a flag will appear on your screen.

⚠️

The flag is randomly generated so the correct flag for you will be different.

©️ 2025 Cyber Skyline. All Rights Reserved. Unauthorized reproduction or distribution of this copyrighted work is illegal.