Műszaki cikk

PDFium Component DocMDP: widget /P, ami módosítást rejtett

A v3.126.2 előtti PDFium Component buildekben a TPdf.AnalyzeSignatureRevisions egy valódi oldaltartalom-szerkesztést megengedett annotáció-módosításként osztályozhatott DocMDP P=3 alatt, mert a revíziószerepek gráfja az aláírás widgetjének a /P visszahivatkozását az oldalára tulajdonlásként kezelte. v3.126.2 óta a PDFium Component szétválasztja a navigációs éleket a tulajdonolt teher éleitől, így az oldaltartalom oldaltartalom marad. A javítás mögötti hibajelentés papíron ártalmatlannak néz ki. Egy tanúsított szerződés annotációkat enged, a partner hozzáfűz egy inkrementális mentést, és az analizátor azt mondja, minden későbbi változás megengedett. Aztán valaki diffeli a renderelt oldalakat, és a 2. oldal fizetési összege más

Ez a cikk a aláírás utáni revízióváltozás-elemzés áttekintésének támadói szemű folytatása, tehát kihagyja a revízió-újjáépítés és a DocMDP-osztályozás alapjait, és egyenesen az objektumgráfra ugrik: hogyan volt modellezve a tulajdonlás, miért dönt egy él iránya biztonsági ítéletet, mi változott v3.126.2-ben, és hogyan auditáld a saját elfogadási logikádat

Miért ment át oldalszerkesztés annotáció-módosításként DocMDP P=3 alatt?

Az oldalszerkesztés azért ment át, mert a régi szerepgráf a szótárban minden indirekt hivatkozást követett, mintha a hivatkozott objektum a hivatkozóhoz tartozna, és az aláírás widget visszamutat az oldalára. Egy annotációszótár /P-t hordoz, indirekt hivatkozást arra az oldalobjektumra, amin ül (ISO 32000-1 §12.5.2). Az a bejegyzés navigációs tipp. A widget nem birtokolja az oldalt; az oldal birtokolja a widgetet a /Annots tömbjén át

Az analizátor minden objektumnak szerepbit-halmazt rendel, mielőtt osztályozná a későbbi változásokat: oldalt, annotációt, formot és validációs anyagot. A gyökérobjektumok a szerepüket a saját szótárukból kapják, és a szerep aztán mindenhova szétterül, amire hivatkoznak. A régi terjesztésben a lánc így ment:

  1. Az aláírás widget egy /Subtype /Widget /FT /Sig-gal, tehát megkapja az annotációszerepet
  2. A widget /P-je az annotációszerepet az oldalszótárra tolja, ami már amúgy is az oldalszerepet hordozza
  3. Az oldal mindkét szerepet a /Contents-ba és a /Resources-ba tolja, és a /Parent-en át felfelé a Pages fába, át minden testvéroldalra
  4. Egy << /Length 812 >> szerű tartalomfolyam-szótárnak nincs /Type-a, így az osztályozó a szerepbitekre esett vissza, és az annotációszerepet nézte az oldalszerep előtt
PDFium Component ábra a v3.126.2 előtti DocMDP szerepgráfról, ahol egy aláírás widget /P visszahivatkozása az annotációszerepet az oldalszótárra tolja, az oldal a /Contents-ön át egy /Type bejegyzés nélküli tartalomfolyamra terjeszti, az osztályozó prckAnnotation-t ad ki, és a P=3 osztályozás prdAllowed-ot ad vissza
v3.126.2 előtt a szerepgráf minden indirekt hivatkozást tulajdonlásként kezelt, így a widget /P bejegyzése az annotációszerepet az oldalra tolta, és egy valódi oldalszerkesztés megengedett annotáció-módosításként hagyta el az analizátort

A módosított tartalomfolyam tehát prckAnnotation-ként jött ki. Az ISO 32000-1 §12.8.2.2 alatt a DocMDP P=3 megengedi az annotációváltozásokat, így a döntés prdAllowed volt, és a riport prasAllowed-ig gördült fel. Ugyanez a fájl P=2 alatt elutasítást kapott, de csak véletlenül: a P=2 tiltja az annotációváltozásokat, tehát a félrecímkézett stream rossz okból utasíttatott el. Egy rögzített négymenates terjesztőciklus második gyengeséget is adott. Az indirekt tömbön át, vagy egy hosszú, visszafelé számozott objektumszámú láncon át elért teher akár egyáltalán nem kapott szerepet

Miért kell egy aláírásvalidátornak kérdeznie, ki birtokol egy objektumot?

