หารายได้ 35,000 ดอลลาร์จากบั๊กบาวน์ตีของ GitHub Private Pages
(robertchen.cc)เรื่องราวของนักเรียน ม.6 ที่มีเวลาว่างเพราะ Covid เลยออกล่าบั๊กบาวน์ตี และได้รับเงิน 35,000 ดอลลาร์จากบั๊กบาวน์ตีของหน้า private ของ GitHub
เขารายงานบั๊กบาวน์ตีของหน้า private ของ GitHub และมีโบนัส CTF อยู่ 2 อย่าง
-
10,000 ดอลลาร์: อ่านแฟล็กจาก
flag.private-org.github.ioได้โดยไม่ต้องมี user interaction และถ้าสามารถอ่านแฟล็กนี้ได้จากบัญชีที่อยู่นอกองค์กรprivate-orgจะมีโบนัสเพิ่มอีก 5,000 ดอลลาร์ -
5,000 ดอลลาร์: อ่านแฟล็กจาก
flag.private-org.github.ioได้ผ่าน user interaction
โฟลว์การยืนยันตัวตน
GitHub Pages โฮสต์อยู่บนโดเมนแยกคือ github.io ดังนั้นคุกกี้ยืนยันตัวตนของ github.com จะไม่ถูกส่งไปยังเซิร์ฟเวอร์ private pages เพราะฉะนั้นการยืนยันตัวตนของหน้า private จะไม่สามารถรู้ตัวตนผู้ใช้ได้หากไม่มีการเชื่อมต่อเพิ่มเติมกับ github.com ดังนั้น GitHub จึงสร้างโฟลว์การยืนยันตัวตนแบบกำหนดเองขึ้นมา
-
เมื่อเข้าเยี่ยมชมหน้า private เซิร์ฟเวอร์จะตรวจสอบว่ามีคุกกี้
__Host-gh_pages_tokenหรือไม่ -
ถ้าไม่มีคุกกี้หรือคุกกี้ไม่ถูกต้อง เซิร์ฟเวอร์ private page จะ redirect ไปที่
https://github.com/login -
การ redirect นี้จะตั้งค่า nonce ไว้ในคุกกี้
__Host-gh_pages_sessionด้วย- คุกกี้นี้ใช้คำนำหน้า __Host- จึงป้องกันไม่ให้ตั้งค่าด้วย JavaScript จากที่อื่นซึ่งไม่ใช่ host domain ได้
-
/loginจะ redirect ไปที่/pages/auth?nonce=&page_id=&path= -
ที่นี่จะสร้างคุกกี้ยืนยันตัวตนชั่วคราวจากพารามิเตอร์
tokenแล้วส่งต่อไปยังhttps://pages-auth.github.com/redirect -
/redirectจะ forward ไปยังhttps://repo.org.github.io/__/auth -
endpoint สุดท้ายนี้จะตั้งค่าคุกกี้ยืนยันตัวตน
__Host-gh_pages_tokenและ__Host-gh_pages_idบนโดเมนrepo.org.github.io -
ที่นี่จะตรวจสอบ
nonceจาก__Host-gh_pages_sessionที่ตั้งค่าไว้ก่อนหน้านี้ด้วย
พาธของคำขอเดิมและ page ID จะถูกเก็บไว้ใน query parameter path, page_id ตามลำดับ และ nonce ก็จะถูกเก็บไว้ในพารามิเตอร์ nonce ด้วย
การโจมตี
การคืนค่า CRLF
-
ช่องโหว่แรกคือการฉีด CRLF ในพารามิเตอร์
page_idของhttps://repo.org.github.io/__/auth -
พบว่าการ parse
page_idจะละเลย whitespace และค่านี้ถูกตั้งลงใน headerSet-Cookieโดยตรง -
แม้จะทำให้การ parse พังได้ด้วยการฉีด CRLF แบบดั้งเดิม แต่ก็ไม่มีผลกระทบอื่น
-
เนื่องจาก header
Location:ถูกต่อท้ายหลัง headerSet-Cookieจึงทำให้ถึงจะเป็น 302 redirect แต่ Location header ถูกมองข้ามและมีการ render body แทน
การโจมตี
-
จากการดูโค้ด GitHub Enterprise ทำให้รู้ว่าเซิร์ฟเวอร์ private page ถูกสร้างด้วย openresty nginx
-
เขาทำ XSS สำเร็จด้วยการเพิ่ม null byte โดย null byte นี้ต้องอยู่ตอนต้นของ body จึงไม่สามารถใช้การโจมตีแบบ header injection ได้
-
จากจุดนี้จึงสามารถรันโค้ด JavaScript ตามอำเภอใจบนโดเมน private page ได้
-
ตอนนี้สิ่งที่เหลืออยู่คือหาวิธีข้าม nonce ให้ได้
ข้าม nonce
-
จากการสังเกตพบว่า sibling private pages ภายในองค์กรเดียวกันสามารถตั้งค่าคุกกี้ให้กันและกันได้
-
คุกกี้ที่ตั้งจาก
private-org.github.ioจะถูกส่งไปยังprivate-page.private-org.github.io -
ถ้าหลบการป้องกันด้วยคำนำหน้า
__Host-ได้ ก็จะข้าม nonce ได้ง่าย -
ไม่ใช่ทุกเบราว์เซอร์ที่จะรองรับสิ่งนี้ และ IE ไม่รองรับคำนำหน้า
__Host- -
แต่ระหว่างพยายามหาวิธีที่ดีกว่า ก็เกิดไอเดียที่น่าสนใจขึ้นมา
-
หลังจากตรวจสอบว่าคุกกี้จัดการตัวพิมพ์เล็กใหญ่แบบใด ก็พบว่า
__HOSTกับ__Hostถูกปฏิบัติแตกต่างกัน และ GitHub private pages จะละเลยตัวพิมพ์ใหญ่เมื่อตอน parse คุกกี้ -
จึงสามารถกำหนด nonce ผ่าน JavaScript ได้
-
ทำให้ได้รับโบนัส 5,000 ดอลลาร์
การปนเปื้อนแคช
-
การตอบกลับของ endpoint
/__/auth?ถูกแคชด้วยค่า integer ของpage_idที่ถูกฟิชชิงมา -
ดังนั้นหากทำ cache poisoning ผ่าน XSS payload สำเร็จ ก็จะกระทบผู้ใช้ที่ไม่ได้มีปฏิสัมพันธ์ด้วยเช่นกัน
-
ผู้โจมตีสามารถโจมตี
unprivileged.org.github.ioเพื่อทำให้การยืนยันตัวตนปนเปื้อน และแคช XSS payload ไว้ได้ -
เพราะคุกกี้ถูกแชร์บน parent domain คือ
org.github.ioผู้โจมตีจึงสามารถโจมตีprivileged.org.github.ioได้ด้วย
หน้า private ที่เป็น public
-
เพื่อจะรับโบนัส 15,000 ดอลลาร์ จำเป็นต้องทำให้ผู้ใช้ที่ไม่อยู่ในองค์กรสามารถทำการโจมตีนี้ได้
-
การตั้งค่าผิดพลาดที่เปิดใช้ private pages บน public repository ทำให้โจมตีแบบนี้ได้
- หมายถึงการสร้าง pages จาก repo แบบ private แล้วค่อยเปลี่ยน repository ให้เป็น public
-
private pages ที่เกิดจากการตั้งค่าผิดพลาดนี้จะบังคับให้ผู้ใช้ทุกคนเข้าสู่โฟลว์การยืนยันตัวตน และทำให้ผู้ใช้นอกองค์กรมีสิทธิ์อ่านได้
ยังไม่มีความคิดเห็น