Technical Article

Delphi'de PDFium ile PDF Güvenlik Risklerini Denetleme

Bir PDF sadece kağıt değildir. Dosya açıldığında çalışan komut dosyalarını, harici programları başlatan bağlantıları, web sunucularına ulaşan bağlantıları, dosyaların içine yerleştirilmiş dosyaları ve birinin kefil olmasından bu yana belgenin değişmediğini iddia eden bir imzayı taşıyabilen bir kaptır. Kontrol etmediğiniz bir kaynaktan bir dosya geldiğinde, en güvenli ilk adım onu işlemek değildir. Dosyanın kendisi hakkında ne söylediğini okumak ve yapmaya çalışabileceği her şeyin envanterini çıkarmaktır, böylece bir insan onun iş akışınıza ait olup olmadığına karar verebilir。

Bu makale, Delphi ve Lazarus için PDFium bileşenini kullanarak bu risk yüzeyi üzerinde statik, salt okunur bir denetim geçişini incelemektedir. Denetim asla bir sayfayı boyamaz. Belge yapısını ayrıştırır, dosyanın davranış taşıyan kısımlarını listeler ve sade bir rapor yazar. Bu, bir yabancıdan kapıda ceplerini boşaltmasını istemek ile gülümsediği için ona güvenmek arasındaki farktır。

Bir denetim nedir ve ne değildir

Sınır konusunda net olun. Korumalı alanda (sandboxed) bir önizleme, bir dosyayı sıkı kısıtlamalar altında işler, böylece kullanıcı dosyanın makinenin geri kalanına dokunmasına izin vermeden ona bakabilir. Bir denetim bundan önce gelir. Tek çıktısı tehdit yüzeyinin bir açıklaması olan, işlemesiz (render-free) bir incelemedir: hangi komut dosyalarının var olduğu, hangi eylemlerin bağlantılara bağlı olduğu, dosyanın imzalanıp imzalanmadığı ve ne kadar sıkı olduğu ve nelerin ekli olduğu. Bunu, bir belge bir güven sınırını geçtiğinde, e-postadan, yükleme formundan veya ortak akışından alırken, daha sonraki herhangi bir aşama onu gerçekten açmadan önce çalıştırırsınız。

Bileşen, bir denetim için de belgeyi diğer her şeyle aynı şekilde yükler. Dosya adını ayarlar ve etkinleştirirsiniz; bu, tek bir sayfayı işlemeden çapraz referans verilerini ve belge kataloğunu ayrıştırır. Aşağıdaki her şey, yüklenmiş, işlenmemiş durumdan okunur。

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;

Ad ağacındaki belge JavaScript'i

Bir PDF, belge düzeyinde JavaScript taşıyabilir: herhangi bir sayfaya veya alana değil, belgenin kendisine eklenmiş, /Names ağacında /JavaScript girişi altında saklanan komut dosyaları. Uyumlu bir görüntüleyici bunları açılışta çalıştırır. Bu, uzun bir PDF kötü amaçlı yazılım hattının arkasındaki mekanizmadır, çünkü bir dosyanın, kullanıcı daha tek bir kelime okumadan çift tıkladığı anda mantığı yürütmesine izin verir。

Bir denetçi bu tür her komut dosyası hakkında iki gerçek ister: var olduğu ve ne içerdiği. Bileşen sayıyı gösterir ve her eylemi, komut dosyasının adını ve tam gövdesini tutan bir kayıt olarak okumanıza olanak tanır. Gövdeyi okumak önemlidir. Doc.0 adındaki bir komut dosyası size hiçbir şey söylemez, ancak metni app.launchURL çağırabilir veya bir dize derleyip gitmemesi gereken bir yere iletebilir. Kaynağı bir incelemecinin okuyabilmesi için dışarı çekmek, açılışta kod çalıştıran bir dosyayı işaretlemenin tüm amacıdır。

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;

Sıfır belge komut dosyası içeren bir dosya otomatik olarak güvenli değildir, çünkü sayfa ve alan komut dosyaları da mevcuttur, ancak belge komut dosyaları içeren bir dosya her zaman ikinci bir bakışı hak eder. Yalnızca varlık sayısı yararlı bir geçittir ve gövde, bir geçidi bir yargıya dönüştüren şeydir。

