Tekninen artikkeli

PDFium Component DocMDP: miten /P piilotti sivumuokkaukset

PDFium Component -koontiversioissa ennen v3.126.2 TPdf.AnalyzeSignatureRevisions saattoi arvostella aidon sivusisällön muokkauksen sallituksi annotaatiomuutokseksi DocMDP P=3:n alla, koska sen revisiorooligraafi käsitteli allekirjoituswidgetin /P-takaviittausta sivuunsa omistajuutena. Versiosta v3.126.2 alkaen PDFium Component erottaa navigointireunat omistetun hyötykuorman reunoista, joten sivusisältö pysyy sivusisältönä. Korjauksen takana oleva bugiraportti näyttää paperilla harmittomalta. Sertifioitu sopimus sallii annotaatiot, vastapuoli lisää yhden inkrementaalisen tallennuksen, ja analysaattori sanoo jokaisen myöhemmän muutoksen olevan sallittu. Sitten joku vertaa renderöityjä sivuja, ja sivun 2 maksusumma on eri

Tämä artikkeli on hyökkääjän silmin kirjoitettu jatko-osa yleiskatsaukseen revision muutosten analysointi allekirjoituksen jälkeen, joten se ohittaa revisioiden rakentamisen ja DocMDP-arvostelun perusteet ja menee suoraan oliograafiin: miten omistajuutta mallinnettiin, miksi reunan suunta päättää turvallisuustuomion, mitä v3.126.2 muutti ja miten auditoidaan omaa hyväksymislogiikkaasi

Miksi sivumuokkaus pääsi läpi annotaatiomuutoksena DocMDP P=3:n alla?

Sivumuokkaus pääsi läpi, koska vanha rooligraafi seurasi jokaista sanakirjan epäsuoraa viittausta ikään kuin viitattu objekti kuuluisi viittaajalle, ja allekirjoituswidget osoittaa takaisin sivuunsa. Annotaation sanakirja kantaa /P-tietuetta, epäsuoraa viittausta siihen sivuobjektiin, jolla se istuu (ISO 32000-1 §12.5.2). Kyseinen tietue on navigointivihje. Widget ei omista sivua; sivu omistaa widgetin /Annots-taulukkonsa kautta

Analysaattori osoittaa jokaiselle objektille roolibittien joukon ennen kuin arvostelee myöhemmät muutokset: sivu, annotaatio, lomake ja validointiaineisto. Juuriobjektit saavat roolinsa omasta sanakirjastaan, ja rooli leviää sitten kaikkeen, mihin ne viittaavat. Vanhassa propagaatiossa ketju meni näin:

  1. Allekirjoituswidget on /Subtype /Widget, jolla on /FT /Sig, joten se saa annotaation roolin
  2. Widgetin /P työntää annotaation roolin sivun sanakirjaan, jolla on jo sivun rooli
  3. Sivu työntää molemmat roolit kohteisiin /Contents ja /Resources ja /Parent:n kautta ylös Pages-puuhun ja sivuille jokaiselle sisarsivulle
  4. Sisältöstreamin sanakirja kuten << /Length 812 >> ei kanna /Type-tietuetta, joten luokittelija palasi roolibitteihin ja tarkisti annotaation roolin ennen sivun roolia
PDFium Component -kaavio DocMDP-rooligraafista ennen v3.126.2, jossa allekirjoituswidgetin /P-takaviittaus työntää annotaation roolin sivun sanakirjaan, sivu levittää sen /Contentsin kautta sisältöstreamiin, jossa ei ole /Type-tietuetta, luokittelija tulostaa prckAnnotationin ja P=3-arvostelu palauttaa prdAllowedin
Ennen v3.126.2 rooligraafi käsitteli jokaista epäsuoraa viittausta omistajuutena, joten widgetin /P-tietue työnsi annotaation roolin sivulle, ja aito sivumuokkaus poistui analysaattorista sallittuna annotaatiomuutoksena

