Teknik Makale

PDFium Component DocMDP: /P ile saklanan sayfa düzenlemesi

v3.126.2 öncesi PDFium Component derlemelerinde TPdf.AnalyzeSignatureRevisions, gerçek bir sayfa içerik düzenlemesini DocMDP P=3 altında izin verilen bir annotation değişikliği diye notlandırabiliyordu; çünkü revizyon rol grafiği, imza widget'ının sayfasına yaptığı /P geri referansını sahiplik sayıyordu. v3.126.2'den beri PDFium Component, navigation kenarlarını owned-payload kenarlarından ayırır; böylece sayfa içeriği sayfa içeriği olarak kalır. Bu düzeltmenin ardındaki hata bildirimi kâğıt üzerinde zararsız görünür. Sertifikalı bir sözleşme annotation'lara izin verir; karşı taraf bir incremental save ekler ve çözümleyici, sonraki her değişikliğin izinli olduğunu söyler. Sonra biri render edilmiş sayfaları diff'ler ve 2. sayfadaki ödeme tutarı farklıdır

Bu yazı, imza sonrası revizyon değişiklik analizi genel bakışının saldırgan gözüyle devamıdır; revizyon yeniden kurmanın ve DocMDP notlandırmanın temellerini atlayıp doğrudan object grafiğine gider: sahiplik nasıl modellendi, bir kenarın yönü güvenlik hükmüne neden karar verir, v3.126.2'de ne değişti ve kendi kabul mantığınızı nasıl denetlersiniz

Bir sayfa düzenlemesi DocMDP P=3 altında annotation değişikliği olarak neden geçti?

Sayfa düzenlemesi geçti, çünkü eski rol grafiği sözlükteki her dolaylı referansı, referans gösterilen object referans gösterenin malıymış gibi izliyordu; imza widget'ı da sayfasına geri işaret eder. Bir annotation sözlüğü, üzerinde durduğu page object'ine dolaylı bir referans olan /P taşır (ISO 32000-1 §12.5.2). O girdi bir navigation ipucudur. Widget sayfaya sahip değildir; sayfa, widget'a kendi /Annots dizisi üzerinden sahiptir

Çözümleyici, sonraki değişiklikleri notlandırmadan önce her object'e bir rol bit kümesi atar: page, annotation, form ve validation malzemesi. Kök object'ler rollerini kendi sözlüklerinden alır ve rol sonra referans gösterdikleri her şeye yayılır. Eski yayılımda zincir şöyle gitti:

  1. İmza widget'ı, /FT /Sig'li bir /Subtype /Widget'tır; dolayısıyla annotation rolünü alır
  2. Widget'ın /P'si, annotation rolünü çoktan page rolü taşıyan sayfa sözlüğüne iter
  3. Sayfa iki rolü de /Contents'e ve /Resources'e, ayrıca /Parent üzerinden Pages ağacına ve her kardeş sayfaya iter
  4. << /Length 812 >> gibi bir content stream sözlüğü /Type taşımaz; dolayısıyla sınıflandırıcı rol bitlerine düşer ve page rolünden önce annotation rolünü kontrol ediyordu
v3.126.2 öncesi DocMDP rol grafiğinin PDFium Component şeması: imza widget /P geri referansı annotation rolünü sayfa sözlüğüne iter; sayfa onu /Contents üzerinden /Type girdisi olmayan bir content stream'e yayar; sınıflandırıcı prckAnnotation çıkarır ve P=3 notlandırması prdAllowed döndürür
v3.126.2 öncesinde rol grafiği her dolaylı referansı sahiplik sayıyordu; dolayısıyla widget /P girdisi annotation rolünü sayfaya itiyordu ve gerçek bir sayfa düzenlemesi, izinli bir annotation değişikliği olarak çözümleyiciden çıkıyordu

Değiştirilmiş content stream bu yüzden prckAnnotation olarak çıkıyordu. ISO 32000-1 §12.8.2.2 uyarınca DocMDP P=3, annotation değişikliklerine izin verir; dolayısıyla karar prdAllowed oldu ve rapor prasAllowed'a toparlandı. Aynı dosya P=2 altında reddedildi ama yalnızca tesadüfen: P=2 annotation değişikliklerini yasaklar; dolayısıyla yanlış etiketli stream yanlış nedenden dolayı reddediliyordu. Sabit dört geçişli bir yayılım döngüsü ikinci bir zaaf ekledi. Dolaylı bir dizi üzerinden ya da object numaraları geriye doğru giden uzun bir zincir üzerinden ulaşan payload hiçbir rol almayabiliyordu