Başlatma ve URI eylemleri

Envanterlenecek bir sonraki davranış bağlantılarda ve ek açıklamalarda yaşar. Bir denetçi için en önemli iki eylem türü vardır. Bir Başlatma (Launch) eylemi, bağlantı tetiklendiğinde harici bir programı başlatır veya yerel bir dosyayı açar. Bir URI eylemi bir web hedefini açar. Şüpheli bir belgeye bakan bir incelemeci, hiçbir şeye tıklamadan, üçüncü sayfadaki bir düğmenin cmd.exe başlatmak veya sayfadaki markayla eşleşmeyen bir URL açmak üzere bağlandığını görebilmelidir。

Bileşen bulduğu bağlantıları sınıflandırır ve her biri için eylem türünü ve hedef yolunu gösterir, böylece bir denetim her Başlatma ve URI eylemini hedefiyle birlikte listeleyebilir. Bu raporlamadır, yürütme değildir. Denetçi eylemi yapıdan okur ve yazar. Asla onu takip etmez。

Belgeleri işleyen görüntüleyici kontrolü, bir eylemin izleneceği yerdir ve varsayılan duruşu bilerek temkinlidir. TPdfView kontrolü, tıklamada hangi bağlantı türlerinin otomatik olarak tetikleneceğine karar veren bir LinkOptions kümesine sahiptir. Varsayılanı [loAutoGoto, loAutoOpenURI] şeklindedir, bu da belge içi atlamaların ve web URL'lerinin açılabileceği anlamına gelir, ancak loAutoLaunch eksiktir, bu nedenle başlatma eylemleri asla otomatik olarak çalışmaz. Bir denetim iş akışı için daha da ileri gider ve kümeyi tamamen temizlersiniz, böylece dosyaya güvenip güvenmeyeceğinize karar verirken hiçbir şey otomatik olarak tetiklenmez。

// 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.

Varsayılan olarak başlatmayı engellemenin arkasındaki gerekçe basittir. Belge içindeki bir atlama zararsızdır ve bir URL görünür ve iptal edilebilirdir, ancak bir tıklamadan rastgele bir harici program başlatmak, bir PDF bağlantısının isteyebileceği en tehlikeli şeydir, bu nedenle siz dahil olmadıkça kapalıdır. Bir denetçi güvenli davranışları bile devre dışı bırakır, çünkü işi hareket etmek değil, bakmaktır。

Dijital imza MDP izin seviyesi

İmzalar soruyu değiştirir. Düz bir imza, imzalama anındaki baytları onaylar. Belge değişikliği tespiti ve önlenmesi kuralıyla oluşturulan türden bir sertifikalandırma imzası (certification signature) daha da ileri gider: belgenin sertifikalandırılmasından sonra neyin yasal olarak değişebileceğini beyan eder ve uyumlu bir görüntüleyici bu iznin dışındaki herhangi bir şeye dokunulursa uyarır. Bu izin seviyesini okumak, bir denetçiye bir dosyanın sertifikalı olup olmadığını ve eğer öyleyse ne kadar kilitli olması gerektiğini söyler。

MDP izni, tanımlanmış üç değeri olan bir tamsayıdır. 1 seviyesi hiçbir değişikliğe izin verilmediği anlamına gelir; herhangi bir değişiklik sertifikayı bozar. 2 seviyesi form doldurma ve imzalamaya izin verir; bu, tamamlanması ve imzalanması amaçlanan ancak başka bir şekilde değiştirilmeyen bir sözleşme için yaygın durumdur. 3 seviyesi form doldurma ve imzalamanın yanı sıra ek olarak ek açıklamalara da izin verir. Seviyeyi bilmek, alım mantığınızın niyet hakkında akıl yürütmesini sağlar: seviye 1'de sertifikalandırılmış olmasına rağmen form alanları veya komut dosyaları taşıyan bir belge kendi kendisiyle çelişiyor demektir ve bu çelişki işaretlenmeye değerdir。

Bileşen, imzaların sayısını okur ve her birini, doğrudan temel FPDFSignatureObj_GetDocMDPPermission çağrısından doldurulan, Permission alanı bu MDP değerini taşıyan bir kayıt olarak sunar. Sıfır izni, imzanın bir sertifikalandırma (DocMDP) imzası olmadığı anlamına gelir, bu nedenle rapor edilecek belge düzeyinde bir kilitlenme yoktur。

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;

