
Bloomberg Memray: Stored XSS tramite metadati della riga di comando non escapati
XSS persistente di Bloomberg Memray tramite metadati della riga di comando non escapati
Ho trovato questo problema mentre analizzavo Memray, il profiler di memoria Python di Bloomberg, con una semplice domanda in mente:
I metadati di runtime controllati da un attaccante possono finire in modo non sicuro nell'output dei report renderizzati nel browser?
In questo caso, la risposta è stata sì.
Il bug si trovava nel percorso di generazione dei report HTML di Memray, dove i metadati della riga di comando venivano renderizzati in un report aperto nel browser senza escaping. Questo ha trasformato un campo operativo in un sink HTML eseguibile e alla fine è diventato CVE-2026-32722.
Progetto: Memray su GitHub
Advisory: GHSA-r5pr-887v-m2w9 /CVE-2026-32722
attacker-controlled argv → metadata.command_line → HTML template sink without escaping → raw markup in generated report → browser-side JavaScript execution
Memray è un profiler di memoria Python.
Strumenta un processo Python, registra il comportamento delle allocazioni e produce report che aiutano gli sviluppatori a capire:
Alcuni di questi report vengono generati come HTML e aperti in un browser.
Questo rende la generazione dei report un vero confine di sicurezza.
La domanda pertinente non è se Memray sia uno "strumento locale".
La domanda pertinente è se i dati controllati da un attaccante possano finire in modo non sicuro nell'output renderizzato dal browser.
In questo caso, potevano.
Molte persone sottovalutano gli strumenti per sviluppatori.
È un errore.
Quando uno strumento:
eredita gli stessi rischi di codifica dell'output di un'applicazione web.
Questo era il problema centrale.
Questo bug non era nella logica di profiling. Non era nel tracciamento delle allocazioni. Non era nella gestione nativa della memoria.
Era un classico fallimento del confine di fiducia:
Questo è sufficiente per creare una vera vulnerabilità.
Non ho affrontato Memray fuzzando opzioni CLI casuali o inseguendo crash.
L'approccio più efficace era identificare per prima la superficie di sicurezza a più alta probabilità.
Per Memray, quella era la generazione di report HTML.
Perché?
Perché l'output HTML introduce un sink del browser, e i sink del browser trasformano i normali bug dei metadati in problemi di sicurezza se:
È esattamente ciò che è successo qui.
Il bug si riduce a due righe.
In:
def get_render_environment() -> jinja2.Environment:
loader = jinja2.PackageLoader("memray.reporters")
env = jinja2.Environment(loader=loader)
l'ambiente Jinja viene creato senza autoescape.
Poi in:
Command line: <code>{{ metadata.command_line }}</code><br>
metadata.command_line viene renderizzato direttamente nell'HTML.
Questa è l'intera vulnerabilità.
Perché metadata.command_line è influenzato dall'attaccante.
Memray registra la riga di comando usata per eseguire il programma profilato.
Ciò significa che i valori controllati dall'utente provenienti da argv vengono conservati come metadati e successivamente inseriti nel report.
Quindi la catena di sfruttamento è semplice:
metadata.command_lineQuesto trasforma i metadati in contenuto eseguibile nel browser.
La distinzione importante è l'esecuzione.
Molti bug producono HTML malformato. Da solo non basta.
Qui, il contenuto controllato dall'attaccante non era semplicemente visibile nel sorgente della pagina. Veniva interpretato dal browser come HTML attivo ed eseguito come JavaScript.
Questa è la differenza tra:
Quindi la domanda non era:
“L'HTML può comparire nel report?”
La vera domanda era:
“L'HTML controllato dall'attaccante può diventare eseguibile quando il report viene aperto?”
La risposta era sì.
Il mio riproduttore iniziale usava uno script esplicito più un argomento controllato dall'attaccante:
cat > victim.py <<'PY'
x = [b"A" * 1024 for _ in range(1000)]
print("done")
PY
python -m memray run -o poc.bin victim.py ''
python -m memray flamegraph -o poc.html poc.bin
L'HTML generato conteneva markup grezzo controllato dall'attaccante:
Command line: <code>~/memray/src/memray/__main__.py run -o poc.bin victim.py </code><br>
Aprire o aggiornare il report generato innescava l'esecuzione di JavaScript.
Questo ha stabilito l'affermazione centrale:
Successivamente, durante la divulgazione coordinata, il maintainer ha ulteriormente semplificato il riproduttore:
python -m memray run -o poc.bin -c '# '
Questa versione è migliore perché isola il confine vulnerabile in modo più diretto:
Il payload era intenzionalmente semplice:
Non si tratta di payload appariscenti.
È una sonda di esecuzione pulita perché:
Per questa classe di bug, è sufficiente.
Un solo tipo di report sarebbe già stato sufficiente a giustificare il problema.
Ma volevo sapere se il problema fosse isolato o strutturale.
Ho confermato lo stesso comportamento in:
--no-webQuesto era importante per due motivi.
Mostrava che il sink vulnerabile veniva riutilizzato in più output HTML.
Dimostrava che il bug non dipendeva da risorse esterne ospitate su CDN.
--no-web riproduceva comunque il problema, il che significa che il problema era nell'HTML generato da Memray e nella gestione dei template, non nel comportamento di JS remoto.
Questo ha reso il caso molto più solido.
La preoccupazione principale del maintainer era il controllo pratico dell'attaccante.
È giusto.
Non è il tipo di problema in cui un attaccante remoto non autenticato colpisce un endpoint HTTP esposto e ottiene un impatto immediato.
La condizione di sfruttamento è più ristretta:
Quindi la classificazione realistica era gravità bassa.
Questo non lo rende un bug debole.
La gravità riguarda le condizioni di sfruttamento e il probabile impatto. La validità riguarda se il problema è reale.
Questo problema era chiaramente reale:
Ecco perché è comunque diventato un CVE.
Il fix è stato minimale e corretto.
Il maintainer ha modificato:
{{ metadata.command_line }}
in:
{{ metadata.command_line|e }}
È il fix giusto perché affronta direttamente il sink vulnerabile.
Invece di emettere markup grezzo come:
il template ora emette testo escapato:
<img src=x onerror=alert(1)>
Questo preserva il valore informativo del campo della riga di comando eliminando al contempo il percorso di esecuzione nel browser.
Il maintainer ha inoltre esaminato il resto del contesto del template e ha concluso che:
|tojsonQuindi il problema è stato correttamente circoscritto a metadata.command_line.
È esattamente il tipo di revisione del fix che vuoi in una divulgazione reale.
È stato segnalato privatamente tramite GitHub Security Advisories.
I maintainer:
Al problema è stato assegnato: CVE-2026-32722
La lezione chiave qui è semplice:
i metadati non sono automaticamente attendibili solo perché sembrano operativi.
Niente di tutto ciò conta quando il contenuto controllato dall'attaccante finisce nell'output renderizzato dal browser senza escaping.
Nel momento in cui uno strumento emette HTML, deve essere trattato come un'applicazione che produce HTML.
Questo è il vero insegnamento.
Questa vulnerabilità non riguardava un payload ingegnoso.
Riguardava l'identificazione del confine giusto.
Memray ha preso i metadati della riga di comando influenzati dall'attaccante e li ha renderizzati nell'HTML generato senza applicare escaping. Il browser ha fatto il resto.
Ecco perché questo è diventato CVE-2026-32722.
Corretto in Memray 1.19.2.
