Featured image of post PHP integer overflow to force a negative sum - Nullcon Goa 2025

PHP integer overflow to force a negative sum - Nullcon Goa 2025

Bypassing numeric input validation through integer overflow in PHP to force a negative sum. Nullcon Goa HackIM 2025 web write-up.

Introduction

Context Explanation

Numberizer gives you a form with five number fields and only prints the flag if the sum comes out negative. The validation blocks the obvious move, submitting a negative number, so we need a sum that goes negative without any single input being negative.

Numberizer Home


Solution

Analyzing the Code and Validation Mechanisms

The PHP source runs each input through a few checks:

<?php
ini_set("error_reporting", 0);

if(isset($_GET['source'])) {
    highlight_file(__FILE__);
}

include "flag.php";

$MAX_NUMS = 5;

if(isset($_POST['numbers']) && is_array($_POST['numbers'])) {

    $numbers = array();
    $sum = 0;
    for($i = 0; $i < $MAX_NUMS; $i++) {
        if(!isset($_POST['numbers'][$i]) || strlen($_POST['numbers'][$i])>4 || !is_numeric($_POST['numbers'][$i])) {
            continue;
        }
        $the_number = intval($_POST['numbers'][$i]);
        if($the_number < 0) {
            continue;
        }
        $numbers[] = $the_number;
    }
    $sum = intval(array_sum($numbers));


    if($sum < 0) {
        echo "You win a flag: $FLAG";
    } else {
        echo "You win nothing with number $sum ! :-(";
    }
}
?>

<html>
    <head>
        <title>Numberizer</title>
    </head>
    <body>
        <h1>Numberizer</h1>
        <form action="/" method="post">
            <label for="numbers">Give me at most 10 numbers to sum!</label><br>
            <?php
            for($i = 0; $i < $MAX_NUMS; $i++) {
                echo '<input type="text" name="numbers[]"><br>';
            }
            ?>
            <button type="submit">Submit</button>
        </form>
        <p>To view the source code, <a href="/?source">click here.</a>
    </body>
</html>

So each of the first five inputs has to:

  1. be present (isset),
  2. be at most 4 characters (strlen),
  3. pass is_numeric(),
  4. and not be negative (if ($the_number < 0) continue;).

What survives gets summed with array_sum(), cast through intval(), and the flag drops if that total is below zero:

if ($sum < 0) {
    echo "You win a flag: $FLAG";
}

Observations and Vulnerability Analysis

Everything looks airtight until you consider scientific notation and how intval() treats it.

intval() handles normal numeric strings ('42', '+42') exactly as you’d expect. But a value in scientific notation like 9e99 is parsed as a float first, and when that float blows past the signed 64-bit max (9223372036854775807), the conversion overflows and clamps or wraps around. Crucially, 9e99 fits in 4 characters and passes is_numeric().

For example:

<?php
echo intval('9e99');        // Outputs: 9223372036854775807 on most 64-bit systems
echo intval(9e99 + 1);      // Outputs: -9223372036854775808 due to integer overflow

9e99 alone lands on the max signed integer, and nudging it by 1 tips it over into -9223372036854775808. That wrap is the whole exploit.


Exploitation

Crafting the Payload

The payload is just:

9e99, 1, 0, 0, 0

9e99 clears every check (numeric, 4 chars, not negative) and intval() turns it into 9223372036854775807. Add the 1 and the sum overflows to -9223372036854775808; the three zeros are padding. The result:

intval(9e99) = 9223372036854775807
intval(9e99 + 1) = -9223372036854775808
Array Sum: (-9223372036854775808) + 0 + 0 + 0 = -9223372036854775808

Numberizer with 9e99 only

Numberizer flag 9e99 and 1

The sum is negative, the check passes, and the flag appears.


Accessing the Flag

Submitting 9e99, 1, 0, 0, 0 gives:

You win a flag: ENO{INTVAL_IS_NOT_ALW4S_P0S1TiV3!}

Technical Insights

  1. Short string, huge value. 9e99 is only 4 characters but represents an astronomically large number, so length and is_numeric checks miss it entirely.
  2. intval() on overflow. It saturates at 9223372036854775807, and one more wraps to -9223372036854775808 under two’s-complement. That’s the negative sum we needed.
  3. Validate the value, not the format. Length and numeric checks say nothing about magnitude; an explicit range check would have closed this.