ยึดธงที่ WordCamp Europe 2022
เผยแพร่แล้ว: 2022-06-13ระหว่าง WordCamp Europe 2022 เราได้จัดการแข่งขัน WordPress Capture The Flag (CTF) ในสี่ความท้าทาย
เราต้องการแนะนำผู้คนให้รู้จักโลกที่น่าติดตามของ CTF และให้ผู้คนได้สัมผัสวิธีที่นักวิจัยด้านความปลอดภัยเข้าถึงการไล่ล่าจุดบกพร่อง เช่น มองหาสิ่งแปลกประหลาดในโค้ดและรวมพวกมันเข้าด้วยกันเพื่อทำสิ่งแปลก ๆ ที่บางครั้งขัดกับสัญชาตญาณ
ความท้าทาย #1 – คุณโชคดีไหม?
ความท้าทาย #2 – บายพาสรายการบล็อก?
ความท้าทาย #3 – ใบอนุญาตในการยึดธง
ความท้าทาย #4 – ใบอนุญาต CTF: ตอนที่ 2
หากคุณสนใจที่จะทดลองใช้ คุณยังสามารถรับไฟล์คำท้าได้ที่นี่:
ท้าทาย #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
การโจมตีครั้งสุดท้ายจึงมีลักษณะดังนี้:
- ส่งคำขอไปที่
/wp-activate.php?rest_route=/hackismet/access-flag-4/$session_id/$rounds
โดยที่$rounds
เป็นตัวเลขที่ค่อนข้างใหญ่ในการทำให้คำขอนี้ใช้เวลานานพอที่จะให้คุณทำตามขั้นตอนที่ 2 ได้ - ส่งคำขอไปที่
/wp-json/hackismet/delete-license/$session_id
ขณะที่คำขอแรกของคุณถูกบล็อกที่ลูปstr_rot13
- รอให้คำขอแรกของคุณเสร็จสิ้น และรับธงของคุณ
บทสรุป
เราหวังว่าคุณจะสนุกกับการเข้าร่วมการแข่งขัน Jetpack Capture The Flag รุ่นแรกเช่นเดียวกับที่เราดำเนินการ เราหวังว่าจะทำเช่นนี้อีกในอนาคต หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ CTF ให้ชำระเงิน CTF101.org
เครดิต
นักออกแบบผู้ท้าชิง: Marc Montpas
ขอขอบคุณเป็นพิเศษสำหรับ Harald Eilertsen สำหรับการประชาสัมพันธ์ด้วยตนเองที่ WordCamp Europe และทีม Jetpack Scan สำหรับคำติชม ความช่วยเหลือ และการแก้ไข