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

چرا امنیت کد در هوک‌ها، به یک ضرورت تبدیل شده است

سه ویژگی هوک‌ها، آن‌ها را به نقطهٔ اصلی نفوذ در افزونه‌های وردپرس تبدیل می‌کند. اول، دسترسی به دادهٔ کاربر. بیشتر hook handlerهایی که می‌نویسیم، به داده‌ای از کاربر یا مدیر دسترسی دارند: تنظیمات فرم، اطلاعات حساب، داده‌های سفارش. هر شکاف در این دسترسی، می‌تواند به لو رفتن داده یا تغییر غیرمجاز آن منجر شود. دوم، اجرای در هر درخواست. یک hook handler روی init یا admin_init در هر بازدید اجرا می‌شود. اگر آسیب‌پذیری در آن باشد، در هر بازدید یک فرصت تازه برای مهاجم است. سوم، اثر مستقیم بر دیگران. hook handler شما می‌تواند داده‌ای را تغییر دهد که افزونه‌های دیگر روی آن کار می‌کنند؛ بنابراین یک ضعف کوچک، می‌تواند به یک شکاف بزرگ تبدیل شود.

در پروژه‌های واقعی، این سه ویژگی باعث می‌شود که بیشتر آسیب‌پذیری‌های افزونه‌های وردپرس، از دلِ هوک‌ها بیرون بیاید. اما خبر خوب این است: این آسیب‌پذیری‌ها با یک الگوی مشخص و چهارلایه‌ای، تقریباً همیشه قابل پیشگیری‌اند. این الگو، موضوع بخش بعدی است.

امنیت کد در هوک‌ها، یک تصمیم یک‌باره نیست؛ عادتی است که در هر hook handler تکرار می‌شود. تفاوت بین افزونه‌ای که هک می‌شود و افزونه‌ای که نمی‌شود، در همین عادت است.

چهار لایهٔ دفاعی که در هر hook handler باید باشد

هر hook handler که با دادهٔ کاربر کار می‌کند، باید چهار لایهٔ دفاعی داشته باشد. این چهار لایه، از بیرون به درون هستند: بررسی دسترسی (چه کسی می‌تواند این کار را انجام دهد؟)، بررسی nonce (آیا این درخواست از منبع مجاز آمده؟)، پاک‌سازی ورودی (آیا دادهٔ ورودی سالم است؟)، و خروج امن (آیا خروجی به‌درستی کدگذاری شده؟). هر لایه، مسئول بخش مشخصی از دفاع است و نبود یکی، شکاف مشخصی ایجاد می‌کند.

در ادامه، هر لایه را با نمونه کد بررسی می‌کنیم. مهم است که این چهار لایه را به‌عنوان یک زنجیره ببینید؛ نه به‌عنوان چهار تصمیم جدا. اگر یکی از آن‌ها نباشد، سه لایهٔ دیگر بی‌فایده می‌شوند. در بسیاری از آسیب‌پذیری‌های واقعی که بررسی کرده‌ام، شکاف از نبودِ یکی از همین چهار مورد آمده، نه از ضعف منطق کد. توضیح کلی این دستهٔ آسیب‌پذیری‌ها در راهنمای امنیت وردپرس برای مبتدیان آمده است.

لایهٔ اول: بررسی دسترسی با current_user_can

اولین لایه، بررسی این است که کاربر جاری اجازهٔ انجام این کار را دارد یا نه. وردپرس برای این کار تابع current_user_can را در اختیار شما می‌گذارد. هر hook handler که چیزی را در دیتابیس تغییر می‌دهد، باید این بررسی را در ابتدای خود داشته باشد. بدون آن، هر کاربر لاگین‌کرده — حتی کاربر عادی — می‌تواند عملیات ادمین را اجرا کند.

// نادرست: بدون بررسی دسترسی
add_action( 'admin_post_wphk_save_settings', 'wphk_bad_save_settings' );

