Nonce 检查失败

问题描述 投票:0回答:2

使用更新的代码进行编辑:仍然没有解决方案

有人能发现这段代码中的错误吗?

自定义页面.php:

<form name="customForm">
                <?php wp_nonce_field('code_check', 'codecheck'); ?>
                Validation Code:<br>
                <input type="password" name="inputcode" id="inputcode" maxlength="6" inputmode="numeric">
                <input type="text" name="message" id="message" style="display:none; background-color: #FFCCCC;"><br>
                <input type="button" name="submitbutton" value="Submit" onClick="customfunction()">
                </form>

自定义.js:

function customfunction() {
const userInput = document.addStamp.inputcode.value;
const token = document.addStamp.codecheck.value;
    fetch(`http://...../wp-json/api/v1/custom?code=${userInput}&token=${token}`).then(r => r.json()).then(data => {
......

API文件.php:

public function custom($request)
    {
    $params = $request->get_params();
        $retrieved_nonce = $params[token];
        if($retrieved_nonce) {
            if (!wp_verify_nonce($retrieved_nonce, 'code_check' ) ) die( 'Failed security check' );
        }
        ....

一切正常,直到我将 nonce 验证代码添加到 api 请求中。

现在,当我单击“提交”按钮时,它不提交,我进入控制台:

Uncaught (in promise) SyntaxError: Unexpected token F in JSON at position 0

所以它失败了,因为“F”是失败消息的点 0。

但是,如果我输出“$retrieved_nonce”,我实际上会得到页面源代码中所示的随机数值,所以看起来它正在到达端点?

我尝试注销并重新登录,但没有任何变化。

我这个代码设置错了吗?

wordpress api nonce
2个回答
1
投票

你可以尝试一下。

表格。

<form name="customForm" method="post">
<?php wp_nonce_field('code_check', 'code_check'); ?>
Validation Code:<br>
<input type="password" name="inputcode" id="inputcode" maxlength="6" inputmode="numeric">
<input type="text" name="message" id="message" style="display:none; background-color: #FFCCCC;"><br>
<input type="button" name="submitbutton" value="Submit" onClick="customfunction()">
</form>

在 customfunction 函数中还发送 wp_nonce_field 字段值。

function customfunction() {
const userInput = document.customForm.inputcode.value;
const code_check = document.customForm.code_check.value;
    fetch('http://...../wp-json/api/v1/custom?code='+userInput+'&code_check'+code_check).then(r => r.json()).then(data => {

现在验证 wp_nonce_field 字段值

public function custom($request)
        {
         $retrieved_nonce = $request['code_check'];
         if (!wp_verify_nonce($retrieved_nonce, 'code_check' ) ) die( 'Failed 
          security check' );

         /***
         you can also try
        if ( isset( $request['code_check'] ) || wp_verify_nonce( $request['code_check'], 'code_check' ) )  
         *******/
 }

0
投票

我遇到了同样的问题,在调试时,我发现随机数与当前登录的用户绑定在一起(这显然对安全性很有好处)。

但是,当我创建随机数时,它无法正确识别我已登录。就我而言,我在其上创建随机数的表单页面是一个自定义端点,使用

add_rewrite_endpoint
并路由到我的自定义通过
template_include
钩子模板。我不知道为什么 WordPress 在查看前端时没有检测到我已登录,因为用户身份验证挂钩在模板挂钩之前运行,但这是另一天的问题。

当 JS 使用该值将该请求发送到后端时,就会出现差异。在后端,它确实知道我已登录,然后使用这些值重新创建随机数哈希。哈希比较失败,因为发送的哈希使用的值与我的用户帐户中的值不同。

我实现的解决方案类似于摘要身份验证,用于创建不与用户帐户绑定的模拟随机数值。所以我创建了这个 PHP 类:

class CustomNonce
{
    private static $endpoint = 'authorization-uri-endpoint'; // E.g., `auth` from `https://example.com/auth/`
    private static $username = 'SomeArbitr@ryT3xt';
    private static $password = 'Some0therArb1traryText';

    /**
     * Required Parts of Digest
     *
     * Feel free to modify the array order to further randomize it from other sites
     *
     * @var array An array of the 9 required parts of digest authentication.
     */
    private static $digest_parts = array('username', 'realm', 'nonce', 'cnonce', 'nc', 'qop', 'opaque', 'uri', 'response');

    /**
     * Digest Delimiter
     *
     * Feel free to modify this character to further randomize it from other sites
     *
     * @var string A string used as a delimiter for digest authentication.
     */
    private static $digest_delimiter = '=^_^=';

    /**
     * Creates nonce.
     *
     * @param string|int $action Scalar value to add context to the nonce.
     *
     * @return string The token.
     */
    public static function create($action = -1)
    {
        $_key = trim(sanitize_key('_nonce_' . self::$endpoint . '_' . $action . '_'));
        $nc_key = $_key . 'nc';
        $nc = get_transient($nc_key); // Get the current count for this particular request
        if (!is_int($nc)) {
            $nc = 1; // Start it at one if it was never called before
        } else {
            $nc++; // Increment it
        }
        set_transient($nc_key, $nc); // Save it without an expiration to make it persistant cache
        $digest = array();
        foreach (self::$digest_parts as $key) {
            switch ($key) {
                case 'username': // username, same as server response
                    $digest[$key] = self::$username;
                    break;
                case 'realm': // realm, same as server response
                    $digest[$key] = self::$endpoint;
                    break;
                case 'nonce': // nonce, generated by remote server to return back as is, the "signature" if you will
                    $digest[$key] = $action;
                    break;
                case 'cnonce': // cnonce, random unique ID generated by client
                    $digest[$key] = md5(uniqid($nc_key . $action, true));
                    set_transient($_key . $nc, $digest[$key], HOUR_IN_SECONDS); // Save it for just 1 hour
                    break;
                case 'nc': // nonce count, hexadecimal serial number for the request, the client should increase this number by one for every request
                    $digest[$key] = sprintf('%06d', $nc); // Convert it to a 6-digit number (hexadecimal), probably with preceeding zeroes
                    break;
                case 'qop': // qop, values are "auth" or "auth-int"
                    $digest[$key] = 'auth-int';
                    break;
                case 'opaque': // opaque, same as server response
                    $digest[$key] = self::$password;
                    break;
                case 'uri': // encoded authentication uri, where authentication should take place
                    $digest[$key] = htmlentities(urlencode(home_url(self::$endpoint)), ENT_QUOTES, 'UTF-8', false);
                    break;
                default:
                    $digest[$key] = '';
                    break;
            }
        }

        /**
         * Validation hash.
         *
         * The server will also make one and compare it to this.
         */
        $digest['response'] = self::generate_hash($digest['realm'], $digest['uri'], $digest['nonce'], $digest['cnonce'], $digest['username'], $digest['opaque'], $digest['nc'], $digest['qop']);

        /**
         * Since we aren't sending it as an Authorization header, we don't need to configure it like this:
         * `Digest username="%s",realm="%s",nonce="%s",cnonce="%s",nc="%s",qop="%s",opaque="%s",uri="%s",response="%s"`
         */
        return implode(self::$digest_delimiter, array_values($digest));
    }

    public static function verify($nonce, $action = -1)
    {
        $nonce = trim(strval($nonce));
        $action = trim(sanitize_text_field($action));
        $request_method = strtoupper(sanitize_key(array_has_key('REQUEST_METHOD', $_SERVER)));
        if (empty($nonce) || empty($action) || $request_method !== 'POST') {
            error_log('Error. Nonce was empty, action was empty, or invalid request method.' . print_r(array(
                'nonce' => $nonce,
                'action' => $action,
                'method' => $request_method
            ), true));
            return false;
        }

        /**
         * Parse the digest value
         *
         * Protect against missing data
         */
        $digest = explode(self::$digest_delimiter, $nonce);
        if (count($digest) !== count(self::$digest_parts)) {
            error_log('Error. Could not parse digest. ' . print_r(array(
                'nonce' => $nonce,
                'digest' => $digest,
            ), true));
            return false;
        }
        $digest = array_combine(self::$digest_parts, $digest);

        /**
         * Validate Requirements
         *
         * Ensure all parts exist and are valid.
         */
        $nc = 0;
        $cnonce = '';
        foreach ($digest as $key => $value) {
            $value = trim(sanitize_text_field($value));
            if (empty($value)) {
                error_log('Error. One or more required parts of the digest are missing.' . PHP_EOL . print_r(array(
                    'key' => $key,
                    'value' => $value,
                    'digest' => $digest,
                    'nonce' => $nonce
                ), true));
                return false;
            }
            if ($key == 'nc') {
                /**
                 * Compare nonce counts and ensure it was issued at all.
                 */
                $digest[$key] = intval($value);
                if (!$digest[$key]) {
                    error_log('Error. Missing nc.' . PHP_EOL . print_r(array(
                        'key' => $key,
                        'value' => $value,
                        'digest' => $digest,
                        'nonce' => $nonce
                    ), true));
                    return false;
                }
                // $_key = trim(sanitize_key('_nonce_' . self::$endpoint . '_' . $action . '_'));
                // $nc_key = $_key . 'nc'; // E.g., "_nonce_magic-mirror_hourly-update_nc"
                $_key = trim(sanitize_key('_nonce_' . self::$endpoint . '_' . $action . '_'));
                $_dkey = trim(sanitize_key('_nonce_' . $digest['realm'] . '_' . $digest['nonce'] . '_'));
                if ($_key !== $_dkey) {
                    error_log('Error. Mismatching nonce keys.' . PHP_EOL . print_r(array(
                        'key' => $key,
                        'value' => $value,
                        'key1' => $_key,
                        'key2' => $_dkey,
                        'digest' => $digest,
                        'nonce' => $nonce
                    ), true));
                    return false;
                }
                $nc = intval(sanitize_text_field(get_transient($_key . 'nc')));
                if ($nc < $digest[$key]) {
                    error_log('Error. Mismatching nc.' . PHP_EOL . print_r(array(
                        'key' => $key,
                        'value' => $value,
                        'nc' => $nc,
                        '_key' => $_key,
                        'digest' => $digest,
                        'nonce' => $nonce
                    ), true));
                    return false;
                }
                $cnonce = trim(sanitize_text_field(get_transient($_key . $nc)));
                if (!$cnonce || $cnonce !== $digest['cnonce']) {
                    error_log('Error. Invalid or expired cnonce.' . PHP_EOL . print_r(array(
                        'key' => $key,
                        'value' => $value,
                        '_key' => $_key . $nc,
                        'cnonce' => $cnonce,
                        'nc' => $nc,
                        'digest' => $digest,
                        'nonce' => $nonce
                    ), true));
                    return false;
                }
            } elseif ($key == 'uri' && $value === 'http://') {
                error_log('Error. Invalid URI.' . PHP_EOL . print_r(array(
                    'key' => $key,
                    'value' => $value,
                    'digest' => $digest,
                    'nonce' => $nonce
                ), true));
                return false;
            } elseif ($key == 'qop' && $value != 'auth-int') {
                error_log('Error. Invalid authorization method' . PHP_EOL . print_r(array(
                    'key' => $key,
                    'value' => $value,
                    'digest' => $digest,
                    'nonce' => $nonce
                ), true));
                return false;
            }
            $digest[$key] = $value;
        }

        /*
         * Verify Digest
         *
         * Make our own hash and compare it with the one sent to us.
         */
        $hash = self::generate_hash(
            self::$endpoint, // Realm
            htmlentities(urlencode(home_url(self::$endpoint)), ENT_QUOTES, 'UTF-8', false), // URI
            $digest['nonce'], // Nonce from client
            $cnonce, // My nonce
            self::$username, // My username
            self::$password, // My password
            $digest['nc'], // The hexidecimal value provided to me
            $digest['qop'] // The auth method provided to me
        );
        if ($hash !== $digest['response']) {
            error_log('Failed digest authentication.' . PHP_EOL . print_r(array(
                'hash' => $hash,
                'response' => $digest['response'],
                'digest' => $digest
            ), true));
            return false;
        }

        // Valid nonce.
        return true;
    }

    /**
     * Get Valid Digest Response
     *
     * @param string $realm The realm, provided by this application.
     * @param string $uri The URL where authentication takes place, provided by the remote request.
     * @param string $nonce Randomly generated string provided by the remote request.
     * @param string $cnonce Randomly generated string provided by this application.
     * @param string $username The username that matches the one in the application, provided by the remote request.
     * @param string $password The password that matches the one in the application, provided by the remote request.
     * @param string $nc Nonce count, hexadecimal serial number for the request, the application should increase this number by one for every request.
     * @param string $qop Values are `auth` or `auth-int`.
     * @param string $method Request method.
     *
     * @param string
     */
    private static function generate_hash($realm, $uri, $nonce, $cnonce, $username, $password, $nc, $qop = 'auth-int', $method = 'POST')
    {
        // Exit early if missing required fields
        if (!$realm || !$uri || $uri == 'http://' || !$nonce || !$cnonce || !$username || !$nc || !$qop || !$method) {
            return '';
        }
        $before = md5($username . ':' . $realm . ':' . $password);
        $after = md5($method . ':' . $uri);
        return md5($before . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . $after);
    }

使用时请更换

<?php wp_nonce_field('code_check', 'codecheck'); ?>

<input type="hidden" name="_wpnonce" value="<?php echo esc_attr(CustomNonce::create('code_check')); ?>">

并更换

wp_verify_nonce($retrieved_nonce, 'code_check' )

CustomNonce::verify($retrieved_nonce, 'code_check')

如果您不想记录失败,也可以删除所有

error_log()
行。我将它们保留在我的开发环境中进行测试,但随后将它们删除以用于生产。

© www.soinside.com 2019 - 2024. All rights reserved.