แนวทาง solve โจทย์ Web STDiO 2023

Bank Eakasit
4 min readDec 16, 2023

--

ขออภัยที่เพิ่งเขียน ก่อนหน้านี้งานเยอะตัวแตก ทำทั้งกลางวันกลางคืน 555 T_T

ก่อนจะลงโจทย์แต่ละข้อ มีคำแนะนำสำหรับโจทย์ทุกข้อ (ทั้งตอนนี้และต่อๆไป สำหรับคนที่เพิ่งเริ่มเล่นแนวเว็บ) คือ

โจทย์ Web ในหลายๆ CTF เดี๋ยวนี้จะเป็นแนว whitebox แล้ว ให้โค้ดมาเลย ไม่ต้องมานั่งเดาสุ่มเดาใจคนออกโจทย์ เอาโค้ดไปดูเลย แล้ว code review หาช่องโหว่ให้เจอ ยิงให้ผ่าน ได้ flag ไป ตัวโค้ดที่ได้มามักจะเป็น zip file ที่ด้านในมี dockerfile หรือ/และ docker-compose.yml ให้ นั่นคือเราสามารถ simulate server เพื่อทดลองบนเครื่องเราเองได้ เอาโค้ดมาแก้ ใส่ echo/print แทรกเข้าไปเป็นจุดๆได้ เพื่อศึกษาการทำงานต่าง ๆ ส่วนติดตั้ง docker เรียกใช้ยังไง ลอง google ดูนะครับ มีเยอะมากละเดี๋ยวนี้

01 — Thailand Cyber Top Terrorist Mail Server

By Kuranasaki

ข้อนี้โค้ดเยอะมาก แต่ถ้าไล่จากหน้าเว็บทีละจุด ตัว function ที่เข้าถึงได้ตอนแรก มันมีแค่ส่วนส่งเมล เราก็เอาโค้ดมาเปิดเทียบว่าโค้ดส่วนไหนคือ backend ของส่งเมล จะลองอ่านโค้ดเพียวๆเลย หรือจะ dynamic test ก็ได้ จะพบว่ามี template injection อยู่ ซึ่งสามารถ leak config token ออกมาได้

ส่วน token เอาไปทำอะไรได้ อันนี้ฝากเป็นการบ้าน

จากนั้นจะเข้าไปถึงหน้า log ซึ่ง flag อยู่ใน id=0 แต่ใส่ 0 เข้าไปตรงๆไม่ได้ แต่ app มันก็ไม่ได้รับ id เข้าไปอย่างเดียว มันรับ count ที่จะเอาไป + id ไปเรื่อยๆด้วย พูดมาถึงจุดนี้หลายคนน่าจะรู้แล้ว มันคือ integer overflow
https://blog.rene.sh/blog/2020/06/22/int-overflow/

จบได้ flag

02 — Rolling Farm

By Exslap

โค้ดเว็บข้อนี้ถือว่ามีขนาดเล็กนะ ถ้ายังอ่านโค้ดไม่ชิน ควรรีบๆทำให้ชิน ไม่งั้นเดี๋ยวจะโดน developer ข้ามสายมาตบแทน 555 ยังไม่ต้องรู้ทุกเรื่องก็ได้ แต่ควรอ่านๆให้ชินตาครับ

เหมือนเดิม app หลักๆมีแค่สองส่วน ปลูก apple กับขาย apple

แน่นอนว่าสิ่งที่ทุกคนน่าจะต้องลองแน่ๆ คือ race condition แต่มันจะทำไม่ได้เพราะ app นี้มันมี lock process อยู่ ซึ่งจะไม่ได้อยู่ใน farm.go แต่เป็น middleware ratelimit

PS: Logic ของ application หลายๆอย่าง ถ้ามันมี pattern เหมือนกันทั้ง app เราจะเขียนให้ middleware จัดการครับ เช่น input blacklist บางอย่าง, logging, cache, การจัดการ role, endpoints, บังคับ login ก่อนเข้าถึง endpoint เป็นต้น

โอเค race condition ไม่ได้ละ มาดูโค้ดจริงๆว่ามันทำอะไร จะเห็นว่า pattern มันชัดเจนมาก คือ ไม่มี database transaction ครอบ และไม่มี error handling ที่เป็น rollback แถมยังเพิ่มเงินให้ก่อนจะไปลดจำนวน apple อีก แปลว่าถ้าเราทำให้ตรงกลางมัน error ได้ เราก็จะปั้มเงินได้เรื่อยๆ ที่เหลือฝากเป็นการบ้านไปไล่ดูต่อนะครับ

ความรู้ที่ใช้ในโจทย์นี้คือเรื่อง Transaction เนื้อหากว้างมากเลยทีเดียว และจะยุบยับมากถ้าเป็น distributed system ด้วย ค่อยๆอ่านจาก Google ไปเรื่อยๆได้

03 — Great Wall of Cookie

By Bankde

