기술 문서

Delphi에서 PDFium으로 PDF 보안 위험 감사하기

PDF는 단순한 종이가 아닙니다. 파일을 열 때 실행되는 스크립트, 외부 프로그램을 시작하는 링크, 웹 서버에 연결하는 링크, 파일 안에 중첩된 파일, 그리고 누군가 보증한 이후로 문서가 변경되지 않았음을 주장하는 서명을 포함할 수 있는 컨테이너입니다. 제어할 수 없는 출처에서 파일이 도착할 때, 가장 안전한 첫 번째 조치는 이를 렌더링하지 않는 것입니다. 파일이 자신에 대해 무엇이라고 말하는지 읽고 수행하려고 시도할 수 있는 모든 것에 대한 인벤토리를 작성하여 사람이 워크플로에 포함할지 여부를 결정할 수 있도록 하는 것입니다

이 기사는 Delphi 및 Lazarus용 PDFium 구성 요소를 사용하여 해당 위험 표면에 대한 정적이고 읽기 전용인 감사 패스를 안내합니다. 감사는 페이지를 렌더링하지 않습니다. 문서 구조를 구문 분석하고, 동작을 포함하는 파일의 일부를 열거하며, 일반 보고서를 작성합니다. 낯선 사람에게 문에서 주머니를 비우라고 요청하는 것과 그들이 웃었다고 해서 그들을 신뢰하는 것의 차이입니다

감사란 무엇이며, 무엇이 아닌가

경계를 명확히 하십시오. 샌드박스 처리된 미리보기는 사용자가 파일이 시스템의 나머지 부분을 건드리지 않고 볼 수 있도록 엄격한 제한 하에 파일을 렌더링합니다. 감사는 그 전에 수행됩니다. 유일한 출력은 위협 표면에 대한 설명인 렌더링 없는 검사입니다. 즉, 어떤 스크립트가 존재하는지, 어떤 작업이 링크에 연결되어 있는지, 파일이 서명되어 있는지 여부와 얼마나 엄격하게 서명되어 있는지, 그리고 무엇이 첨부되어 있는지 등을 나타냅니다. 이메일, 업로드 양식 또는 파트너 피드에서 수신할 때 신뢰 경계를 넘을 때, 나중에 실제 열기 전에 이 검사를 실행합니다

구성 요소는 감사할 때나 다른 작업에서와 동일한 방식으로 문서를 로드합니다. 파일 이름을 설정하고 활성화하면 단일 페이지를 렌더링하지 않고도 상호 참조 데이터와 문서 카탈로그를 구문 분석합니다. 아래의 모든 내용은 해당 로드되고 렌더링되지 않은 상태에서 읽습니다

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'Incoming_Invoice.pdf';
    Pdf.Active := True;          // parses structure, renders nothing
    // audit the loaded document here
  finally
    Pdf.Free;
  end;
end;

이름 트리의 문서 JavaScript

코드 열거가 가장 먼저입니다. PDF에는 페이지나 필드가 아닌 문서 자체에 첨부된 문서 수준 JavaScript 스크립트를 포함할 수 있으며, 이들은 /Names 트리 아래 /JavaScript 항목에 저장됩니다. 규칙을 준수하는 뷰어는 파일을 열 때 이를 실행합니다. 이것이 긴 PDF 맬웨어 라인 이면의 메커니즘입니다. 사용자가 단어를 읽기도 전에 두 번 클릭하는 순간 파일이 논리를 실행하도록 허용하기 때문입니다

감사자는 이러한 각 스크립트에 대해 두 가지 사실을 알고자 합니다. 즉, 스크립트가 존재한다는 것과 스크립트 내용입니다. 구성 요소는 개수를 노출하고 스크립트의 이름과 전체 본문을 포함하는 레코드로 각 작업을 읽을 수 있도록 합니다. 본문을 읽는 것이 중요합니다. Doc.0이라는 이름의 스크립트는 아무것도 알려주지 않지만, 해당 텍스트는 app.launchURL을 호출하거나 문자열을 조립하여 가서는 안 되는 곳으로 전달할 수 있습니다. 검토자가 읽을 수 있도록 소스를 추출하는 것이 열 때 코드를 실행하는 파일을 플래그 지정하는 핵심 목적입니다

var
  I: Integer;
  Action: TPdfJavaScriptAction;
begin
  if Pdf.JavaScriptActionCount > 0 then
    WriteLn('WARNING: document runs ', Pdf.JavaScriptActionCount,
            ' script(s) on open');
  for I := 0 to Pdf.JavaScriptActionCount - 1 do
  begin
    Action := Pdf.JavaScriptAction[I];
    WriteLn('  script "', Action.Name, '":');
    WriteLn(Action.Script);   // full body, for a human to read
  end;
end;