function wphk_bad_save_settings() {
    update_option( 'wphk_custom_option', $_POST['wphk_value'] );
}

// درست: با بررسی دسترسی
add_action( 'admin_post_wphk_save_settings', 'wphk_good_save_settings' );

function wphk_good_save_settings() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'دسترسی غیرمجاز.', 'خطای دسترسی', array( 'response' => 403 ) );
    }

    // ادامهٔ پردازش
}

سه نکتهٔ کلیدی در این نمونه: اول، استفاده از admin_post_* به‌جای init — این هوک فقط برای فرم‌های پیشخوان اجرا می‌شود. دوم، بررسی current_user_can در ابتدای تابع که پیش از هر کاری انجام می‌شود. سوم، استفاده از wp_die با کد پاسخ صریح ۴۰۳ که هم برای کاربر و هم برای ابزارهای پایش پیام روشنی می‌دهد.

برای افزونه‌هایی که نقش کاربری سفارشی دارند، جای manage_options از یک capability اختصاصی استفاده می‌شود. قاعدهٔ کلی: هر عملیات، نیاز به یک capability مشخص دارد. مثلاً برای ویرایش نوشته، edit_post به‌همراه شناسهٔ نوشته؛ برای حذف کاربر، delete_users. انتخاب دقیق capability، بخشی از طراحی امنیتی افزونهٔ شما است. برای مطالعهٔ این مفهوم در عمق بیشتر، توابع وردپرس برای مدیریت نقش‌ها و دسترسی‌ها مرجع کاملی است.

لایهٔ دوم: nonce و محافظت در برابر CSRF

لایهٔ دوم، محافظت در برابر حملهٔ CSRF (Cross-Site Request Forgery) است. در این نوع حمله، مهاجم کاربر را وادار می‌کند که بدون اطلاع خودش، یک درخواست تغییردهنده به سایت شما ارسال کند. وردپرس برای این کار، مکانیزم «nonce» را در اختیار شما می‌گذارد که یک توکن امنیتی موقت است و به هر درخواست، از منبع مجاز ضمیمه می‌شود. الگوی استفاده در فرم‌ها و در پردازش‌ها:

سمت نمایش فرم:

<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
    <?php wp_nonce_field( 'wphk_save_settings', 'wphk_nonce_field' ); ?>
    <input type="hidden" name="action" value="wphk_save_settings" />
    <input type="text" name="wphk_value" />
    <input type="submit" value="ذخیره" />
</form>

سمت پردازش:

add_action( 'admin_post_wphk_save_settings', 'wphk_secure_save_settings' );

function wphk_secure_save_settings() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'دسترسی غیرمجاز.', '', array( 'response' => 403 ) );
    }

    if ( ! isset( $_POST['wphk_nonce_field'] ) ||
         ! wp_verify_nonce( $_POST['wphk_nonce_field'], 'wphk_save_settings' ) ) {
        wp_die( 'درخواست نامعتبر.', '', array( 'response' => 403 ) );
    }

    // پردازش امن
}

سه نکتهٔ کلیدی در این الگو. اول، wp_nonce_field و wp_verify_nonce باید از یک نام یکسان استفاده کنند (wphk_save_settings در این نمونه)؛ اگر نام‌ها فرق کند، بررسی همیشه رد می‌شود. دوم، بررسی nonce همیشه بعد از بررسی دسترسی می‌آید؛ چون بررسی nonce برای کاربر بدون دسترسی، معنی ندارد. سوم، wp_die با کد ۴۰۳ در رد nonce، پیام واضحی به ابزارهای پایش می‌دهد. توضیح تفصیلی این مکانیزم در نانس وردپرس و نقش آن در امنیت فرم‌ها و نمونهٔ عملی آن در پیاده‌سازی نانس در فرم‌های سفارشی آمده است.

