📓
Pentesting
  • Writeups
  • HackTheBox
    • Easy Machines
      • Beep Writeup
      • Shocker Writeup
      • Lame Writeup
      • Jerry Writeup
      • Legacy Writeup
      • Blue Writeup
  • TryHackMe
    • Walkthroughs: Easy
      • CC: Steganography
      • Cryptography for Dummies
      • Cross-site Scripting
      • SQL Injection Lab
      • SQL Injection
      • ZTH: Web 2
      • SSRF
      • XXE
      • Authenticate
      • Injection
      • Blaster
      • The Cod Caper
      • Hardening Basics Part 1
      • What the Shell?
      • Game Zone
      • Upload Vulnerabilities
      • Bolt
      • Erit Securus 1
      • CC: Pentesting
      • JavaScript Basics
      • OverPass 2 - Hacked
      • Linux: Local Enumeration
      • Ice
      • Linux Backdoors
      • Avengers Blog
      • DNS in Detail
      • Putting it all together
      • Kenobi
      • Common Linux Privesc
      • Network Services 2
      • Network Services
      • The Hacker Methodology
      • The Find command
      • HTTP in Detail
      • Web Fundamentals
      • How Websites Work
      • Introductory Networking
    • Challenges (CTF): Easy
      • VulNet: Roasted
      • VulNet: Internal
      • Git Happens
      • Kiba
      • VulNet: Node
      • Memory Forensics
      • Smag Grotto
      • Investigating Windows
      • Cat Pictures
      • Juicy Details
      • Anthem
      • Tony The Tiger
      • Jack-of-All-Trades
      • JPGChat
      • Blueprint
      • All in One
      • Gotta Catch'em All
      • Mustacchio
      • Break Out The Cage
      • HeartBleed
      • Poster
      • Madness
      • Source
      • Thompson
      • Library
      • Magician
      • Anonforce
      • Dav
      • GLITCH
      • Fowsniff CTF
      • Team
      • H4cked
      • Easy Peasy
      • ColddBox: Easy
      • Archangel
      • Cyborg
      • Chocolate Factory
      • Brute It
      • Year of the Rabbit
      • ChillHack
      • Gaming Server
      • Brooklyn Nine Nine
      • Wgel CTF
      • Tomghost
      • ToolsRus
      • Skynet
      • Startup
      • Agent Sudo
      • Lian-Yu
      • OhSINT
      • Overpass
      • Crack The Hash
      • Ignite
      • Inclusion
      • Bounty Hunter
      • LazyAdmin
      • RootMe
      • Pickle Rick
      • Basic Pentesting
      • Simple CTF
  • Crackmes.one
    • 1 Difficulty Rating
      • easyAF
      • Easy Keyg3nme
      • Random
Powered by GitBook
On this page
  • Challenge
  • Notes
  • Background
  • Code Review - Identifying the Vulnerability
  • Debugging the Application Locally
  • Exploitation
  • Prevention & Patching

Was this helpful?

  1. Crackmes.one
  2. 1 Difficulty Rating

Random

PreviousEasy Keyg3nme

Last updated 4 days ago

Was this helpful?

Challenge

  • So first thing to do is to check how the login is working on the web browser

  • We are sending a HTTP request with JSON data of username and password, now lets look at the code base

  • In red we are checking if the username and password fields are set, then we pass the username into the get_user function, which we check out soon

  • In blue, we see a type juggling vulnerability, with the ==, we can try and set both values equal to each other, for the password, this can be achieved by setting it as an array which will be null, so now we have to set the $user variable to null as well. Lets look at the get_user function.

  • So we're basically checking if the username we provided is within in the database, if not, it will return null, that is good to know as we want to be able to return the username value as null . Next lets check how the username is being checked in profile.php

  • So it looks like we are using the strpos function to check if admin is in the username, and if not, return false, if yes, then run the function to get the flag

  • Based on this, it is looking for the string admin within the username we provide. This is flawed as we can pass in something like 1234admin and that would work.

  • So we know that we have to set the value of username to have admin in the string and also pass in a username that is not valid so that we can set the value of username to null

  • We can achieve this by setting the username to 1234admin and also set the password to null by making it an array

  • And this should get you admin access and also the flag!