Muokattu sisältöstream tuli siksi ulos muodossa prckAnnotation. Kohdan ISO 32000-1 §12.8.2.2 mukaan DocMDP P=3 sallii annotaatiomuutokset, joten päätös oli prdAllowed ja raportti koottiin muotoon prasAllowed. Sama tiedosto P=2:n alla hylättiin, mutta vain sattumalta: P=2 kieltää annotaatiomuutokset, joten väärin luokiteltu stream torjuttiin väärästä syystä. Kiinteä neljä kierroksen propagaatiosilmukka lisäsi toisen heikkouden. Hyötykuorma, joka saavutettiin epäsuoran taulukon kautta tai pitkän ketjun kautta, jonka objektinumerot kulkevat taaksepäin, ei ehkä koskaan saanut mitään roolia lainkaan

Miksi allekirjoituksen validoijan on kysyttävä, kuka omistaa objektin?

Allekirjoituksen validoijan on kysyttävä, kuka omistaa objektin, koska PDF:n inkrementaaliset päivitykset (ISO 32000-1 §7.5.6) antavat kenelle tahansa luvan liittää revisio, joka määrittelee olemassa olevan objektinumeron uudelleen, eikä uudelleen määritelty runko ilmoita, mitä se on. Allekirjoitus vahvistuu yhä, sillä se kattaa vain oman revisionsa tavut. Jokainen puolustus allekirjoituksen jälkeistä manipulointia vastaan riippuu siksi siitä, että jokainen muutettu objekti kartoitetaan rakenteeseen, joka sitä käyttää, ja kysytään sitten, salliko allekirjoittajan kyseisen rakenteen muuttamisen

Useat julkaistut hyökkäysluokat toimivat juuri tuossa raossa. Inkrementaalisten tallennusten hyökkäykset liittävät revision, joka vaihtaa sivusisällön, ja nojaavat siihen, että validoija tarkistaa vain allekirjoitetun tavualueen. Varjohyökkäykset istuttavat piilotettua sisältöä ennen allekirjoitusta ja aktivoivat sen jälkeen pienellä, viattomalta näyttävällä muutoksella. Sertifioitujen asiakirjojen hyökkäykset hyväksikäyttävät sitä, että P=2 ja P=3 sallivat eksplisiittisesti jotkin myöhemmät muokkaukset, ja pukevat sitten kielletyn muokkauksen sallitun asuun. Validoija, joka luokittelee objektit sellaisilla nimikkeillä kuin /Type /Annot tai millä tahansa viittauspolulla, joka sattuu niihin ulottumaan, on altis kolmannelle luokalle: hyökkääjän tarvitsee vain yksi sallittu rakenne, joka ulottuu kiellettyyn

Siksi kysymys ei ole siitä, mitkä objektit muuttuivat, vaan siitä, kuka niitä omistaa. Sivusta /Contentsin kautta saavutettava sisältöstream on sivusisältöä riippumatta siitä, mitä muuta siihen osoittaa. Annotaatio, joka osoittaa takaisin sivuun /P:n kautta, kertoo, missä annotaatio asuu, ei sitä, mitä se omistaa

Miten PDFium Component v3.126.2 mallintaa omistajuuden?

PDFium Component v3.126.2 käsittelee takaviittauksia navigointina, pitää ne roolipropagaation ulkopuolella ja päättää siitä sanakirjan rakenteellisen roolin perusteella, joka niitä kantaa, mitkä avaimet lasketaan navigoinniksi, ei pelkän avaimen nimen perusteella. Taulukko tiivistää navigointiavaimet, jotka eivät enää kanna omistajuutta

Omistava sanakirjaNavigointina käsiteltävät avaimetSpesifikaatioviittaus
Sivu- tai Pages-solmu/Parent, /Kids, /AnnotsISO 32000-1 §7.7.3
Annotaatio tai widget/PISO 32000-1 §12.5.2
Widget- tai kenttäsankirja/ParentISO 32000-1 §12.7.3
PDFium Component v3.126.2 -oliograafi, joka erottaa omistetun hyötykuorman reunat, kuten /Contents ja /Annots, jotka levittävät sivun ja annotaation roolit, navigointireunoista, kuten widgetin /P, jotka eivät kanna rooleja, navigointiavaimineen omistajasanakirjaa kohden, ja prckPageContent säilyy sisältöstreamissa myös väärennetyn /Type:n alla
v3.126.2 pitää takaviittaukset roolipropagaation ulkopuolella: roolit kulkevat vain todellisen omistajuuden kautta, joten sisältöstream pysyy sivusisältönä eikä /P-vihje päätä mitään

