详解 WordPress `wp_nonce_field()` 函数的源码:如何生成表单中的 `Nonce` 字段。

Alright everyone, gather ’round! Let’s unravel the mystery of wp_nonce_field() in WordPress. Think of it as your website’s secret handshake, ensuring that the forms your users submit aren’t malicious imposters. We’re going to dissect its code, understand its purpose, and learn how it keeps your site safe and sound.

The Grand Entrance: What is a Nonce?

Before we dive into the code, let’s understand what a nonce actually is. The word "nonce" is short for "number used once." It’s a cryptographically secure token, generated uniquely for a specific action, user, and timeframe. Think of it like a disposable password. Once it’s used (or expires), it’s no longer valid.

Why do we need it? Imagine a scenario where a hacker crafts a fake form that looks exactly like yours. Without a nonce, they could trick users into submitting data through their malicious form, and your website would be none the wiser. The nonce acts as a verification, confirming that the request originated from your legitimate website and not from some shady corner of the internet.

The Star of the Show: wp_nonce_field()

The wp_nonce_field() function is a convenient tool in WordPress for automatically generating and embedding a nonce within your HTML forms. It’s a wrapper around other functions, making the process of adding nonces to your forms incredibly simple.

Now, let’s see how it works under the hood.

Peeking Behind the Curtain: The Source Code

The wp_nonce_field() function resides in wp-includes/functions.php. Here’s a simplified breakdown of its core logic (I’ve omitted some less critical parts for clarity):

function wp_nonce_field( $action = -1, $name = '_wpnonce', $referer = true , $echo = true ) {
    $nonce_field = wp_nonce_form_field( $action, $name, $referer, $echo );
    if ( $echo ) {
        echo $nonce_field;
    }
    return $nonce_field;
}

As you can see, wp_nonce_field() is essentially a wrapper function that calls wp_nonce_form_field(). Let’s examine wp_nonce_form_field():

function wp_nonce_form_field( $action = -1, $name = '_wpnonce', $referer = true, $echo = true ) {
    $name = esc_attr( $name );
    $nonce = wp_create_nonce( $action );
    $nonce_field = '<input type="hidden" id="' . $name . '" name="' . $name . '" value="' . esc_attr( $nonce ) . '" />';

    if ( $referer ) {
        $nonce_field .= wp_referer_field( false );
    }

    if ( $echo ) {
        echo $nonce_field;
    }

    return $nonce_field;
}

Let’s break down what’s happening in wp_nonce_form_field():

  1. Sanitizing the Name: The $name parameter (the name of the hidden input field) is sanitized using esc_attr() to prevent potential HTML injection vulnerabilities.

  2. Creating the Nonce: The heart of the process is the call to wp_create_nonce( $action ). This function generates the actual nonce value.

  3. Building the HTML: An HTML hidden input field is constructed. The nonce value is inserted into the value attribute, and the field is named according to the $name parameter. esc_attr() is used again to sanitize the nonce value before inserting it into the HTML.

  4. Adding Referer Field (Optional): If $referer is set to true (the default), it calls wp_referer_field() to add another hidden input field that stores the referring URL. This adds an extra layer of security.

  5. Outputting the HTML: If $echo is true (the default), the generated HTML is echoed directly to the output.

  6. Returning the HTML: The function returns the HTML string, regardless of whether it was echoed.

Let’s dive into wp_create_nonce() and wp_referer_field()

The Nonce Factory: wp_create_nonce()

This function is the magician behind the curtain. It takes an $action and crafts a unique, time-sensitive nonce. Let’s see what it does:

function wp_create_nonce( $action = -1 ) {
    $user = wp_get_current_user();
    $uid = (int) $user->ID;
    if ( ! $uid ) {
        $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    }

    $token = wp_get_session_token();
    $i = wp_nonce_tick();

    return substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
}

Here’s a step-by-step explanation:

  1. Getting the User ID: It retrieves the current user’s ID using wp_get_current_user(). If the user isn’t logged in, it applies a filter (nonce_user_logged_out) to allow plugins to handle the scenario.

  2. Getting the Session Token: It obtains a session token using wp_get_session_token(). This token helps to further identify the user’s session and prevent cross-site request forgery (CSRF) attacks.

  3. The Tick: A Time-Based Component: The function wp_nonce_tick() returns a value based on the current time. This ensures that nonces expire after a certain period.

  4. Hashing It All Together: The core of the function is the wp_hash() call. It concatenates the $i (the "tick"), the $action, the $uid (user ID), and the $token, then hashes the resulting string using the wp_hash() function with the ‘nonce’ algorithm.

  5. Truncating the Hash: Finally, it takes a substring of the hash (from the last 12th character, taking 10 characters), returning a 10-character nonce. This truncation helps to keep the nonce relatively short while still maintaining sufficient security.

