
Proof-of-concept che dimostra un bypass del controllo di accesso di Windows Cloud Files (CVE-2026-83991) che converte un file di sola lettura in un segnaposto cloud tramite la semantica di supersede, con analisi tecnica dettagliata e passaggi di riproduzione.
Ho tenuto queste righe una accanto all'altra perché separarle fa sembrare il bug più complicato di quanto non sia:
CreateFileW, GENERIC_WRITE error 5
DeleteFileW error 5
CfCreatePlaceholders, SUPERSEDE S_OK
per-entry Result S_OK
L'apertura in scrittura, il tentativo di eliminazione e la chiamata a Cloud Files usano lo stesso pathname e lo stesso token del thread. Prima dell'ultima chiamata, il nome appartiene a un file ordinario con una DACL protetta e nessun reparse point. Dopo, FSCTL_GET_REPARSE_POINT restituisce IO_REPARSE_TAG_CLOUD.
Microsoft ha pubblicato quel risultato come CVE-2026-83991, la vulnerabilità di manomissione del driver minifilter Windows Cloud Files. Nel report l'avevo descritta come autorizzazione errata e avevo suggerito CWE-863. Microsoft ha invece assegnato CWE-306.
Il programma installa il suo token di impersonificazione limitato prima di creare la directory di test, il file, le DACL o la sync root. Non c'è alcuna configurazione elevata seguita da un cambio di token solo per la chiamata interessante. Se l'account ha un SID Administrators, CreateRestrictedToken lo rende deny-only; se il SID non era mai presente, il programma registra anche quel caso. L'integrità è impostata a Medium.
La directory padre e la foglia deliberatamente non hanno DACL corrispondenti. L'utente corrente riceve questo sulla directory:
FILE_GENERIC_READ |
FILE_GENERIC_WRITE |
FILE_GENERIC_EXECUTE |
DELETE
Il file esistente riceve FILE_GENERIC_READ, e la sua DACL è protetta dall'ereditarietà. SYSTEM e Administrators mantengono il controllo completo. Poiché il SID Administrators del token è assente o deny-only, l'ACE di autorizzazione per Administrators non può concedere accesso a questo thread.
Il bit DELETE sul padre sembra sospetto finché non si rende esplicito l'oggetto a cui si applica. Permette l'eliminazione della directory stessa. Eliminare uno dei suoi figli è FILE_DELETE_CHILD, un bit diverso che l'ACE del padre non contiene. DeleteFileW accetta sia DELETE sulla foglia sia il permesso delete-child sul padre. Il test non ha né l'uno né l'altro, ed è per questo che l'errore 5 è il risultato di controllo atteso.
Ho lasciato FILE_GENERIC_WRITE sul padre. Il suo bit FILE_WRITE_DATA è FILE_ADD_FILE quando l'oggetto è una directory. Il chiamante può creare un figlio, e il padre soddisfa quindi il requisito documentato di WRITE_DATA o WRITE_DAC per CfCreatePlaceholders. Senza quel diritto, l'API fallisce il controllo della directory di base prima di raggiungere il caso che ho segnalato.
Il programma non registra la sync root a meno che questo blocco sia vero:
write open denied with ERROR_ACCESS_DENIED
DeleteFileW denied with ERROR_ACCESS_DENIED
read open succeeded
leaf DACL protected
starting object not a reparse point
L'ho fatto fermare lì perché un S_OK più avanti nell'esecuzione sarebbe inutile se la foglia fosse già scrivibile, eliminabile o un reparse point.
Dopo quei controlli, il programma registra il padre come sync root, lo connette con una tabella di callback contenente solo la voce terminante, e invia il nome file esistente con flag 0x4:
placeholder.RelativeFileName = leaf_name;
placeholder.FsMetadata.BasicInfo = basic_info;
placeholder.FsMetadata.FileSize = standard_info.EndOfFile;
placeholder.FileIdentity = &run_id;
placeholder.FileIdentityLength = sizeof(run_id);
placeholder.Flags = CF_CREATE_SUPERSEDE;
create_hr = cf.Create(root, &placeholder, 1, 0, &processed);
CF_PLACEHOLDER_CREATE_FLAG_SUPERSEDE è documentato come sovrascrittura di un placeholder esistente. Questo file non lo è. Il PoC imposta FILE_ATTRIBUTE_NORMAL prima di applicare la DACL della foglia e conferma che il bit del reparse point è azzerato prima di chiamare Cloud Files.
Sul sistema interessato, la chiamata batch e la voce hanno entrambe restituito S_OK, e CreateUsn era diverso da zero. Il file ha acquisito l'attributo reparse-point. Chiedere al file system i dati di reparse ha restituito il tag 0x9000001A, che è IO_REPARSE_TAG_CLOUD.
EntriesProcessed == 1 non prova il successo perché la documentazione dell'API conta anche le voci fallite. La conferma arriva da entrambi gli HRESULT, dall'USN di creazione, dall'attributo reparse e dal tag Cloud.
FILE_ID_INFO è rimasto invariato durante la chiamata. L'oggetto file esistente ha acquisito lo stato di Cloud Files; un altro file non è semplicemente apparso sotto lo stesso pathname.
La build interessata più vecchia elencata da MSRC è 17763.0, la generazione di Windows 10 versione 1809 e Windows Server 2019. Questo colloca la traccia pubblica nel 2018, quasi otto anni prima della correzione di settembre 2026.