یک نکتهٔ مهم که در پروژه‌های واقعی زیاد دیده‌ام: حتی اگر فرم، تنها به کاربرانِ مدیر نمایش داده شود، باز هم باید nonce داشته باشد. منطق نمایش، هرگز جایگزین بررسی nonce نیست؛ چون مهاجم می‌تواند درخواست را مستقیم به admin-post.php بفرستد بدون این‌که فرم را ببیند. به همین دلیل، در هر پردازش POST که چیزی را تغییر می‌دهد، بررسی nonce الزامی است.

لایهٔ سوم: پاک‌سازی ورودی‌ها

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

تابعمناسب براینمونهٔ استفاده
sanitize_text_fieldمتن یک‌خطی سادهنام، عنوان، ایمیل کوتاه
sanitize_emailآدرس ایمیلایمیل کاربر، ایمیل تماس
sanitize_urlURLآدرس سایت، لینک ورودی
sanitize_keyکلید ساده (حروف کوچک و خط تیره)slug، شناسهٔ تنظیمات
wp_kses_postHTML مجاز برای نوشتهمحتوای متنی با تگ‌های محدود
intval یا absintعدد صحیحشناسه، تعداد، مدت زمان
sanitize_textarea_fieldمتن چندخطیتوضیحات، یادداشت

نمونهٔ زیر، یک hook handler امن را نشان می‌دهد که چند نوع داده را با توابع مناسب پاک‌سازی می‌کند:

add_action( 'admin_post_wphk_save_profile', 'wphk_secure_save_profile' );

function wphk_secure_save_profile() {
    if ( ! current_user_can( 'edit_user' ) ) {
        wp_die( 'دسترسی غیرمجاز.', '', array( 'response' => 403 ) );
    }
    if ( ! isset( $_POST['wphk_profile_nonce'] ) ||
         ! wp_verify_nonce( $_POST['wphk_profile_nonce'], 'wphk_save_profile' ) ) {
        wp_die( 'درخواست نامعتبر.', '', array( 'response' => 403 ) );
    }

    $user_id = absint( $_POST['user_id'] );
    $name    = sanitize_text_field( wp_unslash( $_POST['display_name'] ) );
    $email   = sanitize_email( wp_unslash( $_POST['email'] ) );
    $website = esc_url_raw( wp_unslash( $_POST['website'] ) );
    $bio     = sanitize_textarea_field( wp_unslash( $_POST['bio'] ) );

    wp_update_user( array(
        'ID'           => $user_id,
        'display_name' => $name,
        'user_email'   => $email,
        'user_url'     => $website,
        'description'  => $bio,
    ) );
}

سه نکتهٔ کلیدی: اول، استفاده از wp_unslash پیش از پاک‌سازی؛ وردپرس داده‌های $_POST را در بعضی موارد با بک‌اسلش ذخیره می‌کند و این تابع آن را حذف می‌کند. دوم، انتخاب تابع پاک‌سازی مناسب برای هر نوع داده — استفاده از sanitize_text_field برای ایمیل، اشتباه است چون ممکن است کاراکترهای معتبر ایمیل را حذف کند. سوم، به‌جای دست‌زدن مستقیم به دیتابیس، از توابع وردپرس مثل wp_update_user استفاده می‌شود که خودشان پاک‌سازی اضافی انجام می‌دهند.

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

لایهٔ چهارم: خروج امن و پیشگیری از XSS

لایهٔ چهارم، خروج امن است. هر داده‌ای که در HTML چاپ می‌شود، باید با توابع esc_* کدگذاری شود تا از حملات XSS (Cross-Site Scripting) جلوگیری شود. در این نوع حمله، مهاجم کد جاوااسکریپت مخرب در دادهٔ ورودی می‌گذارد و اگر آن داده بدون کدگذاری در HTML چاپ شود، اسکریپت مخرب در مرورگر کاربران دیگر اجرا می‌شود و می‌تواند کوکی‌ها، توکن‌ها و حتی کل نشست کاربر را بدزدد. توابع اصلی برای خروج امن:

