PHP Type Juggling

What is PHP Type Juggling?

is the automatic conversion between different data types by PHP when performing operations or comparisons. This behaviour is rooted in PHP's nature as a loosely-typed language, designed to make development easier but often leading to unexpected results and potential security vulnerabilities if not managed properly.

Key Concepts:

  • Comparisons:

    • Loose Comparison(==):

"123" == 123   // true
"0" == false   // true
null == ""     // true
var_dump(0 == 'test'); // true, because 'test' is converted to 0
var_dump('0' == 0);    // true, both are treated as integers
  • Arithmetic operations:

    • performing math operations between strings numbers causes strings to be converted to numbers

"10" + 5    // 15
"10abc" + 5 // 15
  • Boolean Contexts:

    • Non-boolean values are converted to true or false based on their value.

if ("0") {.....}    // Evaluates to false
if (0) {......}     // Evaluates to false
if ("foo") {.....}  // Evaluates to true
if (bool(False)) {} // Evaluates to false
if (bool(None)) {}  // Evaluates to false
if (bool(0))  {}    // Evaluates to false
if (bool("")) {}    // Evaluates to false
if (bool(())) {}    // Evaluates to false
if (bool([])) {}    // Evaluates to false
if (bool({})) {}    // Evaluates to false
  • String Operations:

    • Concatenating numbers with strings converts numbers to strings.

"Number: " . 10 // "Number: 10"
var_dump(0 === '0');   // false, different types (int and string)

How Type Juggling Works

  1. String to Number:

    • Non-numeric strings are partially converted to numbers until an invalid character is encountered.

(int) "123abc" // 123
(int) "abc123" // 0

Boolean to Integer:

(int) true // 1
(int) false // 0
  1. Loose Comparison:

    • PHP converts both operands to a common type for comparison.

"10" == 10     // true
0 == false     // true
0 == bool({})  // true
  1. Strict Comparison:

    • Compares both value and type, avoiding type juggling.

"10" === 10 // false
0 === false // false

Identifying Type Juggling

Source Code Review & Analysis

  1. Code Review:

    • Look for operations between different types, especially comparisons.

    • Identify any loose comparisons (==, !=) that could cause unexpected results.

    • Examine the use of user inputs in operations and comparisons.

    • Review functions or methods where types are not explicitly handled.

  2. Static Analysis Tools:

    • Use tools like PHPStan or Psalm to identify potential type juggling issues by analyzing code for type inconsistencies.

  3. Explicit Type Declarations:

    • Ensure proper type hints and declarations (e.g., function parameters, return types) are used where possible.

  4. Testing and Debugging:

    • Test with varied inputs to observe behaviour. Use var_dump() similar functions to print variable types and values before and after operations.

$input = "123";
var_dump($input);  // string(3) "123"
$input += 1;
var_dump($input);  // int(124)

Why Did This Happen?

PHP automatically tries to convert types based on context:

  • PHP converts the string to a number when you try to perform an arithmetic operation on a string containing numeric characters.

  • "123" is a valid numeric string, so PHP treats it as an integer 123 and adds 1, resulting in 124.

External Testing and Observation

  • Fuzzing Testing:

curl -X POST -d "input=123" <http://example.com/test>
curl -X POST -d "input=abc" <http://example.com/test>
  • Behavioural Analysis:

    • Compare the application's responses to different types of input. Check for discrepancies in behaviour when inputs are of different types but logically similar (e.g., "0" vs. 0).

# Test numeric vs. string input
curl -X POST -d "amount=10" <http://example.com/process>
curl -X POST -d "amount=10abc" <http://example.com/process>
  • Boundary Testing:

    • Test edge cases where type boundaries might be crossed, such as large numbers, special characters, or unexpected boolean values.

curl -X POST -d "input=" <http://example.com/test>  # Empty input
curl -X POST -d "input=9999999999" <http://example.com/test>  # Large number
curl -X POST -d "input=0" <http://example.com/test>  # Zero

Exploiting Type Juggling

Authentication Bypass:

// Vulnerable code
if ($_POST['password'] == $storedPassword) {
    // Grant access
}

Weak Hash Comparisons:

Hash comparisons using == can be bypassed by crafting inputs that evaluate the same numeric value.

// Vulnerable hash comparison
$hash = "0e123456789"; // MD5 or SHA1 hash with leading 0e\
if ($userInput == $hash) {
    echo "Hash match!";
}
// Attacker can use "0e123456780" or similar crafted string
$hash1 = '0e12345'; // Starts with '0e', PHP interprets this as scientific notation (0 * 10^12345)
$hash2 = '0e54321'; // Same result (0)

if ($hash1 == $hash2) {
    // Vulnerable to type juggling attack
}

Array Key Manipulation:

// Vulnerable array access
$arr = ["foo" => "bar", 0 => "zero"];
echo $arr["0"]; // Attacker might access $arr[0] using "0"

Unexpected Value Substitution:

// Vulnerable arithmetic operation
$amount = "10abc";
$total = $amount + 5; // Results in 15 due to type juggling

Mitigation Strategies

  1. Use Strict Comparisons (===):

    • Always use strict comparisons to avoid type juggling.

if ($input === "password123") {
    // Secure check
}
  1. Explicit Type Casting:

$amount = (int) $_POST['amount'];
  1. Input Validation and Sanitization:

    • Validate and sanitize input to ensure it matches the expected type.

$input = filter_var($_POST['input'], FILTER_VALIDATE_INT);
  1. Type Declarations and Hints:

    • Use type hints in function parameters and return types to enforce correct types.

function calculateTotal(int $amount): int {
    return $amount * 1.2;
}
  1. Avoid Implicit Conversions:

    • Write code that avoids relying on PHP's implicit type conversions.

$result = (int) $input + (int) $number;

Last updated