PDFドキュメントは信じられないほど強力ですが、その強力さには固有のセキュリティリスクが伴います。PDFは埋め込みファイル、インタラクティブなJavaScript、複雑なバイナリストリームをサポートしているため、マルウェアの配信ベクターとして頻繁に使用されます。不適切に記述されたPDFパーサーのバッファオーバーフロー、境界外読み取り、および整数オーバーフローは、リモートコード実行(RCE)につながる可能性があります。
DelphiでユーザーがアップロードしたPDFを受け入れるアプリケーション(たとえば、ドキュメント取り込みポータル)を構築している場合、メモリセーフなPDF解析を確保することは不可欠なセキュリティ要件です。
一般的なPDFの攻撃ベクター
悪意のあるPDFは通常、オペレーティングシステムではなくパーサー自体の脆弱性を標的とします。一般的な手法は次のとおりです。
- 不正な相互参照(XRef)テーブル: 境界外につながるポインタオフセットを作成し、パーサーをクラッシュさせたり、メモリの開示を許可したりします。
- 無限ループ: PDFオブジェクト間で循環参照を作成し(たとえば、オブジェクトAがオブジェクトBを参照し、それがオブジェクトAを参照する)、スタックの枯渇を引き起こします。
- 爆発的な解凍(Zip爆弾): 数キロバイトから数ギガバイトに解凍されるFlateDecodeストリームによって、システムメモリを枯渇させます。
Delphiにおける防御的解析戦略
DelphiでPDFをネイティブに解析する場合は、防御的なプログラミングを行う必要があります。PDF辞書で提供されるメタデータを信頼することはできません。
1. 循環参照の切断
PDFオブジェクトツリーを再帰的に探索する場合、無限ループを防ぐために、訪問したオブジェクトの履歴を保持する必要があります。
uses
System.Generics.Collections, System.SysUtils;
// A safe recursive function to walk the PDF tree
procedure ParsePDFDictionary(DictObj: TPDFDictionary; Visited: TList<Integer>);
var
ObjID: Integer;
begin
ObjID := DictObj.ObjectID;
if Visited.Contains(ObjID) then
begin
Writeln('Warning: Circular reference detected. Aborting branch.');
Exit;
end;
Visited.Add(ObjID);
try
// Process child objects safely...
finally
// Allow siblings to traverse, but prevent vertical recursion loops
Visited.Remove(ObjID);
end;
end;
2. Zip爆弾からの保護
ストリームを解凍するためにFlateDecodeフィルターを適用する場合は、最大展開サイズを厳密に制限する必要があります。`/Length`辞書キーに基づいて盲目的にメモリを割り当ててはいけません。
const
MAX_DECOMPRESSED_SIZE = 1024 * 1024 * 50; // 50 MB safety limit
procedure DecompressPDFStream(CompressedStream, OutputTarget: TStream);
var
ZLibStream: TZDecompressionStream;
Buffer: array[0..8191] of Byte;
BytesRead, TotalRead: Integer;
begin
ZLibStream := TZDecompressionStream.Create(CompressedStream);
try
TotalRead := 0;
repeat
BytesRead := ZLibStream.Read(Buffer[0], SizeOf(Buffer));
if BytesRead > 0 then
begin
TotalRead := TotalRead + BytesRead;
if TotalRead > MAX_DECOMPRESSED_SIZE then
raise Exception.Create('Security Exception: Decompression bomb detected!');
OutputTarget.WriteBuffer(Buffer[0], BytesRead);
end;
until BytesRead = 0;
finally
ZLibStream.Free;
end;
end;
堅牢化されたエンジンと安全なコンポーネントの活用
完全に安全な PDF パーサーをゼロから記述することは非常に困難な作業です。業界標準のアプローチは、PDFium のような堅牢化され、徹底的にファジングテストされたエンジンを使用するか、厳密にテストされたネイティブライブラリに依存することです。
PDFium は Google Chrome で使用されているコアレンダリングエンジンです。Chrome は毎日何百万もの信頼できない PDF を処理するため、PDFium は Google の Project Zero による積極的かつ継続的なファジングを受けています。不正な形式の XRef、破損したストリーム、および循環参照を適切に処理します。
同様に、HotPDF Component や Delphi PDF Library などのネイティブコンポーネントは、強力な防御的解析戦略を標準で組み込んでいます。これらは、Delphi および C++Builder 環境向けに特別に設計された、厳密な境界チェック、再帰的な深度制限、およびメモリリーク防止メカニズムを実装しています。
レンダリングのために Delphi ラッパーを介して PDFium を利用する場合でも、ドキュメントの生成や処理に HotPDF のようなネイティブコンポーネントを使用する場合でも、自身で防御的なパーサーを記述することなく、エンタープライズグレードのセキュリティ境界を継承し、ユーザーやサーバーを悪意のあるペイロードから保護できます。
注: 安全でファジングテスト済みの解析機能は、HotPDF Component、Delphi PDF Library、および PDFium Component を含む当社のスイート全体で利用可能です。