โจทย์ข้อนี้เว็บเล็กมาก แต่มีสองส่วนคือ PHP และ Python ลักษณะโจทย์จริงๆค่อนข้างฟ้องชัดเจนว่าต้องการให้ทำ Parser Differential พูดง่ายๆ คือ PHP และ Python มัน parse ข้อมูลคนละวิธีกัน ทำให้ข้อมูลบางอย่างไหลผ่าน PHP ไป ไม่ถูกตรวจสอบ แต่พอไปถึง Python ดันโดนแปลงกลายเป็นอีกค่านึงซะงั้น เป็นช่องโหว่ตระกูลนึงที่ปัญหาไม่ได้อยู่ในโค้ด แต่อยู่ที่การนำไปใช้งาน

ถ้าเข้าใจตรงนี้แล้ว ที่เหลือก็คือ Google ละครับ หรือจะลองไปงม Flask framework ผมก็จะดีใจมาก แต่ท่าไหนก็ได้ ไม่ห้าม บางคน fuzz มาจนผ่านก็ตามใจ (แต่ควรหาอ่านจนเข้าใจ เพราะถ้าไปเจอโจทย์ที่ fuzz ไม่ได้จะทำไม่ได้)

สำหรับ Python จะมีการ encode/decode cookie แบบ octet ครับ แต่มีเงื่อนไขว่ามันจะ decode ก็ต่อเมื่อ cookie นั้นเริ่มต้นและลงท้ายด้วย double quotes เท่านั้น (ทำไมต้องเป็นแบบนี้ ก็ Python เขาตกลงกันแบบนี้อ๊ะ ผมก็ไม่รู้ 555) ลองไปดูตาม link ก็ได้ แล้วจะเห็นว่ามันไม่ใช่ magic เลย ไม่เฉลย

มีคำถามที่ดีที่หลายคนถาม มันไม่ใช่ zero-day เหรอครับพี่ คำตอบสั้นๆคือไม่ใช่ เพราะ Python ก็ secure ของมัน, PHP ก็ secure ของมัน เพียงแต่เราเอามาประกอบกันจนมีปัญหาเอง developer Flask ไม่ได้มีหน้าที่ต้องไปดู framework ทั้งโลกว่ามีอะไรบ้างแล้วเขียนให้รองรับอะ คนเอาไปใช้ต้องระวังเอง

Parser Differential ที่เจอบ่อยที่สุดและเห็นภาพชัดที่สุด น่าจะเป็น HTTP parameter ครับ ถ้ายิง parameter ซ้ำมา เช่น /createUser?name=A&name=B แต่ละ framework จัดการกันยังไง…

04 — I love Pickle 2

By Bankde

โจทย์สั้นๆเหมือนเดิมครับ ใส่ตัวกรองเพิ่มเข้ามา ไอเดียของโจทย์คือ คนเล่นต้องรู้ว่า Pickle ทำงานยังไง ตัวกรองทำงานยังไง มี limitation อะไร โจทย์ข้อนี้ต้อง Google research พอสมควร (ถ้า Google แล้วหวังว่าจะเจอ payload มาก๊อปแปะ จะทำไม่ได้) แต่พอเข้าใจแล้ว ยิงโป้งเดียวแตกเลย ไม่มีพลิกแพลง

ไอเดียของ Python Pickle มันคือการปั้นชุดคำสั่ง (ตอน Pickle.dumps) เพื่อส่งชุดคำสั่งไปให้อีกเครื่องทำงานตาม (ตอน Pickle.loads) ซึ่งมันจะไม่เหมือนกัน serialization ในภาษาอื่นๆ ลองเอา __reduce__ os.system RCE โยนลง Pickletools ดูได้ครับ จะเห็นเป็นคำสั่งเลย

เมื่อมันเป็นชุดคำสั่ง ก็สามารถปั้นเป็น Syntax Tree ได้ ตัว Fickling มันคือไปกรอง bytecode คำสั่งประเภท function call ออก (ดังน้ัน __reduce__ ทั้งหลายแหล่ก็ใช้งานไม่ได้) หลายคนยึดติดกับ __reduce__ มากเกินไป แต่ถ้าลองก้าวถอยออกมาลองศึกษาจากอะไรง่ายๆ จะเห็นการทำงานครับ

อันนี้คือไอเดียของเทพที่ทำได้ เขาพยายามหาท่าต่างๆที่ pickle มันพยายาม serialize แบบ reference ซึ่งก็หาจนเจอ มันคือ function นั่นเอง

import pickle
import pickletools
import os

def test():
return 1337

pickletools.dis(pickle.dumps(test))
    0: \x80 PROTO      4
2: \x95 FRAME 21
11: \x8c SHORT_BINUNICODE '__main__'
21: \x94 MEMOIZE (as 0)
22: \x8c SHORT_BINUNICODE 'test'
28: \x94 MEMOIZE (as 1)
29: \x93 STACK_GLOBAL
30: \x94 MEMOIZE (as 2)
31: . STOP
highest protocol among opcodes = 4