تابعمناسب برایزمینهٔ استفاده
esc_htmlمتن سادهمحتوای داخل تگ
esc_attrمقدار ویژگیمقدار داخل class, id, data-*
esc_urlURL برای نمایش در HTMLhref, src
esc_jsمتن داخل جاوااسکریپتمتغیرهای درون اسکریپت
esc_textareaمحتوای textareaمتن چندخطی داخل textarea
wp_kses_postHTML با تگ‌های مجازخروجی محتوای کاربر که HTML دارد

نمونهٔ زیر، یک hook handler را نشان می‌دهد که از دادهٔ سفارشی برای خروجی HTML استفاده می‌کند و همه‌چیز را به‌درستی کدگذاری می‌کند:

add_action( 'woocommerce_admin_order_data_after_billing_address', 'wphk_show_custom_field_admin' );

function wphk_show_custom_field_admin( $order ) {
    $custom = get_post_meta( $order->get_id(), '_wphk_custom_field', true );
    if ( ! $custom ) {
        return;
    }
    echo '<p><strong>فیلد سفارشی:</strong> ' . esc_html( $custom ) . '</p>';
}

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

<a href="<?php echo esc_url( $custom_url ); ?>" target="_blank" rel="noopener">
    <?php echo esc_html( $link_text ); ?>
</a>

سه اصل در این بخش: اول، هر داده‌ای که چاپ می‌شود، حتی اگر از دیتابیس آمده باشد، باید کدگذاری شود. چون دیتابیس ممکن است دادهٔ آلوده داشته باشد که در مرحلهٔ پاک‌سازی وارد نشده. دوم، تابع کدگذاری مناسب برای هر زمینه. esc_html برای متن، esc_attr برای ویژگی و esc_url برای URL. سوم، استفاده از wp_kses_post وقتی می‌خواهید HTML محدودی اجازه دهید. برای مطالعهٔ عمیق‌تر در مورد این حملات، حملات XSS چیست و چگونه دفع می‌شود را ببینید.

تزریق امن در قالب و ویجت‌ها

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

نکتهٔ اول: تزریق در wp_head و wp_footer

وقتی در wp_head یا wp_footer کدی تزریق می‌کنید، دادهٔ متغیر را با esc_* کدگذاری کنید. حتی اگر داده از تنظیمات خودتان می‌آید، چون کاربر ممکن است بتواند از طریق Customizer آن را تغییر دهد:

add_action( 'wp_head', 'wphk_tracking_script' );

function wphk_tracking_script() {
    $tracking_id = get_option( 'wphk_tracking_id', '' );
    if ( empty( $tracking_id ) ) {
        return;
    }
    printf(
        '<script>var trackingId = "%s";</script>',
        esc_js( $tracking_id )
    );
}

نکتهٔ دوم: تزریق در بدنهٔ نوشته

هر محتوایی که به the_content یا the_excerpt اضافه می‌کنید، اگر از دادهٔ کاربر آمده، حتماً با wp_kses_post یا esc_html کدگذاری شود:

add_filter( 'the_content', 'wphk_append_note' );

function wphk_append_note( $content ) {
    if ( ! is_singular( 'post' ) ) {
        return $content;
    }
    $note = get_post_meta( get_the_ID(), '_wphk_note', true );
    if ( empty( $note ) ) {
        return $content;
    }
    return $content . '<div class="post-note">' . wp_kses_post( $note ) . '</div>';
}

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

در ویجت‌های سفارشی که خودتان می‌سازید، تمام مقادیر قابل‌ویرایش در پنل ادمین را با esc_html یا esc_attr چاپ کنید. الگوهای مشابه در مبحث هوک‌های وردپرس در توسعه قالب چه کاربردی دارند و هوک‌های مناسب برای تغییر خروجی قالب آمده است. برای نمونه‌های کاربردی تزریق امن در قالب‌های چایلد، قالب وردپرس چایلد چیست و چه زمانی به آن نیاز داریم را ببینید.

