ยึดธงที่ WordCamp Europe 2022

เผยแพร่แล้ว: 2022-06-13

ระหว่าง WordCamp Europe 2022 เราได้จัดการแข่งขัน WordPress Capture The Flag (CTF) ในสี่ความท้าทาย

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

ความท้าทาย #1 – คุณโชคดีไหม?

ความท้าทาย #2 – บายพาสรายการบล็อก?

ความท้าทาย #3 – ใบอนุญาตในการยึดธง

ความท้าทาย #4 – ใบอนุญาต CTF: ตอนที่ 2

หากคุณสนใจที่จะทดลองใช้ คุณยังสามารถรับไฟล์คำท้าได้ที่นี่:

hackismet-docker.zipดาวน์โหลด

ท้าทาย #1 – คุณโชคดีไหม? (250 คะแนน)

ตัวอย่างโค้ดที่เกี่ยวข้อง

 register_rest_route( 'hackismet', '/am-i-lucky', [
     'methods' => WP_Rest_Server::READABLE,
     'callback' => 'hackismet_am_i_lucky',
     'permission_callback' => '__return_true',
 ]);

 function hackismet_am_i_lucky( $request ) {
     $flag = get_option( 'secret_hackismet_flag_2' );
     if( hash_equals( crypt( $request['payload'] . $flag . random_bytes(32), '$1$sup3r_s3kr3t_s4lt' ), $request['hash'] ) ) {
        return rest_ensure_response( $flag );
     }
     return rest_ensure_response(false);
 }

จะแก้ไขได้อย่างไร?

ความท้าทายนี้นำเสนอปลายทาง REST API ที่สามารถเข้าถึงได้ผ่านเส้นทาง /wp-json/hackismet/am-i-lucky ได้รับการออกแบบมาเพื่อรับพารามิเตอร์คำขอเพย์โหลดและแฮช เชื่อม request['payload'] กับแฟล็กและสตริงของไบต์สุ่มที่ปลอดภัยด้วยการเข้ารหัส 32 ไบต์ และเปรียบเทียบแฮชที่ได้กับ request['hash']

เมื่ออ่านเอกสารของฟังก์ชัน crypt() จะพบว่าฟังก์ชันนี้ไม่ปลอดภัยสำหรับไบนารี (แต่!) หมายความว่าสามารถใช้ไบต์ว่าง (%00) เพื่อตัดสตริงที่จะถูกแฮชก่อนแฟล็กและ 32 ไบต์สุ่ม ทั้งนี้เนื่องจากการใช้งานฟังก์ชันนั้นใน PHP ในปัจจุบันเป็นเพียงนามแฝงของฟังก์ชัน C พื้นฐานที่มีชื่อเดียวกัน และสตริง C จะสิ้นสุดด้วยไบต์ว่าง

เพื่อให้ได้แฟล็กของคุณ สิ่งที่คุณต้องทำคือคำนวณแฮชด้วยข้อความที่คุณควบคุมและเกลือเข้ารหัสที่ใช้ในโค้ดของปลั๊กอิน ใช้แฮชที่เป็นผลลัพธ์ในพารามิเตอร์ "แฮช" และใส่ข้อความของคุณใน "เพย์โหลด" พารามิเตอร์ ต่อด้วยไบต์ว่าง (%00)

นี่คือลักษณะของการหาประโยชน์ที่ประสบความสำเร็จ:

/wp-json/hackismet/am-i-lucky?payload=lel%00&hash=$1$sup3r_s3$sThhFzCqsprSVMNFOAm5Q/

ความท้าทาย #2 – บายพาสรายการบล็อก? (250 คะแนน)

ตัวอย่างโค้ดที่เกี่ยวข้อง

 register_rest_route( 'hackismet', '/get-option/(?P<option_key>\w+)', [
     'methods' => WP_Rest_Server::READABLE,
     'callback' => 'hackismet_get_option',
     'permission_callback' => 'hackismet_validate_option',
 ]);

 function hackismet_validate_option( $request ) {
     $option_key = trim( strtolower( $request['option_key'] ) );
     if( empty( $option_key ) ) {
        return false;
     }
 
     if( ! preg_match( '/^hackismet_/i', $option_key) ) {
        return false;
     }
 
     if( $option_key == 'hackismet_flag_1' ) {
        return false;
     }
     return true;
 }

 function hackismet_get_option( $request ) {
    $option_key = trim( strtolower( $request['option_key'] ) );
    return rest_ensure_response( get_option( $option_key ) );
 }

