Enterprise 版本
内容解除与重构 — 深度参考
本页是 NextPDF\Enterprise\Security\Cdr 模块的深度参考。该模块会解除一份不可信 PDF 的武装,并从其安全对象重构出一个干净文件。流水线为:解析、准入控制、威胁检测、过滤、引用清除、重构。输出是输入的安全投影,绝不是取证副本。如需工作流指引,请先阅读 CDR 能力页面。
可用性与许可
标题为“可用性与许可”的章节此能力随 NextPDF Enterprise(nextpdf/enterprise)一同发布,并通过 Enterprise 层级的许可信封激活。没有该授权的部署不会加载此能力的类。比较各版本并获取许可证。
公开 API 面
标题为“公开 API 面”的章节| 符号 | 参数 | 默认行为 | 返回 | 抛出或失败于 | 说明 |
|---|---|---|---|---|---|
CdrEngine::__construct | 无 | 构造内部的检测器与重构器 | CdrEngine | 未声明任何抛出 | 无可注入的协作者 |
CdrEngine::sanitize | string $pdfData、?CdrPolicy $policy = null | 在 CdrPolicy::standard() 下运行完整流水线 | CdrResult | 对敌意输入不抛出异常;解析与准入失败会返回被拒绝的结果 | 结果将拒绝与清洗区分报告 |
CdrPolicy::__construct | 七个可选具名参数,见代码块 | 空移除集合;allowUriActions 为 false;flattenIncrementalUpdates 为 true;限制为 100000 个对象、256 MiB 解码、10000 页、1000.0 膨胀比 | CdrPolicy | 未声明任何抛出 | final readonly;空的 removeThreatTypes 列表不会检测任何内容 |
CdrPolicy::standard | 无 | 传统威胁集合;移除 URI 动作;默认限制 | self | 未声明任何抛出 | 不包含七个有损的 Strip* 枚举项 |
CdrPolicy::paranoid | 无 | 传统威胁集合搭配更紧的限制:50000 个对象、128 MiB、5000 页、100.0 膨胀比 | self | 未声明任何抛出 | 不包含七个有损的 Strip* 枚举项 |
CdrPolicy::permissive | 无 | 仅移除 JavaScript、LaunchAction、NamedJavaScript、SubmitForm、ImportData;保留 URI 动作 | self | 未声明任何抛出 | 面向可信来源 |
CdrPolicy::allThreatTypes | 无 | 返回每一个 ThreatType 枚举项,包括有损的 Strip* 项 | list<ThreatType> | 未声明任何抛出 | 显式的最大化剥离选择项 |
CdrPolicy::legacyThreatTypes | 无 | 返回除七个 Strip* 项之外的每一个枚举项 | list<ThreatType> | 未声明任何抛出 | standard() 与 paranoid() 的默认移除集合 |
CdrPolicy::shouldRemove | ThreatType $type | 针对 removeThreatTypes 的成员测试 | bool | 未声明任何抛出 | 当 allowUriActions 为 true 时,对 UriAction 返回 false |
ThreatDetector::detect | PdfReader $reader、CdrPolicy $policy | 扫描每个对象以及尾部目录(trailer catalog)以查找策略指定的威胁类型 | list<DetectedThreat> | 不抛出;无法解析的对象会变成一个 UnparseableObject 威胁 | 目录扫描覆盖 /Names/JavaScript 树 |
CdrRebuilder::rebuild | PdfReader $reader、list<int> $safeObjNums、list<int> $removedObjNums、CdrPolicy $policy | 将安全对象序列化为一个单修订版 %PDF-2.0 文件 | string | 未声明任何抛出;重新读取或 /Length 校验失败的对象会被跳过 | $policy 为未来的序列化调整保留 |
DetectedThreat::__construct | ThreatType $type、int $objectNumber、string $description、string $location = '' | 不可变的发现值对象 | DetectedThreat | 未声明任何抛出 | 四个属性均为 public readonly |
ThreatType | 字符串支撑的枚举 | 二十个枚举项:十三个传统项加七个可选加入的 Strip* 项 | 不适用 | 不适用 | 参见下方枚举清单 |
入口点签名
标题为“入口点签名”的章节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: stringThreatType 枚举清单
标题为“ThreatType 枚举清单”的章节十三个传统项构成默认移除集合。Strip* 项在设计上是有损的,永远不会进入任何默认策略。
| 枚举项 | 支撑值 | 检测面 |
|---|---|---|
ThreatType::JavaScript | javascript | 任意对象上的 /JS 键,或一个 /S /JavaScript 动作 |
ThreatType::AdditionalActions | additional-actions | 任意对象上的 /AA 字典 |
ThreatType::OpenAction | open-action | 任意对象上的 /OpenAction 键 |
ThreatType::LaunchAction | launch-action | /S /Launch 动作 |
ThreatType::RemoteGoTo | remote-goto | /S /GoToR 或 /S /GoToE 动作 |
ThreatType::SubmitForm | submit-form | /S /SubmitForm 动作 |
ThreatType::ImportData | import-data | /S /ImportData 动作 |
ThreatType::EmbeddedFiles | embedded-files | /EmbeddedFiles 名称树或 /EF 字典 |
ThreatType::RichMedia | rich-media | /Subtype /RichMedia |
ThreatType::NamedJavaScript | named-javascript | 目录中的 /Names/JavaScript 名称树 |
ThreatType::UriAction | uri-action | /S /URI 动作;当 allowUriActions 为 true 时被抑制 |
ThreatType::Xfa | xfa | /XFA 键 |
ThreatType::UnparseableObject | unparseable-object | 任何解析失败的对象或目录 |
ThreatType::StripJavaScript | strip-javascript | 可选加入的超集:/JS 键、/S /JavaScript 或 /Subtype /JavaScript |
ThreatType::StripEmbeddedFiles | strip-embedded-files | 可选加入:/Type /EmbeddedFile、/Type /Filespec、/EmbeddedFiles 或 /EF |
ThreatType::StripFormFields | strip-form-fields | 可选加入:/Subtype /Widget、/FT 键或 /AcroForm 键 |
ThreatType::StripAnnotationsRich | strip-annotations-rich | 可选加入的子类型:Movie、Sound、FileAttachment、3D、RichMedia、Screen |
ThreatType::StripOcgNonDefault | strip-ocg-non-default | 可选加入:带有 /Usage 或 /Visibility 键的 /Type /OCG |
ThreatType::StripDigitalSignaturesAtRebuild | strip-digital-signatures-at-rebuild | 可选加入:/Type /Sig、/FT /Sig、/DSS、/VRI 或 /ByteRange |
ThreatType::Strip3dAndRichMedia | strip-3d-and-rich-media | 可选加入的子类型:3D、U3D、PRC、RMF、RichMedia、Sound、Movie |
行为契约
标题为“行为契约”的章节CdrEngine::sanitize 执行六个有序阶段,且对敌意输入绝不抛出异常。
- 解析。 解析失败会返回一个
admitted为 false、并带有解析错误拒绝原因的结果。此情况下清洗后的输出为空。 - 准入控制。 对象数量、聚合的解码流字节数、每流膨胀比以及页数会被对照策略限制进行检查。超限文档会被拒绝,而非清洗。拒绝与清洗被区分报告。
- 检测。
ThreatDetector::detect扫描每个对象以及尾部目录以查找策略指定的威胁类型。无法解析的对象会被记录为ThreatType::UnparseableObject发现,而不是被跳过。 - 过滤。 携带发现的对象会被排入移除队列。文档目录永远不会作为整个对象被移除。目录层级的发现(
OpenAction、AdditionalActions、NamedJavaScript)改由键剥离来修复。 - 引用清除。 在序列化过程中,指向已移除对象的每个间接引用都会被替换为
null。 - 重构。
CdrRebuilder::rebuild生成一个单修订版%PDF-2.0文件,其中对象已重新编号,附带一张经典交叉引用表以及一个全新的尾部。安全的流字节以逐字节相同的方式复制。重构后的目录会丢弃/OpenAction、/AA与/Names;/AA会从每个对象上丢弃。
返回的 CdrResult 暴露重构后的字节、被移除威胁的列表、两侧的字节大小、准入标志以及拒绝原因。如果源文件曾有一个可解析的 /Root 而重构输出丢失了它,引擎会拒绝该输出,而不是返回一个结构损坏的文件。这是一项失败即关闭(fail-closed)的保证:admitted 为 true 即意味着输出仍然携带一个文档目录引用。
增量更新永远不会存留:重构在每种策略下都精确地序列化恰好一个修订版,因此影子式的后期修订会因构造方式而被拍平。原有的数字签名无法在重构后保持有效,因为字节范围(byte range)不再与输出匹配。
架构红线。 CDR 是一个安全投影层,而非保全层。其输出不得用于法律证据保全、与原件的哈希比对或归档副本。
边缘情况与失败模式
标题为“边缘情况与失败模式”的章节null策略会解析为CdrPolicy::standard()。以默认空removeThreatTypes构造的策略不会检测也不会移除任何内容。- 将
allowUriActions设为true会抑制UriAction移除,即使该枚举项存在于removeThreatTypes中也是如此。 flattenIncrementalUpdates在本版本中是声明性的:重构在每种策略下都生成单个修订版,包括将该标志设为false的permissive()。- 膨胀比检查将原始流长度为零视为一,因此从无到有膨胀的流仍然受到约束。当不保留任何解码形式时,原始流长度会计入聚合预算。
- 页数准入检查是尽力而为的:目录或页面树读取失败本身不会拒绝文档。对象数量与解压预算始终强制执行。
- 一个原始流长度与其整数
/Length条目不一致的对象会在重构时被跳过(多态文件防御)。指向此类被跳过对象的引用会保留其源对象编号,且可能无法在输出中解析。sanitize()会拒绝可检测的损坏结果(缺失/Root),但直接驱动底层CdrRebuilder::rebuild()的调用方必须自行重新校验输出结构与引用完整性。 - 当源尾部携带
/ID时,重构后的尾部会携带一个全新生成的随机/ID,而非原来的那个。其他尾部条目(包括/Info)不会被沿用;重构后的尾部持有/Size、可解析时的/Root以及重新生成的/ID。 - 解码后的名称与键字节在重新生成时,会对分隔符、空白以及不可打印字节使用十六进制转义,因此敌意名称无法向输出中注入字典语法。
- 位于已知名称取值集合之外的字典键之下的字符串值,会被保守地以字面字符串形式生成。
CdrPolicy::legacyThreatTypes()会将任何未来的枚举项视为默认移除,除非它被登记为Strip*项,因此新的有损项无法悄然进入默认策略。- CDR 不是一个密码学模块。它唯一的随机性用途是重新生成的尾部
/ID。签名验证不在此处范围内;参见 签名深度参考。
符合性
标题为“符合性”的章节| 主张 | 标准 | 条款 |
|---|---|---|
| 调用一个 ECMAScript 动作会使 PDF 处理器执行其嵌入脚本。 | ISO 32000-2 | §12.6.4.17 |
JavaScript 名称树中的文档级脚本会在文档打开时全部执行。 | ISO 32000-2 | §12.6.4.17 |
目录名称字典可以持有一个由文档级脚本动作构成的 JavaScript 名称树。 | ISO 32000-2 | §7.7.4 (Table 32) |
| 一个启动(launch)动作会启动一个应用程序,或打开或打印一个文档。 | ISO 32000-2 | §12.6.4.6 |
/AA 附加动作字典会扩展注释、页面、字段以及目录上的触发事件。 | 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/enterprise3.1.0 发布的接口面。 - 一切都在你的主机上进程内运行。清洗期间不发生任何网络访问。
CdrPolicy与DetectedThreat是final readonly;要更改限制请构造一个新的策略实例。CdrEngine在内部构造其检测器与重构器。ThreatDetector与CdrRebuilder仍可直接使用,以供那些提供自己PdfReader的分阶段流水线。CdrRebuilder::rebuild的$policy参数目前是保留的;源码将其记录为为了调用点兼容性以及未来按策略的序列化调整而保留。- 输出是结构可复现的,而非按位可复现的:当源文件曾携带
/ID时,重新生成的/ID在每次运行时都不同。 - 结果类型
CdrResult(sanitize()的返回值)在上文已从行为上覆盖;其字段为public readonly,并以hadThreats()与threatCount()作为便捷方法。
- 内容解除与重构(CDR) — 带有工作流与策略指引的能力页面。
- 安全 — 深度参考
- 验证 — 深度参考
- 取证 — 深度参考
发布边界
标题为“发布边界”的章节本页仅记录外部可观测的行为以及受支持的公开 API 面。内部命名空间路径、辅助类、机制表、运行手册文件名以及工单前缀均不在范围内。