Bir imza doğrulayıcı neden bir object'in sahibini sormalı?

Bir imza doğrulayıcı bir object'in sahibini sormalı, çünkü PDF incremental update'leri (ISO 32000-1 §7.5.6), var olan bir object numarasını yeniden tanımlayan bir revizyon eklemeye herkese izin verir ve yeniden tanımlanan gövde ne olduğunu ilan etmez. İmza yine de doğrulanır; çünkü yalnızca kendi revizyonunun baytlarını kapsar. İmza sonrası müdahaleye karşı her savunma bu yüzden, değişen her object'i onu kullanan yapıya eşlemeye ve sonra imzalayanın o yapının değişmesine izin verip vermediğini sormaya dayanır

Yayımlanmış birkaç saldırı sınıfı tam o boşlukta çalışır. Incremental saving saldırıları, sayfa içeriğini değiştiren bir revizyon ekler ve doğrulayıcının yalnızca imzalı bayt aralığını kontrol etmesine bel bağlar. Shadow saldırıları, imzadan önce gizli içerik eker ve sonra küçük, masum görünümlü bir değişiklikle onu etkinleştirir. Sertifikalı belgelere yönelik saldırılar, P=2 ile P=3'ün bazı sonraki düzenlemelere açıkça izin verdiği gerçeğini suiistimal eder ve yasak bir düzenlemeyi izinli bir kılığına sokar. Object'leri /Type /Annot gibi etiketlerle ya da tesadüfen onlara ulaşan herhangi bir referans yoluyla sınıflandıran bir doğrulayıcı üçüncü sınıfa açıktır: saldırganın yasak olana ulaşabilen tek bir izinli yapıyı bulması yeter

Bu yüzden soru hangi object'lerin değiştiği değil, kimin onlara sahip olduğudur. Bir sayfadan /Contents üzerinden ulaşılan bir content stream, başka ne işaret ederse etsin sayfa içeriğidir. /P üzerinden sayfaya geri işaret eden bir annotation, annotation'ın nerede yaşadığını söyler, neye sahip olduğunu değil

PDFium Component v3.126.2 sahipliği nasıl modeller?

PDFium Component v3.126.2, geri referanslarını navigation sayar, onları rol yayılımının dışında tutar ve hangi anahtarların navigation sayılacağını, anahtar adından değil onları tutan sözlüğün yapısal rolünden belirler. Tablo, artık sahiplik taşımayan navigation anahtarlarını özetliyor

Sahip sözlükNavigation sayılan anahtarlarŞartname referansı
Page ya da Pages node'u/Parent, /Kids, /AnnotsISO 32000-1 §7.7.3
Annotation ya da widget/PISO 32000-1 §12.5.2
Widget ya da field sözlüğü/ParentISO 32000-1 §12.7.3
PDFium Component v3.126.2 object grafiği: page ile annotation rollerini yayan /Contents ile /Annots gibi owned-payload kenarlarını, hiç rol taşımayan widget /P gibi navigation kenarlarından ayırır; sahip sözlük başına navigation anahtarları ve sahte bir /Type altında bile content stream'de tutulan prckPageContent
v3.126.2, geri referanslarını rol yayılımının dışında tutar: roller yalnızca gerçek sahiplikten akar; dolayısıyla content stream sayfa içeriği olarak kalır ve /P ipucu hiçbir şeye karar vermez

Anahtar adına göre küresel filtrelemek yeni bir delik açardı. Bir font ya da XObject kaynağı meşru olarak /P, /Parent ya da /Annots adını taşıyabilir; /P girdisini yayılımdan atan bir /Resources sözlüğü, saldırganın sayfaya ait bir XObject'i masum bir kaynak adının ardına saklamasına izin verirdi. v3.126.2'de navigation filtresi yalnızca sahip sözlük gerçekten bir page, Pages node'u, annotation, widget ya da field olduğunda uygulanır. Bu sözlüklerden biri çiftlenmiş bir navigation anahtarı taşıyorsa — bir widget'ta iki /P girdisi gibi — çözümleyici, görüntüleyicinin hangi kopyayı kullanacağını tahmin etmez; rol kurulumu başarısız olur ve imza Indeterminate olur

