跳到內容
getnextpdf.com

Enterprise 版本

內容解除武裝與重建 — 深入參考

本頁是 NextPDF\Enterprise\Security\Cdr 模組的深入參考。此模組解除不受信任 PDF 的武裝,並從其安全物件重建出一份乾淨檔案。管線為:解析、准入控制、威脅偵測、過濾、參照清洗、重建。輸出是輸入的安全投影,絕非具證據效力的副本。若需工作流程指引,請先閱讀 CDR 能力頁面

此能力隨 NextPDF Enterprisenextpdf/enterprise)出貨,並以 Enterprise 層級的授權封套啟用。未持有該權利的部署不會載入此能力的類別。比較各版本並取得授權

符號參數預設行為回傳拋出或失敗於備註
CdrEngine::__construct建構內部偵測器與重建器CdrEngine未宣告任何拋出無可注入的協作者
CdrEngine::sanitizestring $pdfData?CdrPolicy $policy = nullCdrPolicy::standard() 下執行完整管線CdrResult對惡意輸入不拋出;解析與准入失敗回傳被拒結果結果會將拒絕與消毒明確區分回報
CdrPolicy::__construct七個選用具名參數,見程式碼區塊空移除集合;allowUriActions 為 false;flattenIncrementalUpdates 為 true;限制 100000 個物件、256 MiB 解碼、10000 頁、1000.0 膨脹比CdrPolicy未宣告任何拋出final readonly;空的 removeThreatTypes 清單不會偵測到任何項目
CdrPolicy::standard舊版威脅集合;移除 URI 動作;預設限制self未宣告任何拋出排除七個有損的 Strip* case
CdrPolicy::paranoid舊版威脅集合搭配更嚴限制:50000 個物件、128 MiB、5000 頁、100.0 膨脹比self未宣告任何拋出排除七個有損的 Strip* case
CdrPolicy::permissive僅移除 JavaScriptLaunchActionNamedJavaScriptSubmitFormImportData;保留 URI 動作self未宣告任何拋出供受信任來源使用
CdrPolicy::allThreatTypes回傳每一個 ThreatType case,含有損的 Strip* caselist<ThreatType>未宣告任何拋出明確的最大化剝除選用啟用
CdrPolicy::legacyThreatTypes回傳除七個 Strip* case 外的每一個 caselist<ThreatType>未宣告任何拋出standard()paranoid() 的預設移除集合
CdrPolicy::shouldRemoveThreatType $type針對 removeThreatTypes 的成員資格測試bool未宣告任何拋出allowUriActionstrue 時,對 UriAction 回傳 false
ThreatDetector::detectPdfReader $readerCdrPolicy $policy掃描每個物件與 trailer catalog 以尋找政策所定的威脅類型list<DetectedThreat>不拋出;無法解析的物件成為 UnparseableObject 威脅catalog 掃描涵蓋 /Names/JavaScript
CdrRebuilder::rebuildPdfReader $readerlist<int> $safeObjNumslist<int> $removedObjNumsCdrPolicy $policy將安全物件序列化為單修訂版 %PDF-2.0 檔案string未宣告任何拋出;重讀或 /Length 驗證失敗的物件會被略過$policy 保留供未來序列化調整
DetectedThreat::__constructThreatType $typeint $objectNumberstring $descriptionstring $location = ''不可變的發現值物件DetectedThreat未宣告任何拋出四個屬性皆為 public readonly
ThreatType字串支撐列舉二十個 case:十三個舊版加七個選用啟用的 Strip* case不適用不適用見下方 case 清單
final class CdrEngine
{
public function __construct()
public function sanitize(string $pdfData, ?CdrPolicy $policy = null): CdrResult
}
final readonly class CdrPolicy
{
public function __construct(
public array $removeThreatTypes = [],
public bool $allowUriActions = false,
public bool $flattenIncrementalUpdates = true,
public int $maxObjects = 100_000,
public int $maxDecodedStreamBytes = 268_435_456,
public int $maxPageCount = 10_000,
public float $maxInflationRatio = 1000.0,
)
public static function standard(): self
public static function paranoid(): self
public static function permissive(): self
public static function allThreatTypes(): array
public static function legacyThreatTypes(): array
public function shouldRemove(ThreatType $type): bool
}
final class ThreatDetector
{
public function detect(PdfReader $reader, CdrPolicy $policy): array
}
final class CdrRebuilder
{
public function rebuild(PdfReader $reader, array $safeObjNums, array $removedObjNums, CdrPolicy $policy): string
}
final readonly class DetectedThreat
{
public function __construct(
public ThreatType $type,
public int $objectNumber,
public string $description,
public string $location = '',
)
}
enum ThreatType: string

