Technical Article

ممیزی خطرات امنیتی PDF با PDFium در Delphi

یک سند PDF فقط کاغذ نیست. این ظرفی است که می‌تواند اسکریپت‌هایی را حمل کند که هنگام باز شدن فایل اجرا می‌شوند، لینک‌هایی که برنامه‌های خارجی را راه‌اندازی می‌کنند، لینک‌هایی که به سرورهای وب دسترسی پیدا می‌کنند، فایل‌های قرار داده شده در داخل فایل‌های دیگر و امضایی که ادعا می‌کند سند از زمانی که شخصی آن را تایید کرده تغییر نکرده است. وقتی فایلی از منبعی می‌رسد که شما آن را کنترل نمی‌کنید، ایمن‌ترین اولین اقدام رندر کردن آن نیست. بلکه خواندن آنچه فایل درباره خودش می‌گوید و ساخت فهرستی از کارهایی است که ممکن است تلاش کند انجام دهد، به طوری که یک انسان بتواند تصمیم بگیرد آیا اصلاً به گردش کار شما تعلق دارد یا خیر.

این مقاله یک مرحله ممیزی ایستا و فقط‌خواندنی را در آن سطح ریسک با استفاده از کامپوننت PDFium برای Delphi و Lazarus بررسی می‌کند. این ممیزی هرگز صفحه‌ای را ترسیم نمی‌کند. این متد ساختار سند را پارس می‌کند، بخش‌هایی از فایل را که رفتار حمل می‌کنند فهرست می‌نماید و یک گزارش ساده می‌نویسد. این تفاوت بین خواستن از یک غریبه برای خالی کردن جیب‌هایش در دم در و اعتماد کردن به او به خاطر لبخندش است.

یک ممیزی چیست و چه چیزی نیست

درباره مرز کار واضح باشید. یک پیش‌نمایش ایزوله‌شده (sandboxed preview) یک فایل را تحت محدودیت‌های شدید رندر می‌کند تا کاربر بتواند بدون برخورد فایل با بقیه سیستم، به آن نگاه کند. ممیزی قبل از آن انجام می‌شود. این یک بازرسی بدون رندر است که تنها خروجی آن توصیفی از سطح تهدید است: کدام اسکریپت‌ها وجود دارند، کدام اکشن‌ها به لینک‌ها متصل شده‌اند، آیا فایل امضا شده است و با چه شدتی، و چه چیزی پیوست شده است. شما آن را زمانی اجرا می‌کنید که یک سند از مرز اعتماد عبور کند، در زمان ورود از ایمیل، فرم آپلود یا فید همکار، قبل از اینکه مراحل بعدی آن را برای واقعی باز کنند.

کامپوننت یک سند را برای ممیزی به همان روشی بارگذاری می‌کند که برای بقیه کارها انجام می‌دهد. شما نام فایل را تنظیم کرده و آن را فعال می‌کنید که داده‌های ارجاع متقاطع و کاتالوگ سند را بدون رندر کردن حتی یک صفحه پارس می‌کند. همه موارد زیر از آن حالت بارگذاری‌شده و رندر نشده خوانده می‌شوند.

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;

جاوااسکریپت سند در درخت نام

اولین چیزی که باید فهرست شود کد است. یک PDF می‌تواند جاوااسکریپت در سطح سند را حمل کند: اسکریپت‌هایی که به هیچ صفحه یا فیلدی متصل نیستند بلکه به خود سند متصل هستند که در درخت /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);
  end;
end;

فایلی با اسکریپت‌های سند صفر به طور خودکار ایمن نیست، زیرا اسکریپت‌های صفحه و فیلد نیز وجود دارند، اما فایلی با اسکریپت‌های سند همیشه شایسته نگاه دوم است. تعداد حضور به تنهایی یک دروازه مفید است و بدنه چیزی است که یک دروازه را به یک قضاوت تبدیل می‌کند.

اکشن‌های Launch و URI

