Mostrare in anteprima un PDF non fidato dentro la vostra applicazione è una decisione di esecuzione, e la parte che conta non è la cornice del viewer ma ciò che il riquadro si rifiuta di fare da solo. Non scrivete il file su disco. Non lasciate che i suoi link escano verso il sistema. Non consegnate ai suoi allegati un percorso. La maggior parte dei danni di un documento ostile non arriva da un exploit del motore ma da un viewer che fa cose perfettamente ordinarie con input fornito da un attaccante: aprire un link file:// verso una condivisione UNC che fa trapelare credenziali NTLM, lasciare una copia temporanea nella directory temp, copiare payload incorporati ovunque una stringa di nome file gli indichi. PDFium Component è un viewer PDF con sorgenti per Delphi, C++Builder e Lazarus, e mette gli interruttori rilevanti a portata di mano: un flag al caricamento che uccide lo scripting, eventi di clic sui link a cui potete opporre veto, accesso agli allegati che passa dal vostro codice e bit di permesso che potete leggere. L'ordine qui sotto segue un documento dal momento in cui arriva al momento in cui un utente ci clicca sopra
Il modello di minaccia di un riquadro di anteprima
Siate onesti su ciò che l'anteprima sicura vi porta davvero. Il renderer analizza byte non fidati qualunque cosa facciate, e l'irrobustimento del motore stesso è il pavimento su cui state. Tutto ciò che sta sopra quel pavimento è politica applicativa: se gli script si inizializzano, che cosa fa il clic su un link, se i file incorporati possono raggiungere il disco, se appunti e stampante sono porte o muri. Una cosa da mettere subito da parte è l'interruttore FPDF_SetSandBoxPolicy del motore. La maggior parte delle restrizioni del motore è compilata dentro, l'interruttore cambia poco nella pratica, e affidargli una parte della vostra storia di isolamento produce solo la falsa sensazione di aver fatto qualcosa. Quando l'input è davvero ostile, per esempio un portale pubblico di caricamento, l'unico isolamento reale è renderizzare in un processo separato a privilegi ridotti e spedire bitmap alla UI. I flag in-process sono politica. Non sono contenimento
Due superfici sono facili da dimenticare proprio perché nessun clic le tocca mai. La prima sono i file temporanei. Se la vostra pipeline appoggia su disco i documenti in arrivo prima dell'anteprima, quelle copie sopravvivono alla sessione a meno che qualcosa non le cancelli in modo verificabile, e un file recuperabile dalla directory temp ha silenziosamente sconfitto ogni controllo che il riquadro applica. Caricate invece dalla memoria tramite TPdfStreamAdapter, così i byte ostili non ottengono mai un percorso proprio. La seconda sono gli appunti. Un'anteprima che consente seleziona e copia ha già esportato il documento, una schermata alla volta, e nessuna intercettazione dei link lo intercetterà
Uccidete JavaScript al caricamento, non nella UI
In PDFium Component il JavaScript di documento si inizializza soltanto insieme all'ambiente di compilazione dei moduli. Caricare con FormFill := False disattiva quindi lo scripting alla radice invece di sopprimerne i sintomi:
procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
Pdf.FileName := FilePath;
Pdf.FormFill := False; // nessun ambiente form, quindi nessun motore JavaScript
Pdf.Active := True;
FPermissions := Pdf.Permissions; // flag grezzi; tutti i bit a 1 = nessun limite
end;
Il compromesso è reale e appartiene alla vostra specifica. Con il form fill disattivato spariscono anche l'interazione legittima con gli AcroForm e gli script di validazione; i campi vengono renderizzati con il loro ultimo aspetto salvato ma non si possono modificare. Per un riquadro di anteprima di solito è la scelta giusta, dato che anteprima significa guardare, non compilare. Ma se la stessa finestra funge anche da superficie di compilazione per documenti interni fidati, la risposta sono due percorsi di caricamento con una decisione esplicita di fiducia in mezzo, non un percorso unico con un'impostazione di compromesso troppo lasca per il caso ostile e troppo stretta per quello fidato. Il lato della compilazione dei moduli ha le sue trappole, trattate in navigazione fra i campi modulo e rigenerazione dell'aspetto
Link: il gestore predefinito esce verso il sistema
Lasciati a sé stessi, i clic sui link vanno dritti al sistema operativo. Le LinkOptions predefinite del viewer includono loAutoOpenURI, che è la fuga da file:// verso condivisione UNC in attesa di accadere. Due eventi formano il punto di strozzatura: OnWebLinkClick per gli URL rilevati nel testo della pagina e OnAnnotationLinkClick per le annotazioni di collegamento che portano azioni URI o launch. Impostate Handled := True in entrambi, senza condizioni, prima di decidere qualsiasi cosa, poi riabilitate solo ciò che la politica consente. Come secondo strato, togliete loAutoOpenURI da LinkOptions per l'input ostile e assicuratevi che loAutoLaunch, disattivo per impostazione predefinita, non rientri mai di soppiatto tramite una configurazione copiata:
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
const Url: WString; var Handled: Boolean);
begin
Handled := True; // mai ricadere sul comportamento predefinito della shell
if (AnsiStartsText('https://', Url) or AnsiStartsText('http://', Url))
and HostIsAllowed(Url) then
OpenInBrowser(Url)
else
FAudit.LogBlockedLink(FDocumentId, Url);
end;
Due dettagli decidono se tutto questo regge davvero. Primo, il controllo dello schema deve essere un controllo di prefisso sulla stringa grezza prima di qualsiasi parsing, perché file://, i percorsi UNC e gli schemi esotici sono esattamente i valori che fanno crashare un parser di URL ingenuo o che sfuggono a uno che normalizza con troppo zelo. Secondo, registrate ogni blocco con l'identità del documento allegata. Una manciata di link file:// bloccati è rumore di fondo; una raffica di essi su molti documenti in arrivo in una finestra breve è un incidente che il vostro team di sicurezza preferisce sentirsi raccontare da voi anziché da altrove
Allegati: politica sulle estensioni e il nome file che non avete scelto
Un PDF è un contenitore, e AttachmentCount con la proprietà AttachmentName[] vi dice che cosa trasporta prima che qualcosa tocchi il disco. Qui contano due controlli distinti, e soltanto uno dei due è ovvio. Quello ovvio è la politica sui tipi: una allowlist di estensioni che possono essere esportate. Quello sottile è che il nome dell'allegato è dato controllato dall'attaccante, punto. Un nome incorporato come ..\..\Startup\update.exe trasforma un salvataggio distratto in un path traversal che deposita un eseguibile in una cartella che Windows esegue al login. Il componente vi consegna il payload come byte tramite Attachment[] e lascia scegliere il percorso al vostro codice, quindi costruite quel percorso da un nome base ripulito e mai dalla stringa incorporata grezza:
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
RawName, SafeName, Ext: string;
Data: TBytes;
begin
RawName := string(Pdf.AttachmentName[Index]);
SafeName := ExtractFileName(RawName); // rimuove ogni componente di percorso
Ext := LowerCase(ExtractFileExt(SafeName));
if not FAllowedExt.Contains(Ext) then // allowlist, non blocklist
raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);
Data := Pdf.Attachment[Index]; // payload incorporato come byte grezzi
TFile.WriteAllBytes(
IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;
Preferite la direzione della allowlist. Una blocklist di estensioni pericolose è una gara che perdete il giorno in cui qualcuno rende offensiva un'estensione di cui non avevate mai sentito parlare; una allowlist di .pdf, .png e .csv fallisce in chiusura
Che cosa promettono davvero i permessi di cifratura
Il gestore di sicurezza standard di ISO 32000-1 codifica flag di permesso per stampa, copia del contenuto e modifica, e le proprietà Permissions e UserPermissions li espongono come maschere di bit grezze una volta aperto il documento. La tabella 22 di ISO 32000-1 definisce i bit, e un file non cifrato riporta tutti i bit impostati. Leggeteli e rispettateli nel vostro livello dei comandi, ma siate chiari su che cosa siano. Per un documento cifrato con una password proprietario e una password utente vuota, il contenuto si decifra completamente all'apertura, e i flag sono una richiesta ai viewer conformi, non un meccanismo di applicazione. Questo ha due conseguenze, che tirano in direzioni opposte. Non presentate mai i flag di permesso agli utenti come una proprietà di sicurezza dei documenti che ricevono, perché non lo sono. Allo stesso tempo, rispettate il bit di estrazione per accessibilità (bit 10) anche dove la copia generale (bit 5) è negata; l'accesso da parte degli screen reader è ritagliato a parte nel modello dei permessi di proposito, e toglierlo perché la copia è disattivata rompe le tecnologie assistive senza alcun guadagno di sicurezza
Applicate le azioni negate al livello dei comandi, non nascondendo i pulsanti della barra degli strumenti. Ctrl+C, i menu contestuali e la selezione trascinata aggirano tutti una barra degli strumenti; un singolo controllo di permesso dentro il comando di copia non aggira nulla
Per i documenti che richiedono davvero una password utente, assegnate Password prima di Active := True e trattate il valore come il segreto che è: recuperatelo dal vostro archivio di credenziali per ogni sessione, tenetelo fuori da log e report di crash, e non conservatelo mai accanto al documento. Un riquadro di anteprima che memorizza le password per comodità è diventato silenziosamente un database di password senza nessuna delle protezioni di uno
La stampa merita una decisione a sé invece di ereditare qualunque cosa sia stata decisa per la copia. Una stampa fisica è per definizione non tracciata, eppure bloccare del tutto la stampa tende a spingere gli utenti verso gli screenshot, che sono peggiori su ogni asse. Una via di mezzo diffusa è consentire la stampa ma marcare ogni pagina con l'identità dell'utente e un timestamp, applicato dentro il comando di stampa. Solo, mantenete le giuste aspettative: una filigrana è deterrenza e attribuzione. Non è prevenzione
Che cosa il triage in ingresso avrebbe già dovuto dirvi
Un riquadro di anteprima prende decisioni migliori quando il file arriva con un dossier già allegato: cifrato o no, JavaScript presente o assente, un censimento degli allegati, il tipo di modulo. Quella passata di ispezione appartiene a monte del viewer, e lo schema descritto in costruire un workbench di revisione dei PDF in ingresso produce esattamente i flag che una politica di anteprima vuole consumare. I file che il triage ha marcato come rischiosi si aprono automaticamente attraverso il percorso irrobustito; i documenti di routine mantengono le loro comodità. Legate le due fasi a un unico oggetto di politica condiviso invece che a due schermate di configurazione, che divergeranno entro il secondo rilascio per quanta cura mettiate nello scriverle la prima volta
Dove cada la linea fra in-process e fuori processo dipende da chi vi manda i file. Per un normale intake aziendale, chi invia documenti è noto e soltanto disattento, e un'anteprima in-process con lo scripting spento e i link intercettati è un livello difendibile. Per i caricamenti pubblici anonimi non lo è, e nessuna quantità di flag in-process lo rende tale; renderizzate quelli in un worker separato a privilegi ridotti e spedite bitmap alla UI, così che un difetto del motore vi costi un worker anziché l'applicazione ospite. Decidete quella separazione in modo deliberato e mettete per iscritto in quale categoria ricada ciascun percorso di acquisizione, perché il costo di sbagliare la previsione è asimmetrico
Licenze, la superficie API legata alla sicurezza e un demo di viewer irrobustito si trovano nella pagina di prodotto: PDFium Component