امروز درگیر بررسی امنیت یکی از سایت‌های WordPress شدم؛ سایتی که در ظاهر بدون مشکل کار می‌کرد، اما با کمی بررسی مشخص شد مهاجم مدت‌ها قبل به آن دسترسی پیدا کرده و چندین مکانیزم برای حفظ دسترسی خود ایجاد کرده است.

این نوشته گزارش تجربه امروز من است؛ از اولین بررسی‌ها تا پیدا کردن Administratorهای جعلی، Backdoorها، فایل‌های مخفی و در نهایت فهمیدن اینکه چرا یک اسکن ساده Antivirus نمی‌تواند برای اطمینان از سلامت WordPress کافی باشد.


#همه‌چیز از کجا شروع شد؟

اولین مرحله بررسی Logهای سرور بود.

در لاگ‌های Apache و PHP درخواست‌هایی به:

wp-login.php

وجود داشت.

در نگاه اول چنین چیزی می‌تواند نشانه حمله Brute Force باشد، اما هنگام بررسی متوجه شدم بخشی از این درخواست‌ها از IP خودم بوده است.

بنابراین صرف وجود یک درخواست مشکوک در Log، دلیل کافی برای اعلام Incident نیست.

در بررسی امنیتی باید همیشه سه چیز را کنار هم گذاشت:

IP
+
Timestamp
+
نوع درخواست و رفتار آن

#اولین مورد مشکوک؛ MU Plugin

در ادامه به مسیر زیر رسیدم:

wp-content/mu-plugins/

Must-Use Pluginها با Pluginهای عادی تفاوت دارند.

آنها بدون نیاز به فعال‌سازی از طریق پنل WordPress بارگذاری می‌شوند.

در این پوشه فایل‌هایی با نام‌هایی شبیه Pluginهای معروف وجود داشت:

akismet.php
contact-form-7.php
wp-fastest-cache.php
wp-fixplugin.php
wp-yoast-seo.php

اما نکته عجیب این بود که این Pluginها اصلاً در سایت نصب نبودند.

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


#Contact Form 7 تقلبی

موردی که ابتدا فقط مشکوک بود، با بررسی کد کاملاً مشخص شد.

فایلی با نام:

wp-content/mu-plugins/contact-form-7.php

خودش را به شکل Contact Form 7 معرفی می‌کرد:

php
/*
Plugin Name: Contact Form 7
Version: 6.1.5
*/

اما این Contact Form 7 واقعی نبود.

داخل فایل مکانیزمی وجود داشت که با یک پارامتر مخفی در HTTP Request فعال می‌شد:

wp_acrions

سپس داده رمزگذاری‌شده را دریافت و Decode می‌کرد.

کد از تکنیک‌هایی مثل:

Base64
ROT13
AES-256-CBC

استفاده می‌کرد.

اما مسئله اصلی بعد از Decode اتفاق می‌افتاد.


#دریافت کد PHP از سرور مهاجم

Backdoor می‌توانست یک URL را از داده رمزگذاری‌شده استخراج کند و با CURL به آن متصل شود:

php
curl_init( $url );
curl_exec( $ch );

سپس Response دریافت‌شده را به‌عنوان PHP بررسی می‌کرد.

بعد آن را در فایلی با پسوند .jpg می‌نوشت:

wp-content/uploads/wpcf7-module-cache/xxxxxxxx_xxxxx.jpg

اما این فایل واقعاً تصویر نبود.

کد PHP بود.

و سپس:

php
include_once $payload_path;

اجرا می‌شد.

یعنی مهاجم می‌توانست کد PHP دلخواه خود را از راه دور دریافت کرده و روی سایت اجرا کند.

بعد از اجرای کد نیز فایل حذف می‌شد.

این دقیقاً یکی از روش‌های رایج برای کاهش ردپا در Backdoorهاست.

ساختار کلی تقریباً چنین بود:

HTTP Request
     ↓
پارامتر مخفی
     ↓
Decode / Decrypt
     ↓
استخراج URL
     ↓
CURL
     ↓
دریافت PHP
     ↓
ذخیره موقت با پسوند .jpg
     ↓
include_once()
     ↓
اجرای کد
     ↓
حذف فایل

در این مرحله دیگر مشخص بود که با یک Plugin خراب یا کد معمولی WordPress طرف نیستیم.

با یک Backdoor واقعی مواجه بودیم.


#سرقت رمز عبور Administrator

