Odborný článok

DocMDP v PDFium Component: widget /P ukryl úpravy strán

V zostaveniach PDFium Component pred v3.126.2 mohol TPdf.AnalyzeSignatureRevisions ohodnotiť skutočnú úpravu obsahu strany ako povolenú zmenu anotácie pod DocMDP P=3, lebo jeho graf rolí revízií bral spätnú referenciu /P podpisového widgetu na svoju stranu ako vlastníctvo. Od v3.126.2 oddeľuje PDFium Component navigačné hrany od hrán vlastneného payloadu, takže obsah strany ostáva obsahom strany. Bug report za touto opravou vyzerá na papieri neškodne. Certifikovaná zmluva povoľuje anotácie, druhá strana pridá jedno inkrementálne uloženie a analyzátor tvrdí, že každá neskoršia zmena je povolená. Potom niekto dá rozdiel medzi vykreslenými stranami a suma platby na strane 2 je iná

Tento článok je pokračovaním očami útočníka k prehľadu analýzy zmien revízií po podpise, takže vynecháva základy obnovy revízií a ohodnocovania DocMDP a ide priamo na graf objektov: ako bolo vlastníctvo modelované, prečo smer hrany rozhoduje o bezpečnostnom verdikte, čo sa zmenilo vo v3.126.2 a ako audítovať vlastnú akceptačnú logiku

Prečo prešla úprava strany ako zmena anotácie pod DocMDP P=3?

Úprava strany prešla, lebo starý graf rolí nasledoval každú nepriamu referenciu v slovníku, ako keby referencovaný objekt patril referujúcemu, a podpisový widget ukazuje späť na svoju stranu. Anotačný slovník nesie /P, nepriamu referenciu na objekt strany, na ktorej sedí (ISO 32000-1 §12.5.2). Táto položka je navigačná návest. Widget nevlastní stranu; strana vlastní widget cez svoje pole /Annots

Analyzátor priradí každému objektu sadu bitov rolí, než ohodnotí neskoršie zmeny: strana, anotácia, formulár a validačný materiál. Koreňové objekty dostanú rolu z vlastného slovníka a rola sa potom rozšíri na všetko, na čo referujú. V starej propagácii išla reťaz takto:

  1. Podpisový widget je /Subtype /Widget s /FT /Sig, takže dostane rolu anotácie
  2. /P widgetu natlačí rolu anotácie na slovník strany, ktorý už má rolu strany
  3. Strana natlačí obe role do /Contents, /Resources a cez /Parent nahor do stromu Pages a nabok na každú sesterskú stranu
  4. Slovník content streamu ako << /Length 812 >> nemá /Type, takže klasifikátor spadol späť na bity rolí a skontroloval rolu anotácie pred rolou strany
Diagram DocMDP grafu rolí PDFium Component z čias pred v3.126.2, kde spätná referencia /P podpisového widgetu natlačí rolu anotácie na slovník strany, strana ju cez /Contents roznesie na content stream bez položky /Type, klasifikátor vydá prckAnnotation a ohodnotenie P=3 vráti prdAllowed
Pred v3.126.2 bral graf rolí každú nepriamu referenciu ako vlastníctvo, takže položka /P widgetu natlačila rolu anotácie na stranu a skutočná úprava strany odišla z analyzátoru ako povolená zmena anotácie

Pozmenený content stream preto vyšiel ako prckAnnotation. Podľa ISO 32000-1 §12.8.2.2 povoľuje DocMDP P=3 zmeny anotácií, takže rozhodnutie bolo prdAllowed a report sa zroloval do prasAllowed. Ten istý súbor pod P=2 bol odmietnutý, ale len náhodou: P=2 zakazuje zmeny anotácií, takže zle označený stream bol odmietnutý zo zlého dôvodu. Fixná propagácia v štyroch priechodoch pridala druhú slabosť. Payload doručený cez nepriame pole alebo cez dlhý reťaz, ktorého čísla objektov bežia vzad, možno nikdy nedostal žiadnu rolu

Prečo sa musí validátor podpisu pýtať, kto vlastní objekt?