페이지 및 필드 스크립트도 존재하므로 문서 스크립트가 0개인 파일이 자동으로 안전한 것은 아니지만, 문서 스크립트가 있는 파일은 항상 다시 살펴볼 가치가 있습니다. 존재 여부만으로도 유용한 관문이 되며, 본문은 관문을 판단으로 바꿉니다

실행(Launch) 및 URI 작업

다음으로 인벤토리에 기록할 동작은 링크와 주석에 존재합니다. 감사자에게 가장 중요한 두 가지 작업 유형이 있습니다. 실행(Launch) 작업은 링크가 트리거될 때 외부 프로그램을 시작하거나 로컬 파일을 엽니다. URI 작업은 웹 대상을 엽니다. 의심스러운 문서를 보는 검토자는 아무것도 클릭하지 않고도 페이지 3의 버튼이 cmd.exe를 실행하도록 연결되어 있거나 페이지의 브랜드와 일치하지 않는 URL을 열도록 연결되어 있음을 알 수 있어야 합니다

구성 요소는 찾은 링크를 분류하고 각 링크에 대한 작업 유형 및 대상 경로를 노출하므로 감사를 통해 대상이 포함된 모든 실행 및 URI 작업을 나열할 수 있습니다. 이것은 실행이 아니라 보고입니다. 감사자는 구조에서 작업을 읽고 기록합니다. 절대 이를 따르지 않습니다

문서를 렌더링하는 뷰어 컨트롤은 작업을 따르는 곳이며 기본 태세는 의도적으로 신중합니다. TPdfView 컨트롤에는 클릭 시 자동으로 실행되는 링크 유형을 결정하는 LinkOptions 집합이 있습니다. 기본값은 [loAutoGoto, loAutoOpenURI]입니다. 이는 문서 내 이동 및 웹 URL이 열릴 수 있음을 의미하지만, loAutoLaunch가 없으므로 실행 작업은 절대 자동으로 실행되지 않습니다. 감사 워크플로의 경우 더 나아가 집합을 완전히 지워서 파일을 신뢰할지 여부를 결정하는 동안 아무것도 자동으로 실행되지 않도록 합니다

// Audit posture for the viewer: nothing auto-runs, nothing auto-opens.
View.LinkOptions := [];

// The shipped default already withholds launch:
//   default = [loAutoGoto, loAutoOpenURI]
//   loAutoLaunch is NOT in the default set, so external programs
//   are never started on a stray click out of the box.

기본적으로 실행을 보류하는 이유는 간단합니다. 문서 내 이동은 무해하며 URL은 표시되고 취소할 수 있지만 클릭 시 임의의 외부 프로그램을 시작하는 것은 PDF 링크에서 요청할 수 있는 단일 가장 위험한 작업이므로 옵트인하지 않는 한 비활성화됩니다. 감사자의 일은 보는 것이지 행동하는 것이 아니기 때문에 안전한 동작조차 옵트아웃합니다

디지털 서명 MDP 권한 수준

서명은 문제를 변화시킵니다. 일반 서명은 서명 당시의 바이트를 증명합니다. 문서 수정 감지 및 방지 규칙으로 생성된 인증 서명은 한 단계 더 나아갑니다. 문서가 인증된 후 합법적으로 변경될 수 있는 사항을 선언하고 규정을 준수하는 뷰어는 해당 허용 범위를 벗어난 사항이 변경되면 경고합니다. 이 권한 수준을 읽으면 감사자에게 파일이 인증되었는지 여부와 인증되었다면 얼마나 잠겨 있어야 하는지 알 수 있습니다

MDP 권한은 세 가지 정의된 값을 가진 정수입니다. 1 수준은 변경이 전혀 허용되지 않음을 의미합니다. 수정하면 인증이 중단됩니다. 2 수준은 양식 채우기 및 서명을 허용하며, 작성 및 서명하기 위해 제공되지만 달리 변경되지 않아야 하는 계약의 일반적인 사례입니다. 3 수준은 양식 채우기 및 서명 외에 주석을 추가로 허용합니다. 수준을 알면 수집 논리가 의도를 파악할 수 있습니다. 수준 1에서 인증되었지만 양식 필드나 스크립트가 포함된 문서는 자체 모순이며, 해당 모순은 플래그를 지정할 가치가 있습니다

구성 요소는 서명 수를 읽고 기본 FPDFSignatureObj_GetDocMDPPermission 호출에서 직접 채워진 Permission 필드가 해당 MDP 값을 전달하는 레코드로 각 서명을 노출합니다. 권한이 0이라는 것은 서명이 인증(DocMDP) 서명이 아니라는 것을 의미하므로 보고할 문서 수준 잠금이 없습니다

var
  I: Integer;
  Sig: TPdfSignature;
