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:
- Allekirjoituswidget on
/Subtype /Widget, jolla on/FT /Sig, joten se saa annotaation roolin - Widgetin
/Ptyöntää annotaation roolin sivun sanakirjaan, jolla on jo sivun rooli - Sivu työntää molemmat roolit kohteisiin
/Contentsja/Resourcesja/Parent:n kautta ylös Pages-puuhun ja sivuille jokaiselle sisarsivulle - Sisältöstreamin sanakirja kuten
<< /Length 812 >>ei kanna/Type-tietuetta, joten luokittelija palasi roolibitteihin ja tarkisti annotaation roolin ennen sivun roolia
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 sanakirja | Navigointina käsiteltävät avaimet | Spesifikaatioviittaus |
|---|---|---|
| Sivu- tai Pages-solmu | /Parent, /Kids, /Annots | ISO 32000-1 §7.7.3 |
| Annotaatio tai widget | /P | ISO 32000-1 §12.5.2 |
| Widget- tai kenttäsankirja | /Parent | ISO 32000-1 §12.7.3 |
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 onprdDisallowed - FieldMDP
Includella taiExcludella analysaattori ei pysty jäljittämään jaettua skalaaria yhteen kenttänimeen, joten päätös onprdIndeterminatearvauksen sijaan - Ilman FieldMDP:tä P=3:n annotaatiosääntö pätee ja muutos pysyy sallittuna
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; ajaTPdf.AnalyzeSignatureRevisionsuudelleen 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
prasNoLaterChangesjaprasAllowed; käsittele muodotprasIndeterminatejaprasSuspiciousluottamattomina - Testaa
Report.RiskssekäReport.Status, koskaprrDuplicateObjectDefinitionei 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