Birkaç kural daha, kalan yeniden etiketleme yollarını kapatır:

  • Pages node'ları kendi haklarıyla page-rol kökleridir; dolayısıyla Pages ağacından miras kalan kaynaklar (ISO 32000-1 §7.7.3.4) sayfa bağlamına gerçek sahiplikten girer, bir çocuk sayfadan /Parent yürüyüşünden değil
  • Bir catalog, Pages node'u, page, annotation ya da field sözlüğüne ulaşan bir annotation ya da form rolü orada durur; çünkü o yapısal object'ler kendi rollerini kurarlar ve gelen bir payload rolü onları ezmemelidir
  • Sınıflandırma sırasında page rolü yetkilidir: sayfaya ait bir object, sonraki bir revizyon onu sahte bir /FT ile ya da /Type /Annot etiketiyle yeniden yazsa ya da bir appearance stream'le paylaşsa bile prckPageContent'tir
  • Yalnızca bir field ya da annotation görünümü olarak kullanılan bir Form XObject, form ya da annotation kategorisini korur; dolayısıyla form doldurma sonrası sıradan görünüm yenilenmesi yine normal izin kuralları altında notlandırılır
  • Kendi /FT'si olmayan bir widget, miras kalan field tipini /Parent zinciri üzerinden çözer ve çözülemeyen bir zincir, annotation'a varsaymak yerine rol kurulumunu düşürür
  • Her sonraki revizyonun rol bitleri, kapsanan revizyonun rollerine birleştirilir; dolayısıyla sonraki bir update, önce bir stream'i koparıp sonra düzenleyerek önceki bir sayfa-sahiplik ilişkisini silemez

Sabit geçiş sayısı yerine sabit nokta

v3.126.2'de rol erişilebilirliği, hiçbir object yeni bir rol bit kazanmayana dek yineleyen bir iş kuyruğu olarak koşar; bu, zincir derinliğinden ya da object numaralandırmasından bağımsız gerçek bir sabit noktadır. Kendi object'i olarak saklanan bir /Contents dizisi gibi dolaylı diziler de yürünür. Her object en çok dört ayrı rol bit kazanabilir; dolayısıyla kuyruk, object numarası başına dört girdiyle sınırlıdır; o bütçeyi aşmak prrResourceLimitExceeded fırlatır. Serbest bir object'e referans, generation uyuşmazlığı ya da bozuk bir object header prrMalformedRevisionChain fırlatır; sıkıştırılmış bir object stream içindeki payload ise prrCompressedObjectUnresolved. Bu başarısızlıkların her biri prasIndeterminate ile biter, asla izinli bir hükümle değil; ve kapsanan revizyonun rolleri kurulurken başarısızlık olursa imza hiç Changes bildirmez

Aşağıdaki rutin, bu analizi atlatan sayfa-içerik düzenlemelerini listeler. Bir prckPageContent değişikliği asla prdAllowed notlandırılmaz: DocMDP P=1, 2 ya da 3 onu prdDisallowed yapar ve DocMDP'siz bir imza onu prdSuspicious notlandırır

uses
  SysUtils, TypInfo, PDFium, FPdfPades;

procedure ListPageContentEdits(const FileName: string);
const
  ShadowTag: array[Boolean] of string = (' (unreferenced shadow)', '');
var
  Pdf: TPdf;
  Report: TPadesRevisionAnalysisReport;
  Sig: TPadesSignatureRevisionAnalysis;
  Change: TPadesRevisionObjectChange;
  i, j: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;
    Report := Pdf.AnalyzeSignatureRevisions;   // record, serbest bırakılacak bir şey yok
    for i := 0 to High(Report.Signatures) do
    begin
      Sig := Report.Signatures[i];
      if not (prrPageContentChanged in Sig.Risks) then
        Continue;
      Writeln(Format('Signature %d (DocMDP P=%d): page content changed',
        [Sig.SignatureIndex, Sig.DocMdpPermission]));
      for j := 0 to High(Sig.Changes) do
      begin
        Change := Sig.Changes[j];
        if Change.Kind <> prckPageContent then
          Continue;
        Writeln(Format('  revision %d  object %d %d R  %s%s',
          [Change.RevisionIndex, Change.ObjectNumber, Change.Generation,
           GetEnumName(TypeInfo(TPadesRevisionDecision), Ord(Change.Decision)),
           ShadowTag[Change.IsAuthoritative]]));
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

