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

چرا PHP امن در وردپرس مهم است؟

در اکوسیستم وردپرس، کد PHP شما با دسترسی کامل روی سرور اجرا می‌شود. اگر کد شما حفره داشته باشد، مهاجم می‌تواند:

  1. داده کاربران را ببیند یا تغییر دهد: از سفارش‌های فروشگاه تا اطلاعات کاربران.
  2. سایت را به یک بستر حمله تبدیل کند: برای ارسال اسپم، حمله به سایت‌های دیگر یا استخراج ارز دیجیتال.
  3. کل سرور را در معرض خطر بگذارد: در بعضی موارد، حفره در PHP می‌تواند به دسترسی سطح سیستم منجر شود.
  4. اعتبار کسب‌وکار را نابود کند: حتی اگر سایت هک شود و بعداً ترمیم شود، اعتماد کاربران از دست می‌رود.

تجربه‌ام این است که در ۹۰٪ پرونده‌های پاکسازی که دیده‌ام، ریشه در نبود یکی از قواعدی است که در ادامه توضیح می‌دهم. این قواعد، الگوهای شناخته‌شده هستند و رعایتشان از روز اول، تفاوت بین سایت امن و سایت آسیب‌پذیر است. مقایسه کلی امنیت در امنیت وردپرس برای مبتدیان و افزونه‌های امنیتی وردپرس آمده است؛ این مقاله، روی لایه PHP تمرکز می‌کند.

کد امن، کدی است که از روز اول امن نوشته شده باشد؛ نه کدی که بعداً پچ شود. پچ کردن همیشه سوراخ دیگری باقی می‌گذارد.

چهار قاعده طلایی امنیت PHP

چهار قاعده که اگر روز اول رعایت شوند، بیش از ۹۰٪ آسیب‌پذیری‌های رایج را حذف می‌کنند:

  1. هر ورودی از کاربر، پاک‌سازی شود (Sanitize).
  2. هر خروجی به HTML، escape شود (Escape).
  3. هر فرم یا درخواست AJAX، با نانس محافظت شود (Nonce).
  4. هر عملیات حساس، با بررسی دسترسی کاربر محافظت شود (Capability check).

این چهار قاعده، به‌تنهایی بیشترین اثر را در امنیت کد PHP وردپرس دارند. یک قاعده پنجم که در کد افزونه‌ها مهم است: استفاده از توابع آماده وردپرس برای کوئری، به‌جای SQL مستقیم. این پنج قاعده در پروژه‌های واقعی، معیار من برای قبول یا رد کد هستند. اگر کد شما این پنج قاعده را رعایت می‌کند، احتمال آسیب‌پذیری جدی به‌شدت کاهش می‌یابد.

پاک‌سازی ورودی (Sanitize)

هر داده‌ای که از کاربر می‌آید — از فرم، از URL، از کوکی، از درخواست AJAX — باید قبل از استفاده یا ذخیره، پاک‌سازی شود. توابع پاک‌سازی در وردپرس، بر اساس نوع داده:

  • متن کوتاه: sanitize_text_field( $input ) — حذف تگ‌ها، کاراکترهای اضافی و فاصله‌های تکراری.
  • متن چندخطی ساده: sanitize_textarea_field( $input ) — مشابه متن کوتاه، اما خطوط جدید حفظ می‌شوند.
  • ایمیل: sanitize_email( $input ) — حذف کاراکترهای غیرمجاز از ایمیل.
  • عدد صحیح: absint( $input ) — تبدیل به عدد صحیح مثبت. اگر منفی بود، صفر می‌شود.
  • عدد اعشاری: floatval( $input ) — برای محاسبات با اعداد اعشاری.
  • URL: esc_url_raw( $input ) — برای ذخیره URL در دیتابیس.
  • کلید (slug): sanitize_key( $input ) — فقط حروف کوچک، اعداد، خط تیره و زیر خط.
  • HTML مجاز: wp_kses_post( $input ) — اجازه HTML محدود و امن. برای محتوایی که ممکن است شامل تگ باشد.
  • نام فایل: sanitize_file_name( $input ) — حذف کاراکترهای خطرناک از نام فایل.

