콘텐츠로 이동
getnextpdf.com

신뢰할 수 없는 PDF 새니타이즈: 콘텐츠 무력화 및 재구성

Spec: ISO 32000-2, §12.6.4

바깥세상에서 도착한 PDF는 문서인 만큼 코드이기도 합니다. 그것은 JavaScript, Launch action, 임베디드 실행 파일, 그리고 파서를 걸려 넘어뜨리도록 만들어진 구조를 나를 수 있습니다. 콘텐츠 무력화 및 재구성(Content Disarm & Reconstruction, CDR)은 그 파일을 신뢰할 수 없는 것으로 취급하고, 안전한 것만 남기며, 살아남은 것들로부터 깨끗한 PDF를 다시 빌드합니다.

이 페이지는 NextPDF Enterprise의 CdrEngine이 그것을 어떻게 하는지를 설명합니다 — 그리고 모든 CDR 공급사가 소리 내어 말해야 할 정직한 한 가지를 다룹니다. 다시 빌드된 파일은 원본의 보안 투영이지, 그것의 충실한 사본이 아닙니다.

PDF의 위험한 부분은 이국적인 것이 아닙니다. 그것들은 표준 기능입니다. 명세는 action의 온전한 카탈로그 — 문서가 열릴 때, 페이지가 보일 때, 필드가 바뀔 때 무슨 일이 일어나는지 — 를 정의하며, 그 카탈로그는 JavaScript와 Launch action을 포함합니다 (Spec: ISO 32000-2, §12.6.4). 포맷을 존중하는 뷰어는 그것들을 기꺼이 실행합니다. 그것이 공격자의 발판입니다. 완벽하게 유효하면서 동시에 완벽하게 악의적인 파일입니다.

여기서 파일 확장자로 필터링하는 것은 아무것도 하지 못합니다. 위협은 잘 형성된 PDF 안에 있으므로, 유일하게 진짜인 방어는 그것을 열고, 이해하고, 능동적 기계 장치를 그것이 렌더러에 닿기도 전에 제거하는 것입니다. 이것이 OWASP의 파일 업로드 및 입력 검증 지침이 기술하는 입력 검증 자세입니다. 결코 바이트를 신뢰하지 말고, 적대적 파일에서 알려진 악성 시그니처를 스캔하기보다 알려진 양호 아티팩트를 다시 빌드하는 쪽을 선호하라는 것입니다.

  • CDR은 입력이 적대적이라고 가정하고, 옛 파일을 패치하는 대신 파일을 만들어 냅니다.
  • CdrEngine::sanitize()는 여섯 단계를 실행합니다. 파싱, 어드미션 컨트롤, 위협 탐지, 필터, 참조 정리, 재빌드입니다.
  • 그것은 무엇이 제거되었는지, 문서가 어드미션되기는 했는지, 그리고 — 그렇지 않다면 — 왜 거부되었는지를 알려 주는 CdrResult를 반환합니다.
  • 출력은 보안 투영입니다. 그것은 명시적으로 증거용 사본, 해시 일치, 또는 보관 아티팩트가 아닙니다. 그것은 단서가 아니라 계약입니다.
  • 이것은 Enterprise 전용입니다. NextPDF 코어는 CDR을 수행하지 않습니다.

엔진 자신의 문서는 그 설계의 레드 라인을 세 단어로 명시합니다. Security Projection Layer. sanitize()는 파괴적이고 비가역적인 변환입니다. 그것은 바이트를 버리는 것이 허용됩니다. 그것에게 허용되지 않는 것은 결과가 같은 문서인 척하는 것입니다.

파이프라인은 의도적으로 순서가 매겨져 있습니다. 각 단계는 다음 단계가 신뢰에 작용하기 전에 신뢰를 좁힙니다.

  1. ParsePdfReader builds the object graph from the raw bytes. A parse failure is a rejection, not a best-effort guess.
  2. Admission controlResource limits are checked first — object count, page count, decoded-stream size, per-stream inflation ratio. Over-budget input is rejected, never sanitised.
  3. DetectThreatDetector walks the graph and records each dangerous feature as a DetectedThreat with its object number and type.
  4. FilterObjects are partitioned into safe and removed. Catalog-level keys are stripped in place so the document catalog itself survives.
  5. Reference scrubPointers to removed objects are cleaned so the rebuilt file has no dangling references.
  6. RebuildCdrRebuilder serialises only the safe objects into a new PDF. The output is fresh structure, not an edited original.
The six phases of CdrEngine::sanitize(), in order. The document is parsed, then admitted or rejected against resource limits, then scanned for threats; dangerous objects are filtered out, dangling references are scrubbed, and only the surviving safe objects are serialised into a brand-new PDF. The original bytes never carry through.