FieldMDP ile bir annotation tek bir object'i paylaştığında ne olur?

Bir field'ın /V'si ile bir annotation'ın /Contents'i aynı dolaylı object'i gösterdiğinde v3.126.2, değişiklik bir annotation düzenlemesi diye sınıflansa bile FieldMDP kilidini yürürlükte tutar. Senaryoyu elle kurmak kolaydır: imzalayan, Total field'ını FieldMDP ile kilitler (ISO 32000-1 §12.8.2.4) ve saldırgan, bir metin annotation'ının /Contents'ini, field değerini tutan aynı string object'ine referans gösterir. P=3 altında annotation düzenlemesine izin verilir; dolayısıyla düzeltmeden önce o paylaşılan string'i yeniden yazmak, izinli bir hükmle kilitli bir field değerini değiştiriyordu

Object artık hem annotation hem form rolünü taşır ve imza bir FieldMDP transform taşıdığında annotation kararı form tarafını yeniden kontrol eder:

  • P=2 altında annotation değişikliği tümüyle yasaktır; tıpkı öncesi gibi
  • FieldMDP All ile her field kilitlidir; dolayısıyla paylaşılan değişiklik prdDisallowed'dur
  • FieldMDP Include ya da Exclude ile çözümleyici, paylaşılan bir skaleri tek bir field adına geri izleyemez; dolayısıyla karar bir tahmin yerine prdIndeterminate'dir
  • FieldMDP yokken P=3 annotation kuralı uygulanır ve değişiklik izinli kalır
Kilitli Total field /V'si ile bir annotation /Contents'inin aynı dolaylı object'i referans gösterdiği PDFium Component FieldMDP karar şeması: aynı paylaşılan düzenleme için DocMDP P=2, FieldMDP All, FieldMDP Include ya da Exclude ve FieldMDP yok dallarından prdDisallowed, prdIndeterminate ya da prdAllowed hükümlerine ayrılır
Tek bir dolaylı object hem annotation hem form rolünü taşıdığında annotation kararı FieldMDP kilidini yeniden kontrol eder; dolayısıyla aynı düzenleme, izinliden yasaklığa ve belirsizliğe kadar değişir

Kapı kodu için bir raporlama ayrıntısı önemlidir. Paylaşılan durum, Decision = prdIndeterminate ile Kind = prckAnnotation diye bildirilir ve prrFieldMdpUnresolved, yalnızca form field diye sınıflanan değişiklikler için risk kümesine eklenir. prrFieldMdpUnresolved'i arayıp Status'i yok sayan bir kapı bu durumu tümüyle kaçırır

Delphi kodu revizyon analizinde kapalıya nasıl düşmeli?

Delphi kodu, imzalı bir belgeyi yalnızca analiz durumu prasNoLaterChanges ya da prasAllowed olduğunda ve yapısal bir risk yokken kabul etmelidir; prasIndeterminate ile prasSuspicious'ı, loglanıp geçilecek uyarılar olarak değil güvenilmez saymalıdır. Indeterminate, çözümleyicinin sonraki revizyonların izinli olduğunu kanıtlayamadığı demektir; saldırgan için, kodunuz içeri alıyorsa kararlı biçimde Indeterminate üreten bir girdi, Allowed üreten bir girdi kadar işe yarar. Global AnalyzePadesSignatureRevisions herhangi bir TStream alır ve onu 0 konumundan okur; belgeyi asla render etmesi gerekmeyen upload handler'larına uyar

uses
  Classes, SysUtils, TypInfo, FPdfPades;

const
  // Çift tanımlar, Status düşürülmeden kaydedilir
  BlockingRisks: TPadesRevisionRisks = [prrMalformedRevisionChain,
    prrDuplicateObjectDefinition, prrUnreferencedObjectDefinition,
    prrSignatureObjectRedefined, prrCompressedObjectUnresolved,
    prrResourceLimitExceeded];

function SignedRevisionsAcceptable(const FileName: string;
  out Reason: string): Boolean;