จะเห็นว่าจาก pickle bytecode ข้อมูล 1337 ของ function ไม่ได้ถูกส่งไปด้วย มันถูกส่งไปหาปลายทางแค่บอกว่า ช่วยเอา __main__.test มาหน่อย ซึ่งตรงนี้ก็จบแล้ว แค่ตั้ง function ชื่อ flag ส่งไปให้ปลายทางมันดึงมาแบบ reference แล้ว web เอาไป __str__(flag) พอดีออกมาเป็นคำตอบ bypass อีกนิดหน่อย เป็นการบ้าน

ซึ่งก็เป็นไอเดียที่ดีมาก ผมไม่ทันคิดไอเดียนี้ สำหรับคนชอบพุ่งชน ให้เริ่มจากเข้าใจ Python bytecode จะพบว่า \x93 STACK_GLOBAL และ c GLOBAL มันคือ from a import b เป็นตัวที่เราต้องการ ก็เหมือนเดิมครับ ปั้น bytecode ขึ้นมาให้มันเป็น from app import flag ก็จะได้คำตอบมาแล้วเหมือนกัน เช่น ถ้าอยาก leak os.environ ก็ใช้โค้ดนี้ เป็นต้น

# อันนี้ปั้นเองทีละตัว ไม่ได้ serialize ออกมา

s = b"\x80\x03cos\nenviron\n."

pickletools.dis(s)
0: \x80 PROTO 3
2: c GLOBAL 'os environ'
14: . STOP

เหตุผลที่ทำโจทย์นี้ บังเอิญไปเจอตัวกรอง Flickling เข้า แล้วเห็นว่า เรื่อง bytecode, stack machine เป็นเรื่องที่สาย security ควรจะรู้จักบ้าง เป็นพื้นฐานของหลายๆเรื่องเลย เช่น Web Assembly, EVM, etc. ไม่ต้องเข้าใจแจ่มแจ้ง แต่ควรพอเข้าใจไอเดียคร่าวๆครับ

ภาพรวมโจทย์ปีนี้

ผมเป็นคนคุมโจทย์ Web ปีนี้เอง โดยเป้าหมายแรกของการออก คืออยากให้คนเล่นได้เห็นของใหม่ๆ research ของใหม่ๆ ที่มากกว่า OWASP top 10 และทันสมัย ซึ่งผมไม่ได้มองเรื่องความยาก อยากให้เห็นชัดๆ research ตรงๆด้วย หลายโจทย์ใครเห็น pattern ก็ตอบเลย คนไม่รู้อาจจะ research นาน ก็พยายามทำโจทย์ให้โค้ดสั้นๆไม่ซับซ้อน (ยกเว้นข้อ mail ที่อยากให้มีหลาย step หน่อยเดี๋ยวเสร็จเร็วเกิน) พิจารณาจากเวลา 2 วันมองว่ากำลังดี ภาพรวมโจทย์ปีนี้คือ

  1. Template injection -> ปั้น token -> integer overflow
  2. Broken transaction
  3. Parser differential
  4. Serialization and Stack machine

ก็หลากหลายดีครับ ไม่รู้ทุกเรื่องก็ไม่แปลก ผมถึงคุมให้โจทย์มันหลากหลาย คนที่รู้เยอะก็ง่าย รู้น้อยก็ research ต่อได้ เดี๋ยวนี้ dev เขาก็มี CICD chain แล้วมี scan ก่อนหลายชั้น dev หลายคนเริ่ม ม ปลาย เป็นปกติ ตัว tools ใหม่ๆที่ใช้กรอง input validation ก็เยอะขึ้น ถ้าเรายังหวังพึ่งแต่ sqlmap ช่วยยิง xss alert(1) เขาเจอเองไปแล้ว ผมก็จะไม่เอาพวกนั้นมาออกครับ CTF มันควรให้อะไรที่มากกว่า DVWA อะ แม้แต่สาย CTF เองเดี๋ยวนี้โจทย์ก็ออกแนวเปิดโค้ดให้ review กันเยอะขึ้นเรื่อยๆทุกปีจนกลายเป็นมาตรฐานใหม่ไปแล้ว

แนะนำให้เริ่มศึกษาพื้นฐานดูนะครับ เรื่อง coding เราสู้สาย developer ตรงๆไม่ได้อยู่แล้ว แต่เรื่องความเข้าใจพื้นฐาน การนำไปใช้งานให้ถูกต้อง ก็ไม่ควรจะแพ้สาย dev อ่านโค้ดไม่ออกทุกภาษาไม่ใช่เรื่องแปลก แต่ไม่ควรกลัวอ่านโค้ด มีการบ้านให้หลายคนไปทำเยอะเลย สุดท้ายคนที่ยังไม่เริ่ม ก็เริ่มช้าดีกว่าไม่เริ่มนะครับ ตัวผมเองก็นับว่าเริ่มช้ากว่าคนอื่นมากเหมือนกัน สู้ๆ ปีหน้าเจอกันใหม่

--

--