I PDFium Component-bygg før v3.126.2 kunne TPdf.AnalyzeSignatureRevisions rangere en ekte sideinnholdsredigering som en tillatt annotasjonsendring under DocMDP P=3, fordi revisjonsrollegrafen behandlet signatur-widgetens /P-bakreferanse til siden sin som eierskap. Siden v3.126.2 skiller PDFium Component navigasjonskanter fra eide-last-kanter, så sideinnhold forblir sideinnhold. Feilrapporten bak denne fiksen ser harmløs ut på papiret. En sertifisert kontrakt tillater annotasjoner, en motpart legger til én inkrementell lagring, og analysatoren sier at enhver senere endring er tillatt. Så diff-er noen de renderete sidene, og betalingsbeløpet på side 2 er annerledes
Denne artikkelen er angriperperspektiv-oppfølgeren til oversikten over analyse av endringer etter signaturrevisjoner, så den hopper over det grunnleggende om revisjonsrebygging og DocMDP-rangering og går rett på objektgrafen: hvordan eierskap ble modellert, hvorfor retningen på en kant avgjør en sikkerhetsdom, hva som endret seg i v3.126.2, og hvordan du reviderer din egen akseptlogikk
Hvorfor passerte en sideendring som en annotasjonsendring under DocMDP P=3?
Sideendringen passerte fordi den gamle rollegrafen fulgte enhver indirekte referanse i en ordbok som om det refererte objektet tilhørte den refererende, og signatur-widgeten peker tilbake på siden sin. En annotasjonsordbok bærer /P, en indirekte referanse til sideobjektet den ligger på (ISO 32000-1 §12.5.2). Den oppføringen er et navigasjonshint. Widgeten eier ikke siden; siden eier widgeten gjennom /Annots-matrisen sin
Analysatoren tildeler hvert objekt et sett rollebiter før den rangerer senere endringer: side, annotasjon, skjema og valideringsmateriale. Rottobjekter får rollen sin fra sin egen ordbok, og rollen sprer seg så til alt de refererer. I den gamle propagasjonen gikk kjeden slik:
- Signatur-widgeten er en
/Subtype /Widgetmed/FT /Sig, så den får annotasjonsrollen - Widgetens
/Pskyver annotasjonsrollen over på sideordboken, som allerede har siderollen - Siden skyver begge roller inn i
/Contents,/Resourcesog, gjennom/Parent, opp i Pages-treet og over til hver søsterside - En innholdsstrøm-ordbok som
<< /Length 812 >>har ingen/Type, så klassifikatoren falt tilbake til rollebiter og sjekket annotasjonsrollen før siderollen
Den endrede innholdsstrømmen kom derfor ut som prckAnnotation. Under ISO 32000-1 §12.8.2.2 tillater DocMDP P=3 annotasjonsendringer, så beslutningen var prdAllowed og rapporten rullet opp til prasAllowed. Samme fil under P=2 ble avvist, men bare ved en tilfeldighet: P=2 forbyr annotasjonsendringer, så den feilmerkte strømmen ble nektet av feil grunn. En fast firedelt propagasjonsløkke la til en andre svakhet. Last som nådde frem gjennom en indirekte matrise, eller gjennom en lang kjede hvis objektnumre løper baklengs, kunne aldri motta noen rolle i det hele tatt
Hvorfor må en signaturvalidator spørre hvem som eier et objekt?
En signaturvalidator må spørre hvem som eier et objekt fordi PDF inkrementelle oppdateringer (ISO 32000-1 §7.5.6) lar hvem som helst legge til en revisjon som redefinerer et eksisterende objektnummer, og den redefinerte kroppen annonserer ikke hva den er. Signaturen verifiseres fortsatt, siden den bare dekker bytene i sin egen revisjon. Hvert forsvar mot manipulasjon etter signering avhenger derfor av å kartlegge hvert endret objekt til strukturen som bruker det, og så spørre om signataren tillot at den strukturen endret seg
Flere publiserte angrepsklasser jobber nøyaktig i det gapet. Incremental saving attacks legger til en revisjon som bytter sideinnhold og stoler på at verifikatoren bare sjekker det signerte byteområdet. Shadow attacks planter skjult innhold før signering og aktiverer det etterpå med en liten, uskyldig utseende endring. Angrep på sertifiserte dokumenter misbruker det faktum at P=2 og P=3 eksplisitt tillater noen senere redigeringer, og kler så en forbudt redigering ut som en tillatt. En verifikator som klassifiserer objekter etter etiketter som /Type /Annot, eller etter en referansesti som tilfeldigvis når dem, er eksponert for den tredje klassen: angriperen trenger bare én tillatt struktur som kan nå den forbudte
Det er derfor spørsmålet ikke er hvilke objekter som endret seg, men hvem som eier dem. En innholdsstrøm nådd fra en side gjennom /Contents er sideinnhold uansett hva annet som peker på den. En annotasjon som peker tilbake på siden gjennom /P sier hvor annotasjonen bor, ikke hva den eier
Hvordan modellerer PDFium Component v3.126.2 eierskap?
PDFium Component v3.126.2 behandler bakreferanser som navigasjon, holder dem utenfor rollepropagasjon, og avgjør hvilke nøkler som teller som navigasjon ut fra den strukturelle rollen til ordboken som holder dem, ikke ut fra nøkkelnavnet alene. Tabellen oppsummerer navigasjonsnøklene som ikke lenger bærer eierskap
| Eierordbok | Nøkler behandlet som navigasjon | Spesifikasjonsreferanse |
|---|---|---|
| Side- eller Pages-node | /Parent, /Kids, /Annots | ISO 32000-1 §7.7.3 |
| Annotasjon eller widget | /P | ISO 32000-1 §12.5.2 |
| Widget- eller feltordbok | /Parent | ISO 32000-1 §12.7.3 |
Å filtrere etter nøkkelnavn globalt ville skapt et nytt hull. En font- eller XObject-ressurs kan legitimt hete /P, /Parent eller /Annots, og en /Resources-ordbok som dropper /P-oppføringen sin fra propagasjonen ville la en angriper skjule et sideeid XObject bak et uskyldig ressursnavn. I v3.126.2 gjelder navigasjonsfilteret bare når eierordboken faktisk er en side, Pages-node, annotasjon, widget eller felt. Bærer én av de ordbøkene en duplisert navigasjonsnøkkel, som to /P-oppføringer i en widget, gjetter ikke analysatoren hvilken kopi en visningsprogram ville brukt; rollebyggingen feiler og signaturen blir Indeterminate
Flere ytterligere regler lukker de gjenværende ommmerkingsrutene:
- Pages-noder er siderolle-røtter i egen rett, så ressurser arvet fra Pages-treet (ISO 32000-1 §7.7.3.4) kommer inn i sidekontekst gjennom ekte eierskap, ikke gjennom en
/Parent-vandring fra en barneside - En annotasjons- eller skjemarolle som ankommer en katalog, Pages-node, side, annotasjon eller feltordbok, stopper der, fordi de strukturelle objektene etablerer sine egne roller, og en innkommende last-rolle må ikke overstyre dem
- Siderollen er autoritativ under klassifisering: et sideeid objekt er
prckPageContentselv om en senere revisjon omskriver det med en forfalsket/FT, en/Type /Annot-etikett, eller deler det med en appearance-strøm - Et Form XObject brukt bare som felt- eller annotasjonsutseende beholder sin skjema- eller annotasjonskategori, så vanlig utseende-regenerering etter utfylling av et skjema rangeres fortsatt under de normale tillatelsesreglene
- En widget uten egen
/FTløser den arvede felttypen gjennom/Parent-kjeden, og en uløselig kjede feiler rollebyggingen i stedet for å velge annotasjon som standard - Rollebiter fra hver senere revisjon slås sammen inn i den dekkede revisjonens roller, så en senere oppdatering ikke kan viske ut en tidligere sideeierskapsrelasjon ved først å koble fra en strøm og så redigere den
Fast punkt i stedet for fast passantall
Rolle-nåbarhet i v3.126.2 kjører som en arbeidskø som itererer til intet objekt vinner en ny rollebit, noe som er et ekte fast punkt uansett kjededybde eller objektnummerering. Indirekte matriser, som en /Contents-matrise lagret som sitt eget objekt, vandres også. Hvert objekt kan vinne høyst fire distinkte rollebiter, så køen er bundet til fire oppføringer per objektnummer; overskrider den budsjettet, reises prrResourceLimitExceeded. En referanse til et fritt objekt, en generasjonsmismatch eller et ødelagt objekthode reiser prrMalformedRevisionChain, og last inni et komprimert objektstrøm-objekt reiser prrCompressedObjectUnresolved. Hver av disse feilene ender med prasIndeterminate, aldri med en tillatt dom, og skjer feilen under bygging av den dekkede revisjonens roller, rapporterer signaturen ingen Changes i det hele tatt
Rutinen under lister sideinnholdsendringene som overlever denne analysen. En prckPageContent-endring rangeres aldri som prdAllowed: DocMDP P=1, 2 eller 3 gjør den til prdDisallowed, og en signatur uten DocMDP rangerer den som 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; // record, ingenting å frigjøre
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;
Hva skjer når FieldMDP og en annotasjon deler ett objekt?
Når et felts /V og en annotasjons /Contents peker på samme indirekte objekt, holder v3.126.2 FieldMDP-låsen i kraft selv om endringen klassifiseres som en annotasjonsredigering. Scenariet er lett å bygge for hånd: en signatar låser Total-feltet med FieldMDP (ISO 32000-1 §12.8.2.4), og angriperen lar en tekstannotasjons /Contents referere det samme strengobjektet som holder feltverdien. Under P=3 er annotasjonsredigeringen tillatt, så før fiksen omskrev omskriving av den delte strengen en låst feltverdi med en tillatt dom
Objektet bærer nå både annotasjons- og skjemarollen, og annotasjonsbeslutningen sjekker skjemasiden på nytt når signaturen har en FieldMDP-transform:
- Under P=2 er annotasjonsendringen avvist på rent ut, nøyaktig som før
- Med FieldMDP
Aller hvert felt låst, så den delte endringen erprdDisallowed - Med FieldMDP
IncludeellerExcludekan ikke analysatoren spore en delt skalar tilbake til ett feltnavn, så beslutningen erprdIndeterminatei stedet for en gjetning - Uten FieldMDP gjelder P=3-annotasjonsregelen, og endringen forblir tillatt
Én rapporteringsdetalj betyr noe for portkode. Det delte tilfellet rapporteres som Kind = prckAnnotation med Decision = prdIndeterminate, og prrFieldMdpUnresolved legges til risikosettet bare for endringer klassifisert som skjemafelt. En port som søker etter prrFieldMdpUnresolved og ignorerer Status, mister dette tilfellet fullstendig
Hvordan bør Delphi-kode feile lukket på revisjonsanalyse?
Delphi-kode bør akseptere et signert dokument bare når analysestatusen er prasNoLaterChanges eller prasAllowed og ingen strukturell risiko finnes, og den bør behandle prasIndeterminate og prasSuspicious som utruelige, ikke som advarsler å logge og slippe gjennom. Indeterminate betyr at analysatoren ikke kunne bevise at de senere revisjonene var tillatt; for en angriper er en inndata som pålitelig produserer Indeterminate like nyttig som én som produserer Allowed, hvis koden din slipper den gjennom. Den globale AnalyzePadesSignatureRevisions tar enhver TStream og leser den fra posisjon 0, noe som passer opplastingsbehandlere som aldri trenger å rendre dokumentet
uses
Classes, SysUtils, TypInfo, FPdfPades;
const
// Duplikate definisjoner registreres uten å nedgradere 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 og prasSuspicious er avvisninger, ikke advarsler
Reason := 'revision status ' +
GetEnumName(TypeInfo(TPadesRevisionAnalysisStatus), Ord(Report.Status));
end;
end;
To grenser er verdt å si rent ut. TPadesRevisionAnalysisReport sier ingenting om CMS-integritet eller sertifikattillit, så denne porten står ved siden av kryptografisk og tillitsvalidering, ikke i stedet for dem. Og en korrekt eiergraf gjør ikke P=3 trygt for enhver arbeidsflyt. P=3 tillater faktisk annotasjoner, og en annotasjon med et ugjennomsiktig utseende kan dekke signert tekst uten å røre en enkelt innholdsstrøm. Er de sertifiserte dokumentene dine kontrakter snarere enn gjennomgangseksemplarer, sertifiserer du enten med P=2 eller ruter tillatte annotasjonsendringer til et menneske, som i denne hjelperen:
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;
Sjekkliste for revisjonsrevisjon av signaturer
Bruk denne listen til å sjekke om verifiseringsrørledningen din var eksponert, og om den nå feiler lukket:
- Bygg av PDFium Component før v3.126.2 kunne rapportere
prasAllowedfor sideinnholdsendringer i DocMDP P=3-dokumenter; kjørTPdf.AnalyzeSignatureRevisionspå nytt på sertifiserte P=3-filer godtatt av eldre bygg - Sjekk P=3-dokumenter med FieldMDP-låser på nytt der en feltverdi og en annotasjon kan dele et indirekte objekt
- Godta bare
prasNoLaterChangesogprasAllowed; behandleprasIndeterminateogprasSuspicioussom utruelige - Test
Report.Riskslike fullt somReport.Status, forprrDuplicateObjectDefinitionendrer ikke statusen av seg selv - Ikke les en tom
Changes-matrise som et rent resultat når signaturens status er Indeterminate; en feilet rollebygging rapporterer ingen endringer - Ikke stol på
prrFieldMdpUnresolvedalene for å fange FieldMDP-problemer, siden det delte annotasjonstilfellet bare kommer til syne gjennom beslutningen og statusen - Avgjør om tillatte annotasjonsendringer under P=3 trenger menneskelig gjennomgang i arbeidsflyten din
- Analyser originalfilens byte; et dokument omskrevet av
SaveAsinneholder ikke lenger revisjonskjeden
Revisjonsanalyse er ett lag av en signatursjekk. Parr den med inspeksjon av digitale PDF-signaturer og PAdES-nivåer for ordboken og baseline-nivået, og med en bredere revisjon av PDF-sikkerhetsrisikoer for JavaScript, start-handlinger og innebygde filer. TPdf.AnalyzeSignatureRevisions, AnalyzePadesSignatureRevisions og den eierskapsbevisste rollegrafen beskrevet her følger med i PDFium Component for Delphi, C++Builder og Lazarus