จะแก้ไขได้อย่างไร?

ความท้าทายนี้นำเสนอจุดปลาย REST API ที่สามารถเข้าถึงได้ผ่าน /wp-json/hackismet/get-option/option_key_you_want

เป้าหมายค่อนข้างง่าย: พยายามทำให้ตัวเลือก “hackismet_flag_1” รั่วไหล

น่าเสียดายที่การเรียกกลับการอนุญาตสำหรับปลายทางนั้นยังทำบางสิ่งเพื่อป้องกันไม่ให้คุณคว้าตัวเลือกใด ๆ บนไซต์:

  • ตรวจสอบว่าคีย์ตัวเลือกเริ่มต้นด้วย “hackismet_”
  • นอกจากนี้ยังช่วยให้แน่ใจว่าตัวเลือกใดๆ ที่คุณต้องการดึงกลับไม่ใช่ hackismet_flag_1 ซึ่งเป็นที่ตั้งของแฟล็ก
  • ในการทำให้สิ่งต่าง ๆ ดู ยากขึ้น เส้นทาง API จำกัด ซึ่งอักขระสามารถทำได้ในพารามิเตอร์เส้นทาง option_key อนุญาตเฉพาะสตริงที่ตรงกับ \w+ regex

ฟังก์ชันเรียกกลับ "hackismet_validate_option" ยังใช้ฟังก์ชัน "strtolower" และ "trim" เพื่อพยายามทำให้พารามิเตอร์ "option_key" เป็นมาตรฐานอีกด้วย นี่เป็นการขัดขวางความพยายามในการใช้พฤติกรรมที่ได้รับการบันทึกไว้อย่างดีจากการเปรียบเทียบ "utf8mb4_unicode_ci" ของ MySQL เช่นข้อเท็จจริงที่ว่าการเปรียบเทียบสตริงไม่คำนึงถึงขนาดตัวพิมพ์ และไม่สนใจช่องว่างต่อท้ายในคอลัมน์ VARCHAR เช่นกัน

เคล็ดลับการเรียงอื่นๆ

เพื่อแก้ปัญหานี้ เราต้องหาลักษณะเฉพาะอื่นๆ ในลักษณะที่ “utf8mb4_unicode_ci” ทำการค้นหาสตริงเพื่อหลีกเลี่ยงการตรวจสอบ และอย่างน้อยก็มีสองวิธีในการดำเนินการดังกล่าว

ความไวของสำเนียง

ตามที่ระบุไว้ในเอกสาร MySQL อย่างเป็นทางการ:

สำหรับชื่อการเปรียบเทียบที่ไม่ใช่ไบนารีซึ่งไม่ได้ระบุความไวของการเน้นเสียง จะกำหนดโดยความไวของตัวพิมพ์

ใส่สั้น ๆ: ความไวต่อสำเนียงเป็นสิ่งที่ การจัดเรียงเริ่มต้นของ WordPress ใช้องค์ประกอบ “_ci” (สำหรับ “ไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่”) ซึ่งหมายความว่าการเปรียบเทียบนั้นไม่คำนึงถึงสำเนียง ด้วย

ดังนั้น การผ่าน “hackismet_flag_1” จะเป็นการข้ามการตรวจสอบใน hackismet_validate_option

น้ำหนักที่ไม่สนใจ

Unicode Collation Algorithm ซึ่งใช้โดย utf8mb4_unicode_ci collation ของ MySQL เพื่อเปรียบเทียบและจัดเรียงสตริง Unicode อธิบายแนวคิดของ “ignorable weights” ดังต่อไปนี้:


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

พูดง่ายๆ ก็คือ อัลกอริธึมจะคำนวณน้ำหนักสำหรับองค์ประกอบการเรียงแต่ละองค์ประกอบ (อักขระ) และบางส่วนถูกกำหนดให้มีน้ำหนักเริ่มต้นเป็นศูนย์ ซึ่งทำให้อัลกอริทึมไม่สนใจองค์ประกอบเหล่านี้เมื่อทำการเปรียบเทียบสตริง