무력화 이전에 어드미션. 단 하나의 위협이 제거되기 전에, 엔진은 문서가 처리할 가치가 있기는 한지를 묻습니다. CdrPolicy는 그 한도 — maxObjects, maxPageCount, maxDecodedStreamBytes, 그리고 압축 폭탄을 방어하는 maxInflationRatio — 를 나릅니다. 그것들을 넘어서는 문서는 거부되고, CdrResultadmitted: falserejectionReason으로 그렇게 말합니다. 이 구분은 하중을 견디는 것입니다. “우리는 그것을 정리했다”와 “우리는 그것을 거부했다”는 다른 결과이고, 결과 타입은 여러분의 오류 보고도 그럴 수 있도록 그것들을 갈라 둡니다.

위협은 추측이 아니라 명명됩니다. ThreatDetector::detect()는 각 위험한 구문을 ThreatTypeJavaScript, LaunchAction, OpenAction, AdditionalActions, RemoteGoTo, SubmitForm, ImportData, EmbeddedFiles, RichMedia, NamedJavaScript, 그리고 그 밖에 더 — 에 매핑하며, 각각은 특정 PDF 기능에 묶여 있습니다. 전혀 파싱될 수 없는 객체는 그 자체의 위협 타입, UnparseableObject가 되는데, 새니타이저가 읽을 수 없는 객체는 그것이 보증할 수 없는 객체이기 때문입니다. 모든 발견 사항은 문제의 객체 번호를 나르는 DetectedThreat이므로, 제거는 정밀합니다.

제거는 페이로드만이 아니라 문서를 보호합니다. 일부 위험한 키는 문서 카탈로그(Root)에 삽니다 — 예를 들어 열릴 때 발화하는 /OpenAction입니다. 키 하나를 죽이려고 Root 객체 전체를 제거하면 카탈로그가 파괴되어 파일이 조용히 깨집니다. 엔진은 대신 이것들을 카탈로그에 대한 제자리 키 제거로 처리하며, fail-closed 가드로서 /Root를 전혀 잃은 재빌드된 문서를 내보내기를 거부합니다. 구조적으로 깨진 파일을 만들면서 성공을 보고하는 새니타이저는, 이 가드가 막기 위해 존재하는 바로 그 실패 양상입니다.

아래의 형태는 실제 진입점입니다. 여러분은 엔진에 원시 바이트와 정책을 넘기고, 무슨 일이 일어났는지에 대해 정직한 CdrResult를 돌려받습니다.

<?php
declare(strict_types=1);
use NextPDF\Enterprise\Security\Cdr\CdrEngine;
use NextPDF\Enterprise\Security\Cdr\CdrPolicy;
$engine = new CdrEngine();
// Standard policy removes the known active threats — JavaScript, Launch
// actions, remote go-to, form submit/import, and the rest — while leaving
// the lossy opt-in "Strip*" cases off by default.
$result = $engine->sanitize($untrustedPdfBytes, CdrPolicy::standard());
if (!$result->admitted) {
// Rejected by admission control (e.g. object/page limit, zip bomb).
// This is NOT a sanitised document. Do not serve it; report the reason.
throw new \RuntimeException($result->rejectionReason);
}
if ($result->hadThreats()) {
// The disarmed bytes are safe to render. Each removed threat carries its
// type and object number for your audit log — never silently.
foreach ($result->removedThreats as $threat) {
error_log(\sprintf(
'CDR removed %s in object %d',
$threat->type->value,
$threat->objectNumber,
));
}
}
$cleanBytes = $result->sanitizedPdf; // The security projection. Not the original.

이 코드가 여전히 위험한 파일을 조용히 돌려주는 경로는 없습니다. 문서는 어드미션되어 무력화되거나, 또는 명시된 이유와 함께 거부됩니다. removedThreats 목록은 무력화가 마법이 아니라 감사 가능하다는 뜻입니다.

“CDR은 그저 단계가 더 붙은 리댁션일 뿐이야.”

그렇지 않으며, 그 둘을 뒤섞는 것은 위험합니다. 리댁션은 정보를 제거합니다 — 이름, 계좌 번호, 사람이 보아서는 안 되는 콘텐츠입니다. CDR은 능력을 제거합니다 — JavaScript, Launch action, 기계가 실행해서는 안 되는 임베디드 페이로드입니다. 그것들은 정반대의 성공 기준을 가집니다. 리댁션은 민감한 콘텐츠가 사라지고 나머지가 한 글자도 틀림없이 보존될 때 올바릅니다. 무력화는 위협이 사라질 때 올바르며, 거기에 이르기 위해 양성 구조를 변경하는 것을 완벽하게 마다하지 않습니다. 여러분의 의도에 맞는 도구를 쓰십시오. 다른 것의 보증을 기대하며 하나에 손을 뻗지 마십시오.