十三個舊版 case 構成預設移除集合。Strip* case 依設計即為有損,永不進入預設政策。

Case支撐值偵測面
ThreatType::JavaScriptjavascript任何物件上的 /JS 鍵,或 /S /JavaScript 動作
ThreatType::AdditionalActionsadditional-actions任何物件上的 /AA 字典
ThreatType::OpenActionopen-action任何物件上的 /OpenAction
ThreatType::LaunchActionlaunch-action/S /Launch 動作
ThreatType::RemoteGoToremote-goto/S /GoToR/S /GoToE 動作
ThreatType::SubmitFormsubmit-form/S /SubmitForm 動作
ThreatType::ImportDataimport-data/S /ImportData 動作
ThreatType::EmbeddedFilesembedded-files/EmbeddedFiles name tree 或 /EF 字典
ThreatType::RichMediarich-media/Subtype /RichMedia
ThreatType::NamedJavaScriptnamed-javascriptCatalog /Names/JavaScript name tree
ThreatType::UriActionuri-action/S /URI 動作;當 allowUriActionstrue 時抑制
ThreatType::Xfaxfa/XFA
ThreatType::UnparseableObjectunparseable-object任何解析失敗的物件或 catalog
ThreatType::StripJavaScriptstrip-javascript選用啟用的超集:/JS 鍵、/S /JavaScript,或 /Subtype /JavaScript
ThreatType::StripEmbeddedFilesstrip-embedded-files選用啟用:/Type /EmbeddedFile/Type /Filespec/EmbeddedFiles,或 /EF
ThreatType::StripFormFieldsstrip-form-fields選用啟用:/Subtype /Widget/FT 鍵,或 /AcroForm
ThreatType::StripAnnotationsRichstrip-annotations-rich選用啟用的 subtype:MovieSoundFileAttachment3DRichMediaScreen
ThreatType::StripOcgNonDefaultstrip-ocg-non-default選用啟用:帶有 /Usage/Visibility 鍵的 /Type /OCG
ThreatType::StripDigitalSignaturesAtRebuildstrip-digital-signatures-at-rebuild選用啟用:/Type /Sig/FT /Sig/DSS/VRI,或 /ByteRange
ThreatType::Strip3dAndRichMediastrip-3d-and-rich-media選用啟用的 subtype:3DU3DPRCRMFRichMediaSoundMovie

CdrEngine::sanitize 執行六個有序階段,且對惡意輸入永不拋出。

  1. 解析。 解析失敗會回傳 admitted 為 false 並帶有解析錯誤拒絕原因的結果。此情況下消毒輸出為空。
  2. 准入控制。 物件數、彙總解碼串流位元組數、每串流膨脹比與頁數,皆會對照政策限制檢查。超限文件會被拒絕,而非消毒。拒絕與消毒會明確區分回報。
  3. 偵測。 ThreatDetector::detect 掃描每個物件與 trailer catalog 以尋找政策所定的威脅類型。無法解析的物件會記錄為 ThreatType::UnparseableObject 發現,而非略過。
  4. 過濾。 帶有發現的物件會排入移除佇列。文件 catalog 絕不會被整個物件移除。Catalog 層級的發現(OpenActionAdditionalActionsNamedJavaScript)改以鍵剝除方式修補。
  5. 參照清洗。 每個指向已移除物件的間接參照,在序列化期間都會被替換為 null
  6. 重建。 CdrRebuilder::rebuild 輸出一份單修訂版 %PDF-2.0 檔案,物件經重新編號、附帶經典交叉參照表與全新 trailer。安全串流位元組會逐位元組相同地複製。重建後的 catalog 會捨棄 /OpenAction/AA/Names/AA 會從每個物件捨棄。

回傳的 CdrResult 揭露重建後的位元組、已移除威脅清單、兩個位元組大小、准入旗標與拒絕原因。若來源具有可解析的 /Root 而重建輸出遺失了它,引擎會拒絕該輸出,而非回傳結構損毀的檔案。這是一項故障關閉保證:admitted 為 true 即代表輸出仍帶有文件 catalog 參照。

增量更新絕不會留存:重建在每個政策下都精確序列化為單一修訂版,因此陰影式的後期修訂會因構造而被扁平化。原有數位簽章無法在重建後維持有效,因為位元組範圍不再與輸出相符。