Validátor podpisu sa musí pýtať, kto vlastní objekt, pretože inkrementálne aktualizácie PDF (ISO 32000-1 §7.5.6) dovoľujú komukoľvek pripojiť revíziu, ktorá predefinuje existujúce číslo objektu, a predefinované telo neoznáma, čím je. Podpis stále verifikuje, keďže pokrýva len bajty vlastnej revízie. Každá obrana proti manipulácii po podpise preto závisí od namapovania každého zmeneného objektu na štruktúru, ktorá ho používa, a potom od otázky, či podpisovateľ dovolil tej štruktúre sa zmeniť

Niekoľko publikovaných tried útokov pracuje presne v tej medzere. Útoky inkrementálnym ukladaním pripoja revíziu, ktorá vymení obsah strany, a spoliehajú sa na verifikátor kontrolujúci len podpísaný bajtový rozsah. Shadow útoky zasadia skrytý obsah pred podpisom a po podpise ho aktivujú malou, nevinnou vyzerajúcou zmenou. Útoky na certifikované dokumenty zneužívajú fakt, že P=2 a P=3 výslovne povoľujú niektoré neskoršie úpravy, a potom oblejú zakázanú úpravu do plášťa povolenej. Verifikátor, ktorý klasifikuje objekty podľa návestí ako /Type /Annot alebo podľa ľubovoľnej referenčnej cesty, ktorá k nim náhodou dôjde, je vystavený tretej triede: útočník potrebuje len jednu povolenú štruktúru, ktorá dosiahne tú zakázanú

Preto otázka nie je, ktoré objekty sa zmenili, ale kto ich vlastní. Content stream dosiahnutý zo strany cez /Contents je obsah strany bez ohľadu na to, čo všetko naň ešte ukazuje. Anotácia ukazujúca späť na stranu cez /P hovorí, kde anotácia býva, nie čo vlastní

Ako modeluje PDFium Component v3.126.2 vlastníctvo?

PDFium Component v3.126.2 berie spätné referencie ako navigáciu, drží ich mimo propagácie rolí a rozhoduje, ktoré kľúče sa počítajú ako navigácia, podľa štruktúrnej role slovníka, ktorý ich drží, nie podľa samotného mena kľúča. Tabuľka sumarizuje navigačné kľúče, ktoré už vlastníctvo nenesú

Vlastniaci slovníkKľúče brané ako navigáciaReferencia špecifikácie
Strana alebo uzol Pages/Parent, /Kids, /AnnotsISO 32000-1 §7.7.3
Anotácia alebo widget/PISO 32000-1 §12.5.2
Slovník widgetu alebo poľa/ParentISO 32000-1 §12.7.3
Graf objektov PDFium Component v3.126.2 oddeľujúci hrany vlastneného payloadu ako /Contents a /Annots, ktoré roznášajú roly strany a anotácie, od navigačných hrán ako widget /P, ktoré neniosú žiadne roly, s navigačnými kľúčmi pre každý vlastniaci slovník a prckPageContent udržaným na content streame aj pod sfalovaným /Type
v3.126.2 drží spätné referencie mimo propagácie rolí: role cestujú len cez skutočné vlastníctvo, takže content stream ostáva obsahom strany a /P návest nerozhoduje o ničom

Globálne filtrovanie podľa mena kľúča by vyrobilo novú dieru. Zdroj písma alebo XObject môže legitímne niesť meno /P, /Parent alebo /Annots a slovník /Resources, ktorý by vypustil svoju položku /P z propagácie, by útočníkovi dovolil skryť strane vlastnený XObject za nevinným menom zdroja. Vo v3.126.2 sa navigačný filter aplikuje len vtedy, keď vlastniaci slovník skutočne je strana, uzol Pages, anotácia, widget alebo pole. Ak jeden z týchto slovníkov nesie zdvojený navigačný kľúč, ako dve položky /P vo widgete, analyzátor nehádže, ktorú kópiu by použil prehliadač; stavba rolí zlyhá a podpis sa stane Indeterminate