رفتار بعدی برای فهرست‌بندی روی لینک‌ها و حاشیه‌نویسی‌ها زندگی می‌کند. دو نوع اکشن برای یک ممیز بیشترین اهمیت را دارند. یک اکشن Launch یک برنامه خارجی را راه‌اندازی می‌کند یا یک فایل محلی را هنگامی که لینک تحریک می‌شود باز می‌نماید. یک اکشن URI یک هدف وب را باز می‌کند. بازبینی که به یک سند مشکوک نگاه می‌کند باید بتواند بدون کلیک روی هیچ چیزی ببیند که دکمه‌ای در صفحه سه برای راه‌اندازی cmd.exe یا باز کردن یک URL که با برند روی صفحه مطابقت ندارد، متصل شده است.

کامپوننت لینک‌هایی را که پیدا می‌کند طبقه‌بندی کرده و نوع اکشن و مسیر هدف را برای هر کدام نشان می‌دهد، بنابراین ممیزی می‌تواند هر اکشن Launch و URI را با مقصد آن لیست کند. این گزارش‌دهی است، نه اجرا. ممیز اکشن را از ساختار می‌خواند و آن را می‌نویسد. هرگز آن را دنبال نمی‌کند.

کنترل نمایشگر (viewer control) که اسناد را رندر می‌کند جایی است که دنبال کردن یک اکشن در آن اتفاق می‌افتد و وضعیت پیش‌فرض آن به طور تعمدی تدافعی است.

کنترل TPdfView دارای مجموعه‌ای از LinkOptions است که تصمیم می‌گیرد کدام انواع لینک به طور خودکار با یک کلیک اجرا شوند. پیش‌فرض آن [loAutoGoto, loAutoOpenURI] است که به این معنی است که پرش‌های درون‌سندی و آدرس‌های وب ممکن است باز شوند، اما loAutoLaunch وجود ندارد، بنابراین اکشن‌های launch هرگز به طور خودکار اجرا نمی‌شوند. برای یک جریان کار ممیزی، شما فراتر می‌روید و مجموعه را کاملاً پاک می‌کنید، به طوری که در حالی که هنوز در حال تصمیم‌گیری برای اعتماد به فایل هستید، هیچ‌چیز به طور خودکار اجرا نشود.

// 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.

استدلال پشت عدم اجازه به launch به طور پیش‌فرض ساده است. پرش درون سند بی‌ضرر است و یک URL مرئی و قابل لغو است، اما راه‌اندازی یک برنامه خارجی دلخواه از طریق یک کلیک خطرناک‌ترین کاری است که یک لینک PDF می‌تواند درخواست کند، بنابراین خاموش است مگر اینکه آن را انتخاب کنید. یک ممیز حتی رفتارهای امن را نیز غیرفعال می‌کند، زیرا کار او نگاه کردن است، نه اقدام کردن.

سطح مجوز MDP امضای دیجیتال

امضاها سوال را تغییر می‌دهند. یک امضای ساده بایت‌ها را در زمان امضا تایید می‌کند. یک امضای تایید (certification signature)، از نوعی که با یک قانون شناسایی و جلوگیری از تغییر سند ساخته شده است، فراتر می‌رود: اعلام می‌کند چه چیزی ممکن است پس از تایید سند به طور قانونی تغییر کند و یک نمایشگر منطبق در صورتی که هر چیزی خارج از آن اجازه لمس شده باشد، هشدار می‌دهد. خواندن آن سطح مجوز به یک ممیز می‌گوید آیا فایلی تایید شده است و اگر چنین است، چقدر قرار است قفل باشد.

مجوز MDP یک عدد صحیح با سه مقدار تعریف شده است. سطح 1 به این معنی است که اصلاً هیچ تغییری مجاز نیست؛ هرگونه تغییر تاییدیه را باطل می‌کند. سطح 2 پر کردن فرم و امضا را مجاز می‌داند، حالت متداول برای قراردادی که قرار است تکمیل و امضا شود اما تغییر دیگری در آن ایجاد نشود. سطح 3 علاوه بر پر کردن فرم و امضا، حاشیه‌نویسی را نیز مجاز می‌داند. دانستن سطح به منطق ورودی شما اجازه می‌دهد تا در مورد هدف استدلال کند: سندی که در سطح ۱ تایید شده اما با این حال فیلدهای فرم یا اسکریپت‌ها را حمل می‌کند، با خودش در تناقض است و این تناقض ارزش علامت‌گذاری دارد.