الگوی صحیح در یک فرم پردازش:

$name  = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );
$age   = absint( $_POST['age'] );
$url   = esc_url_raw( $_POST['url'] );

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

Escape خروجی

خروجی هر داده‌ای که از دیتابیس یا از کاربر می‌آید، قبل از نمایش در HTML، باید escape شود. تفاوت escape با sanitize: sanitize قبل از ذخیره، escape قبل از نمایش. سه تابع اصلی:

  • esc_html( $text ): برای متن بین تگ‌ها — حذف تگ‌های HTML و کاراکترهای خطرناک.
  • esc_attr( $text ): برای مقادیر attribute — ایمن‌سازی برای قرار گرفتن در class=""، value="" و مشابه.
  • esc_url( $url ): برای URL در href و src — حذف پروتکل‌های خطرناک مثل javascript:.
  • esc_js( $text ): برای داده در جاوااسکریپت inline.
  • esc_textarea( $text ): برای محتوا در <textarea>.

الگوی صحیح در template:

// متن ساده
<h2><?php echo esc_html( $title ); ?></h2>

// attribute
<div class="<?php echo esc_attr( $class_name ); ?>">

// URL
<a href="<?php echo esc_url( $url ); ?>">لینک</a>

اشتباه رایج: نمایش خروجی بدون escape. کدی مثل <h2><?php echo $title; ?></h2> خطر XSS جدی دارد. اگر داده از کاربر آمده باشد، مهاجم می‌تواند کد JS در آن تزریق کند. راهنمای کامل در پاک‌سازی داده‌ها در وردپرس آمده است.

Sanitize و escape، دو روی یک سکه امنیت PHP هستند؛ یکی قبل از ذخیره، دیگری قبل از نمایش.

نانس و محافظت از فرم

نانس (Nonce)، یک مقدار یکتا و موقت است که برای هر فرم یا درخواست AJAX تولید می‌شود و به سرور می‌گوید که این درخواست از منبع معتبر آمده است. سه کاربرد اصلی:

  • محافظت از فرم در برابر CSRF: جلوگیری از ارسال درخواست از یک سایت دیگر.
  • محافظت از درخواست AJAX: اطمینان از اینکه درخواست از پنل ادمین سایت شما آمده، نه از یک منبع بیرونی.
  • محافظت از لینک‌های حساس: مثل حذف یک نوشته یا تغییر تنظیمات از طریق URL.

الگوی صحیح در فرم:

<form method="post">
    <?php wp_nonce_field( 'my_action', 'my_nonce' ); ?>
    <input type="text" name="title" />
    <button type="submit">ذخیره</button>
</form>

و بررسی در پردازش:

if ( ! isset( $_POST['my_nonce'] )
    || ! wp_verify_nonce( $_POST['my_nonce'], 'my_action' ) ) {
    wp_die( 'خطای اعتبارسنجی' );
}
// ادامه پردازش

راهنمای کامل در نانس وردپرس و امنیت فرم و الگوی AJAX در پیاده‌سازی نانس در فرم‌های سفارشی آمده است.

Capability check و دسترسی

پس از نانس، باید بررسی شود که کاربر جاری دسترسی لازم برای عملیات را دارد. تابع اصلی:

if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( 'شما دسترسی به این عملیات ندارید' );
}

Capabilityهای رایج در وردپرس:

  • manage_options — مدیریت تنظیمات سایت. فقط ادمین.
  • edit_posts — ویرایش نوشته‌ها. برای نویسنده و بالاتر.
  • publish_posts — انتشار نوشته. برای نویسنده و بالاتر.
  • edit_users — ویرایش کاربران. برای ادمین.
  • manage_categories — مدیریت دسته‌بندی‌ها.

راهنمای کامل در توابع نقش‌ها و دسترسی‌ها در وردپرس و الگوی دقیق در امن‌سازی ورود ادمین وردپرس آمده است. یک نکته مهم: برای عملیات در پیشخوان، از capabilityهایی مثل manage_options استفاده کنید. برای عملیات در front-end (مثل ارسال فرم تماس)، بررسی is_user_logged_in کافی است، اما اگر فرم عمومی است، نانس کافی است و نیازی به capability نیست.