Suodattaminen avaimen nimen perusteella globaalisti olisi luonut uuden reiän. Fontti- tai XObject-resurssi voi laillisesti olla nimeltään /P, /Parent tai /Annots, ja /Resources-sanakirja, joka pudottaisi /P-tietueensa propagaatiosta, antaisi hyökkääjän piilottaa sivun omistaman XObjectin viattoman resurssinimen taakse. Versiossa v3.126.2 navigointisuodatin soveltuu vain, kun omistava sanakirja oikeasti on sivu, Pages-solmu, annotaatio, widget tai kenttä. Jos jokin kyseisistä sanakirjoista kantaa toistettua navigointiavainta, kuten kahta /P-tietuetta widgetissa, analysaattori ei arvaa, kumpaa kopiota katselinohjelma käyttäisi; roolin rakentaminen epäonnistuu ja allekirjoituksesta tulee Epävarma

Useat lisäsäännöt sulkevat jäljellä olevat uudelleennimeämisreitit:

  • Pages-solmut ovat sivuroolin juuria omalla oikeudellaan, joten Pages-puusta perityt resurssit (ISO 32000-1 §7.7.3.4) tulevat sivukontekstiin todellisen omistajuuden kautta, ei lapsisivun /Parent-kävelyn kautta
  • Annotaation tai lomakkeen rooli, joka saapuu katalogiin, Pages-solmuun, sivuun, annotaatioon tai kenttäsankirjaan, pysähtyy siihen, koska kyseiset rakenteelliset objektit perustavat omat roolinsa, eikä saapuva hyötykuormarooli saa ylikirjoittaa niitä
  • Sivun rooli on auktoritatiivinen luokittelun aikana: sivun omistama objekti on prckPageContent, vaikka myöhempi revisio kirjoittaisi sen uudelleen väärennetyllä /FTllä, /Type /Annot-nimikkeellä tai jakaisi sen ulkoasustreamin kanssa
  • Form XObject, jota käytetään vain kentän tai annotaation ulkoasuna, säilyttää lomake- tai annotaatioluokkansa, joten tavallinen ulkoasun uudelleenluonti lomakkeen täytön jälkeen arvostellaan yhä tavallisten lupasääntöjen alla
  • Widget, jolla ei ole omaa /FTä, ratkaisee perityn kenttätyypin /Parent-ketjun kautta, ja ketju, jota ei voi ratkaista, kaataa roolin rakentamisen annotaatio-oletuksen sijaan
  • Jokaisen myöhemmän revision roolibitit yhdistetään katetun revision rooleihin, joten myöhempi päivitys ei voi pyyhkiä aiempaa sivun omistajuussuhdetta irrottamalla streamin ensin ja muokkaamalla sitä sen jälkeen

Kiintopiste kiinteän kierrosmäärän sijaan

Roolien saavutettavuus versiossa v3.126.2 ajetaan työjonona, joka iteroidaan, kunnes mikään objekti ei saa uutta roolibittiä, mikä on aito kiintopiste riippumatta ketjun syvyydestä tai objektien numeroinnista. Epäsuoria taulukoita, kuten /Contents-taulukko, joka on tallennettu omana objektinaan, kuljetaan läpi myös. Jokainen objekti voi saada korkeintaan neljä erillistä roolibittiä, joten jono on rajattu neljään merkintään objektinumeroa kohden; budjetin ylittäminen nostaa poikkeuksen prrResourceLimitExceeded. Viittaus vapaaseen objektiin, sukupolviristiriita tai rikki mennyt objektin otsikko nostaa poikkeuksen prrMalformedRevisionChain, ja hyötykuorma pakatun objektistreamin sisällä nostaa poikkeuksen prrCompressedObjectUnresolved. Jokainen näistä epäonnistumisista päättyy muotoon prasIndeterminate, ei koskaan sallittuun tuomioon, ja kun epäonnistuminen tapahtuu katetun revision rooleja rakennettaessa, allekirjoitus ei ilmoita lainkaan Changesia

