Semaphore UI e HFS: due falle di autorizzazione che abbiamo segnalato
GHSA-ffp6-w57q-758q e GHSA-c3w7-ff56-j345: in Semaphore UI un Manager poteva spostare nel proprio progetto il vault di un altro progetto, in HFS un visitatore anonimo poteva sostituire il file appena caricato da un altro. Cosa abbiamo trovato e quali versioni installare.

Il 9 novembre 2026, nel giro di un'ora, due progetti open source molto diversi hanno pubblicato un advisory di sicurezza con lo stesso nome nei crediti. Il primo è Semaphore UI, l'interfaccia web con cui tanti team lanciano Ansible e Terraform sui propri server. Il secondo è HFS, il server di file che dal 2003 permette di condividere una cartella del proprio computer con un semplice browser. In Semaphore un utente di un progetto poteva sottrarre a un altro progetto la configurazione del vault di un template. In HFS un visitatore anonimo poteva sostituire il file che un altro anonimo aveva appena caricato, in una cartella dove cancellare era vietato.
Sono gli advisory GHSA-ffp6-w57q-758q, corretto in Semaphore UI 2.19.20, e GHSA-c3w7-ff56-j345, corretto in HFS 3.4.1. Li abbiamo segnalati noi: entrambe le scoperte sono di Liam Giannini (Liam-G su GitHub), hacker etico e cofondatore di Webcracy. Ringraziamo i maintainer di Semaphore e Massimo Melina, autore di HFS, che hanno pubblicato advisory e versioni corrette lo stesso giorno.
I due difetti sembrano lontanissimi, ma hanno la stessa radice. In entrambi i casi il programma controllava un'identità, solo che era quella sbagliata.
Webcracy e la sicurezza informatica
Oltre a sviluppare siti e applicazioni, Webcracy si occupa di sicurezza informatica in tutte le sue parti: proteggiamo reti e server, li sorvegliamo con il nostro SOC giorno e notte, interveniamo quando c'è un incidente, facciamo audit, consulenza e hardening dei sistemi, e mettiamo alla prova le difese con penetration test e revisione del codice. La ricerca sul software open source che usiamo noi e i nostri clienti è una parte di questo lavoro. Liam cerca le falle con i metodi di un attaccante, ma per farle chiudere: le segnala in privato ai maintainer e le rende pubbliche solo quando la correzione è disponibile. È la stessa divulgazione responsabile seguita per ProjectSend, exarch e FreeScout.
Semaphore UI: il vault che passava da un progetto all'altro
Perché Semaphore UI conta
Semaphore UI è un'interfaccia web e un'API per eseguire Ansible, Terraform, OpenTofu, Terragrunt, Pulumi e PowerShell senza passare dalla riga di comando. Esiste dal 2014, quando si chiamava ancora Ansible Semaphore, è scritto in Go e distribuito con licenza MIT. Su GitHub ha oltre 14'400 stelle, più di 1'300 fork e oltre 180 contributori; l'immagine Docker ufficiale semaphoreui/semaphore supera gli 8,6 milioni di download, a cui si aggiungono i circa 600'000 della vecchia immagine ansiblesemaphore/semaphore. Il sito del progetto parla di oltre 100'000 installazioni: è un dato del produttore, ma l'ordine di grandezza è coerente con il resto. Accanto alla versione open source esistono le edizioni a pagamento Pro ed Enterprise.
Nel mondo Ansible è l'alternativa leggera ad AWX e ad Ansible Automation Platform di Red Hat: è un solo binario, può partire con un database SQLite e non richiede un cluster Kubernetes come AWX. Per questo è una scelta frequente per chi vuole un'interfaccia per Ansible senza mettere in piedi un'altra infrastruttura.
E qui una falla di autorizzazione pesa più che altrove. Uno strumento del genere contiene le chiavi SSH, le credenziali dei cloud, le password che sbloccano i segreti cifrati: è il punto da cui partono i comandi verso tutta l'infrastruttura. Per questo è diviso in progetti, e spesso la stessa installazione serve più team, o più clienti. Il confine tra un progetto e l'altro è, di fatto, il confine tra chi può toccare cosa.
Cosa sono i vault dei template
In Semaphore un template descrive un'attività da eseguire: quale playbook, su quale inventario, con quali variabili. Molti playbook contengono segreti cifrati con Ansible Vault, e per decifrarli serve una password. Il template indica da dove prenderla con uno o più collegamenti al vault: una chiave salvata nel progetto, oppure uno script che la recupera da un sistema esterno. Nel database ogni collegamento è una riga della tabella project__template_vault, con il proprio identificativo numerico, il progetto e il template a cui appartiene.
Dove mancava il controllo
Per creare un template si chiama POST /api/project/{project_id}/templates. Il middleware verifica correttamente che l'utente sia membro del progetto indicato nell'URL e che possa gestirne le risorse, cosa che il ruolo Manager permette. Il nuovo template viene assegnato a quel progetto, ma la lista vaults arriva al database così come l'ha inviata il client.
Il difetto stava nella funzione UpdateTemplateVaults. Per ogni voce con un identificativo diverso da zero, la funzione presumeva che si trattasse di un collegamento esistente da aggiornare, ed eseguiva:
UPDATE project__template_vault
SET project_id=?, template_id=?, vault_key_id=?, name=?, type=?, script=?
WHERE id=?
La riga veniva scelta solo per id, senza verificare a quale progetto appartenesse, e l'aggiornamento riscriveva proprio project_id e template_id, assegnandoli al nuovo template dell'attaccante. La cancellazione successiva dei collegamenti superflui era filtrata per progetto, ma arrivava quando la riga era già stata spostata.
All'attaccante bastava quindi creare un template nel proprio progetto, inserendo nella lista vaults l'identificativo di un collegamento del progetto vittima. Con il tipo script il campo della chiave viene azzerato, quindi non serviva nessun'altra informazione sul progetto altrui. E gli identificativi sono numeri assegnati in sequenza dal database, non segreti. Il controllo verificava il progetto nell'URL, ma l'oggetto modificato era scelto da un numero nel corpo della richiesta: il catalogo CWE lo chiama CWE-639, autorizzazione aggirata tramite una chiave controllata dall'utente, e CWE-863, autorizzazione errata.
Quanto è grave
Dopo la richiesta, il template del progetto vittima non ha più il suo collegamento al vault, e quello dell'attaccante sì. Il risultato più probabile è che le esecuzioni che devono decifrare segreti falliscano finché qualcuno non ricrea la configurazione. Nel nostro test non l'abbiamo misurato: è una conseguenza plausibile, non dimostrata.
Siamo altrettanto precisi su quello che non abbiamo trovato: nessuna lettura di segreti, nessun uso della chiave della vittima, nessuna esecuzione di codice. Serve inoltre il ruolo di Manager, o permessi equivalenti, in almeno un progetto. Per questo la gravità è moderata. Ma in uno strumento che orchestra l'infrastruttura, "moderata" significa comunque che un utente poteva modificare un progetto in cui non aveva alcun ruolo, e chi gestisce i progetti di altri team o clienti non l'avrebbe visto arrivare.
Come l'abbiamo verificato
Il problema è emerso leggendo il codice di Semaphore UI 2.19.18 (commit b621d14) e seguendo la lista vaults dal router fino alla query SQL. Per confermarlo, Liam ha scritto un test di regressione in Go che usa il database SQLite in memoria della release e i componenti originali: middleware dei progetti, controllo dei permessi, creazione dei template e strato SQL. Il test crea due progetti, A e B, dà all'utente il ruolo di Manager solo in A, crea in B un template con un vault, poi chiede di creare un template in A con l'identificativo del collegamento di B. L'API ha risposto 201, il template di B è passato da un collegamento a zero, e il nuovo template di A conteneva quello originale di B.
La stessa query compare nel codice di Semaphore almeno dalla versione 2.11. L'advisory indica solo la versione che abbiamo verificato, la 2.19.18, e non stabilisce quali versioni precedenti siano sfruttabili allo stesso modo. Conviene presumere di sì.
La correzione nella 2.19.20
La versione 2.19.20 contiene la correzione, nel commit 9445ee9 della pull request #4414. L'aggiornamento non tocca più project_id e template_id, e trova la riga solo se appartiene davvero al progetto e al template autorizzati:
UPDATE project__template_vault
SET vault_key_id=?, name=?, type=?, script=?
WHERE id=? AND project_id=? AND template_id=?
Un identificativo di un altro progetto non produce più alcun effetto. I maintainer hanno aggiunto anche un test, TestManagerCannotMoveForeignProjectVault, che riproduce lo scenario dell'advisory. Nella segnalazione avevamo suggerito anche di rifiutare la richiesta quando l'aggiornamento non trova nessuna riga, e di creare template e collegamenti in un'unica transazione: la correzione pubblicata chiude il percorso di attacco, le altre misure restano una scelta dei maintainer.
Cosa fare se usate Semaphore UI
Aggiornate almeno alla 2.19.20, meglio ancora alla 2.19.21, uscita pochi minuti dopo, che secondo le note di rilascio corregge un altro problema: un utente poteva cambiare la propria password tramite l'API senza conoscere quella attuale. Con Docker, fissate una versione esplicita invece di latest.
Dopo l'aggiornamento controllate che i template che usano Ansible Vault abbiano ancora i loro collegamenti: se uno ha perso il vault senza motivo apparente, ora sapete una delle possibili cause. E rivedete chi ha il ruolo di Manager, e in quali progetti. Se poi la separazione tra due gruppi di utenti è davvero critica, ad esempio tra clienti diversi di un fornitore di servizi, i progetti di un'unica installazione sono un confine utile ma non assoluto: istanze separate, con database separati, limitano i danni del prossimo errore di autorizzazione.
HFS: il file appena caricato che un altro anonimo poteva sostituire
Perché HFS conta
HFS, HTTP File Server, è uno di quei programmi che chi lavora in informatica da qualche anno ha quasi certamente incontrato. Lo sviluppa dai primi anni Duemila Massimo Melina, noto online come rejetto: si avvia sul proprio computer, si scelgono le cartelle da condividere, e chiunque abbia l'indirizzo può scaricare o caricare file con un browser, senza servizi cloud in mezzo. Su SourceForge il progetto conta oltre 970'000 download dal 2003, senza contare quelli dal sito dell'autore. HFS 3, riscritto in TypeScript su Node.js e distribuito con licenza GPL-3.0, gira su Windows, Linux, macOS, FreeBSD e Android. Su GitHub ha quasi 4'000 stelle e oltre 370 fork; i pacchetti delle sue release sono stati scaricati più di 500'000 volte, l'immagine Docker rejetto/hfs oltre 116'000, e il pacchetto npm circa 44'000 volte nell'ultimo anno.
Si presta a passare file in una rete locale o a ricevere documenti da clienti e collaboratori: contesti in cui una cartella aperta al caricamento anonimo, ma protetta dalla cancellazione, è una configurazione normale: tutti possono consegnare un file, nessuno può toglierlo.
I dieci secondi concessi a WebDAV
Dalla versione 3.1.0, pubblicata ad aprile 2026, HFS supporta WebDAV, il protocollo che permette di collegare una cartella condivisa come unità di rete in Esplora file di Windows o nel Finder del Mac. Alcuni client WebDAV hanno un comportamento particolare: il Finder, ad esempio, prima crea un file vuoto e subito dopo lo sovrascrive con il contenuto vero. Sovrascrivere un file però richiede il permesso di cancellare, e in una cartella "solo caricamento" quel permesso non c'è.
Per non rompere questi client, HFS concede a chi ha appena caricato un file con un client WebDAV riconosciuto dieci secondi per sostituirlo, anche senza permesso di cancellazione. È un compromesso ragionevole. Il punto è come HFS decideva chi era "chi ha appena caricato".
Tutti gli anonimi hanno lo stesso nome
Il permesso temporaneo veniva memorizzato con una chiave composta da due elementi: il percorso del file e il nome dell'utente. Per gli utenti registrati funziona, e c'era perfino un test automatico che verificava che due account diversi non potessero usare il permesso l'uno dell'altro. Ma per chi non ha effettuato l'accesso, HFS restituisce come nome una stringa vuota. Tutti i visitatori anonimi avevano lo stesso nome, e quindi la stessa chiave.
Un secondo visitatore anonimo, senza i cookie né le credenziali del primo, poteva così inviare entro dieci secondi una richiesta PUT sullo stesso percorso, con un user agent che contenesse la parola WebDAV. HFS trovava il permesso, cancellava il file esistente prima ancora di elaborare il nuovo caricamento, e saltava sia il controllo can_delete sia la protezione che lega i caricamenti anonimi alla sessione di chi li ha fatti. Se il nuovo caricamento andava a buon fine il file era sostituito; se falliva o veniva interrotto, l'originale restava comunque cancellato. L'opzione force_webdav_login, attiva per impostazione predefinita, non bloccava questa richiesta, perché una semplice PUT senza intestazioni specifiche di WebDAV non veniva trattata come una richiesta di autenticazione WebDAV. Anche qui il catalogo parla di CWE-863, autorizzazione errata.
Quanto è grave
Le condizioni sono strette. Serve una cartella con caricamento anonimo e cancellazione vietata, un file appena caricato da un client WebDAV, la conoscenza del suo percorso, e una seconda richiesta entro dieci secondi. I file più vecchi non sono raggiungibili. La gravità è moderata.
Ma dentro quella finestra l'effetto è concreto: un file appena pubblicato, magari già condiviso con altri tramite il suo indirizzo, poteva essere sostituito da chiunque con un contenuto diverso, allo stesso indirizzo. In una cartella di consegna, significa che il documento ricevuto potrebbe non essere quello che il mittente ha inviato.
Come l'abbiamo verificato
Liam ha seguito il percorso nel codice di HFS 3.3.4 (commit bb6f718) e confermato che lo stesso codice era ancora presente nel ramo principale. Poi ha compilato il server e lo ha avviato in un container Docker senza accesso alla rete, con una cartella uploads configurata con caricamento permesso e cancellazione vietata. Una prima sessione anonima ha caricato un file con il contenuto first usando lo user agent del client WebDAV di Windows. Una seconda sessione, senza cookie, ha provato a cancellarlo e HFS ha risposto correttamente 403. La stessa seconda sessione ha poi inviato una PUT sullo stesso percorso con il contenuto second: HFS ha risposto 200, e il file all'indirizzo originale conteneva second.
Le due richieste partivano dallo stesso indirizzo IP, ma la chiave non contiene né la sessione né l'IP: il codice indica che il risultato sarebbe identico da indirizzi diversi, anche se quel caso non l'abbiamo testato separatamente. L'advisory indica come interessate tutte le versioni dalla 3.1.0 alla 3.4.0.
La correzione nella 3.4.1
La versione 3.4.1, uscita il 9 novembre, elenca nelle note di rilascio una sola modifica, nel commit 9a33df2: il permesso temporaneo è ora legato al nome utente oppure, per i visitatori anonimi, all'identificativo della loro sessione. Se non c'è nessuna delle due, il permesso non viene concesso. È esattamente la soluzione che avevamo proposto. Il nuovo test automatico verifica entrambi i lati: un'altra sessione anonima riceve 403, mentre chi ha caricato il file può ancora sovrascriverlo come fa il Finder.
Cosa fare se usate HFS
Aggiornate alla 3.4.1 o successiva. HFS ha una procedura di aggiornamento automatico, disponibile anche quando gira come servizio, quindi di solito l'operazione è rapida. Se per qualche motivo non potete aggiornare subito e avete cartelle con caricamento anonimo, valutate di richiedere un account per caricare, almeno finché non avete installato la correzione.
Più in generale, una cartella aperta al caricamento anonimo è una porta aperta per definizione. Ha senso in molti casi, ma conviene usarla per ricevere file, non per pubblicarli ad altri, e controllare periodicamente cosa contiene.
Lo stesso errore, due volte
Semaphore verificava che l'utente potesse lavorare nel progetto dell'URL, poi modificava un oggetto scelto da un numero nella richiesta. HFS verificava che il file fosse stato caricato dallo stesso utente, ma usava un nome che per tutti gli anonimi era identico. In entrambi i casi il controllo c'era, era scritto con cura, e in un caso era perfino coperto da un test. Mancava la domanda giusta: l'identità che sto controllando è davvero quella della persona e dell'oggetto su cui sto per agire?
Sono errori che non si vedono usando l'applicazione normalmente. Si vedono seguendo un dato dalla richiesta fino al punto in cui viene usato, e chiedendosi ogni volta "chi ha deciso questo valore?". È una parte del lavoro che facciamo.
Perché facciamo ricerca su software open source
Strumenti come Semaphore e HFS sono il motivo per cui un piccolo team riesce a gestire decine di server o a scambiare file senza dipendere da un servizio cloud: open source, gratuiti, facili da installare. Ma più un'applicazione è usata, più contano i suoi controlli interni. Leggere il codice dei progetti che usiamo e consigliamo, e segnalare quello che troviamo, è il nostro modo di restituire qualcosa a questi progetti e di sapere cosa installiamo per i nostri clienti.
Se usate strumenti di automazione, server di file o applicazioni con più utenti e ruoli, e volete sapere se i permessi reggono davvero, ne parliamo volentieri. Il nostro lavoro di sicurezza informatica comprende protezione e monitoraggio delle reti, risposta agli incidenti, audit, hardening e penetration test, e in ogni caso partiamo da qui: leggere il codice e la configurazione, capire dove un controllo manca, e dirvi con precisione cosa abbiamo verificato e cosa no.
HAI UN PROGETTO?
Parliamo di come possiamo aiutare la tua azienda con soluzioni digitali su misura.
CONTATTACI