امنیت در هوک‌های AJAX و REST API

AJAX و REST API، نقاط ویژه‌ای هستند که در آن‌ها سه لایهٔ اول امنیت شکل متفاوتی به خود می‌گیرند. در این مسیرها، ince و بررسی دسترسی، بخشی از قرارداد استاندارد می‌شوند و نبودشان مستقیم به آسیب‌پذیری منجر می‌شود.

امنیت در هوک‌های AJAX

در درخواست‌های AJAX وردپرس، سه تابع کلیدی دارید:

// سمت نمایش فرم/دکمه:
wp_localize_script( 'wphk-script', 'wphk_ajax', array(
    'ajax_url' => admin_url( 'admin-ajax.php' ),
    'nonce'    => wp_create_nonce( 'wphk_ajax_action' ),
) );

// سمت پردازش:
add_action( 'wp_ajax_wphk_action', 'wphk_ajax_handler' );

function wphk_ajax_handler() {
    check_ajax_referer( 'wphk_ajax_action', 'nonce' );

    if ( ! current_user_can( 'edit_posts' ) ) {
        wp_send_json_error( 'دسترسی غیرمجاز', 403 );
    }

    $value = isset( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : '';

    // منطق پردازش

    wp_send_json_success( array( 'message' => 'عملیات انجام شد' ) );
}

سه نکتهٔ کلیدی: اول، استفاده از check_ajax_referer که پیش از هر کار دیگری بررسی nonce می‌کند. دوم، بررسی current_user_can برای دسترسی، به‌همان ترتیب قبل. سوم، در نهایت wp_send_json_success یا wp_send_json_error که پاسخ را با کد HTTP درست ارسال می‌کند.

امنیت در هوک‌های REST API

در REST API وردپرس، مکانیزم permission_callback جای بررسی دسترسی را می‌گیرد. اگر این تابع را نداشته باشید یا همیشه true برگردانید، endpoint شما برای همه باز است:

add_action( 'rest_api_init', 'wphk_register_rest_route' );

function wphk_register_rest_route() {
    register_rest_route( 'wphk/v1', '/settings', array(
        'methods'             => 'POST',
        'callback'            => 'wphk_rest_save_settings',
        'permission_callback' => function() {
            return current_user_can( 'manage_options' );
        },
        'args'                => array(
            'value' => array(
                'required'          => true,
                'sanitize_callback' => 'sanitize_text_field',
            ),
        ),
    ) );
}

function wphk_rest_save_settings( WP_REST_Request $request ) {
    $value = $request->get_param( 'value' );
    update_option( 'wphk_custom_value', $value );
    return rest_ensure_response( array( 'success' => true ) );
}

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

سه پروندهٔ واقعی از شکاف‌های امنیتی در کد

برای این‌که این مفاهیم انتزاعی نمانند، سه پروندهٔ واقعی از پروژه‌های خودم را بازگو می‌کنم — بدون جزئیات هویتی، ولی با ساختار دقیق مشکل و راه‌حل.

پروندهٔ اول: فرم نظرسنجی که به دروازهٔ اسپم تبدیل شد

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

پروندهٔ دوم: تزریق در خروجی که به سرقت کوکی منجر شد

در یک فروشگاه، یک ویجت سفارشی وجود داشت که پیام تبریک کاربران را در سایدبار نمایش می‌داد. دادهٔ پیام، از یک متای کاربر می‌آمد که کاربر می‌توانست آن را در پروفایلش ویرایش کند. مشکل: در چاپ این داده، از esc_html استفاده نشده بود. مهاجم با تغییر این فیلد، یک اسکریپت جاوااسکریپت در پروفایلش گذاشت و با اشتراک‌گذاری لینک پروفایل، کوکی‌های مدیران را دزدید. راه‌حل: اضافه‌کردن esc_html در خروجی، پاک‌سازی در ذخیره، و بازبینی تمام مسیرهای چاپ داده در ویجت‌ها. درس بزرگ این پرونده: پاک‌سازی در ذخیره کافی نیست؛ باید خروجی را هم در زمان چاپ کدگذاری کرد. شرح عمیق‌تر این الگو در حملات XSS چیست و چگونه دفع می‌شود آمده است.

پروندهٔ سوم: endpoint REST که اطلاعات حساس را باز می‌کرد

در یک افزونهٔ اختصاصی، یک endpoint REST وجود داشت که اطلاعات سفارش‌ها را برمی‌گرداند. توسعه‌دهندهٔ قبلی، permission_callback را روی true گذاشته بود تا در تست راحت باشد. این تصمیم در نسخهٔ نهایی باقی ماند و به هر کسی اجازه می‌داد اطلاعات سفارش‌ها را دریافت کند. راه‌حل: تغییر permission_callback به تابعی که بررسی کند کاربر جاری اجازهٔ دسترسی به سفارش را دارد، و اضافه‌کردن بررسی مالکیت. درس این پرونده: تصمیم‌های موقتِ تست که در کد نهایی باقی می‌مانند، از شایع‌ترین شکاف‌های امنیتی هستند. برای مطالعهٔ الگوهای امنیتی در API، امنیت API و چگونه REST API امن بسازیم مرجع کاملی هستند.

سه پروندهٔ بالا تصادفی انتخاب نشده‌اند. در هر سه، شکاف از یک تصمیم کوچک آمده — یک nonce جاافتاده، یک esc_html فراموش‌شده، یا یک permission_callback که در حالت تست رها شده. و در هر سه، سادگی اصلاح، در مقابل شدت پیامد قرار داشت.

روش‌های حرفه‌ای برای پایدار نگه‌داشتن امنیت در هوک‌ها

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

روش اول: الگوی چک‌لیست در هر hook handler

عادت کنید هر hook handler که چیزی را تغییر می‌دهد، با یک الگوی ثابت شروع شود. این الگو، چهار لایهٔ دفاعی را در یک ترتیب مشخص اجرا می‌کند:

function wphk_secure_action_handler() {
    // 1. بررسی دسترسی
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'دسترسی غیرمجاز.', '', array( 'response' => 403 ) );
    }

    // 2. بررسی nonce
    if ( ! isset( $_POST['wphk_nonce'] ) ||
         ! wp_verify_nonce( $_POST['wphk_nonce'], 'wphk_action' ) ) {
        wp_die( 'درخواست نامعتبر.', '', array( 'response' => 403 ) );
    }

    // 3. پاک‌سازی ورودی
    $value = sanitize_text_field( wp_unslash( $_POST['value'] ?? '' ) );

    // 4. منطق اصلی
    update_option( 'wphk_value', $value );
}