Egy aláírásvalidátornak azért kell kérdeznie, ki birtokol egy objektumot, mert a PDF inkrementális frissítései (ISO 32000-1 §7.5.6) bárkinek megengedik, hogy olyan revíziót fűzzön hozzá, ami újradefiniál egy meglévő objektumszámot, és az újradefiniált test nem jelenti be, mi ő. Az aláírás továbbra is ellenőriz, mert csak a saját revíziója bájtjait fedi. Minden védekezés az aláírás utáni manipuláció ellen ezért azon múlik, hogy minden megváltozott objektumot arra a struktúrára képez le, ami használja, aztán megkérdezi, engedte-e az aláíró, hogy az a struktúra változzon

Több publikált támadásosztály pontosan ebben a résben dolgozik. Az inkrementális mentéses támadások olyan revíziót fűznek hozzá, ami kicseréli az oldaltartalmat, és arra számítanak, hogy a verifikáló csak az aláírt bájtartományt nézi. A shadow attackek aláírás előtt ültetnek el rejtett tartalmat, és utána aktiválják egy kis, ártatlannak néző változtatással. A tanúsított dokumentumok elleni támadások azt használják ki, hogy a P=2 és P=3 expliciten megenged néhány későbbi szerkesztést, aztán tiltott szerkesztést öltöztetnek megengedettnek. Egy verifikáló, ami /Type /Annot szerű címkék alapján osztályoz objektumokat, vagy bármely, véletlenül odáig érő hivatkozási út alapján, ki van téve a harmadik osztálynak: a támadónak csak egy megengedett struktúrára van szüksége, ami eléri a tiltottat

Ezért nem az a kérdés, mely objektumok változtak, hanem ki birtokolja őket. Egy tartalomfolyam, ami egy oldalból /Contents-en át érhető el, oldaltartalom, bármi más is mutat rá. Egy annotáció, ami /P-n át mutat vissza az oldalra, azt mondja meg, hol lakik az annotáció, nem azt, mit birtokol

Hogyan modellezi a tulajdonlást a PDFium Component v3.126.2?

A PDFium Component v3.126.2 a visszahivatkozásokat navigációként kezeli, kizárja őket a szerepterjesztésből, és arról dönt, mely kulcsok számítanak navigációnak, az őket tároló szótár szerkezeti szerepéből, nem a kulcsnévből önmagában. A táblázat összefoglalja azokat a navigációs kulcsokat, amik többé nem hordoznak tulajdonlást

Tulajdonos szótárNavigációként kezelt kulcsokSpec hivatkozás
Page vagy Pages csomópont/Parent, /Kids, /AnnotsISO 32000-1 §7.7.3
Annotáció vagy widget/PISO 32000-1 §12.5.2
Widget vagy mező szótár/ParentISO 32000-1 §12.7.3
PDFium Component v3.126.2 objektumgráf, ami szétválasztja a tulajdonolt teher éleit, mint a /Contents és /Annots, amik az oldal és annotációszerepet terjesztik, a navigációs éleitől, mint a widget /P, amik szerepet nem hordoznak, a navigációs kulcsokkal tulajdonos szótáronként, és a prckPageContent a tartalomfolyamon marad hamisított /Type alatt is
v3.126.2 a visszahivatkozásokat kizárja a szerepterjesztésből: a szerepek csak valódi tulajdonláson át utaznak, így a tartalomfolyam oldaltartalom marad, a /P tipp pedig semmit sem dönt

A kulcsnév szerinti globális szűrés új lyukat vájt volna. Egy font vagy XObject erőforrás legit módon hordozhat /P-t, /Parent-et vagy /Annots-ot, és egy /Resources szótár, ami a /P bejegyzését kihagyná a terjesztésből, lehetővé tenné, hogy a támadó egy oldal által birtokolt XObjectet rejtsen el ártatlannak néző erőforrásnév mögé. v3.126.2-ben a navigációs szűrő csak akkor él, ha a tulajdonos szótár tényleg page, Pages csomópont, annotáció, widget vagy mező. Ha valamelyik ilyen szótár duplikált navigációs kulcsot hordoz, például két /P bejegyzést egy widgetben, az analizátor nem találgatja, melyik példányt használná egy nézegető; a szerepépítés elbukik, és az aláírás Indeterminate lesz

