Nonce lấy từ API REST không hợp lệ và khác với nonce được tạo trong wp_localize_script


10

Đối với những người đến từ Google: Có lẽ bạn không nên lấy nonces từ API REST , trừ khi bạn thực sự biết bạn đang làm gì. Xác thực dựa trên cookie với API REST chỉ có ý nghĩa đối với các plugin và chủ đề. Đối với một ứng dụng trang duy nhất, có lẽ bạn nên sử dụng OAuth .

Câu hỏi này tồn tại bởi vì tài liệu không / không rõ ràng về cách bạn thực sự nên xác thực khi xây dựng các ứng dụng trang đơn, JWTs không thực sự phù hợp với các ứng dụng web và OAuth khó thực hiện hơn so với xác thực dựa trên cookie.


Cuốn cẩm nang có một ví dụ về cách máy khách JavaScript xương sống xử lý các nonces và nếu tôi làm theo ví dụ này, tôi nhận được một thông báo mà các điểm cuối được xây dựng như / wp / v2 / post chấp nhận.

\wp_localize_script("client-js", "theme", [
  'nonce' => wp_create_nonce('wp_rest'),
  'user' => get_current_user_id(),

]);

Tuy nhiên, sử dụng Backbone là không cần thiết, và các chủ đề cũng vậy, vì vậy tôi đã viết plugin sau:

<?php
/*
Plugin Name: Nonce Endpoint
*/

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => wp_create_nonce('wp_rest'),
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      return [
        'valid' => (bool) wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

Tôi đã sửa lại trong bảng điều khiển JavaScript một chút và viết như sau:

var main = async () => { // var because it can be redefined
  const nonceReq = await fetch('/wp-json/nonce/v1/get', { credentials: 'include' })
  const nonceResp = await nonceReq.json()
  const nonceValidReq = await fetch(`/wp-json/nonce/v1/verify?nonce=${nonceResp.nonce}`, { credentials: 'include' })
  const nonceValidResp = await nonceValidReq.json()
  const addPost = (nonce) => fetch('/wp-json/wp/v2/posts', {
    method: 'POST',
    credentials: 'include',
    body: JSON.stringify({
      title: `Test ${Date.now()}`,
      content: 'Test',
    }),
    headers: {
      'X-WP-Nonce': nonce,
      'content-type': 'application/json'
    },
  }).then(r => r.json()).then(console.log)

  console.log(nonceResp.nonce, nonceResp.user, nonceValidResp)
  console.log(theme.nonce, theme.user)
  addPost(nonceResp.nonce)
  addPost(theme.nonce)
}

main()

Kết quả dự kiến ​​là hai bài viết mới, nhưng tôi nhận được Cookie nonce is invalidtừ bài đầu tiên, và bài thứ hai tạo ra bài thành công. Đó có lẽ là do các nonces khác nhau, nhưng tại sao? Tôi đã đăng nhập với cùng một người dùng trong cả hai yêu cầu.

nhập mô tả hình ảnh ở đây

Nếu cách tiếp cận của tôi là sai, làm thế nào tôi có được nonce?

Chỉnh sửa :

Tôi đã cố gắng gây rối với toàn cầu mà không gặp nhiều may mắn . Có một chút may mắn hơn bằng cách sử dụng hành động wp_loaded:

<?php
/*
Plugin Name: Nonce Endpoint
*/

$nonce = 'invalid';
add_action('wp_loaded', function () {
  global $nonce;
  $nonce = wp_create_nonce('wp_rest');
});

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      error_log("verify $nonce $user");
      return [
        'valid' => (bool) wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

Bây giờ khi tôi chạy JavaScript ở trên, hai bài đăng được tạo, nhưng điểm cuối xác minh không thành công!

nhập mô tả hình ảnh ở đây

Tôi đã đi đến gỡ lỗi wp_verify_nonce:

function wp_verify_nonce( $nonce, $action = -1 ) {
  $nonce = (string) $nonce;
  $user = wp_get_current_user();
  $uid = (int) $user->ID; // This is 0, even though the verify endpoint says I'm logged in as user 2!

Tôi đã thêm một số đăng nhập

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

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

và mã JavaScript bây giờ dẫn đến các mục sau. Như bạn có thể thấy, khi điểm cuối xác minh được gọi, uid là 0.

[01-Mar-2018 11:41:57 UTC] verify 716087f772 2
[01-Mar-2018 11:41:57 UTC] expected 1 b35fa18521 received 716087f772 uid 0 action wp_rest
[01-Mar-2018 11:41:57 UTC] expected 2 dd35d95cbd received 716087f772 uid 0 action wp_rest
[01-Mar-2018 11:41:58 UTC] expected 1 716087f772 received 716087f772 uid 2 action wp_rest
[01-Mar-2018 11:41:58 UTC] expected 1 716087f772 received 716087f772 uid 2 action wp_rest

Câu trả lời:


3

Hãy nhìn kỹ vào function rest_cookie_check_errors().

Khi bạn nhận được thông báo /wp-json/nonce/v1/get, bạn sẽ không gửi thông tin ở vị trí đầu tiên. Vì vậy, chức năng này vô hiệu hóa xác thực của bạn, với mã này:

if ( null === $nonce ) {
    // No nonce at all, so act as if it's an unauthenticated request.
    wp_set_current_user( 0 );
    return true;
}

Đó là lý do tại sao bạn nhận được một thông báo khác với cuộc gọi REST của bạn so với việc nhận nó từ chủ đề. Cuộc gọi REST cố ý không nhận ra thông tin đăng nhập của bạn (trong trường hợp này thông qua cookie auth) vì bạn đã không gửi một thông báo hợp lệ trong yêu cầu nhận.

Bây giờ, lý do mã wp_loaded của bạn hoạt động là vì bạn đã nhận được thông báo và lưu nó vào toàn cầu trước khi mã còn lại này vô hiệu hóa đăng nhập của bạn. Xác minh thất bại vì mã còn lại vô hiệu hóa đăng nhập của bạn trước khi xác minh diễn ra.


Tôi thậm chí đã không nhìn vào chức năng đó, nhưng điều đó có thể có ý nghĩa. Vấn đề là, tại sao tôi nên bao gồm một nonce hợp lệ cho yêu cầu GET? .
Christian

Dựa trên nguồn của rest_cookie_check_errors, tôi nên thay đổi điểm cuối của mình để nó không kiểm tra $_GET['nonce'], nhưng tiêu đề hoặc $_GET['_wpnonce']tham số nonce . Chính xác?
Christian

1

Trong khi giải pháp này hoạt động, nó không được khuyến khích . OAuth là lựa chọn ưu tiên.


Tôi nghĩ rằng tôi đã nhận nó.

Tôi nghĩ rằng wp_verify_nonce bị hỏng, vì wp_get_civerse_user không nhận được đối tượng người dùng phù hợp.

Nó không phải, như minh họa bởi Otto.

May mắn thay, nó có một bộ lọc: $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

Sử dụng bộ lọc này, tôi có thể viết như sau và mã JavaScript thực thi như sau:

nhập mô tả hình ảnh ở đây

<?php
/*
Plugin Name: Nonce Endpoint
*/

$nonce = 'invalid';
add_action('wp_loaded', function () {
  global $nonce;
  $nonce = wp_create_nonce('wp_rest');
});

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

  register_rest_route('nonce/v1', 'verify', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      $nonce = !empty($_GET['nonce']) ? $_GET['nonce'] : false;
      add_filter("nonce_user_logged_out", function ($uid, $action) use ($user) {
        if ($uid === 0 && $action === 'wp_rest') {
          return $user;
        }

        return $uid;
      }, 10, 2);

      return [
        'status' => wp_verify_nonce($nonce, 'wp_rest'),
        'user' => $user,
      ];
    },
  ]);
});