Seuraava rutiini listaa sivusisällön muokkaukset, jotka selviävät tästä analysista. prckPageContent-muutosta ei koskaan arvostella muotoon prdAllowed: DocMDP P=1, 2 tai 3 tekee siitä prdDisallowed:n, ja allekirjoitus ilman DocMDP:tä arvostelee sen muotoon 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;   // tietue, ei mitään vapautettavaa
    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;

Mitä tapahtuu, kun FieldMDP ja annotaatio jakavat yhden objektin?

Kun kentän /V ja annotaation /Contents osoittavat samaan epäsuoraan objektiin, v3.126.2 pitää FieldMDP-lukon voimassa, vaikka muutos luokiteltaisiin annotaatiomuokkaukseksi. Skenaario on helppo rakentaa käsin: allekirjoittaja lukitsee Total-kentän FieldMDP:llä (ISO 32000-1 §12.8.2.4), ja hyökkääjä saa tekstimuotoisen annotaation /Contentsin viittaamaan samaan merkkijonoobjektiin, joka kantaa kentän arvoa. P=3:n alla annotaatiomuokkaus on sallittu, joten ennen korjausta kyseisen jaetun merkkijonon uudelleenkirjoittaminen muutti lukittua kenttäarvoa sallitulla tuomiolla

Objekti kantaa nykyään sekä annotaation että lomakkeen roolit, ja annotaation päätös tarkistaa lomakepuolen uudelleen aina, kun allekirjoituksella on FieldMDP-muunnos:

  • P=2:n alla annotaatiomuutos on kielletty suoraan, täsmälleen kuten ennenkin
  • FieldMDP Allilla jokainen kenttä on lukittu, joten jaettu muutos on prdDisallowed
  • FieldMDP Includella tai Excludella analysaattori ei pysty jäljittämään jaettua skalaaria yhteen kenttänimeen, joten päätös on prdIndeterminate arvauksen sijaan
  • Ilman FieldMDP:tä P=3:n annotaatiosääntö pätee ja muutos pysyy sallittuna
PDFium Componentin FieldMDP-päätöskaavio, jossa lukitun Total-kentän /V ja annotaation /Contents viittaavat samaan epäsuoraan objektiin, haarautuen DocMDP P=2:n, FieldMDP Allin, FieldMDP Includen tai Excluden ja FieldMDP:n puutteen yli tuomioihin prdDisallowed, prdIndeterminate tai prdAllowed samalle jaetulle muutokselle
Kun yksi epäsuora objekti kantaa sekä annotaation että lomakkeen roolit, annotaation päätös tarkistaa FieldMDP-lukon uudelleen, joten sama muutos vaihtelee sallitusta kiellettyyn ja epävarmaan

Yksi raportointiyksityiskohta merkitsee porttikoodille. Jaettu tapaus ilmoitetaan muodossa Kind = prckAnnotation ja Decision = prdIndeterminate, ja prrFieldMdpUnresolved lisätään riskijoukkoon vain lomakekentiksi luokitelluille muutoksille. Portti, joka etsii prrFieldMdpUnresolvedia ja ohittaa Statusin, missaa tämän tapauksen kokonaan

Miten Delphi-koodi toteuttaa fail closed -periaatteen revisioanalyysissä?

