Nonce ที่ดึงมาจาก REST API นั้นไม่ถูกต้องและแตกต่างจาก nonce ที่สร้างใน wp_localize_script


10

สำหรับผู้ที่เดินทางมาจาก Google: คุณอาจจะไม่ควรจะได้รับขณะปัจจุบันจากส่วนที่เหลือของ APIเว้นแต่คุณจริงๆจะรู้ว่าสิ่งที่คุณทำ รับรองความถูกต้องของคุกกี้ที่ใช้กับ REST API เป็นเพียงความหมายสำหรับปลั๊กอินและธีม สำหรับการใช้งานหน้าเดียวคุณอาจจะใช้OAuth

คำถามนี้มีอยู่เนื่องจากเอกสารไม่ได้ / ไม่ชัดเจนว่าคุณควรรับรองความถูกต้องจริงอย่างไรเมื่อสร้างแอปพลิเคชันหน้าเดียว JWT ไม่เหมาะสำหรับเว็บแอปและ OAuth นั้นยากที่จะใช้งานมากกว่าการรับรองความถูกต้องตามคุกกี้


หนังสือคู่มือมีตัวอย่างเกี่ยวกับวิธีที่ไคลเอ็นต์ Backbone JavaScript จัดการค่าคงที่และหากฉันทำตามตัวอย่างฉันจะได้รับ nonce ที่ปลายทางที่มีอยู่แล้วภายในเช่น / wp / v2 / posts ยอมรับ

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

]);

อย่างไรก็ตามการใช้ Backbone ไม่เป็นปัญหาและมีธีมดังนั้นฉันจึงเขียนปลั๊กอินต่อไปนี้:

<?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,
      ];
    },
  ]);
});

ฉันซ่อมแซมคอนโซล JavaScript เล็กน้อยและเขียนสิ่งต่อไปนี้:

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()

ผลลัพธ์ที่คาดหวังคือสองโพสต์ใหม่ แต่ฉันได้รับCookie nonce is invalidจากโพสต์แรกและโพสต์ที่สองสร้างโพสต์สำเร็จ นั่นอาจเป็นเพราะความแตกต่างระหว่าง แต่ทำไม? ฉันเข้าสู่ระบบด้วยชื่อผู้ใช้เดียวกันในคำขอทั้งสอง

ป้อนคำอธิบายรูปภาพที่นี่

หากวิธีการของฉันไม่ถูกต้องฉันควรได้รับ nonce อย่างไร

แก้ไข :

ฉันพยายาม messing กับ Globals โดยไม่ต้องโชคดีมาก มีโชคดีขึ้นเล็กน้อยโดยใช้การกระทำ 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,
      ];
    },
  ]);
});

ตอนนี้เมื่อฉันเรียกใช้ JavaScript ด้านบนโพสต์ทั้งสองจะถูกสร้างขึ้น แต่จุดตรวจสอบความล้มเหลว!

ป้อนคำอธิบายรูปภาพที่นี่

ฉันไปตรวจแก้จุดบกพร่อง 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!

ฉันเพิ่มการบันทึกบางส่วน

// 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;
}

และรหัส JavaScript ตอนนี้ผลลัพธ์ในรายการต่อไปนี้ อย่างที่คุณเห็นเมื่อปลายทางการตรวจสอบเรียกว่า uid คือ 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

คำตอบ:


3

function rest_cookie_check_errors()ใช้เวลามองใกล้ที่

เมื่อคุณได้รับ nonce ผ่าน/wp-json/nonce/v1/getคุณจะไม่ส่ง nonce ในสถานที่แรก ดังนั้นฟังก์ชั่นนี้จะยกเลิกการรับรองความถูกต้องของคุณด้วยรหัสนี้:

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

นั่นเป็นเหตุผลที่คุณได้รับ nonce ที่แตกต่างจากการโทร REST ของคุณและรับมันจากธีม การเรียกใช้ REST ไม่ได้รับการยอมรับข้อมูลรับรองการเข้าสู่ระบบของคุณ (ในกรณีนี้ผ่านการตรวจสอบคุกกี้) เนื่องจากคุณไม่ได้ส่งข้อความที่ไม่ถูกต้องในคำขอรับ