架構紅線。 CDR 是安全投影層,而非保存層。其輸出不得用於法律證據保存、與原件的雜湊比對,或封存副本。

  • null 政策會解析為 CdrPolicy::standard()。以預設空 removeThreatTypes 建構的政策不會偵測或移除任何項目。
  • allowUriActions 設為 true 會抑制 UriAction 移除,即使該 case 存在於 removeThreatTypes 中亦然。
  • flattenIncrementalUpdates 在本版本中為宣告式:重建在每個政策下都輸出單一修訂版,包括將該旗標設為 falsepermissive()
  • 膨脹比檢查將原始串流長度為零者視為一,因此從零膨脹的串流仍受限。當未保留任何解碼形式時,原始串流長度會計入彙總預算。
  • 頁數准入檢查為盡力而為:catalog 或 page-tree 讀取失敗本身不會拒絕該文件。物件數與解壓縮預算則始終強制執行。
  • 原始串流長度與其整數 /Length 條目不符的物件,會在重建時被略過(多語言檔防禦)。指向此類被略過物件的參照會保留其來源物件編號,且在輸出中可能無法解析。sanitize() 會拒絕可偵測到的損毀結果(缺少 /Root),但直接驅動低階 CdrRebuilder::rebuild() 的呼叫方,必須自行重新驗證輸出結構與參照完整性。
  • 當來源 trailer 帶有 /ID 時,重建後的 trailer 會帶有全新產生的隨機 /ID,而非原件的。其他 trailer 條目,包括 /Info,都不會沿用;重建後的 trailer 持有 /Size、可解析時的 /Root,以及重新產生的 /ID
  • 解碼後的 name 與 key 位元組會以十六進位跳脫方式重新輸出分隔符、空白與不可列印位元組,使惡意 name 無法將字典語法注入輸出。
  • 位於已知 name 值集合之外的字典鍵下的字串值,會保守地以字面字串輸出。
  • CdrPolicy::legacyThreatTypes() 會將任何未來的列舉 case 視為預設移除,除非它被登記為 Strip* case,因此新的有損 case 無法悄然進入預設政策。
  • CDR 不是密碼學模組。其唯一的隨機性使用是重新產生的 trailer /ID。簽章驗證不在此範圍;見 簽章深入參考
主張標準條款
叫用 ECMAScript 動作會使 PDF 處理器執行內嵌的指令碼。ISO 32000-2§12.6.4.17
JavaScript name tree 中的文件層級指令碼會在文件開啟時全部執行。ISO 32000-2§12.6.4.17
Catalog name 字典可持有一個文件層級指令碼動作的 JavaScript name tree。ISO 32000-2§7.7.4 (Table 32)
launch 動作會啟動應用程式,或開啟或列印文件。ISO 32000-2§12.6.4.6
/AA additional-actions 字典會擴充註解、頁面、欄位與 catalog 上的觸發事件。ISO 32000-2§12.6.3
不受信任檔案匯入必須限制傳入檔案的存在、數量與內容。OWASP ASVS 5.0§5.2
系統應防止上傳檔案的不當執行並偵測危險內容。OWASP ASVS 5.0§5.3

所有條款皆為釋義;NextPDF 不重現規範性文字。NextPDF 不作任何認證主張。 CDR 在所配置政策下移除 ThreatType 所列舉的主動內容面;它是一項能力,而非經認證的消毒器。CDR 不是防毒掃描器,也不偵測惡意軟體特徵;它補足而非滿足諸如 OWASP ASVS 5.4.3 防毒掃描等控制項。經解除武裝的檔案對某個匯入管線是否可接受,仍屬營運者的風險決策。

  • 模組原始碼帶有 @since 1.9.0;本參考記錄的是 nextpdf/enterprise 3.1.0 出貨的介面。
  • 一切皆在你的主機上行程內執行。消毒期間不會發生網路存取。
  • CdrPolicyDetectedThreat 皆為 final readonly;要變更限制請建構一個新的政策實例。
  • CdrEngine 在內部建構其偵測器與重建器。ThreatDetectorCdrRebuilder 仍可直接使用,供供應自有 PdfReader 的分階段管線。
  • CdrRebuilder::rebuild$policy 參數目前為保留;原始碼記載它是為呼叫端相容性與未來的每政策序列化調整而保留。
  • 輸出為結構可重現,而非位元可重現:當來源帶有 /ID 時,重新產生的 /ID 在每次執行時皆不同。
  • 結果型別 CdrResultsanitize() 的回傳值)已於上方以行為方式涵蓋;其欄位為 public readonly,並以 hadThreats()threatCount() 作為便利方法。

本頁僅記錄外部可觀察的行為與所支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表、運維手冊檔名與工單前綴皆不在範圍內。