کوئری امن با wpdb

کوئری مستقیم SQL در وردپرس، باید از طریق $wpdb->prepare() انجام شود تا از تزریق SQL جلوگیری شود. الگوی صحیح:

$author_id = absint( $_GET['author'] );
$status    = sanitize_key( $_GET['status'] );

$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts}
     WHERE post_author = %d AND post_status = %s",
    $author_id,
    $status
);
$results = $wpdb->get_results( $sql );

پلیس‌هولدرها در prepare:

  • %d — عدد صحیح.
  • %f — عدد اعشاری.
  • %s — رشته.

مثال اشتباه که خطر SQL Injection دارد:

// اشتباه: بدون prepare
$wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_author = {$_GET['author']}" );

در این مثال، مهاجم می‌تواند با ارسال author=1 OR 1=1 تمام نوشته‌ها را ببیند یا با author=1; DROP TABLE wp_posts; جدول را پاک کند. راهنمای کامل در توابع کوئری سفارشی در وردپرس و بهینه‌سازی کوئری‌های وردپرس آمده است.

جلوگیری از XSS

XSS (Cross-Site Scripting)، یکی از پرتکرارترین آسیب‌پذیری‌هاست. الگوی رخداد:

  1. کاربر ورودی مخرب مثل <script>alert('xss')</script> ارسال می‌کند.
  2. کد شما آن را بدون پاک‌سازی ذخیره می‌کند.
  3. صفحه برای کاربر دیگری نمایش داده می‌شود و آن کاربر، اسکریپت مخرب را اجرا می‌کند.

راه‌های جلوگیری:

  • Sanitize در ورودی: هر داده از کاربر، قبل از ذخیره پاک‌سازی شود. اگر HTML باید نگه داشته شود، از wp_kses_post استفاده کنید.
  • Escape در خروجی: حتی اگر ورودی پاک‌سازی شده باشد، در نمایش هم escape کنید. لایه دوم دفاع است.
  • Content Security Policy: هدر CSP در پاسخ سرور، اجرای اسکریپت‌های غیرمجاز را مسدود می‌کند. مسیر تنظیم در هدرهای امنیتی HTTP.

یک نکته مهم: در وردپرس، فیلتر the_content به‌طور پیش‌فرض با wp_kses_post اجرا می‌شود. اما اگر داده‌ای در جای دیگری (متاباکس، فیلد سفارشی) نمایش می‌دهید، آن escape به عهده شماست. نمونه پرتکرار XSS در سایت‌های وردپرسی، از نمایش متادیتا بدون escape در قالب می‌آید.

XSS، حمله‌ای است که از اعتماد نابجای سایت به داده کاربر شروع می‌شود. لایه دوم escape، حتی اگر لایه اول را فراموش کردید، شما را نجات می‌دهد.

جلوگیری از CSRF

CSRF (Cross-Site Request Forgery)، حمله‌ای است که در آن، سایت مهاجم درخواستی به سایت شما می‌فرستد و از نشست کاربر معتبر استفاده می‌کند. مثال:

  1. کاربر در سایت شما لاگین است.
  2. کاربر به سایت مهاجم می‌رود.
  3. سایت مهاجم یک تصویر یا فرم مخفی دارد که به سایت شما درخواست می‌فرستد.
  4. مرورگر کاربر، کوکی لاگین را به‌طور خودکار می‌فرستد و درخواست اجرا می‌شود.

پیشگیری از CSRF در وردپرس، با نانس انجام می‌شود. الگوی پایه:

// در فرم
<?php wp_nonce_field( 'delete_post_' . $post_id, 'delete_nonce' ); ?>

// در پردازش
if ( ! wp_verify_nonce( $_POST['delete_nonce'], 'delete_post_' . $post_id ) ) {
    wp_die( 'درخواست نامعتبر' );
}