Bir denetim burada imzanın kriptografisini doğrulamaz; sertifika zincirini doğrulamak ayrı bir konudur. Rapor ettiği şey beyan edilen niyettir: bu dosya bu seviyede kilitlendiğini söylüyor. Bu, bir incelemecinin sonraki değişikliklerin veya yalnızca etkin içeriğin varlığının, yazarın belgeyi nasıl mühürlediğiyle tutarlı olup olmadığını yargılamak için ihtiyaç duyduğu tam bağlamdır。

Yüzeyin geri kalanı: gömülü dosyalar ve XFA

İki öğe daha tam bir envanteri tamamlar. Gömülü dosyalar, PDF'nin içinde ek olarak taşınan tüm belgelerdir ve klasik bir dağıtım aracıdır; çünkü zararsız görünen bir rapor, ek ağacında bir yürütülebilir dosya veya ikinci bir kötü amaçlı PDF gönderebilir. Bileşen, ek sayısını ve her ekin adını gösterir, böylece denetim hiçbirini çıkarmadan veya açmadan neyin eşlik ettiğini listeleyebilir。

XFA varlığı diğer bayraktır. Bir XFA formu, statik AcroForm'u kendi işleme ve komut dosyası oluşturma modelini getiren XML tabanlı bir form mimarisiyle değiştirir; bu, düz bir formdan daha büyük ve daha karmaşık bir yüzeydir. Orada olduğunu belirtmek için XFA'yı işlemeniz gerekmez; yalnızca varlığı, dosyanın daha yakından bakmaya değer daha zengin bir etkileşimli katman taşıdığının bir işaretidir. Bileşen bunu tek bir boolean olarak bildirir。

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;

Rapor yazan tek bir salt okunur rutin

Parçaları bir araya getirdiğinizde denetim; bir belgeyi yükleyen, komut dosyalarını ve gövdelerini listeleyen, Başlatma ve URI hedeflerini listeleyen, imza MDP seviyesini bildiren, ekleri ve XFA'yı not eden ve bulguları bir günlüğe yazan tek bir prosedürdür. Hiçbir şeyi işlemez (render), dolayısıyla ucuzdur ve düşmanca sayfa içeriğini görüntülemek üzere kandırılamaz. Çıktı, bir incelemecinin veya bir sonraki kuralın üzerinde işlem yapabileceği düz, insan tarafından okunabilir bir kayıttır。

Pratikte iyi çalışan şekil, her bulguyu bir satır olarak toplamak, gerçekten riskli olanları bir inceleme kuyruğunun en üstüne sıralanacak şekilde ön eklemek ve her şeyi dosyanın yanında kalıcı hale getirmektir. Komut dosyası olmayan, başlatma eylemi olmayan, eki olmayan, XFA'sı olmayan ve imzası olmayan ya da tutarlı bir sertifikası olan bir belge sessizce geçer. Aynı anda birkaç bayrağı tetikleyen bir belge, daha sonraki herhangi bir aşama onu açmadan önce bir kişinin görmesi gereken belgedir. Denetim sizin yerinize güven kararını vermez. Kararın körü körüne değil, bilgiye dayalı olmasını sağlar。

Bir dosya denetimi geçtikten sonra ve ona bakmanız gerektiğinde, bunu varsayılan bir görüntüleyici yerine kısıtlama altında yapın. Delphi'de güvenli bir PDF önizlemesi oluşturma kılavuzumuzdaki yaklaşım, kontrollü bir bakış sırasında bağlantı otomatik yönetiminin ve etkin içeriğin hareket etmesini nasıl önleyeceğinizi gösterir. Bu envanteri incelemeci araçlarıyla tam bir alım işlem hattına katlamak için PDF alımı ve inceleme tezgahı makalesine bakın. Her ikisi de aynı salt okunur, işlemesiz temele dayanır ve bu blogun başka yerlerinde ele alınan işleme, metin, form ve imza API'lerinin yanı sıra Delphi ve C++Builder için PDFium Bileşeninin bir parçası olarak gönderilir。