分析 WordPress `check_ajax_referer()` 函数的源码:如何验证 AJAX 请求中的 `Nonce`。

欢迎来到今天的WordPress安全小讲堂!今天我们要一起扒一扒WordPress里一个非常重要的小函数——check_ajax_referer()。它就像AJAX请求的门卫,专门负责检查进来的客人(也就是AJAX请求)有没有携带正确的“通行证”(Nonce)。

准备好了吗?让我们开始吧!

一、什么是Nonce?为什么要用它?

首先,我们得搞清楚什么是Nonce。Nonce,全称是"Number used once",顾名思义,它是一个只能使用一次的随机数。在WordPress的世界里,它主要用来防止CSRF(Cross-Site Request Forgery)攻击。

想象一下,如果没有Nonce,坏人可以伪造一个AJAX请求,冒充你执行一些操作,比如删除你的文章,修改你的用户资料。这可太可怕了!

Nonce就像一个秘密的握手协议。只有知道这个秘密的人才能顺利通过验证。每次请求,这个秘密都是不同的,这样就大大提高了安全性。

二、check_ajax_referer() 的作用

check_ajax_referer() 的主要作用就是验证AJAX请求中携带的Nonce是否有效。如果Nonce有效,说明这个请求很可能是来自你的站点,而不是坏人伪造的。如果Nonce无效,那就果断拒绝,保护你的站点安全。

三、check_ajax_referer() 函数的源码剖析

让我们一起深入wp-includes/functions.php,看看 check_ajax_referer() 的源码:

function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
    if ( -1 == $action ) {
        _doing_it_wrong( __FUNCTION__, __( 'The action should always be named in check_ajax_referer().' ), '4.2.0' );
        return false;
    }

    $result = wp_verify_nonce( $_REQUEST[ $query_arg ], $action );

    if ( false === $result ) {
        /**
         * Fires when an Ajax request fails the nonce check.
         *
         * @since 2.5.0
         *
         * @param string $action  The name of the action that failed.
         * @param string $result  Result of the nonce verification.
         */
        do_action( 'wp_ajax_nopriv_nonce_mismatch', $action, $result );
        do_action( 'wp_ajax_nonce_mismatch', $action, $result );

        if ( $die ) {
            wp_nonce_ays( $action );
            exit;
        } else {
            return false;
        }
    }

    return true;
}

这段代码看起来有点长,但其实逻辑很简单。我们来一步一步分析:

  1. 参数说明:

    • $action (string, required): 一个唯一的字符串,用来标识这个Nonce。 这个$action值应该和你生成Nonce时使用的$action值保持一致。 否则,验证肯定会失败。
    • $query_arg (string|false, optional): Nonce值在$_REQUEST数组中的键名。 默认值是false,意味着函数会默认查找$_REQUEST['_wpnonce']
    • $die (bool, optional): 如果验证失败,是否立即停止脚本执行。 默认值是true,表示立即停止。
  2. 检查 $action 是否设置:

    if ( -1 == $action ) {
        _doing_it_wrong( __FUNCTION__, __( 'The action should always be named in check_ajax_referer().' ), '4.2.0' );
        return false;
    }

    这段代码首先检查 $action 是否等于 -1。 如果是,说明你没有提供$action参数,这是一个非常糟糕的习惯。 _doing_it_wrong() 函数会触发一个警告,告诉你哪里出错了。 然后函数直接返回 false,表示验证失败。 永远不要忽略这个警告!

  3. 验证 Nonce:

    $result = wp_verify_nonce( $_REQUEST[ $query_arg ], $action );

    这是最关键的一步。 它调用了 wp_verify_nonce() 函数来验证Nonce。 wp_verify_nonce() 函数会检查 $_REQUEST 数组中 $query_arg 键对应的值是否是一个有效的Nonce,并且与 $action 关联。 如果 $query_argfalse,它会默认查找 $_REQUEST['_wpnonce']

    wp_verify_nonce() 函数返回 1 (Nonce 在有效期内), 2 (Nonce 过期时间小于 12 小时), 或者 false (Nonce 无效)。

  4. 处理验证失败的情况:

    if ( false === $result ) {
        /**
         * Fires when an Ajax request fails the nonce check.
         *
         * @since 2.5.0
         *
         * @param string $action  The name of the action that failed.
         * @param string $result  Result of the nonce verification.
         */
        do_action( 'wp_ajax_nopriv_nonce_mismatch', $action, $result );
        do_action( 'wp_ajax_nonce_mismatch', $action, $result );
    
        if ( $die ) {
            wp_nonce_ays( $action );
            exit;
        } else {
            return false;
        }
    }

    如果 wp_verify_nonce() 返回 false,说明Nonce无效。 这段代码会触发两个Action Hook:wp_ajax_nopriv_nonce_mismatchwp_ajax_nonce_mismatch。 你可以使用这些Hook来记录日志,或者执行其他自定义操作。

    然后,它会检查 $die 参数的值。 如果 $dietrue (默认值),它会调用 wp_nonce_ays() 函数,显示一个错误信息,并立即停止脚本执行。 如果 $diefalse,它会返回 false,表示验证失败,但不会停止脚本执行。 你可以根据自己的需要来设置 $die 参数。

  5. 验证成功:

    return true;

    如果 wp_verify_nonce() 返回 12,说明Nonce有效。 函数返回 true,表示验证成功。

四、如何使用 check_ajax_referer()

现在我们知道了 check_ajax_referer() 的工作原理,接下来看看如何使用它。

步骤 1: 生成 Nonce

首先,你需要在前端生成一个Nonce。 WordPress 提供了 wp_create_nonce() 函数来生成Nonce。

$nonce = wp_create_nonce( 'my_ajax_action' );