มี หลาย วิธีในการ (ab) ใช้พฤติกรรมนั้นเพื่อเอาชนะความท้าทาย ซึ่งรวมถึง:

  • การเพิ่ม null ไบต์ที่ใดที่หนึ่งในสตริง (เช่น hackismet_fl%00ag_1 )
  • การแทรกลำดับ UTF-8 ที่ไม่ถูกต้องภายในสตริง (เช่น hackismet_fl%c2%80ag_1 )

คุณสามารถหาชุดค่าผสมอื่นๆ ได้มากมายในการใช้งาน UCA ของ MySQL

ข้ามการจำกัดอักขระพารามิเตอร์ “option_key”

ตัวแปรเส้นทาง “option_key” ถูกกำหนดให้ไม่ให้สิ่งอื่นใดนอกจาก \w+ ผ่าน นั่นเป็นปัญหา PHP ปฏิบัติต่อทุกสตริงเป็นชุดของไบต์แทนที่จะเป็นอักขระ Unicode เช่น MySQL ดังนั้นให้ส่งคำขอไปที่ “/wp-json/hackismet/get-option/hackismet_flag_1” หรือ “/wp-json/hackismet/get-option/hackismet_fla %00g_1” จะไม่ทำงาน

เพื่อหลีกเลี่ยงปัญหานั้น เอกสารอย่างเป็นทางการของ WordPress เกี่ยวกับการเขียน REST API endpoints ช่วยได้เล็กน้อย โดยเฉพาะบรรทัดที่เขียนว่า:

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

ในทางปฏิบัติหมายความว่าอย่างไรเมื่อไปที่ /wp-json/hackismet/get-option/test?option_key=hackismet_fla%00g_1 พารามิเตอร์ option_key จะมี “hackismet_fla%00g_1” และไม่ใช่ “test” ซึ่งจะบังคับให้ ปลั๊กอินเพื่อให้ธงแก่คุณ

ความท้าทาย #3 – ใบอนุญาตในการยึดธง (500 คะแนน)

ตัวอย่างโค้ดที่เกี่ยวข้อง

 register_rest_route( 'hackismet', '/generate-license/(?P<session_id>[0-9a-f\-]+)/(?P<rounds>\d+)', [
     'methods' => WP_Rest_Server::READABLE,
     'callback' => 'hackismet_generate_license',
     'permission_callback' => '__return_true',
     'args' => [
         'session_id' => [
            'required' => true,
            'type' => 'string',
            'validate_callback' => 'wp_is_uuid'
         ]
     ]
 ]);

 register_rest_route( 'hackismet', '/access-flag-3/(?P<session_id>[0-9a-f\-]+)/(?P<rounds>\d+)', [
     'methods' => WP_Rest_Server::READABLE,
     'callback' => 'hackismet_access_flag_3',
     'permission_callback' => 'hackismet_validate_license',
     'args' => [
         'session_id' => [
            'required' => true,
            'type' => 'string',
            'validate_callback' => 'wp_is_uuid'
         ]
     ]
 ]);

 register_rest_route( 'hackismet', '/delete-license/(?P<session_id>[0-9a-f\-]+)', [
     'methods' => WP_Rest_Server::READABLE,
     'callback' => 'hackismet_delete_license',
     'permission_callback' => '__return_true',
     'args' => [
         'session_id' => [
            'required' => true,
            'type' => 'string',
            'validate_callback' => 'wp_is_uuid'
         ]
     ]
 ]);

 function hackismet_generate_license( $request ) {
     // 128 bits of entropy should be enough to prevent bruteforce.
     $license_key = bin2hex( random_bytes(40) );
     // Here for added security
     for($i = $request['rounds']; $i > 0; $i--) {
         $license_key = str_rot13($license_key);
     }
     // Reset it.
     update_option( 'secret_hackismet_license_key_' . $request['session_id'], bin2hex( random_bytes( 64 ) ) );
     return rest_ensure_response('License successfully generated!');
 }

 function hackismet_delete_license( $request ) {
     // Remove existing key.
     delete_option('secret_hackismet_license_key_' . $request['session_id']);
     return rest_ensure_response('License successfully deleted!');
 }

 function hackismet_validate_license( $request ) {
    // Ensure a key has been set
    if( ! get_option( 'secret_hackismet_license_key_' . $request['session_id'] ) ) {
        return new WP_Error('no_license', 'No license exists for this session_id!');
    }
    $license_key = $request['key'];
     // Here for added security
     for($i = $request['rounds']; $i > 0; $i--) {
         $license_key = str_rot13($license_key);
     }
    if( $license_key == get_option( 'secret_hackismet_license_key_' . $request['session_id'] ) ) {
        return true;
    }
    return false;
 }

 function hackismet_access_flag_3( $request ) {
     return rest_ensure_response( get_option( 'secret_hackismet_flag_3' ) );
 }

