Eine PDF-Datei ist nicht einfach nur Papier. Sie ist ein Container, der Skripte tragen kann, die beim Öffnen der Datei ausgeführt werden, Links, die externe Programme starten, Links, die Webserver kontaktieren, ineinander verschachtelte Dateien und eine Signatur, die bestätigt, dass sich das Dokument nicht geändert hat, seit jemand dafür gebürgt hat. Wenn eine Datei aus einer Quelle eintrifft, die Sie nicht kontrollieren, ist der sicherste erste Schritt nicht das Rendern. Es ist das Auslesen dessen, was die Datei über sich selbst aussagt, und das Erstellen eines Inventars über alles, was sie tun könnte. So kann ein Mensch entscheiden, ob sie überhaupt in Ihren Workflow gehört.
Dieser Artikel führt durch eine statische, schreibgeschützte Überprüfung (Audit) dieser Risikofläche unter Verwendung der PDFium-Komponente für Delphi und Lazarus. Das Audit zeichnet niemals eine Seite. Es parst die Dokumentenstruktur, listet die Teile der Datei auf, die Verhalten tragen, und schreibt einen einfachen Bericht. Es ist the difference zwischen asking a stranger to empty their pockets at the door and trusting them because they smiled.
Was ein Audit ist und was nicht
Die Grenze muss klar sein. Eine Sandbox-Vorschau rendert eine Datei unter strengen Einschränkungen, sodass ein Benutzer sie ansehen kann, ohne dass die Datei den Rest des Rechners berührt. Ein Audit geht dem voraus. Es ist eine renderfreie Inspektion, deren einzige Ausgabe eine Beschreibung der Bedrohungsfläche ist: welche Skripte vorhanden sind, welche Aktionen mit Links verknüpft sind, ob die Datei signiert ist (und wie restriktiv) und was angehängt ist. Sie führen dieses Audit aus, wenn ein Dokument eine Vertrauensgrenze überschreitet – beim Eingang per E-Mail, über ein Upload-Formular oder einen Partner-Feed –, bevor irgendein späterer Schritt die Datei tatsächlich öffnet.
Die Komponente lädt ein Dokument für ein Audit genauso wie für jeden anderen Zweck. Sie legen den Dateinamen fest und aktivieren sie, was die Querverweisdaten (XRef) und den Dokumentenkatalog parst, ohne eine einzige Seite zu rendern. Alles Folgende liest aus diesem geladenen, ungerenderten Zustand.
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'Incoming_Invoice.pdf';
Pdf.Active := True; // parses structure, renders nothing
// audit the loaded document here
finally
Pdf.Free;
end;
end;
JavaScript auf Dokumentenebene im Namensbaum
Als Erstes muss Code erfasst werden. Eine PDF-Datei kann JavaScript auf Dokumentenebene enthalten: Skripte, die nicht an eine bestimmte Seite oder ein Feld angehängt sind, sondern an das Dokument selbst, gespeichert im /Names-Baum unter einem /JavaScript-Eintrag. Ein konformer Viewer führt diese beim Öffnen aus. Dies ist der Mechanismus hinter einer langen Reihe von PDF-Malware, da die Datei im selben Moment Logik ausführen kann, in dem ein Benutzer doppelt darauf klickt – noch bevor er ein Wort gelesen hat.
Ein Auditor benötigt zwei Fakten über jedes dieser Skripte: ob es existiert und was es enthält. Die Komponente gibt die Anzahl aus und ermöglicht es Ihnen, jede Aktion als Datensatz zu lesen, der den Namen des Skripts und seinen vollständigen Inhalt enthält. Den Inhalt zu lesen ist wichtig. Ein Skript namens Doc.0 sagt Ihnen nichts, aber sein Text könnte app.launchURL aufrufen oder eine Zeichenfolge zusammensetzen und sie an einen Ort senden, an den sie nicht gelangen sollte. Den Quellcode auszulesen, damit ein Prüfer ihn lesen kann, ist der eigentliche Zweck der Kennzeichnung einer Datei, die beim Öffnen Code ausführt.
var
I: Integer;
Action: TPdfJavaScriptAction;
begin
if Pdf.JavaScriptActionCount > 0 then
WriteLn('WARNING: document runs ', Pdf.JavaScriptActionCount,
' script(s) on open');
for I := 0 to Pdf.JavaScriptActionCount - 1 do
begin
Action := Pdf.JavaScriptAction[I];
WriteLn(' script "', Action.Name, '":');
WriteLn(Action.Script); // full body, for a human to read
end;
end;
Eine Datei ohne Dokumentenskripte ist nicht automatisch sicher, da es auch Seiten- und Feldskripte gibt, aber eine Datei mit Dokumentenskripten verdient immer einen genaueren Blick. Die bloße Anzahl ist eine nützliche Hürde, und der Skriptinhalt macht aus einer einfachen Kennzeichnung ein verlässliches Urteil.
Launch- und URI-Aktionen
Das nächste zu erfassende Verhalten befindet sich auf Links und Annotationen. Zwei Aktionstypen sind für einen Auditor am wichtigsten. Eine Launch-Aktion startet ein externes Programm oder öffnet eine lokale Datei, wenn der Link ausgelöst wird. Eine URI-Aktion öffnet ein Webziel. Ein Prüfer, der ein verdächtiges Dokument untersucht, sollte ohne einen Klick sehen können, ob eine Schaltfläche auf Seite drei so verdrahtet ist, dass sie cmd.exe startet oder eine URL öffnet, die nicht zur Marke auf der Seite passt.
Die Komponente klassifiziert die gefundenen Links und gibt für jeden den Aktionstyp und den Zielpfad aus, sodass ein Audit jede Launch- und URI-Aktion mit ihrem Ziel auflisten kann. Dies ist Berichterstattung, keine Ausführung. Der Auditor liest die Aktion aus der Struktur und schreibt sie auf. Er folgt ihr niemals.
Das Viewer-Steuerelement, das Dokumente rendert, ist der Ort, an dem das Folgen einer Aktion stattfinden würde, und sein Standardverhalten ist bewusst vorsichtig. Das TPdfView-Steuerelement besitzt eine LinkOptions-Menge, die entscheidet, welche Linktypen bei einem Klick automatisch ausgeführt werden. Standardmäßig lautet diese [loAutoGoto, loAutoOpenURI], was bedeutet, dass dokumenteninterne Sprünge und Web-URLs geöffnet werden dürfen, loAutoLaunch jedoch fehlt. Start-Aktionen werden somit niemals automatisch ausgeführt. Für einen Audit-Workflow geht man noch weiter und leert diese menge vollständig, sodass überhaupt nichts automatisch ausgelöst wird, während Sie noch entscheiden, ob Sie der Datei vertrauen.
// Audit posture for the viewer: nothing auto-runs, nothing auto-opens.
View.LinkOptions := [];
// The shipped default already withholds launch:
// default = [loAutoGoto, loAutoOpenURI]
// loAutoLaunch is NOT in the default set, so external programs
// are never started on a stray click out of the box.
Die Begründung für das standardmäßige Sperren von Launch-Aktionen is einfach. Ein Sprung innerhalb des Dokuments ist harmlos und eine URL ist sichtbar und abbrechbar. Aber das Starten eines beliebigen externen Programms per Klick ist das Gefährlichste, was ein PDF-Link verlangen kann, weshalb dies standardmäßig deaktiviert ist, es sei denn, Sie aktivieren es explizit. Ein Auditor deaktiviert selbst die sicheren Verhaltensweisen, da es seine Aufgabe ist zu beobachten und nicht zu handeln.
Die MDP-Berechtigungsstufe digitaler Signaturen
Signaturen verändern die Fragestellung. Eine einfache Signatur bestätigt die Bytes zum Zeitpunkt der Unterzeichnung. Eine Zertifizierungssignatur – die Art, die mit einer Regel zur Erkennung und Verhinderung von Dokumentenänderungen (DocMDP) erstellt wird – geht weiter: Sie deklariert, was sich nach der Zertifizierung des Dokuments rechtmäßig ändern darf, und ein konformer Viewer warnt, wenn etwas außerhalb dieses zulässigen Rahmens berührt wurde. Das Auslesen dieser Berechtigungsstufe sagt einem Auditor, ob eine Datei zertifiziert ist und wie stark sie gesperrt sein soll.
Die MDP-Berechtigung ist eine Ganzzahl mit drei definierten Werten. Eine Stufe von 1 bedeutet, dass überhaupt keine Änderungen zulässig sind; jede Änderung bricht die Zertifizierung auf. Eine Stufe von 2 erlaubt das Ausfüllen von Formularen und das Signieren – der typische Fall für einen Vertrag, der ausgefüllt und unterschrieben, aber ansonsten nicht verändert werden soll. Eine Stufe von 3 erlaubt zusätzlich zu Formularausfüllung und Signatur auch Anmerkungen (Annotations). Die Kenntnis dieser Stufe ermöglicht es Ihrer Eingangslogik, über die Absicht zu entscheiden: Ein Dokument, das auf Stufe 1 zertifiziert ist, aber dennoch Formularfelder oder Skripte enthält, widerspricht sich selbst, und dieser Widerspruch sollte gekennzeichnet werden.
Die Komponente liest die Anzahl der Signaturen aus und stellt jede als Datensatz bereit, dessen Feld Permission diesen MDP-Wert enthält, der direkt aus dem zugrundeliegenden Aufruf von FPDFSignatureObj_GetDocMDPPermission gefüllt wird. Eine Berechtigung von Null bedeutet, dass es sich nicht um eine Zertifizierungssignatur (DocMDP) handelt, sodass keine Sperre auf Dokumentenebene zu melden ist.
var
I: Integer;
Sig: TPdfSignature;
begin
if Pdf.SignatureCount = 0 then
WriteLn('document is not signed')
else
for I := 0 to Pdf.SignatureCount - 1 do
begin
Sig := Pdf.Signature[I];
case Sig.Permission of
1: WriteLn('certified: no changes allowed');
2: WriteLn('certified: form fill and signing allowed');
3: WriteLn('certified: form fill, signing and annotations allowed');
else
WriteLn('signed, but not a DocMDP certification');
end;
end;
end;
Ein Audit validiert hier nicht die Kryptografie der Signatur; die Überprüfung der Zertifikatskette ist ein separater Aspekt. Was es berichtet, ist die deklarierte Absicht: Diese Datei sagt aus, dass sie auf dieser Stufe gesperrt wurde. Das ist genau der Kontext, den ein Prüfer benötigt, um zu beurteilen, ob spätere Änderungen oder das bloße Vorhandensein aktiver Inhalte mit der Art und Weise übereinstimmen, wie der Autor das Dokument versiegelt hat.
Der Rest der Oberfläche: Eingebettete Dateien und XFA
Zwei weitere Punkte verknüpfen ein vollständiges Inventar. Eingebettete Dateien sind vollständige Dokumente, die als Anhänge innerhalb der PDF-Datei transportiert werden. Sie sind ein klassischer Übertragungskanal, da ein harmlos aussehender Bericht eine ausführbare Datei oder eine zweite, bösartige PDF-Datei in seinem Anhangsbaum mitführen kann. Die Komponente gibt die Anzahl der Anhänge und den Namen jedes Anhangs aus, sodass das Audit auflisten kann, was mitgeführt wird, ohne etwas davon zu extrahieren oder zu öffnen.
Das Vorhandensein von XFA is das andere Flag. Ein XFA-Formular ersetzt das statische AcroForm durch eine XML-basierte Formulararchitektur, die ein eigenes Rendering- und Skriptmodell mitbringt – eine größere und komplexere Oberfläche als ein einfaches Formular. Sie müssen das XFA nicht verarbeiten, um festzustellen, dass es da is; seine bloße Existenz ist ein Signal dafür, dass die Datei eine reichhaltigere interaktive Schicht enthält, die einen genaueren Blick wert ist. Die Komponente meldet dies als einfachen Boolean-Wert.
var
I: Integer;
begin
if Pdf.XFA then
WriteLn('NOTE: document contains an XFA form layer');
if Pdf.AttachmentCount > 0 then
begin
WriteLn('embedded files: ', Pdf.AttachmentCount);
for I := 0 to Pdf.AttachmentCount - 1 do
WriteLn(' - ', Pdf.AttachmentName[I]);
end;
end;
Eine schreibgeschützte Routine, die einen Bericht schreibt
Fügt man die Teile zusammen, ist das Audit eine einzige Prozedur, die ein Dokument lädt, seine Skripte und deren Inhalte auflistet, seine Launch- und URI-Ziele erfasst, die MDP-Stufe der Signatur berichtet, Anhänge und XFA vermerkt und die Ergebnisse in ein Protokoll schreibt. Sie rendert nichts, ist daher kostengünstig und kann nicht dazu verleitet werden, schädliche Seiteninhalte anzuzeigen. Die Ausgabe ist ein einfaches, für den Menschen lesbares Protokoll, auf das ein Prüfer oder eine nachgelagerte Regel reagieren kann.
Die Form, die sich in der Praxis bewährt hat, besteht darin, jedes Ergebnis als Zeile zu erfassen, den tatsächlich riskanten Ergebnissen ein Präfix voranzustellen, damit sie in einer Prüfwarteschlange ganz oben einsortiert werden, und das Ganze direkt neben der Datei zu speichern. Ein Dokument mit keinem Skript, keinem Launch-Aktion, keinem Anhang, keinem XFA und entweder keiner Signatur oder einer schlüssigen Zertifizierung wird geräuschlos durchgewinkt. Ein Dokument, das mehrere Flags gleichzeitig auslöst, ist dasjenige, das sich eine Person ansehen sollte, bevor ein späterer Schritt es öffnet. Das Audit nimmt Ihnen die Vertrauensentscheidung nicht ab. Es stellt sicher, dass die Entscheidung fundiert und nicht blind getroffen wird.
Sobald eine Datei das Audit bestanden hat und Sie sie ansehen müssen, tun Sie dies unter Einschränkungen und nicht in einem Standard-Viewer. Der Ansatz in our walkthrough on building a secure PDF preview in Delphi zeigt wie Sie verhindern, dass automatische Link-Verarbeitung und aktive Inhalte während einer kontrollierten Ansicht ausgeführt werden. Wie Sie diese Erfassung in eine vollständige Eingangs-Pipeline mit Prüfer-Werkzeugen integrieren, beschreibt der Artikel über die PDF-Eingangs- und Prüf-Workbench. Beide bauen auf derselben schreibgeschützten, renderfreien Grundlage auf und werden als Teil der PDFium Component für Delphi und C++Builder ausgeliefert, zusammen mit den APIs für Rendering, Text, Formulare und Signaturen, die an anderer Stelle in diesem Blog behandelt werden.