Let’s examine wp_nonce_tick() and wp_hash()

The Timekeeper: wp_nonce_tick()

This seemingly simple function is crucial for the nonce’s time-sensitive nature.

function wp_nonce_tick() {
    /**
     * Filters the lifespan of nonces in seconds.
     *
     * @since 4.7.0
     *
     * @param int $lifespan The lifespan of nonces in seconds. Default 24 hours.
     */
    $nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS );

    return ceil( time() / ( $nonce_life / 2 ) );
}

Here’s how it works:

  1. Nonce Lifespan: It gets the nonce lifespan from the nonce_life filter. By default, this is set to DAY_IN_SECONDS (24 hours).

  2. Calculating the Tick: It divides the current timestamp (time()) by half of the nonce lifespan. The ceil() function rounds the result up to the nearest integer.

The important thing to understand is that this tick value changes every 12 hours (by default). When WordPress verifies a nonce, it checks not only the current tick but also the previous one. This means a nonce is valid for a 24-hour window (the current 12-hour period and the previous one).

The Hashing Powerhouse: wp_hash()

This function provides a consistent hashing mechanism within WordPress.

function wp_hash( $data, $scheme = 'auth' ) {
    static $wp_hasher;

    if ( empty( $wp_hasher ) ) {
        require_once ABSPATH . WPINC . '/class-phpass.php';
        $wp_hasher = new PasswordHash( 8, true );
    }

    return $wp_hasher->HashPassword( $data );
}

Here’s the breakdown:

  1. Singleton Pattern: It uses a static variable $wp_hasher to ensure that the PasswordHash object is only instantiated once. This optimizes performance.

  2. Including PasswordHash: If $wp_hasher is empty, it includes the class-phpass.php file, which contains the PasswordHash class. This class provides a portable PHP password hashing framework.

  3. Instantiating PasswordHash: It creates a new PasswordHash object with a cost of 8 and a portable flag set to true.

  4. Hashing the Data: It calls the HashPassword() method of the PasswordHash object to hash the input $data.

  5. Returning the Hash: It returns the resulting hash value.

The PasswordHash class uses a strong hashing algorithm (based on bcrypt) to ensure that the nonce is resistant to brute-force attacks.

The Referer Guardian: wp_referer_field()

This function adds a hidden input field containing the referring URL. While not directly related to the nonce itself, it provides an additional layer of security.

function wp_referer_field( $echo = true ) {
    $referer_field = '<input type="hidden" name="_wp_http_referer" value="' . esc_attr( wp_unslash( $_SERVER['REQUEST_URI'] ) ) . '" />';
    if ( $echo ) {
        echo $referer_field;
    }
    return $referer_field;
}

Here’s what it does:

  1. Building the HTML: It creates a hidden input field named _wp_http_referer. The value of this field is set to the current request URI ($_SERVER['REQUEST_URI']), which represents the URL of the page where the form is located. wp_unslash removes slashes added by magic quotes, and esc_attr escapes the URL for safe use in HTML attributes.

  2. Outputting the HTML: If $echo is true, it echoes the HTML to the output.

  3. Returning the HTML: It returns the HTML string.

This field helps to verify that the request originated from the expected page. However, it’s important to note that the referer can be easily spoofed, so it shouldn’t be relied upon as the sole security measure.

Putting It All Together: A Practical Example

Let’s say you have a form for editing a post. You would add the nonce field like this:

<form action="process_post.php" method="post">
    <!-- Your form fields here -->
    <?php wp_nonce_field( 'edit_post', 'edit_post_nonce' ); ?>
    <input type="submit" value="Save Changes">
</form>

In this example:

  • 'edit_post' is the $action. This should be a unique string that identifies the specific action being performed (editing a post in this case).
  • 'edit_post_nonce' is the $name. This is the name of the hidden input field that will contain the nonce value. You can choose any name you like, as long as it’s consistent.

The code above will generate HTML that looks something like this:

<input type="hidden" id="edit_post_nonce" name="edit_post_nonce" value="aBcDeFgHiJ">
<input type="hidden" name="_wp_http_referer" value="/wp-admin/post.php?post=123&action=edit">

Verification Time: wp_verify_nonce()

Creating the nonce is only half the battle. You also need to verify it when the form is submitted. This is where the wp_verify_nonce() function comes in.

function wp_verify_nonce( $nonce, $action = -1 ) {
    $nonce = (string) $nonce;
    $user = wp_get_current_user();
    $uid = (int) $user->ID;
    if ( ! $uid ) {
        $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );
    }

    if ( empty( $nonce ) ) {
        return false;
    }

    $token = wp_get_session_token();
    $i = wp_nonce_tick();

    // Nonce generated 0-12 hours ago
    $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
        return 1;
    }

    // Nonce generated 12-24 hours ago
    $i = wp_nonce_tick() - 1;
    $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 );
    if ( hash_equals( $expected, $nonce ) ) {
        return 2;
    }

    /**
     * Fires when a nonce verification fails.
     *
     * @since 4.5.0
     *
     * @param string|int $nonce  The nonce that was attempted to be verified.
     * @param string|int $action The action that was used to generate the nonce.
     */
    do_action( 'wp_nonce_failed', $nonce, $action );

    return false;
}

Here’s how it works:

  1. Sanitizing Input: Type casts the nonce to a string.

  2. Getting User Information: Retrieves the user ID and session token, similar to wp_create_nonce().

  3. Empty Check: If the nonce is empty, returns false.

  4. Calculating Expected Nonces: It calculates the expected nonce value based on the current tick and the previous tick.

  5. Comparing Nonces: It uses hash_equals() to compare the provided nonce with the expected nonce values. hash_equals() is crucial because it provides a timing-attack safe comparison, preventing attackers from potentially inferring information about the nonce based on how long the comparison takes.

  6. Returning the Result: If the nonce matches either the current or previous tick, it returns 1 or 2 respectively (indicating the nonce’s age). If the nonce doesn’t match either tick, it returns false.

  7. Action Hook: Fires wp_nonce_failed action hook upon failure.

In your process_post.php file, you would verify the nonce like this:

if ( isset( $_POST['edit_post_nonce'] ) && wp_verify_nonce( $_POST['edit_post_nonce'], 'edit_post' ) ) {
    // Process the form data
    echo "Nonce is valid! Processing the form...";
} else {
    // Handle the error (e.g., display an error message)
    echo "Nonce is invalid! Please try again.";
}

Best Practices and Important Considerations

  • Use Unique Actions: Always use unique $action values for different forms and actions. This prevents a nonce from being valid for multiple purposes.
  • Don’t Rely Solely on Nonces: Nonces are a valuable security measure, but they shouldn’t be the only security measure. Always sanitize and validate all user input.
  • Understand the Lifespan: Be aware that nonces expire. If your form is used infrequently, consider increasing the nonce lifespan using the nonce_life filter. However, keep in mind that longer lifespans can slightly increase the risk of replay attacks.
  • Handle Nonce Failures Gracefully: Provide informative error messages to users when a nonce verification fails. This helps them understand why the form submission failed and encourages them to try again.
  • HTTPS is Essential: Nonces rely on secure communication. Always use HTTPS to protect the nonce from being intercepted by attackers.
  • Test Thoroughly: Always test your forms with nonces to ensure that they are working correctly and that the nonce verification is successful.

In Summary: The Flow of a Nonce

Let’s recap the entire process:

Step Function(s) Involved Description
1. Generation wp_nonce_field(), wp_nonce_form_field(), wp_create_nonce(), wp_nonce_tick(), wp_hash() The nonce is generated based on the action, user ID, session token, and a time-based component. It’s then embedded in a hidden form field.
2. Submission N/A The user submits the form with the nonce value.
3. Verification wp_verify_nonce() The server verifies the nonce by comparing it to the expected value based on the same parameters used during generation.
4. Action (Your Code) If the nonce is valid, the form data is processed. Otherwise, an error message is displayed.

Conclusion

wp_nonce_field() and its related functions provide a robust and convenient way to protect your WordPress forms from CSRF attacks. By understanding how these functions work, you can ensure that your website remains secure and that your users’ data is protected. Remember to use unique actions, sanitize all user input, and handle nonce failures gracefully. Happy coding!

发表回复

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