Nếu bạn phát hiện ra một vấn đề bảo mật với bản sửa lỗi, xin vui lòng cho tôi một tiếng hét, ngay bây giờ tôi không thể thấy bất cứ điều gì sai trái với nó, ngoại trừ toàn cầu.


0

Nhìn vào tất cả các mã này có vẻ như vấn đề của bạn là việc sử dụng các bao đóng. Ở initgiai đoạn bạn chỉ nên đặt hook và không đánh giá dữ liệu vì không phải tất cả các lõi đã tải xong và được khởi tạo.

Trong

add_action('rest_api_init', function () {
  $user = get_current_user_id();
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () use ($user) {
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

cái $usernày bị ràng buộc sớm được sử dụng trong bao đóng, nhưng không ai hứa với bạn rằng cookie đã được xử lý và người dùng đã được xác thực dựa trên chúng. Một mã tốt hơn sẽ là

add_action('rest_api_init', function () {
  register_rest_route('nonce/v1', 'get', [
    'methods' => 'GET',
    'callback' => function () {
    $user = get_current_user_id();
      return [
        'nonce' => $GLOBALS['nonce'],
        'user' => $user,
      ];
    },
  ]);

Như mọi khi với hook hook trong wordpress, hãy sử dụng hook mới nhất có thể và không bao giờ cố gắng tính toán trước bất cứ điều gì bạn không phải làm.


Tôi đã sử dụng phần hành động & hook của Trình theo dõi truy vấn để tìm ra những gì chạy và theo thứ tự nào, set_civerse_user chạy trước init & after_setup_theme, không nên có vấn đề với $ user được xác định bên ngoài & trước khi đóng.
Christian

@Christian và tất cả chúng có thể không liên quan trong ngữ cảnh của API json. Tôi sẽ rất ngạc nhiên nếu trình giám sát truy vấn hoạt động trong bối cảnh đó
Mark Kaplun
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.