begin
  if Pdf.SignatureCount = 0 then
    WriteLn('document is not signed')
  else
    for I := 0 to Pdf.SignatureCount - 1 do
    begin
      Sig := Pdf.Signature[I];
      case Sig.Permission of
        1: WriteLn('certified: no changes allowed');
        2: WriteLn('certified: form fill and signing allowed');
        3: WriteLn('certified: form fill, signing and annotations allowed');
      else
        WriteLn('signed, but not a DocMDP certification');
      end;
    end;
end;

감사는 여기서 서명의 암호화를 검증하지 않습니다. 인증서 체인 검증은 별개의 문제입니다. 보고하는 것은 선언된 의도입니다. 즉, 이 파일은 이 수준에서 잠겼다고 말합니다. 이것은 나중에 변경되거나 단순히 활성 콘텐츠의 존재가 작성자가 문서를 봉인한 방법과 일치하는지 검토자가 판단하는 데 필요한 컨텍스트입니다

나머지 표면: 포함된 파일 및 XFA

두 가지 항목이 더 있으면 인벤토리가 완료됩니다. 포함된 파일은 첨부 파일로 PDF 내부에 포함된 전체 문서이며 안전해 보이는 보고서가 실행 파일이나 두 번째 악성 PDF를 첨부 파일 트리에 전달할 수 있기 때문에 고전적인 전달 수단입니다. 구성 요소는 첨부 파일 수와 각 첨부 파일의 이름을 노출하므로 감사를 통해 추출하거나 열지 않고도 같이 전달되는 항목을 나열할 수 있습니다

XFA 존재는 다른 플래그입니다. XFA 양식은 정적 AcroForm을 자체 렌더링 및 스크립팅 모델을 도입하는 XML 기반 양식 아키텍처로 대체하며, 일반 양식보다 더 크고 복잡한 표면입니다. XFA가 있다는 것을 알아차리기 위해 XFA를 처리할 필요는 없습니다. 그 존재만으로도 파일에 자세히 살펴볼 가치가 있는 더 풍부한 상호 작용 레이어가 포함되어 있다는 신호입니다. 구성 요소는 단일 부울 값으로 보고합니다

var
  I: Integer;
begin
  if Pdf.XFA then
    WriteLn('NOTE: document contains an XFA form layer');

  if Pdf.AttachmentCount > 0 then
  begin
    WriteLn('embedded files: ', Pdf.AttachmentCount);
    for I := 0 to Pdf.AttachmentCount - 1 do
      WriteLn('  - ', Pdf.AttachmentName[I]);
  end;
end;

보고서를 작성하는 단일 읽기 전용 루틴

조각들을 합치면 감사는 문서를 로드하고, 스크립트와 그 본문을 열거하며, 실행(Launch) 및 URI 대상을 나열하고, 서명 MDP 수준을 보고하며, 첨부 파일과 XFA를 기록하고, 결과를 로그에 쓰는 단일 절차입니다. 렌더링하는 것이 없기 때문에 비용이 저렴하고 적대적인 페이지 콘텐츠를 표시하도록 속일 수 없습니다. 출력은 평면적이고 사람이 읽을 수 있는 레코드이며 검토자나 후속 규칙이 처리할 수 있습니다

실제로 잘 작동하는 형태는 각 검색 결과를 행으로 수집하고 진정으로 위험한 항목에 접두사를 붙여 검토 대기열의 맨 위에 정렬하고 전체를 파일 옆에 유지하는 것입니다. 스크립트, 실행 작업, 첨부 파일, XFA가 없고 서명이 없거나 일관된 인증이 있는 문서는 조용히 통과합니다. 한 번에 여러 플래그를 트리거하는 문서는 다음 단계에서 열기 전에 사람이 보아야 하는 문서입니다. 감사는 사용자를 대신하여 신뢰 결정을 내리지 않습니다. 맹목적이 아니라 정보에 입각한 결정을 내릴 수 있도록 합니다

파일이 감사를 통과하고 해당 파일을 살펴보아야 하는 경우 기본 뷰어가 아닌 제한 하에 수행하십시오. Delphi에서 안전한 PDF 미리보기 구축에 대한 연습의 접근 방식은 링크 자동 처리 및 활성 콘텐츠가 통제된 보기 중 작동하지 못하도록 방지하는 방법을 보여줍니다. 이 열거를 검토자 도구가 포함된 전체 수집 파이프라인으로 접으려면 PDF 수집 및 검토 워크벤치 기사를 참조하십시오. 두 가지 모두 동일한 읽기 전용, 렌더링 없는 기반 위에 구축되며 이 블로그의 다른 곳에서 다루는 렌더링, 텍스트, 양식 및 서명 API와 함께 Delphi 및 C++Builder용 PDFium Component의 일부로 제공됩니다