وقتی این الگو تبدیل به یک عادت شود، دیگر نیازی نیست در هر hook handler بابت امنیت نگران باشید؛ چون در ساختار خودش هست.

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

هر سه ماه یک بار، فایل‌های افزونه و چایلد تم خود را با چشم امنیتی بازبینی کنید. سه سؤال کلیدی در این بازبینی: آیا هر hook handler که چیزی را تغییر می‌دهد، بررسی دسترسی و nonce دارد؟ آیا هر داده‌ای که چاپ می‌شود، کدگذاری شده است؟ آیا endpointهای REST، permission_callback دقیق دارند؟ این بازبینی نیم‌ساعته، در تجربهٔ من چندین شکاف را پیش از تبدیل شدن به حادثه کشف کرده است. برای مرور فهرست مواردی که باید در بازبینی بررسی شوند، اشتباهات رایج امنیتی وردپرس مرجع مناسبی است.

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

سه ابزار که در پروژه‌های خودم به‌طور دوره‌ای اجرا می‌کنم: اول، Plugin Check (افزونهٔ رسمی وردپرس) برای بررسی افزونه‌های اختصاصی از نظر استانداردها و امنیت. دوم، اسکنرهای بدافزار مانند Wordfence یا Sucuri برای کشف کدهای ناخواسته. سوم، سرویس‌های بررسی آسیب‌پذیری که کتابخانه‌های استفاده‌شده را ارزیابی می‌کنند. توصیهٔ عملی: این ابزارها را در محیط استجینگ اجرا کنید تا در صورت گزارش مثبت، امکان بررسی بدون خطر وجود داشته باشد. راهنمای جامع این ابزارها در بهترین ابزارهای اسکن بدافزار و بهترین افزونه‌های امنیتی وردپرس آمده است.

