نسخههای جدید PHP دو آسیبپذیری RCE بحرانی را رفع کردند
حدود ۷ دقیقه مطالعه
تیم PHP نسخههای 8.5.12، 8.4.27، 8.3.36 و 8.2.35 را منتشر کرد تا دو آسیبپذیری بحرانی از نوع اجرای کد از راه دور (RCE) بدون نیاز به احراز هویت را رفع کند. هر دو مورد در GitHub advisory رسمی امتیاز 9.2 از 10 (CVSS v4) گرفتهاند و همهی نسخههای قبل از این چهار نسخه در هر شاخه آسیبپذیرند. advisory ها روز ۲۱ اکتبر ۲۰۲۶ منتشر شدند و ChangeLog رسمی PHP برای 8.5.12 تاریخ ۲۲ اکتبر را ثبت کرده است. در همین ChangeLog هر دو شناسهی GHSA زیر یک مورد مشترک در بخش Core آمدهاند.
ضعف اول: خرابی حافظه هنگام کمبود RAM
شناسهی این مورد CVE-2026-109618 (GHSA-w8hw-w9fx-j79w) است. طبق advisory، PHP هنگام خطای out-of-memory با longjmp از context فعلی بیرون میپرد. در بعضی مسیرها حافظهی request پیش از مقداردهی کامل قابل دسترسی است؛ اگر همانجا یک allocation شکست بخورد، حافظهی request نامعتبر میماند و مرحلهی shutdown آن را میخواند یا بر اساسش شاخه میرود (use-after-free). allocation ای که شکست میخورد لازم نیست ربطی به حافظهی نامعتبر داشته باشد.
متن advisory چند راه رساندن request به OOM را شرح میدهد. درخواست multipart بدون content length قابلاستفاده میتواند از کنترل اولیهی post_max_size رد شود و دادهی بدنه را ادامه دهد. یک بخش multipart میتواند تعداد نامحدودی header داشته باشد که داخل PHP حافظهی خیلی بیشتری از حجم روی سیم مصرف میکند. file controlهای خالی در max_file_uploads شمرده نمیشوند ولی آرایهی metadata میسازند و نامهای تو در تو این کار را چند برابر میکنند. ورودیهای مخصوص خود اپلیکیشن هم میتوانند به OOM برسند. روی Apache mod_php اجرای کامل کد از راه HTTP با تنظیمات پیشفرض input و memory و راهاندازی prefork معمولی تأیید شده است. برای PHP-FPM فقط تحلیل کد حساس بودنش را نشان میدهد و exploit کامل نشان داده نشده؛ FrankenPHP و LSAPI هم «احتمالاً» حساساند و تأیید نشدهاند. اثر تأییدشده، اجرای کد بدون احراز هویت با دسترسی حساب worker وب است و قابلیت اطمینان exploit به build، extension ها، رفتار frontend و چیدمان allocator بستگی دارد.
ضعف دوم: state سراسری کهنه
شناسهی این مورد CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp) است. PHP حالتی سراسری (خطا، session، object و extension) نگه میدارد که worker پایدار بعد از پایان request هم حفظش میکند. این state ممکن است به حافظهی متعلق به request اشاره کند که آزاد شده ولی reset نشده است، و بعضی عملیات پیش از راهاندازی عادی request آن را میخوانند؛ نتیجه use-after-free است. state کهنه از دو طرف میآید: خطاهای fatal که عملیات را قبل از reset رها میکنند (خطای parse یا compile، خطای کشفنشده، شکست require، «Maximum execution time exceeded» و OOM)، و هشدارهای غیر fatal که هنگام shutdown یا پاکسازی سرور دوباره state خطا را پر میکنند. خوانندهها هم دو مسیر هستند: diagnostic های INI که پیش از شروع request هنگام اعمال بعضی مقدارها صادر میشوند (تغییر error_reporting، استفاده از @ یا خاموش کردن نمایش و لاگ جلوی آن را نمیگیرد) و session upload progress که پیش از راهاندازی عادی session، state را میخواند.
روی mod_php این مورد تأیید شده است، چون تنظیمهای htaccess و php_value و php_flag و php_admin در سطح دایرکتوری پیش از شروع request اعمال میشوند. PHP-FPM فقط وقتی در معرض است که frontend پارامترهای PHP_VALUE یا PHP_ADMIN_VALUE بفرستد که یک diagnostic بسازد. LSAPI نامزد مشروط است و برای FrankenPHP در پیکربندی پیشفرض خوانندهی معادل پیدا نشده، ولی این به معنی مصون بودن نیست. قویترین حالت تأییدشده PHP 8.5 با فعال بودن fatal_error_backtraces است و exploit تأییدشده روی mod_php بر پایهی همین حالت است. مسیر دوم، چرخهی session upload progress است که OOM هنگام پردازش input آن را فعال میکند و روی mod_php و FPM تأیید شده، هرچند در FPM به یک عملیات مناسب در اپلیکیشن بستگی دارد. اثر آن use-after-free، افشای حافظه و اجرای کد با دسترسی worker است و request تولیدکننده و request مصرفکننده باید به همان worker برسند.
رفع مشکل
برای مورد اول، patch دیگر از longjmp برای OOM استفاده نمیکند. بهجایش یک OOM نرم و تأخیری در نزدیکترین نقطهی امن VM انجام میشود، و اگر مصرف حافظه به آستانهی دوم و بالاتر برسد، یک OOM سخت فوری با _exit انجام میشود. صفحهی advisory برای مورد دوم فقط راهحل را میگوید (آپدیت) و جزئیات تغییر کد را در متنی که خواندم نداشت؛ تنها توضیح رسمی خط ChangeLog با عنوان «Avoid state corruption via bailout» است. توسعهدهندههای رفع arnaud-lb، iluuu1994، bukka و ndossche بودند و گزارشدهندهها lcfr-hax، sagitzadik-wiz، tomash-areion و amyb-oai.
هر دو advisory آپدیت به نسخهی patch شده را «تنها راهحل کامل» میدانند. بعد از آپدیت، هر worker وب را restart کن و چک کن باینریای که ترافیک وب را سرو میکند همان نسخهی جدید است، چون به نوشتهی یک وبلاگ ثانویه خروجی CLI ممکن است build دیگری را نشان بدهد:
php -v
طبق Laravel News، PHP برای نسخههای 7.4، 8.0 و 8.1 که دیگر پشتیبانی نمیشوند هم minimal source backport منتشر کرده است؛ این را در advisory رسمی ندیدم.
اگر فوراً نمیتوانی آپدیت کنی
طبق advisory برای مورد اول، تنظیم memory_limit روی ۱- را (با اعمال مدیریتی و restart همهی worker ها) مهمترین گام موقت میداند و میگوید کامل نیست، چون اگر mmap یا allocation زیرین شکست بخورد، longjmp همچنان قابل رسیدن است. در کنارش post_max_size را محافظهکارانه نگه دار، اندازهی decode شدهی request را در frontend محدود کن، max_input_nesting_level را کم کن و تعداد input variable ها، بخشهای multipart، headerهای هر بخش و آپلودها را محدود کن؛ اگر اپ از multipart استفاده نمیکند، چنین درخواستهایی را در frontend ببند. memory.max در container یا cgroup جایگزین است، با این محدودیت که تضمین نمیکند allocation شکست نخورد و کشتن container میتواند master و همهی worker ها را ببرد. از RLIMIT_AS و RLIMIT_DATA دوری کن، چون باعث شکست mmap در حد مرز و رفتن به همان مسیر آسیبپذیر میشوند. برای مورد دوم روی PHP 8.5 غیرفعال کردن fatal_error_backtraces و restart همهی worker ها تولیدکنندهی تأییدشده را حذف میکند ولی بقیهی خانوادههای state را نه، و خاموش کردن session upload progress مسیر دیگر را میبندد؛ advisory پیشنهاد میکند اگر استفاده نمیشود حتی بعد از patch هم خاموش بماند:
memory_limit = -1
fatal_error_backtraces = 0
session.upload_progress.enabled = 0
راههای دیگر کاهش ریسک در advisory عبارتاند از حذف fatal error هایی که مهاجم به آنها میرسد، حذف diagnostic های INI که پیش از شروع request اعمال میشوند، رفتار با timeout مثل fatal error، و در صورت تحمل performance استفاده از pm.max_requests برابر ۱. طبق یک منبع ثانویه (DEV Community) memory_limit برابر ۱- یعنی یک request میتواند کل حافظهی سیستم را مصرف کند؛ این تحلیل خود نویسنده است و در advisory نیامده. این تنظیمها فقط راه موقتاند و advisory صریح میگوید جای آپدیت را نمیگیرند.
منابع:
- GHSA-w8hw-w9fx-j79w: RCE via OOM corruption (php-src)
- GHSA-6wmf-4rv5-p3rp: RCE via stale global state (php-src)
- PHP 8 ChangeLog (php.net)
- PHP Updates Released For Two Critical Code Execution Vulnerabilities (Phoronix)
- PHP Security Releases Fix Two Critical RCE Vulnerabilities (Laravel News)
- PHP Patched the Two Critical RCE Flaws (DEV Community)