这里的 'my_ajax_action'$action 参数,它必须是一个唯一的字符串。 你可以使用任何你喜欢的字符串,只要它能唯一标识你的AJAX请求。

步骤 2: 将 Nonce 传递给前端

你需要将生成的Nonce传递给前端,以便在AJAX请求中使用。 你可以使用 wp_localize_script() 函数来做到这一点。

wp_enqueue_script( 'my-ajax-script', get_template_directory_uri() . '/js/my-ajax-script.js', array( 'jquery' ), '1.0', true );

wp_localize_script( 'my-ajax-script', 'my_ajax_object', array(
    'ajax_url' => admin_url( 'admin-ajax.php' ),
    'nonce'    => $nonce
) );

这段代码会将 ajax_urlnonce 传递给名为 my_ajax_object 的JavaScript对象。 然后在你的JavaScript代码中,你可以通过 my_ajax_object.ajax_urlmy_ajax_object.nonce 来访问这些值。

步骤 3: 在 AJAX 请求中包含 Nonce

在你的JavaScript代码中,你需要将Nonce包含在AJAX请求中。

jQuery.ajax({
    url: my_ajax_object.ajax_url,
    type: 'POST',
    data: {
        action: 'my_ajax_action',
        security: my_ajax_object.nonce,
        // 其他数据
    },
    success: function(response) {
        // 处理响应
    }
});

这里,我们将Nonce作为 security 参数传递给AJAX请求。 action 参数的值必须与你在 add_action() 中使用的值相同。

步骤 4: 在服务器端验证 Nonce

在服务器端,你需要使用 check_ajax_referer() 函数来验证Nonce。

add_action( 'wp_ajax_my_ajax_action', 'my_ajax_callback' );
add_action( 'wp_ajax_nopriv_my_ajax_action', 'my_ajax_callback' );

function my_ajax_callback() {
    check_ajax_referer( 'my_ajax_action', 'security' );

    // 处理AJAX请求
    $data = $_POST['data'];
    $response = 'OK';

    wp_send_json_success( $response );
}

在这里,我们使用 check_ajax_referer() 函数来验证Nonce。 'my_ajax_action'$action 参数,'security'$query_arg 参数,它指定了Nonce在 $_REQUEST 数组中的键名。

五、一些需要注意的地方

  • $action 的唯一性: $action 参数必须是唯一的。 如果你在多个AJAX请求中使用相同的$action,可能会导致安全问题。
  • Nonce 的有效期: Nonce的有效期是12个小时。 这意味着,如果用户在12个小时内没有使用Nonce,它就会失效。 你可以使用 wp_nonce_tick() 函数来调整Nonce的有效期,但这通常不是一个好主意。
  • 不要在 GET 请求中使用 Nonce: Nonce应该只在 POST 请求中使用。 因为 GET 请求的参数会显示在URL中,这可能会导致Nonce泄露。
  • 始终验证 Nonce: 无论你的AJAX请求有多么简单,都应该始终验证Nonce。 这可以防止CSRF攻击。
  • 错误处理: 如果Nonce验证失败,你应该采取适当的措施,例如显示错误信息,或者记录日志。

六、一个完整的例子

让我们来看一个完整的例子,演示如何使用 check_ajax_referer() 来保护你的AJAX请求。

PHP (functions.php 或插件文件中):

add_action( 'wp_enqueue_scripts', 'my_enqueue_ajax_script' );
function my_enqueue_ajax_script() {
    wp_enqueue_script( 'my-ajax-script', get_template_directory_uri() . '/js/my-ajax-script.js', array( 'jquery' ), '1.0', true );

    wp_localize_script( 'my-ajax-script', 'my_ajax_object', array(
        'ajax_url' => admin_url( 'admin-ajax.php' ),
        'nonce'    => wp_create_nonce( 'my_ajax_action' )
    ) );
}

add_action( 'wp_ajax_my_ajax_action', 'my_ajax_callback' );
add_action( 'wp_ajax_nopriv_my_ajax_action', 'my_ajax_callback' );

function my_ajax_callback() {
    check_ajax_referer( 'my_ajax_action', 'security' );

    $data = $_POST['data'];
    $response = '你发送的数据是: ' . $data;

    wp_send_json_success( $response );
}

JavaScript (my-ajax-script.js):

jQuery(document).ready(function($) {
    $('#my-button').click(function() {
        var data = $('#my-input').val();

        $.ajax({
            url: my_ajax_object.ajax_url,
            type: 'POST',
            data: {
                action: 'my_ajax_action',
                security: my_ajax_object.nonce,
                data: data
            },
            success: function(response) {
                $('#my-result').text(response.data);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log('Error: ' + textStatus + ' - ' + errorThrown);
                $('#my-result').text('发生了错误!');
            }
        });
    });
});

HTML (在你的主题文件中):

<input type="text" id="my-input" value="Hello, World!">
<button id="my-button">发送数据</button>
<div id="my-result"></div>

在这个例子中,我们创建了一个简单的表单,包含一个输入框和一个按钮。 当用户点击按钮时,JavaScript代码会发送一个AJAX请求到服务器端,并将输入框中的数据传递过去。 服务器端会验证Nonce,然后返回一个包含接收到的数据的响应。 JavaScript代码会将响应显示在 my-result div中。

七、总结

check_ajax_referer() 是一个非常重要的函数,它可以帮助你保护你的WordPress站点免受CSRF攻击。 通过理解它的工作原理,并正确地使用它,你可以大大提高你的站点的安全性。

记住,安全是一个持续的过程,而不是一个一次性的任务。 所以,请务必时刻关注你的站点的安全性,并采取适当的措施来保护它。

希望今天的讲座对你有所帮助! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注