Introduction
Context Explanation
Numberizer is a seemingly simple web-based CTF challenge, where users are required to submit five numbers in an HTML form such that the sum of all the numbers is negative. However, the challenge implements input validation and sanitization mechanisms to prevent trivial solutions like using negative numbers.
Our goal is to bypass these controls and successfully achieve the required negative sum to retrieve the flag.
Solution
Analyzing the Code and Validation Mechanisms
The provided PHP source code implements a number of validation checks:
|
|
The key authentication logic imposes validation rules through several mechanisms:
Input Validation Rules:
-
Input must exist:
Each user-provided field is confirmed to have been submitted usingisset($_POST['numbers'][$i])
. -
Input character length limit (≤4):
The input must not exceed 4 characters in length, as verified by thestrlen($_POST['numbers'][$i])
check. -
Input must be numeric:
Inputs must passis_numeric()
, ensuring they are numbers or valid numeric representations. -
Negative numbers are rejected:
Even if a negative number is submitted and passes the above checks, the conditionif ($the_number < 0)
will exclude it from further processing. -
Fixed number of inputs:
Only the first$MAX_NUMS = 5
inputs are processed; additional inputs are ignored. -
Summation and validation:
The sum of valid inputs is calculated usingarray_sum()
and cast back to an integer usingintval()
. The challenge requires this sum to be negative in order to retrieve the flag:1 2 3
if ($sum < 0) { echo "You win a flag: $FLAG"; }
Observations and Vulnerability Analysis
While these constraints appear robust, the root vulnerability lies in how PHP handles numeric input in scientific notation (e.g., 9e99
) and its interaction with the intval()
function.
Behavior of intval()
with Large Numbers
PHP’s intval()
function behaves inconsistently depending on the provided input:
- Small and normal-sized numbers or valid numeric strings (
'42'
,'+42'
) are converted directly to integers. - Numbers in scientific notation (e.g.,
9e99
) are treated as floating-point doubles.- If these floating-point values exceed the maximum range of integers on a 64-bit system (
9223372036854775807
for signed integers), integer overflow occurs, converting the number into either the maximum or minimum range value.
- If these floating-point values exceed the maximum range of integers on a 64-bit system (
To demonstrate:
|
|
Key Observations:
- A single input of
9e99
results in the maximum integer value (9223372036854775807
). - Adding
1
to9e99
causes an integer overflow, wrapping the sum to-9223372036854775808
on a 64-bit system.
This overflow behavior serves as the core of our exploit.
Exploitation
Crafting the Payload
To bypass validation and generate a negative output, we supply the following numbers:
|
|
Here’s how this works step-by-step:
9e99
appears as a valid numeric input, asis_numeric('9e99')
evaluates totrue
.- The length of
9e99
is only 4 characters, passing the length check (strlen('9e99') <= 4
). intval('9e99')
produces9223372036854775807
, the maximum integer value for signed 64-bit integers.- Adding
1
to9e99
causes integer overflow in PHP, andintval()
then interprets the sum as a negative number (-9223372036854775808
), due to the signed two’s complement representation used for integers. - The remaining values (
0
,0
,0
) do not affect the total.
Calculation:
|
|
Since the sum is now negative, the challenge validation is bypassed, and the flag is revealed.
Accessing the Flag
Submitting the payload 9e99, 1, 0, 0, 0
through the form results in the following:
|
|
Technical Insights
-
Scientific Notation and Integer Overflow:
Numbers in scientific notation (9e99
) appear deceptively small when measured in characters but represent astronomically large values. When processed throughintval()
, these large values often exceed the signed integer range and result in overflow. -
Behavior of
intval()
on Overflow:- The function returns the maximum signed integer value (
9223372036854775807
) on overflow. - Adding any small integer (
+1
) causes wrapping into the negative range, producing-9223372036854775808
, as PHP uses two’s complement arithmetic for signed integers.
- The function returns the maximum signed integer value (
-
Validation Oversights:
The challenge failed to account for edge cases involving scientific notation, where large values still satisfy length and numeric checks but lead to unintended behavior like integer overflow. -
Impact of Signed Integers:
The root cause of this vulnerability lies in the interpretation of integers as signed 64-bit, which allows wrapping into negative values when handling excessively large numbers.