จะแก้ไขได้อย่างไร?

แนวคิดเบื้องหลังความท้าทายนี้คือการจำลองระบบการจัดการและการตรวจสอบใบอนุญาตที่เสียหาย (มาก)

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

ความท้าทายนำเสนอจุดปลายสามจุด แม้ว่าจะมีเพียงสองจุดที่จำเป็นในการรับธง:

  • /hackismet/generate-license/(?P<session_id>[0-9a-f\-]+)/(?<rounds>\d+)
  • /hackismet/access-flag-3/(?P<session_id>[0-9a-f\-]+)/(?<rounds>\d+)
  • /hackismet/delete-license/(?P<session_id>[0-9a-f\-]+)

generate-license endpoint เติมไลเซนส์คีย์เฉพาะเซสชัน ซึ่งจากนั้นจะถูกตรวจสอบโดยใช้การเรียกกลับการอนุญาต hackismet_validate_license ของ access-flag-3 น่าเสียดาย เนื่องจากคุณไม่เคยเห็นคีย์ใบอนุญาตที่สร้างขึ้นจริงๆ เลย คุณต้องหาวิธีเลี่ยงการตรวจสอบใบอนุญาตทั้งหมดเพื่อให้ได้แฟล็ก

    $license_key = $request['key'];
     // Here for added security
     for($i = $request['rounds']; $i > 0; $i--) {
         $license_key = str_rot13($license_key);
     }
    if( $license_key == get_option( 'secret_hackismet_license_key_' . $request['session_id'] ) ) {
        return true;
    }

วิธีหนึ่งในการทำเช่นนั้นคือการให้ $request['key'] มีค่าบูลีนเป็น "จริง" และ $request['rounds'] มีค่าเป็นศูนย์ การทำเช่นนี้ คุณมั่นใจได้ว่า $request['key'] ไม่ถูกแก้ไขโดยการเรียก str_rot13 หลายครั้ง และเนื่องจากการตรวจสอบใบอนุญาตทำได้โดยใช้ตัวดำเนินการเปรียบเทียบแบบหลวมของ PHP การเปรียบเทียบจึงกลับเป็นจริงเสมอ

อย่างไรก็ตาม คุณไม่สามารถทำเช่นนั้นได้ด้วยพารามิเตอร์ GET หรือ POST ปกติ เนื่องจากสิ่งเหล่านี้จะมีเฉพาะสตริงหรืออาร์เรย์เท่านั้น โชคดีที่ WordPress REST API ให้คุณส่งเนื้อหาคำขอ JSON ได้ แม้แต่ในปลายทางที่ลงทะเบียนเพื่อใช้เมธอด GET HTTP เท่านั้น ด้วยเหตุนี้ การส่งคำขอต่อไปนี้จะทำให้คุณได้รับธงของคำท้า:

curl –url 'https://ctfsite.com/wp-json/generate-license/$your_session_id/1234'
curl –url 'https://ctfsite.com/wp-json/access-flag-3/$your_session_id/0' -X GET –data '{"key":true}' -H 'Content-Type: application/json'

ความท้าทาย #4 – ใบอนุญาต CTF: ส่วนที่ 2 (500 คะแนน)

ตัวอย่างโค้ดที่เกี่ยวข้อง