두 번째 오해는 깨끗한 재빌드가 원본이 깨끗했음을 증명한다는 것입니다. 그것은 원본에 관해 아무것도 증명하지 않습니다. 그것은 오직 출력이 탐지된 위협을 담고 있지 않다는 것만 증명합니다. 입력은 무기였을 수도 있습니다. CDR의 일은 여러분이 하류로 전달하는 것이 그렇지 않도록 만드는 것입니다.

이것은 브로슈어가 건너뛰는 부분이므로, 우리는 그것을 분명하게 말하겠습니다.

  • 출력은 증거용 사본이 아니라 보안 투영입니다. CdrEngine 소스는 이것을 아키텍처 레드 라인으로 나릅니다. 새니타이즈된 PDF는 법적 증거 보존을 위해, 원본과의 해시 비교를 위해, 또는 보관 사본으로 사용되어서는 안 됩니다. 그 변환은 설계상 파괴적이고 비가역적입니다.
  • 탐지에는 범위가 있습니다. CDR은 자신이 명명할 줄 아는 위협을 제거합니다. 그것은 심층 방어 스택 안의 강하고 감사 가능한 계층이지 — 파일이 가능한 모든 미래 기법으로부터 자유롭다는 보장이 아닙니다. 그것을 OWASP의 파일 업로드 지침이 기술하는 것과 같은 업로드 검증, 콘텐츠 타입 검사, 그리고 최소 권한 처리 뒤에 두십시오.
  • 일부 정책은 의도적으로 손실적입니다. 옵트인 Strip* 케이스는 임베디드 파일, 서명, 폼 필드, 레이어, 그리고 3D 미디어를 제거합니다. 그것들은 강력하며 정당한 콘텐츠를 — 이를테면 ZUGFeRD/Factur-X 인보이스 페이로드를 — 삭제할 것입니다. 그것들이 정확히 그 이유로 기본적으로 꺼져 있습니다. 알고서 켜십시오.
  • 서명은 재빌드를 살아남지 못합니다. 파일을 재구성하면 그 바이트가 바뀌므로, 어떤 원본 디지털 서명이든 더 이상 그 바이트 범위와 일치하지 않습니다. 무력화된 문서는 소스에 대해 서명되지 않은 상태입니다. 서명된 아티팩트가 필요하다면, 깨끗한 출력을 새로운 행위로 서명하십시오.
Content Disarm & Reconstruction (CDR) — edition availability
EditionAvailability
Core

사용 불가. NextPDF 코어는 CDR을 수행하지 않습니다. 그것은 PDF를 파싱하고, 렌더링하고, 작성합니다. 그것은 신뢰할 수 없는 입력을 위협 탐지하거나 재빌드하지 않습니다.

Pro

Pro 에디션에서는 사용 불가.

Enterprise

CdrEngine을 통해 사용 가능합니다. 무력화된 출력은 소스의 보안 투영입니다 — 그것은 원본의 증거용, 해시 비교 가능, 또는 보관 사본으로 취급되어서는 안 됩니다.

  • CDR(Content Disarm & Reconstruction) — 신뢰할 수 없는 파일을 파싱하고, 능동적이거나 위험한 구성 요소를 제거하며, 알려진 악성 시그니처를 스캔하는 대신 안전한 나머지로부터 깨끗한 파일을 다시 빌드하는 새니타이제이션 전략입니다.
  • 보안 투영(security projection) — 위협의 제거를 보장하면서 소스의 충분한 부분을 유용하도록 보존하는 새니타이즈된 출력입니다. 그것은 의도적으로 바이트 충실하지 않으며, 증거, 해싱, 또는 보관에 적합하지 않습니다.
  • 어드미션 컨트롤(admission control) — 어떤 무력화 작업이 시작되기 전에, 자원 한도(객체 수, 페이지 수, 디코드된 스트림 크기, 인플레이션 비율)를 초과하는 문서를 거부하는 새니타이제이션 이전 관문입니다.
  • Action — 문서 열기나 필드 변경 같은 트리거에 무언가 일어나게 만드는 PDF 구문입니다. action 타입 카탈로그 (Spec: ISO 32000-2, §12.6.4)는 JavaScript와 Launch action 을, 곧 CDR의 정식 위협 표면을 포함합니다.
  • 댕글링 참조(dangling reference) — 필터링 후 더 이상 존재하지 않는 객체를 가리키는 포인터입니다. 참조 정리 단계는 재빌드된 파일이 구조적으로 일관되게 유지되도록 이것들을 제거합니다.