Notes

Now that we have discussed what type juggling is and under which conditions it is performed, let us explore how it can lead to an unexpected outcome of a comparison that can result in an authentication bypass.


Background

Before analyzing our sample web application, let us establish how type juggling can lead to an authentication bypass in PHP.

Strcmp Bypass

As a first simple example, let us consider the following code snippet:

Code: php

$admin_pw = "P@ssw0rd!";

if(isset($_POST['pw'])){
    if(strcmp($_POST['pw'], $admin_pw) == 0){
        // successfully authenticated
        <SNIP>
    } else {
        // invalid credentials
        <SNIP>
    }
}

Code: http

POST / HTTP/1.1
Host: typejuggling.htb
Content-Type: application/x-www-form-urlencoded
Content-Length: 8

pw[]=pwn

Note: The behavior of strcmp was changed in PHP 8.0.0 to throw an error if any argument is not a string. Thus, the bypass only works in PHP versions prior to 8.0.0.

Magic Hashes

In a more realistic scenario, the password is hashed before the comparison, resulting in the comparison of two variables of the data type string. Consider the following code snippet:

Code: php

$hashed_password = '0e66298694359207596086558843543959518835691168370379069085301337';

if(isset($_POST['pw']) and is_string($_POST['pw'])){ 
    if(hash('sha256', $_POST['pw']) == $hashed_password){
        // successfully authenticated
        <SNIP>
    } else {
        // invalid credentials
        <SNIP>
    }
}

Code Review - Identifying the Vulnerability

Now that we discussed how type juggling could lead to an authentication bypass, let us jump into our sample web application:

Logging in with the provided credentials for the user htb-stdnt, we can see that we are unauthorized to access the post-login page:

Looking at the network traffic, we can see that the web application sends our login data in JSON format, which is interesting for a PHP web application:

Let us investigate the login logic in the provided PHP code in index.php:

Code: php

<?php
   require_once ('config.php');
   session_start();

   // parse json body
   $json = file_get_contents('php://input');
   $data = json_decode($json, true);

   // check login
   if(isset($data['username']) and isset($data['password'])){
    $user = get_user($data['username']);

    if($user) {
        // check password
        if ($data['password'] == $user['password']){
            $_SESSION['username'] = $data['username'];
            $_SESSION['loggedin'] = True;
            echo "Success";
            exit;
        }
    }
    echo "Fail";
    exit;
}

?>

Furthermore, we have the following PHP code in profile.php:

Code: php

<?php
   require_once ('config.php');
   session_start();

   if (!$_SESSION['loggedin']) {
    header('Location: login.php');
    exit;
   }

   $content = "Unauthorized!";
   // allow access to all our admin users
   if(strpos($_SESSION['username'], 'admin') != false) {
    $content = get_admin_info();
   }
?>

Analyzing the source code, we see two loose comparisons leading to potentially unexpected cases of type juggling. The first is in the password check in index.php, and the second is in the username check in profile.php. The username check grants access to all users containing the string admin in the username. Since we cannot change our username, there is no way to bypass this check easily.


Debugging the Application Locally

Authentication Bypass

PHP Fatal error: Uncaught mysqli_sql_exception: Connection refused in src/config.php:8 Stack trace: #0 src/config.php(8): mysqli_connect('127.0.0.1', 'db', Object(SensitiveParameterValue), 'db') #1 src/index.php(2): require_once('...') #2 {main} thrown in src/config.php on line 8

Fatal error: Uncaught mysqli_sql_exception: Connection refused in src/config.php:8 Stack trace: #0 src/config.php(8): mysqli_connect('127.0.0.1', 'db', Object(SensitiveParameterValue), 'db') #1 src/index.php(2): require_once('...') #2 {main} thrown in src/config.php on line 8

Looking at the file config.php referenced in the error message it contains the following code:

Code: php

<?php

$servername="127.0.0.1";
$dbusername="db";
$password="db-password";
$dBName="db";