register_rest_route( 'hackismet', '/access-flag-4/(?P<session_id>[0-9a-f\-]+)/(?P<rounds>\d+)', [
    'methods' => WP_Rest_Server::READABLE,
    'callback' => 'hackismet_access_flag_4',
    'permission_callback' => 'hackismet_validate_license',
    'args' => [
        'session_id' => [
           'required' => true,
           'type' => 'string',
           'validate_callback' => 'wp_is_uuid'
        ],
        'key' => [
            'required' => true,
            'type' => 'string'
        ]
    ]
]);

 function hackismet_access_flag_4( $request ) {
     return rest_ensure_response( get_option( 'secret_hackismet_flag_4' ) );
 }

// (... and basically every other code snippets from Challenge #3! )

จะสามารถแก้ไขได้อย่างไร?

ความท้าทายนี้นำเสนอจุดสิ้นสุดสามจุด (และจำเป็นต้องแก้ไขทั้งสามจุด จริง ๆ ):

  • /hackismet/generate-license/(?P<session_id>[0-9a-f\-]+)/(?P<rounds>\d+)
  • /hackismet/delete-license/(?P<session_id>[0-9a-f\-]+)
  • /hackismet/access-flag-4/(?P<session_id>[0-9a-f\-]+)/(?P<rounds>\d+)

อย่างที่คุณเห็น สิ่งเหล่านี้เป็นจุดสิ้นสุดที่เหมือนกันมากกับความท้าทายสุดท้าย ความแตกต่างเพียงอย่างเดียวในตอนนี้คือเรามั่นใจว่า $request['key'] เป็นสตริงที่ป้องกันปัญหาการเรียงพิมพ์ที่เรากล่าวถึงในการท้าทายอื่น ๆ

เส้นทางการ delete-license ที่อธิบายตนเองได้ตรงตามที่คุณคาดหวัง: ลบใบอนุญาตปัจจุบันออกจากฐานข้อมูล ในทำนองเดียวกัน access-flag-4 เพียงแค่ส่งคืนแฟล็ก สมมติว่ามีการเรียกกลับค่าอนุญาต hackismet_validate_license อนุญาตให้เกิดขึ้น

ดังที่คุณเห็นจากข้อมูลโค้ด hackismet_validate_license การเรียกกลับการอนุญาตที่เรียกว่า get_option สองครั้ง ครั้งหนึ่งเพื่อตรวจสอบความถูกต้องของรหัสใบอนุญาต และอีกอันหนึ่งเพื่อเปรียบเทียบกับรหัสที่เราให้ไว้จริง การเรียกทั้งสองนั้นแยกจากกันด้วยลูป str_rot13 ซึ่งทำงานหลายรอบตามที่กำหนดไว้ในตัวแปรเส้นทาง $request['rounds']

สิ่งนี้ทำให้สภาพการแข่งขันเกิดขึ้นได้โดยการส่งตัวแปรรอบจำนวนมากเพื่อชะลอคำขอนานพอที่เราจะไปถึง /hackismet/delete-license ซึ่งเป็นการลบใบอนุญาตอย่างมีประสิทธิภาพก่อนที่จะเปรียบเทียบกับของเราเอง

ความจริงที่ว่า get_option() ตั้งค่าเริ่มต้นให้คืนค่าบูลีนเท็จ หากไม่พบตัวเลือกที่กำหนดคือเชอร์รี่บนเค้ก เนื่องจากฟังก์ชันจะไม่ตรวจสอบว่า $request['key'] ว่างหรือไม่ และ false == ““ เมื่อเปรียบเทียบประเภทต่าง ๆ ใน PHP อย่างหลวม ๆ สิ่งนี้จะทำให้เราสามารถข้ามการตรวจสอบความปลอดภัยได้อย่างสมบูรณ์

แต่นี่เป็นเพียงในทางทฤษฎี!

แคชเพื่อช่วยชีวิต!

ดังที่เห็นได้จากซอร์สโค้ดของฟังก์ชัน get_option แคชผลลัพธ์ของตัวเลือกใดๆ ก็ตามที่ดึงมา ดังนั้นคำขอเพิ่มเติมสำหรับตัวเลือกนั้นในคำขอ HTTP เดียวกันจะไม่ส่งการสืบค้น SQL แยกต่างหากเพิ่มเติม เพียงอย่างเดียวนี้ป้องกันการโจมตีสภาพการแข่งขันของเราจากการทำงาน แม้ว่าคำขออื่นจะลบตัวเลือกใบอนุญาตในขณะที่เรากำลังวนซ้ำผ่านการเรียก str_rot13 ทั้งหมดนั้น get_option จะไม่ทราบเนื่องจากผลลัพธ์ถูกแคชสำหรับคำขอนั้นแล้ว!