További szabályok zárják le a maradék újracímkézési utakat:

  • A Pages csomópontok önmagukban oldalszerepű gyökerek, tehát a Pages fából örökölt erőforrások (ISO 32000-1 §7.7.3.4) valódi tulajdonláson át érkeznek oldal-kontextusba, nem gyerekoldalból indult /Parent bejáráson át
  • Egy katalógusba, Pages csomópontba, oldalba, annotációba vagy mezőszótárba érkező annotáció- vagy formaszerep ott megáll, mert azok a szerkezeti objektumok maguk állapítják meg a szerepüket, és egy beérkező teher szerepe nem írhatja felül őket
  • Az oldalszerep az osztályozás alatt mérvadó: egy oldal által birtokolt objektum prckPageContent, akkor is, ha egy későbbi revízió hamisított /FT-vel, /Type /Annot címkével írja át, vagy megosztja egy appearance streammel
  • Egy csak mező- vagy annotáció-appearanceként használt Form XObject megtartja a form vagy annotáció kategóriáját, így egy űrlapkitöltés utáni rendes appearance-újragenerálás továbbra is a rendes engedélyszabályok alatt osztályozódik
  • Egy saját /FT nélküli widget az örökölt mezőtípust a /Parent láncon oldja fel, és feloldhatatlan lánc a szerepépítést buktatja meg, annotáció-alapérték helyett
  • Minden későbbi revízió szerepbitjei összeolvadnak a lefedett revízió szerepeivel, így egy későbbi frissítés nem törölhet el egy korábbi oldal-tulajdonlási viszonyt azzal, hogy előbb lecsatol egy streamet, aztán szerkeszti

Fixpont rögzített menetszám helyett

A szerepelérhetőség v3.126.2-ben munkasorként fut, ami addig iterál, amíg egyetlen objektum sem kap új szerepbitet, ami valódi fixpont, lánchossztól és objektumszámozástól függetlenül. Az indirekt tömbök, mint a saját objektumként tárolt /Contents tömb, szintén bejárandók. Minden objektum legfeljebb négy különböző szerepbitet kaphat, tehát a sor objektumszámonként négy bejegyzésnél van korlátozva; a túllépés prrResourceLimitExceeded-et dob. Egy szabad objektumra mutató hivatkozás, generációs eltérés vagy törött objektumfej prrMalformedRevisionChain-et dob, a tömörített objektumstreamen belüli teher pedig prrCompressedObjectUnresolved-et. Mindegyik ilyen bukás prasIndeterminate-tel ér véget, soha nem megengedett ítélettel, és amikor a bukás a lefedett revízió szerepeinek építése közben történik, az aláírás egyáltalán nem jelent Changes-t

Az alábbi rutin felsorolja azokat az oldaltartalom-szerkesztéseket, amik túlélik ezt az elemzést. Egy prckPageContent változás soha nem osztályozódik prdAllowed-ként: a DocMDP P=1, 2 vagy 3 prdDisallowed-dé teszi, egy DocMDP nélküli aláírás pedig prdSuspicious-ként osztályozza

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;   // rekord, nincs mit felszabadítani
    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;

Mi történik, amikor FieldMDP és egy annotáció ugyanazt az objektumot osztja meg?

Amikor egy mező /V-je és egy annotáció /Contents-e ugyanarra az indirekt objektumra mutat, a v3.126.2 megtartja a FieldMDP zárat, akkor is, ha a változás annotáció-szerkesztésként osztályozódik. A forgatókönyv kézzel könnyen megépíthető: az aláíró FieldMDP-vel zárolja a Total mezőt (ISO 32000-1 §12.8.2.4), a támadó pedig egy szöveges annotáció /Contents-e ugyanarra a string objektumra mutat, ami a mezőértéket tartja. P=3 alatt az annotáció-szerkesztés megengedett, tehát a javítás előtt az a megosztott string átírása zárolt mezőértéket változtatott megengedett ítélettel

Az objektum mostantól mind az annotáció-, mind a formszerepet hordozza, és az annotációdöntés újraellenőrzi a form oldalt, valahányszor az aláírásnak van FieldMDP transzformációja:

  • P=2 alatt az annotációváltozás egy csapásra tiltott, pontosan mint korábban
  • FieldMDP All-nal minden mező zárolt, tehát a megosztott változás prdDisallowed
  • FieldMDP Include vagy Exclude mellett az analizátor nem tudja visszavezetni egy megosztott skalárt egyetlen mezőnévig, tehát a döntés prdIndeterminate, találgatás helyett
  • FieldMDP nélkül a P=3 annotációszabály érvényesül, és a változás megengedett marad
PDFium Component FieldMDP döntési ábra, ahol egy zárolt Total mező /V-je és egy annotáció /Contents-e ugyanarra az indirekt objektumra mutat, elágazva DocMDP P=2, FieldMDP All, FieldMDP Include vagy Exclude meg FieldMDP nélkül felett prdDisallowed, prdIndeterminate vagy prdAllowed ítéletekre ugyanarra a megosztott szerkesztésre
Amikor egy indirekt objektum mind az annotáció-, mind a formszerepet hordozza, az annotációdöntés újraellenőrzi a FieldMDP zárat, így ugyanaz a szerkesztés megengedettől tiltotton át határozatlanig terjedhet