هر فرم یا لینکی که عملیات مهمی انجام می‌دهد — حذف، ویرایش، تغییر وضعیت — باید با نانس محافظت شود. راهنمای کامل در نانس وردپرس و امنیت فرم و CSRF چیست و چگونه دفع می‌شود آمده است. یکی از نکات مهمی که کمتر گفته می‌شود: نانس‌های وردپرس، هر ۱۲ ساعت منقضی می‌شوند؛ بنابراین فرم‌های طولانی‌مدت ممکن است نانس منقضی داشته باشند. مسیر مدیریت این سناریو در همان راهنمای نانس آمده است.

جدول چک‌لیست امنیتی

جمع‌بندی چک‌لیست امنیتی PHP در وردپرس:

لایهقاعدهتابع یا ابزار
ورودیپاک‌سازی ورودی بر اساس نوعsanitize_text_field، absint، sanitize_email
خروجیEscape خروجی بر اساس contextesc_html، esc_attr، esc_url
فرمنانس در همه فرم‌هاwp_nonce_field، wp_verify_nonce
دسترسیCapability check قبل از عملیاتcurrent_user_can
کوئریاستفاده از prepare یا توابع آمادهwpdb->prepare
فایلاعتبارسنجی نوع و حجم فایل آپلودwp_check_filetype، wp_handle_upload
هدرهدرهای امنیتی HTTPCSP، X-Frame-Options، HSTS

ابزارهای بررسی امنیت کد

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

  • PHP_CodeSniffer با استاندارد WordPress: ابزار رسمی بررسی کیفیت و امنیت کد. مسیر پیاده‌سازی در استفاده از استانداردها در پروژه‌ها.
  • Psalm یا PHPStan: ابزار تحلیل ایستا که حفره‌های منطقی و امنیتی را کشف می‌کند.
  • افزونه Query Monitor و افزونه‌های امنیتی: پایش رفتار غیرمعمول کوئری‌ها و درخواست‌ها. راهنمای کامل در افزونه‌های امنیتی وردپرس.

تجربه‌ام این است که این سه ابزار در کنار هم، در سه هفته اول، سطح امنیت کد را محسوس بالا می‌برند. اگر می‌خواهید این بررسی‌ها را به‌صورت خودکار در CI اجرا کنید، مسیر در CI/CD در وردپرس آمده است.

اشتباهات امنیتی رایج

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

  • نبود escape در نمایش متادیتا: echo get_post_meta( $id, 'key', true ); بدون escape. مسیر درست: echo esc_html( get_post_meta( $id, 'key', true ) );
  • کوئری مستقیم بدون prepare: هر کوئری با متغیر کاربر، نیاز به $wpdb->prepare() دارد.
  • نبود نانس در فرم‌های مدیریت: فرم‌های حذف و ویرایش، پرتکرارترین اهداف CSRF هستند.
  • نبود capability check در AJAX handler: هر AJAX handler که عملیات حساس انجام می‌دهد، باید بررسی کند که کاربر لاگین‌شده و دارای دسترسی است.
  • استفاده از $_REQUEST بدون پاک‌سازی: $_REQUEST هم GET و هم POST را می‌گیرد و پاک‌سازی را سخت‌تر می‌کند. توصیه: استفاده از $_GET یا $_POST به‌صورت مشخص.
  • نمایش خطا به کاربر در Production: display_errors = On در Production، مسیر فایل‌ها و ساختار سرور را افشا می‌کند. توصیه: غیرفعال کردن نمایش خطا و ذخیره در لاگ.
  • نبود rate limiting در فرم‌های عمومی: فرم تماس بدون محدودیت، به یک بستر ارسال اسپم تبدیل می‌شود. راه‌حل: افزونه‌های ضداسپم یا محدودسازی در سطح سرور.

یک اشتباه کم‌تکرار اما گران‌قیمت: نادیده‌گرفتن امنیت در کد سفارشی که توسط توسعه‌دهنده خارجی نوشته شده. اگر پروژه‌ای را تحویل می‌گیرید، لایه امنیتی کد را مستقل از سازنده اصلی بررسی کنید. مسیر کامل در پیدا کردن بدافزار مخفی در وردپرس و ابزارهای اسکن بدافزار آمده است.

