یک حسابدار با دسترسی «فقط خواندنی» (read-only) می‌توانست در ابزار حسابداری متن‌باز Akaunting، فاکتورها را لغو کرده و تاریخچه پرداخت‌ها را پاک کند که این امر کسب‌وکارهای کوچک را در معرض از دست رفتن بی‌صدای داده‌ها قرار می‌داد. این نقص در نسخه 3.2.0 اصلاح شد، اما این اشتباه — یعنی گره زدن بررسی‌های دسترسی به یک لیست ثابت (hard-coded) از نام متدها — همچنان هر سیستمی را که بر کنترل دسترسی مبتنی بر نقش (RBAC) متکی است، تهدید می‌کند.

چگونه این باگ نفوذ کرد

API ابزار Akaunting حقوق کاربر را با بررسی یک لیست مجاز (allowlist) از نام متدها تأیید می‌کند. این لیست عملیات معمول CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) را پوشش می‌داد، اما چندین نقطه پایانی (endpoint) تغییر وضعیت را نادیده گرفته بود:

  • markSent
  • markCancelled
  • markReceived

از آنجایی که این هندلرها در لیست نبودند، فریم‌ورک هنگام اجرای آن‌ها هرگز روتین بررسی دسترسی را فراخوانی نمی‌کرد. کاربری با نقش «فقط خواندنی» می‌توانست یک درخواست ساده GET به نقطه پایانی markCancelled ارسال کند و سیستم با آن به عنوان یک تغییر وضعیت مشروع برخورد می‌کرد.

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

آنچه تست‌ها نشان دادند

این آسیب‌پذیری در ایمیج رسمی Docker مربوط به Akaunting مشاهده شد:

  • یک درخواست استاندارد PUT برای به‌روزرسانی یک فاکتور، خطای 403 Forbidden را برگرداند که تأیید می‌کرد مسیر معمول به‌روزرسانی محافظت شده است.
  • یک درخواست GET به نقطه پایانی لغو، بدون هیچ خطای احراز هویتی با موفقیت انجام شد که این شکاف امنیتی را آشکار کرد.

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

صورت‌های مالی می‌توانند بدون یک ردپای حسابرسی (audit trail) شفاف تغییر کنند، که این امر شناسایی کلاهبرداری و اصلاح اشتباهات ناخواسته را دشوارتر می‌کند.

راه حل

نسخه 3.2.0 نقشه دسترسی‌ها را گسترش می‌دهد تا اقدامات مربوط به وضعیت که قبلاً نادیده گرفته شده بودند را شامل شود. از آن نسخه به بعد، هر درخواستی که وضعیت یک سند را تغییر دهد — چه به عنوان ارسال‌شده، لغو‌شده یا دریافت‌شده علامت‌گذاری شود — باید از همان فرآیند تأیید نقش که برای به‌روزرسانی استاندارد استفاده می‌شود، عبور کند. این امر انتظار این موضوع را برمی‌گرداند که یک نقش «فقط خواندنی» واقعاً نمی‌تواند داده‌ها را تغییر دهد.

درس‌هایی برای توسعه‌دهندگان

  • هرگز نام متدها را با امنیت یکی ندانید. افزودن یک نقطه پایانی جدید به طور خودکار شامل محافظت نمی‌شود؛ هر متد عمومی را از نظر اثرات جانبی (side effects) بررسی کنید.
  • لیست‌های مجاز (Allowlists) تنها به اندازه خودِ لیست کامل هستند. یک لیست ایستا از فعل‌های «مجاز»، در را برای سهل‌انگاری باز می‌گذارد.
  • قصد کاربر را از فعل HTTP جدا کنید. متد GET برای حالت «فقط خواندنی» در نظر گرفته شده است، اما در اینجا یک تغییر وضعیت انجام داد. تغییرات (mutations) را به متدهای POST، PUT، DELETE و PATCH محدود کنید.
  • بررسی پوشش دسترسی‌ها را خودکار کنید. ابزارهای تحلیل ایستا (Static analysis) می‌توانند متدهای کنترلر را که فاقد فراخوانی احراز هویت هستند شناسایی کنند و شکاف‌ها را پیش از عرضه محصول پیدا کنند.
  • با حساب‌هایی با کمترین سطح دسترسی تست کنید. تست مبتنی بر Docker از یک کاربر با دسترسی «فقط خواندنی» استفاده کرد؛ بازسازی چنین سناریوهایی در خط لوله CI باعث می‌شود مشکلات مشابه در مراحل اولیه شناسایی شوند.

گام‌های بعدی

جامعه کاربران Akaunting از قبل نسخه اصلاح‌شده را منتشر کرده است. مدیران باید نسخه نمونه (instance) خود را بررسی کرده و به‌روزرسانی را سریعاً اعمال کنند.

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