var
  Source: TFileStream;
  Report: TPadesRevisionAnalysisReport;
begin
  Result := False;
  Reason := '';
  Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Report := AnalyzePadesSignatureRevisions(Source);
  finally
    Source.Free;
  end;
  if Report.SignatureCount = 0 then
  begin
    Reason := 'no signature anchors the analysis';
    Exit;
  end;
  if Report.Risks * BlockingRisks <> [] then
  begin
    Reason := 'structural risk in the revision chain';
    Exit;
  end;
  case Report.Status of
    prasNoLaterChanges, prasAllowed:
      Result := True;
  else
    // prasIndeterminate ile prasSuspicious reddediliştir, uyarı değil
    Reason := 'revision status ' +
      GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
  end;
end;

İki sınırı açıkça söylemeye değer. TPadesRevisionAnalysisReport, CMS bütünlüğü ya da sertifika güveni hakkında hiçbir şey söylemez; dolayısıyla bu kapı, kriptografik ve güven doğrulamasının yanında durur, yerine geçmez. Ve doğru bir sahiplik grafiği, P=3'ü her iş akışı için güvenli yapmaz. P=3 annotation'lara gerçekten izin verir ve opak görünümlü bir annotation, tek bir content stream'e dokunmadan imzalı metni kaplayabilir. Sertifikalı belgeleriniz inceleme kopyası değil sözleşmeysa ya P=2 ile sertifikalayın ya da izinli annotation değişikliklerini bir insana yönlendirin; şu yardımcıdaki gibi:

function AllowedAnnotationEditsUnderP3(
  const Report: TPadesRevisionAnalysisReport): Integer;
var
  i, j: Integer;
begin
  Result := 0;
  for i := 0 to High(Report.Signatures) do
    if Report.Signatures[i].DocMdpPermission = 3 then
      for j := 0 to High(Report.Signatures[i].Changes) do
        if (Report.Signatures[i].Changes[j].Kind = prckAnnotation) and
           (Report.Signatures[i].Changes[j].Decision = prdAllowed) then
          Inc(Result);
end;

İmza revizyonu denetim listesi

Bu listeyi, doğrulama hattınızın açıkta kalıp kalmadığını ve artık kapalıya düşüp düşmediğini kontrol için kullanın:

  • v3.126.2 öncesi PDFium Component derlemeleri, DocMDP P=3 belgelerindeki sayfa içerik düzenlemeleri için prasAllowed bildirebiliyordu; eski derlemelerin kabul ettiği sertifikalı P=3 dosyalarında TPdf.AnalyzeSignatureRevisions'i yeniden koşturun
  • Bir field değeri ile bir annotation'ın dolaylı bir object'i paylaşabileceği, FieldMDP kilitli P=3 belgelerini yeniden kontrol edin
  • Yalnızca prasNoLaterChanges ile prasAllowed'ı kabul edin; prasIndeterminate ile prasSuspicious'ı güvenilmez sayın
  • Report.Status'in yanında Report.Risks'i de test edin; çünkü prrDuplicateObjectDefinition tek başına durumu değiştirmez
  • İmza durumu Indeterminate iken boş bir Changes dizisini temiz bir sonuç diye okumayın; başarısız bir rol kurulumu hiç değişiklik bildirmez
  • FieldMDP sorunlarını yakalamak için yalnızca prrFieldMdpUnresolved'e bel bağlamayın; paylaşılan annotation durumu karar ve durum üzerinden görünür
  • P=3 altındaki izinli annotation değişikliklerinin iş akışınızda insan incelemesi gerekip gerekmediğine karar verin
  • Özgün dosya baytlarını analiz edin; SaveAs ile yeniden yazılmış bir belge artık revizyon zincirini içermez

Revizyon analizi, bir imza kontrolünün bir katmanıdır. Sözlük ve baseline düzeyi için PDF dijital imzalarını ve PAdES düzeylerini inceleme ile; JavaScript, launch action'ları ve gömülü dosyalar için daha geniş bir PDF güvenlik riski denetimi ile eşleştirin. TPdf.AnalyzeSignatureRevisions, AnalyzePadesSignatureRevisions ve burada anlatılan sahiplik duyarlı rol grafiği, Delphi, C++Builder ve Lazarus için PDFium Component ile gelir