دید مهندسی

از منظر مهندسی، نوشتن PHP امن در وردپرس یک تصمیم معماری پیوسته است، نه یک چک‌لیست که یک‌بار اجرا شود. سه لایه را در پروژه‌های حرفه‌ای همیشه مرور می‌کنم. لایه اول، دفاع در عمق: هر داده باید در چند لایه محافظت شود — sanitize در ورودی، escape در خروجی، CSP در مرورگر، capability در منطق. اگر یک لایه شکست، لایه‌های دیگر جلوی فاجعه را می‌گیرند. تجربه‌ام این است که پروژه‌هایی که فقط یک لایه امنیتی دارند، در روز حادثه، فاجعه‌بارتر از پروژه‌هایی هستند که چند لایه دارند. تفصیل این استراتژی را در امنیت وردپرس برای مبتدیان و چرا وردپرس هدف حملات سایبری است آورده‌ام. لایه دوم، امنیت به‌عنوان بخشی از کد، نه افزودنی: کدی که بعداً امن شود، هرگز کاملاً امن نمی‌شود. اگر تابعی می‌نویسید که داده از کاربر می‌گیرد، sanitize و escape باید همزمان با نوشتن تابع اضافه شوند، نه در مرحله بازبینی. لایه سوم، پایش مستمر: امنیت، وضعیت نیست؛ فرآیند است. در پروژه‌های خودم، هر سه ماه یک بازبینی امنیتی انجام می‌دهم — اجرای PHP_CodeSniffer، بررسی لاگ‌ها، و تست سناریوهای حمله رایج. مسیر کامل در تست امنیت وب‌سایت آمده است.

یک نکته تکمیلی برای تیم‌های فنی: در پروژه‌های بزرگ، امنیت PHP باید در سطح قواعد تیمی مستندسازی و در چرخه بازبینی کد اجرا شود. تجربه من این است که در تیمی که چهار قاعده طلایی این مقاله را در بازبینی کد اجرا می‌کند، در شش ماه، سطح امنیت کد محسوس بالا می‌رود. اگر می‌خواهید این بررسی‌ها را به‌صورت خودکار در CI اجرا کنید، مسیر کامل در استفاده از استانداردها در پروژه‌ها و CI/CD در وردپرس آمده است. تفاوت بین پروژه‌ای که امنیت را در جریان کاری دارد و پروژه‌ای که ندارد، در روز حادثه، ساعت‌ها کار و اعتبار است — نه در ظاهر.

جمع‌بندی

نوشتن PHP امن در وردپرس، در چهار قاعده طلایی خلاصه می‌شود: پاک‌سازی ورودی، escape خروجی، نانس در فرم‌ها و AJAX، و capability check قبل از عملیات. قاعده پنجم که در افزونه‌ها مهم است: کوئری با wpdb->prepare یا توابع آماده وردپرس. سه اصل در پایان تاکید می‌کنم: اول، امنیت را از روز اول رعایت کنید، نه بعداً. دوم، ابزارهای بررسی امنیت را در جریان کاری خود بگنجانید، نه بازبینی یک‌بار در سال. سوم، دفاع در عمق؛ هیچ لایه‌ای به‌تنهایی کافی نیست.

اگر همین امروز در حال کار روی یک پروژه وردپرسی هستید، پیشنهاد عملی من سه گام است: ابتدا کد فعلی خود را با PHP_CodeSniffer بررسی کنید و خطاهای امنیتی را ببینید؛ سپس هر تابعی که داده از کاربر می‌گیرد را بازبینی کنید که آیا sanitize و escape دارد؛ و در پایان، در همه فرم‌ها و AJAX handlerها، نانس و capability check را اضافه کنید. اگر در هر مرحله‌ای گیر کردید یا تجربه‌ای از یک حادثه امنیتی دارید، در دیدگاه‌ها بنویسید. تجربه شما از یک پروژه امن یا یک حادثه امنیتی، برای توسعه‌دهنده بعدی که در همین نقطه ایستاده، ارزشمندترین راهنماست. 🔒