ตอนนี้สาเหตุที่โค้ด wp_loaded ของคุณทำงานนั้นเป็นเพราะคุณได้รับ nonce และบันทึกเป็นโกลบอลก่อนที่รหัสที่เหลือนี้จะทำให้การเข้าสู่ระบบของคุณเป็นโมฆะ การตรวจสอบล้มเหลวเนื่องจากรหัสที่เหลือเป็นโมฆะการเข้าสู่ระบบของคุณก่อนที่การตรวจสอบจะเกิดขึ้น


ฉันไม่ได้ดูฟังก์ชั่นนั้น แต่นั่นอาจจะสมเหตุสมผล ทำไมฉันควรรวม nonce ที่ถูกต้องสำหรับคำขอ GET? (ตอนนี้ฉันเข้าใจแล้ว แต่มันก็ยังไม่ชัดเจน) จุดทั้งหมดของจุดตรวจสอบ / ยืนยันคือฉันสามารถตรวจสอบว่า nonce นั้นยังคงใช้ได้อยู่หรือไม่
Christian

จากแหล่งที่มาของ rest_cookie_check_errors ฉันควรเปลี่ยนจุดสิ้นสุดของฉันเพื่อที่จะไม่ได้ตรวจสอบ$_GET['nonce']แต่ส่วนหัวหรือ$_GET['_wpnonce']พารามิเตอร์ที่ไม่ใช่ แก้ไข?
คริสเตียน

1

ในขณะที่การแก้ปัญหานี้การทำงานจะไม่แนะนำ OAuth เป็นตัวเลือกที่ต้องการ


ฉันคิดว่าฉันได้รับมัน

ฉันคิดว่า wp_verify_nonce นั้นใช้งานไม่ได้เนื่องจาก wp_get_current_user ล้มเหลวในการรับวัตถุผู้ใช้ที่เหมาะสม

มันไม่ได้เป็นตามที่อ็อตโตแสดง

โชคดีที่มันมีตัวกรอง: $uid = apply_filters( 'nonce_user_logged_out', $uid, $action );

การใช้ตัวกรองนี้ฉันสามารถเขียนสิ่งต่อไปนี้และรหัส JavaScript ดำเนินการตามที่ควร:

ป้อนคำอธิบายรูปภาพที่นี่

<?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,
      ];
    },
  ]);
});

หากคุณพบปัญหาด้านความปลอดภัยเกี่ยวกับการแก้ไขโปรดแจ้งให้ฉันทราบทันทีตอนนี้ฉันไม่เห็นอะไรผิดปกติ


0

ดูรหัสทั้งหมดนี้ดูเหมือนว่าปัญหาของคุณคือการใช้การปิด ในinitขั้นตอนที่คุณควรตั้ง hooks และไม่ประเมินข้อมูลเพราะแกนทั้งหมดไม่ได้โหลดเสร็จแล้วและกำลังเริ่มต้น

ใน

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,
      ];
    },
  ]);

$userถูกผูกไว้ก่อนที่จะถูกนำมาใช้ในการปิด แต่ไม่มีใครสัญญากับคุณว่าคุกกี้ที่ได้รับการจัดการแล้วและผู้ใช้ที่ได้รับการรับรองความถูกต้องขึ้นอยู่กับพวกเขา รหัสที่ดีกว่าจะเป็น

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,
      ];
    },
  ]);

ใช้ตะขอตัวล่าสุดใน wordpress ใช้ตะขอตัวล่าสุดที่เป็นไปได้และอย่าพยายามคำนวณสิ่งใดก็ตามที่คุณไม่จำเป็นต้องทำ


ฉันใช้ Query Monitors actions & hooks ส่วนเพื่อคิดออกว่ารันอะไรและในลำดับใด set_current_user รันก่อน init & after_setup_theme ไม่ควรมีปัญหากับการกำหนดผู้ใช้ $ ภายนอกและก่อนปิด
คริสเตียน

@Christian และทั้งหมดอาจไม่เกี่ยวข้องในบริบทของ json API ฉันจะแปลกใจมากหากการตรวจสอบข้อความค้นหาทำงานในบริบทนั้น
Mark Kaplun
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.