Delphi-koodin pitäisi hyväksyä allekirjoitettu asiakirja vain, kun analysin tila on prasNoLaterChanges tai prasAllowed eikä rakenteellista riskiä ole läsnä, ja sen pitäisi käsitellä muodot prasIndeterminate ja prasSuspicious luottamattomina, ei lokitettavina ja läpiajettavina varoituksina. Epävarma tarkoittaa, ettei analysaattori pystynyt todistamaan myöhempien revisioiden olleen sallittuja; hyökkääjälle syöte, joka tuottaa luotettavasti Epävarman, on yhtä hyödyllinen kuin sellainen, joka tuottaa Sallitun, jos koodisi päästää sen läpi. Globaali AnalyzePadesSignatureRevisions ottaa minkä tahansa TStreamin ja lukee sen kohdasta 0, mikä sopii latauskäsittelijöille, joiden ei koskaan tarvitse renderöidä asiakirjaa

uses
  Classes, SysUtils, TypInfo, FPdfPades;

const
  // Kaksoismääritykset tallennetaan ilman Statusin alentamista
  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 ja prasSuspicious ovat hylkäyksiä, ei varoituksia
    Reason := 'revision status ' +
      GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
  end;
end;

Kaksi rajaa kannattaa sanoa suoraan. TPadesRevisionAnalysisReport ei sano mitään CMS-eheydestä tai varmenteen luottamuksesta, joten tämä portti istuu kryptografisen ja luottamusvalidoinnin vieressä, ei niiden tilalla. Ja oikea omistajuusgraafi ei tee P=3:sta turvallista jokaiselle työnkululle. P=3 sallii aidosti annotaatiot, ja annotaatio, jolla on läpinäkymätön ulkoasu, voi peittää allekirjoitetun tekstin koskematta yhteenkään sisältöstreamiin. Jos sertifioimasi asiakirjat ovat sopimuksia eivätkä katselukappaleita, joko sertifiointi P=2:lla tai ohjaa sallitut annotaatiomuutokset ihmiselle, kuten tämä apuri:

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;

Allekirjoituksen revisioiden auditointitarkistuslista

Käytä tätä listaa tarkistaaksesi, oliko validointiputkesi altistunut ja epäonnistuuko se nyt suljettuna:

  • PDFium Componentin koontiversiot ennen v3.126.2 saattoivat ilmoittaa prasAllowedin sivusisällön muokkauksille DocMDP P=3 -asiakirjoissa; aja TPdf.AnalyzeSignatureRevisions uudelleen vanhempien koontiversioiden hyväksymille sertifioiduille P=3 -tiedostoille
  • Tarkista uudelleen FieldMDP-lukkoja sisältävät P=3 -asiakirjat, joissa kenttäarvo ja annotaatio saattavat jakaa epäsuoran objektin
  • Hyväksy vain muodot prasNoLaterChanges ja prasAllowed; käsittele muodot prasIndeterminate ja prasSuspicious luottamattomina
  • Testaa Report.Risks sekä Report.Status, koska prrDuplicateObjectDefinition ei muuta tilaa yksinään
  • Älä lue tyhjää Changes-taulukkoa puhtaana tuloksena, kun allekirjoituksen tila on Epävarma; epäonnistunut roolin rakentaminen ei ilmoita muutoksia
  • Älä nojaa pelkkään prrFieldMdpUnresolvediin FieldMDP-ongelmien havaitsemisessa, sillä jaettu annotaatiotapaus paljastuu vain päätöksen ja tilan kautta
  • Päätä, tarvitsevatko sallitut annotaatiomuutokset P=3:n alla ihmiskatselmointia työnkulussasi
  • Analysoi tiedoston alkuperäiset tavut; SaveAslla uudelleenkirjoitettu asiakirja ei enää sisällä revisioketjua

Revisioanalyysi on yksi kerros allekirjoituksen tarkistusta. Yhdistä se katsaukseen PDF:n digitaalisten allekirjoitusten ja PAdES-tasojen tarkastelu sanakirjan ja perustason osalta sekä laajempaan PDF:n turvallisuusriskien auditointiin JavaScriptin, käynnistystoimintojen ja upotettujen tiedostojen osalta. TPdf.AnalyzeSignatureRevisions, AnalyzePadesSignatureRevisions ja tässä kuvattu omistajuustietoinen rooligraafi toimitetaan mukana paketissa PDFium Component Delphille, C++Builderille ja Lazarukselle