$conn = mysqli_connect($servername, $dbusername, $password, $dBName);

Authentication Bypass

PyrusHacks@htb[/htb]$ docker run -p 3306:3306 -e MYSQL_USER='db' -e MYSQL_PASSWORD='db-password' -e MYSQL_DATABASE='db' -e MYSQL_ROOT_PASSWORD='db' mysql

This creates a new MySQL server with the credentials given in config.php. However, the database is empty. So, let us create a users table with a dummy user. To do so, we need to create a file called db.sql with the following contents:

Code: sql

CREATE TABLE `users` (
  `id` int(11) NOT NULL,
  `username` varchar(256) NOT NULL,
  `password` varchar(256) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

#htb-stdnt:Academy_student!
INSERT INTO `users` (`id`, `username`, `password`) VALUES
(1, "htb-stdnt", "44891a5fc2dad49eab817badff4cb98adec418e43e6c6cb39984f8d090c6b0c4");

Afterward, kill the docker container we started previously and start a new one with the following command from the directory containing the db.sql file:

Authentication Bypass

PyrusHacks@htb[/htb]$ docker run -p 3306:3306 -e MYSQL_USER='db' -e MYSQL_PASSWORD='db-password' -e MYSQL_DATABASE='db' -e MYSQL_ROOT_PASSWORD='db' --mount type=bind,source="$(pwd)/db.sql",target=/docker-entrypoint-initdb.d/db.sql mysql

Afterward, we can run the web application using PHP's built-in web server by clicking on Create new launch.json and selecting the Launch Built-in web server debugger in the drop-down menu on the left. Then, we can access the web application at the URL printed in the debug console.

Note: Keep in mind that the behavior of type juggling differs depending on the PHP version. Thus, we need to ensure that our local PHP version matches the PHP version used by the target web server.


Exploitation

Since the web application supports JSON parameters, we are not limited to the data type string, enabling us to bypass the authentication check due to type juggling. The password is not hashed, thus, we can provide any data type to the comparison. Looking back at the table in the previous section, we can see that the comparison 0 == "php" evaluates to true in PHP versions before 8.0.0. Thus, if we provide the number 0 as the password and it is compared to the admin user's password, we can bypass the authentication check due to type juggling:

Since we are now logged in as the admin user, we can access the post-login page.


Prevention & Patching

The prevention of vulnerabilities resulting from type juggling is simple - use the strict comparison operators === and !== instead of the loose ones == and !=. In most cases, the result of a loose comparison is unexpected and undesired. In particular, strict comparisons should always be used for sensitive operations such as authentication.

The function returns 0 if the two compared strings are equal. How can we bypass this authentication check without knowing the admin password? If we supply a variable of the data type array, the function strcmp returns null, resulting in the comparison null == 0, which is true after type juggling. We can send an array as a POST variable by sending a request like this:

Our provided password is hashed using SHA-256 and loosely compared to the hashed admin password. If we look at the correct password hash, we can see that it starts with a 0e followed by only numbers. As discussed in the previous section, PHP will compare two strings numerically if both can be treated as numbers. Since the hashed password is of a valid number format (in this case, the scientific float notation is equal to 0), we simply need to provide a password for which the hash follows the same format. These hash values are called magic hashes. Luckily for us, there are pre-compiled lists of values that result in such magic hashes, for example, is a collection on GitHub. Selecting SHA-256, we can see that the password 34250003024812 results in the hash 0e46289032038065916139621039085883773413820991920706299695051332, which follows the correct format for our bypass. PHP then compares the two hashes numerically, converting both strings to the number 0 and thus evaluating the comparison to true such that we successfully bypass authentication.

image

To debug the web application locally, we need to install the VS Code extension. Afterward, we can open the file index.php in VS Code, click Debug and Run, and select the PHP Debugger. However, doing so results in an error message. In the debug console, we can see the following error:

The web application attempts to connect to a MySQL instance on localhost, which is currently not running. Instead of installing a MySQL server on our local machine, we can use a container. To match the parameters provided in config.php, we can start the docker container using the following parameters:

image
strcmp
here
PHP Debug
MySQL Docker