امنیت کد در هوک‌ها، محصول یک‌بار تصمیم نیست؛ حاصل یک عادت روزانه است. کد خوب، از یک تصمیم خوب در هر روز ساخته می‌شود، نه از یک تصمیم بزرگ در یک روز.

چک‌لیست امنیت کد در هر hook handler

برای این‌که همهٔ این مباحث در یک صفحهٔ کوچک جمع شود، چک‌لیست زیر را در کنار دست نگه می‌دارم. هر بار که hook handler جدیدی می‌نویسم، این پنج مرحله را به‌ترتیب اجرا می‌کنم:

  1. آیا این hook handler چیزی را تغییر می‌دهد؟ اگر بله، بررسی current_user_can در ابتدای آن هست؟
  2. آیا داده‌ای از $_POST، $_GET یا $_REQUEST می‌خواند؟ اگر بله، بررسی nonce با wp_verify_nonce یا check_ajax_referer هست؟
  3. آیا ورودی‌ها را با توابع sanitize_* مناسب پاک‌سازی می‌کند؟
  4. آیا خروجی‌ها را با توابع esc_* مناسب کدگذاری می‌کند؟
  5. آیا در endpointهای REST، permission_callback دقیق دارد (نه همیشه true)؟

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

نقشهٔ نهایی و مسیر پیشنهادی

هوک‌های وردپرس و افزایش امنیت کد، دو مفهوم به‌هم گره‌خورده‌اند. چهار لایهٔ دفاعی — بررسی دسترسی، بررسی nonce، پاک‌سازی ورودی، و خروج امن — در هر hook handler که با دادهٔ کاربر کار می‌کند، ضروری‌اند. نبود هر لایه، شکاف مشخصی ایجاد می‌کند و آسیب‌پذیری‌های واقعی، تقریباً همیشه از همین شکاف‌ها آمده‌اند. الگوهای خاص AJAX و REST API، در کنار این چهار لایه، به سازوکارهایی مثل check_ajax_referer و permission_callback نیاز دارند. و پایدار نگه‌داشتن امنیت، به یک عادت روزانه و بازبینی دوره‌ای تبدیل می‌شود، نه به یک تصمیم یک‌باره.

گام بعدی عملی که پیشنهاد می‌کنم: در همین امروز، فایل‌های افزونه یا چایلد تم خود را باز کنید و فهرست تمام hook handlerهایی که چیزی را تغییر می‌دهند تهیه کنید. برای هرکدام، چک‌لیست پنج مرحله‌ای بالا را اجرا کنید. اگر در بیش از یکی دو مورد، یک لایهٔ دفاعی غایب بود، احتمالاً کد شما در معرض آسیب‌پذیری‌های شناخته‌شده است. این تمرین نیم‌ساعته، امنیت سایت شما را به‌طور محسوس بالا می‌برد. اگر در پروژه‌ای با شکاف امنیتی ناشی از هوک‌ها روبه‌رو شده‌اید و روش جالبی برای کشف یا حل آن پیدا کرده‌اید، برای من جالب است بدانید کدام لایه بود که جا افتاده بود و چگونه کشفش کردید — تجربهٔ خودتان را در دیدگاه‌ها بنویسید؛ به‌ویژه اگر روشی پیدا کرده‌اید که بدون بازبینی دستی، امنیت hook handlerها را در سطح پروژه پایش می‌کند. 🛡️