کامپوننت تعداد امضاها را می‌خواند و هر کدام را به عنوان یک رکورد نشان می‌دهد که فیلد Permission آن مقدار MDP را حمل می‌کند، که مستقیماً از فراخوانی زیرین FPDFSignatureObj_GetDocMDPPermission پر شده است. مجوز صفر به این معنی است که امضا یک امضای تایید (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

دو مورد دیگر یک فهرست کامل را کامل می‌کنند. فایل‌های تعبیه‌شده (Embedded files) کل اسنادی هستند که در داخل PDF به عنوان پیوست حمل می‌شوند و آن‌ها یک وسیله تحویل کلاسیک هستند، زیرا گزارشی با ظاهر بی‌خطر می‌تواند یک فایل اجرایی یا یک PDF مخرب دوم را در درخت پیوست خود ارسال کند. کامپوننت تعداد پیوست‌ها و نام هر پیوست را نشان می‌دهد، بنابراین ممیزی می‌تواند بدون استخراج یا باز کردن هیچ بخشی از آن، آنچه را که به همراه دارد لیست کند.

حضور XFA پرچم دیگر است. یک فرم XFA به جای AcroForm ایستا، یک معماری فرم مبتنی بر XML را جایگزین می‌کند که مدل رندر و اسکریپت‌نویسی خود را به همراه دارد، سطح بزرگ‌تر و پیچیده‌تری نسبت به یک فرم ساده. شما نیازی به پردازش 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 را ثبت کرده و یافته‌ها را در یک لاگ می‌نویسد. این متد چیزی را رندر نمی‌کند، بنابراین ارزان است و نمی‌توان آن را برای نمایش محتوای صفحه خصمانه فریب داد. خروجی یک رکورد تخت و خوانا برای انسان است که یک بازبین یا یک قانون پایین‌دستی می‌تواند روی آن عمل کند.

شکلی که در عمل به خوبی کار می‌کند این است که هر یافته را به عنوان یک خط جمع‌آوری کنید، برای یافته‌های واقعاً پرخطر پیشوند بگذارید تا در بالای صف بازبینی مرتب شوند و کل موضوع را در کنار فایل ذخیره کنید. سندی بدون اسکریپت، بدون اکشن launch، بدون پیوست، بدون XFA و بدون امضا یا دارای یک تاییدیه منسجم به آرامی عبور می‌کند. سندی که همزمان چندین پرچم را فعال می‌کند، سندی است که یک شخص باید قبل از اینکه هر مرحله بعدی آن را باز کند ببیند. ممیزی تصمیم اعتماد را برای شما نمی‌گیرد، بلکه اطمینان حاصل می‌کند که تصمیم به جای کورکورانه بودن، آگاهانه گرفته می‌شود.

هنگامی که یک فایل ممیزی را پاک کرد و شما نیاز به نگاه کردن به آن داشتید، این کار را تحت محدودیت انجام دهید تا در یک نمایشگر پیش‌فرض. رویکرد موجود در راهنمای ما برای ساخت پیش‌نمایش ایمن PDF در Delphi نشان می‌دهد که چگونه می‌توان از عملکرد خودکار لینک و محتوای فعال در طول یک نگاه کنترل‌شده جلوگیری کرد. برای گنجاندن این فهرست در یک خط لوله ورودی کامل با ابزار بازبینی، به مقاله کارگاه بررسی و ورودی PDF مراجعه کنید. هر دو بر روی همان پایه فقط‌خواندنی و بدون رندر ساخته شده‌اند و به عنوان بخشی از کامپوننت PDFium برای Delphi و C++Builder در کنار APIهای رندر، متن، فرم و امضا که در بخش‌های دیگر این وبلاگ پوشش داده شده‌اند، ارائه می‌شوند.