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:
- Az aláírás widget egy
/Subtype /Widget/FT /Sig-gal, tehát megkapja az annotációszerepet - A widget
/P-je az annotációszerepet az oldalszótárra tolja, ami már amúgy is az oldalszerepet hordozza - 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 - 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
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ár | Navigációként kezelt kulcsok | Spec hivatkozás |
|---|---|---|
| Page vagy Pages csomópont | /Parent, /Kids, /Annots | ISO 32000-1 §7.7.3 |
| Annotáció vagy widget | /P | ISO 32000-1 §12.5.2 |
| Widget vagy mező szótár | /Parent | ISO 32000-1 §12.7.3 |
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
/Parentbejá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 /Annotcí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
/FTnélküli widget az örökölt mezőtípust a/Parentlá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ásprdDisallowed - FieldMDP
IncludevagyExcludemellett az analizátor nem tudja visszavezetni egy megosztott skalárt egyetlen mezőnévig, tehát a döntésprdIndeterminate, 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
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 aTPdf.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 ésprasAllowed-ot fogadj el; aprasIndeterminate-et ésprasSuspicious-ot kezeld nem megbízhatónak - Teszteld a
Report.Risks-t is, nem csak aReport.Status-t, mert aprrDuplicateObjectDefinitionönmagában nem változtatja a státuszt - Ne olvasd üres
Changestö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