Niekoľko ďalších pravidiel zatvára zostávajúce cesty preznačovania:

  • Uzly Pages sú koreňmi roly strany samy o sebe, takže zdroje zdedené zo stromu Pages (ISO 32000-1 §7.7.3.4) vstupujú do kontextu strany cez skutočné vlastníctvo, nie cez prechádzanie /Parent z detskej strany
  • Rola anotácie alebo formulára dorazivšia do katalógu, uzla Pages, strany, anotácie alebo slovníka poľa sa tam zastaví, lebo tieto štruktúrne objekty si stanovujú vlastné roly a prichádzajúca rola payloadu ich nesmie prebiť
  • Rola strany je autoritatívna počas klasifikácie: objekt vlastnený stranou je prckPageContent, aj keď neskoršia revízia ho prepíše sfalovaným /FT, návestím /Type /Annot alebo ho podelí s appearance streamom
  • Form XObject použitý len ako appearance poľa alebo anotácie si drží kategóriu formulára či anotácie, takže obyčajná regenerácia appearance po vyplnení formulára sa stále ohodnocuje pod normálnymi pravidlami povolení
  • Widget bez vlastného /FT rozlíši zdedený typ poľa cez reťaz /Parent a nerozlíšiteľný reťaz zlyhá pri stavbe rolí namiesto predvolenia anotácie
  • Bity rolí z každej neskoršej revízie sa zlúčia do rolí pokrytej revízie, takže neskoršia aktualizácia nemôže vymazať skorší vzťah vlastníctva strany tým, že najprv stream odpojí a potom ho upraví

Fixný bod namiesto fixného počtu priechodov

Dostupnosť rolí vo v3.126.2 beží ako pracovná fronta, ktorá iteruje, kým žiadny objekt nezíska nový bit roly, čo je pravý fixný bod bez ohľadu na hĺbku reťazca alebo číslovanie objektov. Nepriame polia ako pole /Contents uložené ako vlastný objekt sa prechádzajú tiež. Každý objekt môže získať nanajvýš štyri rôzne bity rolí, takže fronta je ohraničená na štyri položky na číslo objektu; jej prekročenie vyhodí prrResourceLimitExceeded. Referencia na voľný objekt, nezhoda generácie alebo pokazená hlavička objektu vyhodí prrMalformedRevisionChain a payload vo vnútri komprimovaného object streamu vyhodí prrCompressedObjectUnresolved. Každé z týchto zlyhaní končí prasIndeterminate, nikdy povoleným verdiktom, a keď zlyhanie nastane počas stavby rolí pokrytej revízie, podpis nenahlási žiadne Changes vôbec

Nasledujúca rutina vypíše úpravy obsahu strany, ktoré prežijú túto analýzu. Zmena prckPageContent sa nikdy neohodnotí ako prdAllowed: DocMDP P=1, 2 alebo 3 z nej spraví prdDisallowed a podpis bez DocMDP ju ohodnotí prdSuspicious

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;   // záznam, nič na uvoľnenie
    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;

Čo sa stane, keď FieldMDP a anotácia zdieľajú jeden objekt?

Keď /V poľa a /Contents anotácie ukazujú na ten istý nepriamy objekt, drží v3.126.2 zámok FieldMDP v sile, aj keď je zmena klasifikovaná ako úprava anotácie. Scenár sa dá ľahko postaviť ručne: podpisovateľ zamkne pole Total cez FieldMDP (ISO 32000-1 §12.8.2.4) a útočník nechá /Contents textovej anotácie referovať ten istý reťazcový objekt, ktorý drží hodnotu poľa. Pod P=3 je úprava anotácie povolená, takže pred opravou prepísanie tohto zdieľaného reťazca zmenilo zamknutú hodnotu poľa s povoleným verdiktom

Objekt teraz nesie obe role, anotáciu aj formulár, a rozhodnutie o anotácii znovu skontroluje formulárovú stranu vždy, keď má podpis transformáciu FieldMDP:

  • Pod P=2 je zmena anotácie odmietnutá rovno, presne ako predtým
  • S FieldMDP All je zamknuté každé pole, takže zdieľaná zmena je prdDisallowed
  • S FieldMDP Include alebo Exclude nedokáže analyzátor vystopovať zdieľaný skalár späť k jednému menu poľa, takže rozhodnutie je prdIndeterminate a nie odhad
  • Bez FieldMDP sa aplikuje pravidlo anotácie P=3 a zmena ostáva povolená
Diagram rozhodovania FieldMDP v PDFium Component, kde zamknuté pole Total /V a anotácia /Contents referujú ten istý nepriamy objekt a vetvenie nad DocMDP P=2, FieldMDP All, FieldMDP Include alebo Exclude a žiadnym FieldMDP vedie k verdiktom prdDisallowed, prdIndeterminate alebo prdAllowed pre tú istú zdieľanú zmenu
Keď jeden nepriamy objekt nesie rolu anotácie aj formulára, rozhodnutie o anotácii znovu skontroluje zámok FieldMDP, takže tá istá zmena sa pohybuje od povolenej cez zakázanú po neurčitú

