یک پلتفرم آموزشی که به دانش‌آموزان اجازه می‌دهد پرس‌وجوهای (queries) MongoDB را بنویسند، ماژول vm در Node.js را کنار گذاشته و اکنون هر پرس‌وجو را قبل از اجرا به یک درخت نحو انتزاعی (AST) تبدیل می‌کند. این تغییر، یک سندباکس (sandbox) را که قابلیت شکسته شدن داشت حذف می‌کند و از بک‌اند در برابر کدهای دلخواهی که ممکن است از طریق یک رشته‌ی نامعتبر تزریق شوند، محافظت می‌کند.

چرا سندباکس اولیه شکست خورد

پیاده‌سازی اولیه، یک هندل (handle) پایگاه داده فعال را در یک فراخوانی vm.runInContext قرار می‌داد و سعی می‌کرد کاربران را با استفاده از یک عبارت منظم (regular expression) محدود کند. این روش تنها اجازه استفاده از نام متدهایی مانند find یا aggregate را می‌داد؛ قرار بود هر شناسه (identifier) دیگری فیلتر شود.

دو نقص باعث می‌شد که آن رویکرد ناامن باشد:

  • فیلتر کردن با Regex قابل دور زدن است. جاوااسکریپت به کد اجازه می‌دهد تا از نحوه‌ی براکت (obj["constructor"]) برای دسترسی به هر ویژگی (property) استفاده کند. یک مهاجم می‌تواند سازنده‌ی Function را بازیابی کند، یک تابع جدید بسازد و هر کدی را که می‌خواهد اجرا کند. عبارت منظم هرگز دسترسی به ویژگی زیرین را نمی‌بیند، زیرا منبع کد را می‌توان به بی‌شمار روش بازنویسی کرد.
  • ماژول vm یک مرز امنیتی نیست. مستندات Node بیان می‌کند که vm شیء global را ایزوله می‌کند، اما کل فرآیند (process) را خیر. با تزریق یک اتصال پایگاه داده فعال به درون سندباکس، کدِ داخلِ آن بافت (context)، توانایی فراخوانی هر متدی را روی آن اتصال حفظ می‌کرد، از جمله متدهایی که داده‌ها را می‌نویسند یا حذف می‌کنند. سندباکس مانع از تأثیرگذاری کد بر فرآیند میزبان (host process) نمی‌شد.

راهکار مبتنی بر AST

تیم توسعه، اجرای کد را با تحلیل استاتیک (static analysis) جایگزین کرد. رشته‌های پرس‌وجو اکنون به پارسر acorn فرستاده می‌شوند که یک AST تولید می‌کند؛ یعنی یک نمایش درختی از ساختار نحوی کد. AST گره‌به‌گره (node-by-node) بر اساس یک لیست سفید (whitelist) سخت‌گیرانه بررسی می‌شود:

  • مقادیر ثابت (Literals)، آرایه‌ها و اشیاء تنها زمانی مجاز هستند که به عنوان مقادیر ساده ظاهر شوند.
  • فراخوانی متدها به مجموعه‌ای از متدهای از پیش تعریف شده (find ،sort ،limit و غیره) محدود شده است. هر فراخوانی دیگری رد می‌شود.
  • دسترسی به ویژگی‌های محاسباتی (مانند obj[expr]) یا هر نوع گره‌ای که صراحتاً لیست نشده باشد، باعث شکست فوری فرآیند می‌شود.

از آنجایی که پارسر روی درخت کار می‌کند و نه روی متن خام، نمی‌توان با املای‌های متفاوت یا ترفندهای نحوه‌ی براکت، آن را فریب داد. یک زنجیره‌ی سازنده (constructor chain) که می‌توانست از فیلتر regex عبور کند، به عنوان یک گره ناشناخته ظاهر شده و قبل از اجرای هر کدی رد می‌شود.

این موضوع چه معنایی برای امنیت دارد

طراحی جدید از فلسفه‌ی «رد کردن به صورت پیش‌فرض» (deny by default) پیروی می‌کند:

  • آنچه مجاز است را تعریف کنید، نه آنچه ممنوع است. لیست سفید کردن متنی نمی‌تواند جامع باشد؛ اما یک AST مجموعه‌ی محدودی از انواع گره‌ها دارد که بررسی جامع آن‌ها را امکان‌پذیر می‌کند.
  • هرگز منابع فعال را در داخل یک سندباکس قرار ندهید. انتقال یک هندل پایگاه داده به یک بافت ایزوله شده، به کدِ سندباکس شده یک مسیر مستقیم به بک‌اند می‌دهد. رویکرد پارسر هرگز یک شیء فعال را به کد کاربر نمی‌دهد؛ بلکه فقط قصد (intent) پرس‌وجو را استخراج می‌کند.
  • ساختار را اعتبارسنجی کنید، سپس با امنیت اجرا کنید. پس از اینکه AST از مرحله‌ی اعتبارسنجی عبور کرد، پلتفرم فراخوانی‌های مجاز را با استفاده از مسیر کد مورد اعتماد خود، به متدهای واقعی درایور MongoDB ترجمه می‌کند.

نکات بعدی برای توجه

  • هرگونه استفاده از eval ،new Function یا vm را در کد خود بررسی کنید. حتی یک لیست سفید نیز می‌تواند توسط ماهیت دینامیک جاوااسکریپت دور زده شود.
  • تا حد امکان از تجزیه (parsing) مبتنی بر AST برای کدهای تولید شده توسط کاربر استفاده کنید. کتابخانه‌هایی مانند acorn ،esprima یا babel-parser این تبدیل را ساده می‌کنند.
  • دسترسی به اشیاء فعال را محدود کنید. اگر یک اتصال پایگاه داده، هندل فایل یا سوکت شبکه باید قابل دسترس باشد، آن را در یک پروکسی (proxy) قرار دهید که فقط حداقل متدهای مورد نظر شما را ارائه دهد.
  • تست موارد خاص (edge cases) را خودکارسازی کنید. پرس‌وجوهایی تولید کنید که از نحوه‌ی براکت، کلیدهای محاسباتی یا دستکاری پروتوتایپ (prototype manipulation) استفاده می‌کنند تا تأیید کنید پارسر شما آن‌ها را رد می‌کند.

نتیجه‌گیری

تکیه بر vm.runInContext به عنوان یک سندباکس، حس امنیت کاذبی ایجاد می‌کند؛ فیلترهای regex نمی‌توانند نحوه‌ی منعطف جاوااسکریپت را پوشش دهند و سندباکس نیز منابع فرآیند را ایزوله نمی‌کند. تجزیه ورودی کاربر به یک AST و لیست سفید کردنِ تنها گره‌هایی که می‌شناسید، یک سد ملموس و قابل نگهداری ایجاد می‌کند که کدهای مخرب را قبل از اجرا متوقف می‌کند. اگر پلتفرم شما به کاربران اجازه نوشتن کد را می‌دهد، همین امروز اجرای سبک eval را با تحلیل استاتیک جایگزین کنید.