อีกครั้ง เมื่อดูซอร์สโค้ด ดูเหมือนว่าวิธีเดียวที่จะป้องกันไม่ให้เกิดขึ้นคือถ้า wp_installing ส่งคืน… จริงหรือ ปรากฏว่าเรา ทำได้

WordPress ติดตั้งยัง?

ฟังก์ชัน wp_installing อาศัยค่าคงที่ WP_INSTALLING เพื่อตรวจสอบว่า WordPress กำลังติดตั้งหรืออัปเดตตัวเองอยู่หรือไม่ การค้นหาตำแหน่งที่กำหนดค่าคงที่นี้ทำให้เกิดผลลัพธ์น้อยมาก สิ่งที่น่าสนใจที่สุดในกรณีของเราคือ wp-activate.php:

<?php
/**
 * Confirms that the activation key that is sent in an email after a user signs
 * up for a new site matches the key for that user and then displays confirmation.
 *
 * @package WordPress
 */
 
define( 'WP_INSTALLING', true );
 
/** Sets up the WordPress Environment. */
require __DIR__ . '/wp-load.php';
 
require __DIR__ . '/wp-blog-header.php';
 
if ( ! is_multisite() ) {
    wp_redirect( wp_registration_url() );
    die();
}

สิ่งที่ทำให้เหมาะสมโดยเฉพาะสำหรับจุดประสงค์ของเราที่นี่คือสิ่งแรกที่ทำคือเรียกใช้ require() บน wp-blog-header.php

เรื่องสั้นโดยย่อ: โค้ดที่เปิดใช้เซิร์ฟเวอร์ REST API จริงๆ นั้นเชื่อมโยงกับการดำเนินการ parse_request ดังนั้นจึงใช้ได้เฉพาะเมื่อ WordPress ตั้งค่าตัวแปรคิวรีที่จำเป็นสำหรับ The Loop ในการทำงานภายในเท่านั้น

สิ่งนี้จะเกิดขึ้นก็ต่อเมื่อมีการเรียกฟังก์ชัน wp() เหมือนใน wp-blog-header.php

เนื่องจากภายใน WordPress ใช้พารามิเตอร์ rest_route เพื่อทราบว่าจะโหลดเส้นทางใด การเพิ่มพารามิเตอร์นั้นลงใน URL ทั้งหมดที่ใช้ในการเปิด API ขณะเยี่ยมชม /wp-activate.php

การโจมตีครั้งสุดท้ายจึงมีลักษณะดังนี้:

  1. ส่งคำขอไปที่ /wp-activate.php?rest_route=/hackismet/access-flag-4/$session_id/$rounds โดยที่ $rounds เป็นตัวเลขที่ค่อนข้างใหญ่ในการทำให้คำขอนี้ใช้เวลานานพอที่จะให้คุณทำตามขั้นตอนที่ 2 ได้
  2. ส่งคำขอไปที่ /wp-json/hackismet/delete-license/$session_id ขณะที่คำขอแรกของคุณถูกบล็อกที่ลูป str_rot13
  3. รอให้คำขอแรกของคุณเสร็จสิ้น และรับธงของคุณ

บทสรุป

เราหวังว่าคุณจะสนุกกับการเข้าร่วมการแข่งขัน Jetpack Capture The Flag รุ่นแรกเช่นเดียวกับที่เราดำเนินการ เราหวังว่าจะทำเช่นนี้อีกในอนาคต หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ CTF ให้ชำระเงิน CTF101.org

เครดิต

นักออกแบบผู้ท้าชิง: Marc Montpas

ขอขอบคุณเป็นพิเศษสำหรับ Harald Eilertsen สำหรับการประชาสัมพันธ์ด้วยตนเองที่ WordCamp Europe และทีม Jetpack Scan สำหรับคำติชม ความช่วยเหลือ และการแก้ไข