Jeden reportovací detail má význam pre gate kód. Zdieľaný prípad sa reportuje ako Kind = prckAnnotation s Decision = prdIndeterminate a prrFieldMdpUnresolved sa do množiny rizík pridáva len pri zmenách klasifikovaných ako formulárové polia. Gate, ktorý hľadá prrFieldMdpUnresolved a ignoruje Status, tento prípad úplne minie

Ako má Delphi kód zlyhávať bezpečne pri analýze revízií?

Delphi kód má prijať podpísaný dokument len vtedy, keď je stav analýzy prasNoLaterChanges alebo prasAllowed a nie je prítomné žiadne štruktúrne riziko, a prasIndeterminate a prasSuspicious má brať ako nedôveryhodné, nie ako varovania na zalogovanie a pustenie. Indeterminate znamená, že analyzátor nedokázal dokázať, že neskoršie revízie boli povolené; pre útočníka je vstup, ktorý spoľahlivo vyrába Indeterminate, rovnako užitočný ako taký, čo vyrába Allowed, ak ho váš kód pustí. Globálne AnalyzePadesSignatureRevisions berie ľubovoľný TStream a číta ho od pozície 0, čo sedí upload handlerom, ktoré nikdy nemusia dokument vykresliť

uses
  Classes, SysUtils, TypInfo, FPdfPades;

const
  // Zdvojené definície sa zaznamenajú bez zhoršenia Status
  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 a prasSuspicious sú odmietnutia, nie varovania
    Reason := 'revision status ' +
      GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
  end;
end;

Dve hranice stoja za povedať nahlas. TPadesRevisionAnalysisReport nič nehovorí o integrite CMS ani o dôveryhodnosti certifikátov, takže táto brána sedí vedľa kryptografickej a trust validácie, nie na ich miesto. A správny graf vlastníctva nerobí P=3 bezpečným pre každý workflow. P=3 anotácie naozaj povoľuje a anotácia s nepriehľadným appearance môže zakryť podpísaný text bez toho, aby sa dotkla jediného content streamu. Ak sú vaše certifikované dokumenty zmluvy a nie kópie na recenziu, certifikujte buď s P=2 alebo púšťajte povolené zmeny anotácií na človeka, ako v tomto pomocníkovi:

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;

Kontrolný zoznam auditu revízií podpisu

Použite tento zoznam na kontrolu, či vaša verifikačná pipeline bola vystavená a či teraz zlyháva bezpečne:

  • Zostavenia PDFium Component pred v3.126.2 mohli reportovať prasAllowed pre úpravy obsahu strany v dokumentoch DocMDP P=3; pustite TPdf.AnalyzeSignatureRevisions znova na certifikované P=3 súbory, ktoré staršie zostavenia prijali
  • Znova skontrolujte dokumenty P=3 so zámkami FieldMDP, kde hodnota poľa a anotácia môžu zdieľať nepriamy objekt
  • Prijímajte len prasNoLaterChanges a prasAllowed; prasIndeterminate a prasSuspicious berte ako nedôveryhodné
  • Testujte Report.Risks ako aj Report.Status, lebo prrDuplicateObjectDefinition nemení status sám o sebe
  • Nečítajte prázdne pole Changes ako čistý výsledok, keď je status podpisu Indeterminate; zlyhaná stavba rolí nenahlási žiadne zmeny
  • Nespoliehajte sa len na prrFieldMdpUnresolved pri odchytávaní problémov FieldMDP, keďže zdieľaný anotačný prípad vychádza na povrch len cez rozhodnutie a status
  • Rozhodnite, či povolené zmeny anotácií pod P=3 potrebujú vo vašom workflow ľudskú kontrolu
  • Analyzujte pôvodné bajty súboru; dokument prepísaný cez SaveAs už reťaz revízií neobsahuje

Analýza revízií je jedna vrstva kontroly podpisu. Skombinujte ju s preskúmaním digitálnych podpisov PDF a úrovní PAdES pre slovníky a baseline úrovne a so širším audítom bezpečnostných rizík PDF pre JavaScript, launch akcie a vložené súbory. TPdf.AnalyzeSignatureRevisions, AnalyzePadesSignatureRevisions a graf rolí zohľadňujúci vlastníctvo popísaný tu prichádzajú v PDFium Component pre Delphi, C++Builder a Lazarus