Egy riportrészlet számít a kapukódnak. A megosztott eset Kind = prckAnnotation-ként jelentkezik Decision = prdIndeterminate-del, és a prrFieldMdpUnresolved csak formmezőként osztályozott változásokra kerül a kockázathalmazba. Egy kapu, ami prrFieldMdpUnresolved-ot keres és ignorálja a Status-t, ezt az esetet teljesen elvéti

Hogyan bukjon el biztonságosan a Delphi kód a revízióelemzésen?

A Delphi kód csak akkor fogadjon el egy aláírt dokumentumot, ha az elemzési státusz prasNoLaterChanges vagy prasAllowed, és nincs szerkezeti kockázat, a prasIndeterminate-et és prasSuspicious-ot pedig nem megbízhatónak kezelje, nem naplózandó figyelmeztetésnek, amit átenged. Az Indeterminate azt jelenti, hogy az analizátor nem tudta bizonyítani, hogy a későbbi revíziók megengedettek; egy támadónak olyan bemenet, ami megbízhatóan Indeterminate-et produkál, épp olyan hasznos, mint ami Allowed-ot, ha a kódod átengedi. A globális AnalyzePadesSignatureRevisions bármely TStream-et átvesz, és 0 pozíciótól olvassa, ami kényelmes azoknak a feltöltéskezelőknek, amiknek soha nem kell renderelni a dokumentumot

uses
  Classes, SysUtils, TypInfo, FPdfPades;

const
  // A duplikált definíciók feljegyződnek Status lefokozása nélkül
  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
    // a prasIndeterminate és prasSuspicious elutasítás, nem figyelmeztetés
    Reason := 'revision status ' +
      GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
  end;
end;

Két határt érdemes fitty nélkül kimondani. A TPadesRevisionAnalysisReport a CMS integritásról és tanúsítványbizalomról semmit sem mond, tehát ez a kapu a kriptográfiai meg bizalmi validálás mellett ül, nem a helyükben. És egy helyes tulajdonlásgráf nem teszi a P=3-at biztonságossá minden munkafolyamatra. A P=3 őszintén megengedi az annotációkat, és egy átláthatatlan appearanceű annotáció aláírt szöveget fedhet le anélkül, hogy egyetlen tartalomfolyamot is érintene. Ha a tanúsított dokumentumaid szerződések, nem korrektúra-példányok, vagy tanúsíts P=2-vel, vagy az engedett annotációváltozásokat irányítsd emberhez, mint ebben a helperben:

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;

Aláírásrevízió audit ellenőrzőlista

Ezzel a listával nézd meg, hogy a verifikációs pipeline-od ki volt-e téve, és hogy most már biztonságosan bukik-e:

  • A v3.126.2 előtti PDFium Component buildek prasAllowed-ot jelenthettek oldaltartalom-szerkesztésekre DocMDP P=3 dokumentumokban; futtasd újra a TPdf.AnalyzeSignatureRevisions-t az öregebb buildek által elfogadott tanúsított P=3 fájlokon
  • Nézd át újra a FieldMDP záras P=3 dokumentumokat, ahol mezőérték és annotáció megoszthatnak egy indirekt objektumot
  • Csak prasNoLaterChanges-t és prasAllowed-ot fogadj el; a prasIndeterminate-et és prasSuspicious-ot kezeld nem megbízhatónak
  • Teszteld a Report.Risks-t is, nem csak a Report.Status-t, mert a prrDuplicateObjectDefinition önmagában nem változtatja a státuszt
  • Ne olvasd üres Changes tömböt tiszta eredményként, amikor az aláírás státusza Indeterminate; egy elbukott szerepépítés egyáltalán nem jelent változásokat
  • Ne hagyatkozz egyedül a prrFieldMdpUnresolved-ra FieldMDP problémák elkapására, mert a megosztott annotációs eset csak a döntésen és a státuszon át bukik felszínre
  • Döntsd el, hogy a P=3 alatti megengedett annotációváltozásokra kell-e emberi átnézés a munkafolyamatodban
  • Az eredeti fájl bájtjait elemezd; a SaveAs-sel átírt dokumentum többé nem tartalmazza a revízióláncot

A revízióelemzés egy rétege az aláírásellenőrzésnek. Pározd a PDF digitális aláírások és PAdES szintek vizsgálatával a szótár és baseline szintre, meg egy tágabb PDF biztonsági kockázati auditaval JavaScriptre, launch akciókra és beágyazott fájlokra. A TPdf.AnalyzeSignatureRevisions, az AnalyzePadesSignatureRevisions és az itt leírt tulajdonlástudatos szerepgráf a PDFium Component-ban szállít Delphihez, C++Builderhez és Lazarushoz