اما قسمت بدتر هنوز باقی مانده بود.

این فایل فقط امکان اجرای کد از راه دور را فراهم نمی‌کرد.

در Hook مربوط به Authentication، رمز عبوری که کاربر هنگام Login وارد می‌کرد دریافت می‌شد:

php
$GLOBALS['_wfc_login_pwd'] = $password;

و سپس هنگام Login:

php
'pwd' => $pwd

داخل Log نوشته می‌شد.

مسیر Log:

wp-content/uploads/logs/wpcf7-integration-debug.txt

بنابراین این Backdoor عملاً قابلیت Credential Logging نیز داشت.

یعنی مهاجم می‌توانست رمز عبور کاربران را جمع‌آوری کند.

این موضوع اهمیت پاک‌سازی را چند برابر می‌کند.


#تلاش برای نوشتن فایل .htaccess

در همین فایل یک رفتار دیگر هم وجود داشت.

کد تلاش می‌کرد در:

wp-content/uploads/logs/

فایل .htaccess ایجاد کند تا دسترسی مستقیم به Logها کنترل شود.

در Log سرور نیز خطایی شبیه این دیده شد:

file_put_contents(.../uploads/logs/.htaccess)
Permission denied

این اتفاق در ابتدا شاید یک خطای ساده به نظر برسد، اما وقتی کل کد را بررسی کردم مشخص شد این عملیات بخشی از همان Backdoor بوده است.


#پیدا شدن Administratorهای جعلی

بعد از بررسی فایل‌ها، سراغ Database و کاربران WordPress رفتم.

دستور:

bash
wp user list --role=administrator --allow-root

چند Administrator ناشناس نشان داد.

نام‌هایی شبیه:

wp_3cojlz
wp_5jkf4j
wp_63f312
wp_amjfno
wp_eycjyd
wp_qy2rjt
wp_skbrjs
wp_t5nagt
wp_59qeju

وجود داشت.

تاریخ ایجاد این حساب‌ها نیز عمدتاً در بازه 17 تا 19 آگوست 2026 بود.

در حالی که Administrator اصلی سایت سال‌ها قبل ایجاد شده بود.

این کاربران پس از بررسی به‌عنوان حساب‌های غیرمجاز شناسایی و حذف شدند.

مثلاً:

bash
wp user delete 23 --allow-root

و در نهایت Administratorهای غیرمجاز حذف شدند.


#چرا ClamAV چیزی پیدا نکرد؟

برای بررسی بیشتر ClamAV را اجرا کردم.

نتیجه:

Scanned files: 11096
Infected files: 0

اما در همان زمان ما چند فایل PHP مخرب واقعی پیدا کرده بودیم.

این تجربه یک نکته مهم را نشان داد:

نتیجه Clean بودن یک Antivirus به معنی Clean بودن WordPress نیست.

ClamAV یک Antivirus عمومی است و بسیاری از Backdoorهای PHP که اختصاصی برای یک سایت ساخته شده‌اند ممکن است در Signatureهای آن وجود نداشته باشند.

برای همین بررسی امنیتی WordPress بهتر است چند لایه داشته باشد:

Antivirus
     +
YARA
     +
Code Review
     +
WordPress Integrity Check
     +
User Audit
     +
Plugin/Theme Audit
     +
Log Analysis

#بررسی WordPress Core

برای بررسی فایل‌های اصلی WordPress می‌توان از WP-CLI استفاده کرد:

bash
wp core verify-checksums --allow-root

این یکی از تست‌های مفید برای اطمینان از سالم بودن فایل‌های Core است.


#بررسی Pluginها

برای مشاهده Pluginهای نصب‌شده:

bash
wp plugin list --allow-root

اما در یک Incident نباید فقط به همین لیست اکتفا کرد.

باید مسیرهایی مثل:

wp-content/plugins/
wp-content/mu-plugins/
wp-content/themes/
wp-content/uploads/

نیز بررسی شوند.

خصوصاً:

mu-plugins

چون فایل‌های PHP این پوشه می‌توانند بدون فعال‌سازی معمول WordPress اجرا شوند.


#پیدا کردن فایل‌های PHP

یک دستور ساده برای پیدا کردن فایل‌های PHP:

bash
find wp-content -type f -name "*.php"

و برای فایل‌های PHP مخفی:

bash
find wp-content -type f -name ".*.php"

#جستجوی کدهای مشکوک

برای یک بررسی سریع می‌توان از grep استفاده کرد:

bash
grep -RInE \
'eval\s*\(|base64_decode\s*\(|gzinflate\s*\(|gzdecode\s*\(|str_rot13\s*\(|shell_exec\s*\(|system\s*\(|passthru\s*\(|proc_open\s*\(|popen\s*\(' \
wp-content

البته این دستور فقط برای Triage مناسب است.

مثلاً base64_decode() می‌تواند در یک Plugin سالم نیز وجود داشته باشد.

بنابراین:

Suspicious ≠ Malicious

و باید Context کد بررسی شود.


#بررسی Cronهای WordPress

Backdoorها گاهی از Cron برای Persistence استفاده می‌کنند.

برای بررسی:

bash
wp cron event list --allow-root

#بررسی PHP Syntax

برای بررسی Syntax یک فایل:

bash
php -l file.php

مثلاً:

bash
php -l wp-content/plugins/example/example.php

این دستور Malware را تشخیص نمی‌دهد؛ فقط Syntax PHP را بررسی می‌کند.


#یک نکته مهم درباره MU Pluginها

در جریان بررسی امروز مشخص شد که صرفاً نگاه کردن به صفحه Plugins کافی نیست.

ممکن است در پنل WordPress هیچ Plugin مشکوکی مشاهده نشود، ولی در:

wp-content/mu-plugins/

فایل PHP مخربی وجود داشته باشد که WordPress آن را به‌صورت خودکار Load کند.

به همین دلیل در بررسی Incident باید این پوشه همیشه بررسی شود.


#مرحله بعد؛ اسکن Plugin قبل از Upload

بعد از این اتفاق به این نتیجه رسیدم که فقط بررسی سایت Production کافی نیست.

بهتر است Plugin قبل از Upload شدن روی Server نیز بررسی شود.

در Linux می‌توان YARA را نصب کرد:

bash
sudo pacman -S yara

و سپس Ruleهای مخصوص PHP Malware را روی فایل‌ها اجرا کرد.

یک اسکنر مناسب می‌تواند ترکیبی از این موارد باشد:

YARA
+
PHP suspicious functions
+
Obfuscation detection
+
Hidden PHP files
+
Remote code loading
+
Dynamic execution
+
PHP syntax check

این روش می‌تواند قبل از ورود یک فایل مشکوک به Production، حداقل یک مرحله دفاعی اضافه ایجاد کند.


#Credentialها؛ چیزی که نباید فراموش شود

بعد از پیدا شدن Backdoor، حذف فایل به‌تنهایی کافی نیست.

وقتی مشخص شد Backdoor قابلیت ثبت Password داشته، باید فرض را بر این گذاشت که Credentialهای مورد استفاده در آن سایت ممکن است افشا شده باشند.

بنابراین پس از پاک‌سازی باید Credentialهای مرتبط بررسی و در صورت نیاز تعویض شوند:

WordPress Administrator
Hosting Panel
FTP/SFTP
SSH
Database
API Keys
SMTP Credentials
Third-party Services

همچنین Sessionها و Loginهای فعال نیز باید بررسی شوند.


#جمع‌بندی

این Incident برای من یک یادآوری مهم بود:

امنیت WordPress با نصب یک Security Plugin یا اجرای یک Antivirus تمام نمی‌شود.

یک مهاجم می‌تواند:

Administrator جعلی ایجاد کند
        ↓
MU Plugin ایجاد کند
        ↓
Backdoor نصب کند
        ↓
Credential سرقت کند
        ↓
Remote Code Execution داشته باشد
        ↓
Persistence ایجاد کند

و در عین حال سایت برای کاربر عادی کاملاً سالم به نظر برسد.

مهم‌ترین چیزی که از بررسی امروز برای من باقی ماند این است:

در امنیت، Clean بودن یک Scan به معنی Clean بودن سیستم نیست.

باید چند منبع مختلف را کنار هم قرار داد:

Logs
+
Filesystem
+
Database
+
WordPress Core
+
Plugins
+
Themes
+
MU Plugins
+
Cron
+
Users
+
Network Activity

و مهم‌تر از همه، بعد از حذف Malware باید دنبال روش ورود و مکانیزم Persistence نیز گشت.

چون اگر فقط فایل آلوده را حذف کنیم اما راه ورود مهاجم را نبندیم، احتمال بازگشت آلودگی همچنان وجود دارد.

این دقیقاً چیزی بود که امروز در بررسی این سایت با آن مواجه شدم.