Integer Overflow (Web Applications)

{{#include ../../banners/hacktricks-training.md}}

This page focuses on how integer overflows/truncations can be abused in web applications and browsers. For exploitation primitives inside native binaries you can continue reading the dedicated page:

{{#ref}}
../../binary-exploitation/integer-overflow-and-underflow.md
{{#endref}}


1. Why integer math still matters on the web

Even though most business-logic in modern stacks is written in memory-safe languages, the underlying runtime (or third-party libraries) is eventually implemented in C/C++. Whenever user-controlled numbers are used to allocate buffers, compute offsets, or perform length checks, a 32-bit or 64-bit wrap-around may transform an apparently harmless parameter into an out-of-bounds read/write, a logic bypass or a DoS.

Typical attack surface:

  1. Numeric request parameters – classic id, offset, or count fields.
  2. Length / size headers – Content-Length, WebSocket frame length, HTTP/2 continuation_len, etc.
  3. File-format metadata parsed server-side or client-side – image dimensions, chunk sizes, font tables.
  4. Language-level conversions – signed↔unsigned casts in PHP/Go/Rust FFI, JS Number β†’ int32 truncations inside V8.
  5. Authentication & business logic – coupon value, price, or balance calculations that silently overflow.

2. Recent real-world vulnerabilities (2023-2025)

Year Component Root cause Impact
2023 libwebp – CVE-2023-4863 Malformed WebP lossless Huffman tables caused a heap overflow while building decoder lookup tables A single malicious image was enough to get heap corruption / renderer RCE in Chromium-based browsers.
2024 Chrome Layout – CVE-2024-7025 Integer overflow in the rendering/layout pipeline reachable from a crafted HTML page Demonstrates that integer bugs are not limited to JS engines: HTML/CSS alone can be enough to reach heap corruption.
2024 Chrome Skia – CVE-2024-9123 Integer overflow in the graphics stack while processing crafted HTML content A page visit could trigger an out-of-bounds memory write in the renderer.

3. Testing strategy

3.1 Boundary-value cheat-sheet

Send extreme signed/unsigned values wherever an integer is expected:

-1, 0, 1,
127, 128, 255, 256,
32767, 32768, 65535, 65536,
2147483647, 2147483648, 4294967295,
9223372036854775807, 9223372036854775808,
0x7fffffff, 0x80000000, 0xffffffff

Other useful formats:
* Hex (0x100), octal (0377), scientific (1e10), JSON big-int (9999999999999999999).
* Very long digit strings (>1kB) to hit custom parsers.

3.2 Burp Intruder template

Β§INTEGERΒ§
Payload type: Numbers
From: -10 To: 4294967300 Step: 1
Pad to length: 10, Enable hex prefix 0x

3.3 Fuzzing libraries & runtimes

  • AFL++/Honggfuzz with libFuzzer harness around the parser (e.g., WebP, PNG, protobuf).
  • Fuzzilli – grammar-aware fuzzing of JavaScript engines to hit V8/JSC integer truncations.
  • boofuzz – network-protocol fuzzing (WebSocket, HTTP/2) focusing on length fields.

3.4 JavaScript and browser coercion cases worth forcing

Not every web integer bug is a native-style size_t wraparound. A lot of exploitable web logic starts with a representation mismatch:

  • JavaScript numbers are IEEE-754 doubles, so integers above Number.MAX_SAFE_INTEGER (2^53 - 1) lose precision.
  • Legacy code frequently uses bitwise operators such as |0, ~~x, x<<0, or x>>>0, which coerce values to 32-bit signed/unsigned integers.
  • Browser-facing code often parses a value once in JS and a second time in the backend, producing different range checks and different final values.

Useful probes:

// Precision loss above 2^53-1
JSON.parse('{"n":9007199254740993}').n

// Signed wrap to negative
(2147483648 | 0)        // -2147483648

// Unsigned wrap to a huge positive
(-1 >>> 0)              // 4294967295

// Common "fast truncation" gadget in legacy code
(4294967297 | 0)        // 1

When a target mixes client-side validation with API-side validation, replay the same field as:

  • JSON number vs JSON string
  • decimal vs hex-like string (4294967295 vs 0xffffffff)
  • plain integer vs scientific notation (10000000000 vs 1e10)
  • positive vs negative boundary (2147483647, 2147483648, -1, 4294967295)

Interesting symptoms:

  • Pagination or limit checks pass, but the query executes with 0, -1, or a huge unsigned value.
  • Frontend blocks a value while the backend accepts it after a second parse.
  • A value displayed in the UI is not the value finally used by the API / renderer / WASM module.

4. Exploitation patterns

4.1 Logic bypass in server-side code (PHP example)

$price = (int)$_POST['price'];          // expecting cents (0-10000)
$total = $price * 100;                  // ← 32-bit overflow possible
if($total > 1000000){
    die('Too expensive');
}
/* Sending price=21474850 β†’ $total wraps to ‑2147483648 and check is bypassed */

4.2 Heap overflow via image decoder (libwebp 0-day)

The WebP lossless decoder bug behind CVE-2023-4863 was a good reminder that browser bugs still start with simple arithmetic mistakes around attacker-controlled metadata. In practice, a crafted image can make the decoder build invalid Huffman lookup tables and write past the heap before consistency checks finish. For web testing this means that image dimensions, chunk sizes, color-table counts and compression metadata are still first-class attack surface when the browser or the backend parses user-supplied files.

4.3 Browser-based XSS/RCE chain

  1. Integer overflow in V8 gives arbitrary read/write.
  2. Escape the sandbox with a second bug or call native APIs to drop a payload.
  3. The payload then injects a malicious script into the origin context β†’ stored XSS.

4.4 Web logic bug β†’ DOM XSS via integer truncation

This pattern is much more common in pentests than full renderer RCE:

const raw = JSON.parse(location.hash.slice(1)).len;
const len = raw | 0;                 // "fast" int cast to signed 32-bit

if (len <= 64) {
  preview.innerHTML = userInput.slice(0, len);
}

If raw=4294967295, then len becomes -1. Depending on the surrounding code, this may:

  • bypass a max-length check,
  • make slice(0, -1) drop the last character and preserve the rest of the payload,
  • or desynchronize validation and the eventual sink (innerHTML, template renderer, markdown preview, etc.).

The offensive lesson is simple: whenever you see bitwise truncation in client-side code, test whether the sanitized/validated length is the same value that later reaches the DOM sink.

4.5 WASM note

If the target uses Emscripten/WASM, a single integer bug in linear-memory management can often be upgraded into DOM XSS by corrupting writable HTML templates instead of the sanitized source string:

{{#ref}}
wasm-linear-memory-template-overwrite-xss.md
{{#endref}}


5. Defensive guidelines

  1. Use wide types or checked math – e.g., size_t, Rust checked_add, Go math/bits.Add64.
  2. Validate ranges early: reject any value outside business domain before arithmetic.
  3. Enable compiler sanitizers: -fsanitize=integer, UBSan, Go race detector.
  4. Adopt fuzzing in CI/CD – combine coverage feedback with boundary corpora.
  5. Stay patched – browser integer overflow bugs are frequently weaponised within weeks.

References