สวัสดีครับผู้อ่านทุกท่าน วันนี้ผมมีบทความเกี่ยวกับช่องโหว่ของ JWT ว่าช่องโหว่นี้สามารถทำให้เว็บไซต์เสี่ยงต่อการถูกโจมตีได้อย่างไร เนื่องจากปัจจุบันมีการใช้ JWT มากมายในหลาย ๆ แอปพลิเคชัน ดังนั้นการออกแบบหรือจัดการ JWT ที่ไม่ถูกต้องอาจทำให้เว็บไซต์เกิดช่องโหว่ได้
แต่ก่อนจะไปดูว่า JWT นั้นถูกโจมตีได้อย่างไร เรามาดูกันก่อนว่า JWT คืออะไรกันครับบบ
JWT คืออะไร
JWT ย่อมาจาก (JSON web tokens) เป็น Token หรือ ตัวอักษรชุดหนึ่งที่ใช้สำหรับการยืนยันตัวตน (Authentication) ว่าผู้ใช้งานดังกล่าวคือใคร และใช้ตรวจสอบสิทธิ์ของผู้ใช้งาน (Authorization) ว่ามีสิทธิ์ทำอะไรได้บ้างในระบบ
รูปแบบของ JWT
JWT เป็นตัวชุดอักษร 3 ส่วนที่ถูกเข้ารหัสด้วย base64 แล้วนำมารวมเข้าด้วยกันซึ่ง 3 ส่วนดังกล่าวประกอบไปด้วย Header, Payload และ Signature
- XXXXX = base64_encode(header)
- YYYYY = base64_encode(payload)
- ZZZZZ = HMACSHA256(base64_encode(header) + “.” + base64_encode(payload),secret))
จาก ZZZZZ ก็เป็นตัวอย่างการ Sign ด้วย HS256
โดยทั้ง 3 ส่วนนี้จะถูกขั้นด้วย .(dot) ดังนี้ xxxxx.yyyyy.zzzzz นี่ก็คือตัวอย่างของ JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I
kpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ทั้ง 3 ส่วนของ JWT
- Header บ่งบอกว่า JWT ชุดนี้ใช้ Algorithm อะไรในการเข้ารหัสและถอดรหัสในส่วนของ Signature ตัวอย่างของ Algorithm ที่ใช้กันคือ HS256, RS256, ES256
{
"alg": "HS256",
"typ": "JWT"
}
- Payload จะเก็บข้อมูลต่าง ๆ ของผู้ใช้งานจริง ๆ ในระบบ เช่น ID, username, e-mail, roles รวมไปถึง
— iss (Issuer) เป็น Unique id ที่เอาไว้ระบุตัว Client
— iat (Issued-at time) เป็นเวลาสร้าง (create time) ของ token
— exp (Expiration time) เป็นเวลาหมดอายุของ token
โดยในระบบจะดึงข้อมูลในส่วนนี้มาใช้งานด้วย ตัวอย่างเช่น ตรวจสอบว่า JWT ที่ได้รับ Roles ไหนก็จะใช้ช่วยในการตรวจสอบสิทธิ์ตาม Roles ของผู้ใช้ได้ เช่น Roles ของผู้ดูแลระบบก็จะเห็น ฟังก์ชันต่าง ๆ ที่เยอะกว่า
{
"sub": "1234567890",
"name": "Alice",
"role": "user",
"iat": 1516239022
}
- Signature ใช้ในการตรวจสอบความถูกต้องของ JWT โดยจะเอา Payload มาเข้ารหัสด้วย Algorithm (alg) ที่ระบุไว้ในส่วนของ Header
ซึ่งส่วนนี้จะสำคัญมาก ๆ เนื่องจาก Signature เป็นส่วนช่วยที่ทำให้ ฝั่ง Server แน่ใจว่าข้อมูลในส่วน Header และ Payload นั้นไม่ถูกแก้ไข ถ้าผู้โจมตีไม่รู้ว่า Key หรือรหัสลับในการสร้าง Token นั้นคืออะไร ก็จะไม่มีทางปลอม JWT ที่ถูกต้องขึ้นมาได้
จากตัวอย่างทั้งหมดจะเห็นว่าโดยปกติแล้ว JWT ที่ส่งมายังฝั่ง Client นั้นไม่ได้ถูกเข้ารหัส (Encryption) แต่อย่างใด เป็นเพียงแค่การเข้ารหัส (Encoding) ที่ใช้ Base64 เท่านั้น ซึ่งทำให้ JWT ไม่เหมาะที่จะเก็บค่าที่เป็น Sensitive หรือ ค่าที่เป็น Secret แต่อย่างใด เป็นเพียงแค่การระบุค่าที่จำเป็นต่อการระบุตัวตนของผู้ใช้งานเท่านั้น
การโจมตี JWT คืออะไร
การโจมตี JWT จะเกี่ยวข้องกับการที่ผู้โจมตีจะพยายามแก้ไขค่าต่าง ๆ ใน JWT เพื่อทำการอะไรบางอย่าง เช่นการยกสิทธิ์ตัวเองหรือการปลอมแปลงเป็นผู้ใช้คนอื่นเพื่อยึดครองบัญชีผู้ใช้
ผลกระทบของการโจมตี
ถ้าผู้โจมตีสามารถโจมตีได้สำเร็จผลกระทบจะค่อนข้างสูง เนื่องจากถ้าผู้โจมตีสามารถสร้าง JWT ได้อย่างอิสระจะทำให้สามารถควบคุมการยืนยันตัวตนรวมไปถึงสิทธิ์ต่าง ๆ ได้ในระบบ เช่น ปลอมตัวเป็นบัญชีผู้ใช้คนอื่นและเข้ายึดครองบัญชีของคน ๆ นั้น หรือการยกระดับสิทธิ์ของตัวเองเพื่อเป็นผู้ดูแลระบบ หรือสิทธิ์ที่สูงกว่าเดิม เป็นต้น
ช่องโหว่ JWT เกิดขึ้นได้อย่างไร
ช่องโหว่ JWT มักเกิดขึ้นเนื่องจากการจัดการ JWT ที่มีข้อบกพร่องภายในแอปพลิเคชันเอง ข้อกำหนดการออกแบบ ต่าง ๆ ที่เกี่ยวข้องกับ JWT นั้นค่อนข้างยืดหยุ่น ส่งผลให้ผู้เขียนโปรแกรมหรือผู้พัฒนาแอปพลิเคชันสามารถใช้งานได้หลากหลาย ซึ่งอาจส่งผลให้มีช่องโหว่เกิดขึ้นได้โดยไม่ได้ตั้งใจ
ข้อบกพร่องในการใช้งานหมายความว่าในส่วน Signature ของ JWT นั้นไม่ได้รับการตรวจสอบอย่างถูกต้อง ทำให้ผู้โจมตีสามารถแก้ไขค่าที่ส่งไปยังแอปพลิเคชันได้ หรือ ถ้า Signature ได้รับการตรวจสอบอย่างถูกต้องแล้ว แต่ถ้า Key ของ Server นั้นรั่วไหลออกไป หรือสามารถคาดเดาได้ ก็ส่งผลให้ ผู้โจมตีสามารถสร้าง Signature ที่ถูกต้องแล้วสามารถสร้าง JWT และแก้ไข Payload ได้ตามใจชอบ
Labs
วันนี้ผมจะมายกตัวอย่าง Lab เพื่อให้ผู้อ่านเห็นภาพได้มากขึ้น และ Lab ที่ผมจะยกตัวอย่างมาก็เป็น Lab ฟรี!! ที่ผู้อ่านสามารถไปหาเล่นกันเองได้ ก็คือ Lab ของ “PortSwigger” นั่นเอง โดยในนั้นก็ยังมีเนื้อหาให้อ่านเพิ่มเติมถ้าผู้อ่านยังไม่เข้าใจ สามารถดูได้จาก Link: https://portswigger.net/web-security/jwt
ก่อนจะเริ่มทำ Lab ผมแนะนำติดตั้ง Extender ใน “Burp Suite” ชื่อ “JWT Editor” ก่อนเพราะ Extender ตัวนี้จะช่วยทำให้การโจมตี JWT นั้นง่ายขึ้น รวมไปถึงช่วยตรวจจับ JWT ตาม HTTP Request ด้วย
Lab 1 Accepting arbitrary signatures
โดย Lab นี้จะไม่มีการตรวจสอบในส่วนของ Signature ทำให้สามารถแก้ไข Payload ได้อย่างอิสระ
ขั้นตอนแรกให้ล็อกอิน เข้า Lab ด้วย User ของ “wiener”
ที่ Burp Suite จะสังเกตได้ว่า HTTP Request ขึ้นแทบสีเขียวเพราะว่า Extender ของ “JWT Editor” ตรวจพบ JWT นั่นเอง
หลังจากนั้นกดไปที่ “JSON Web Token” จะพบข้อมูล JWT ในส่วนของ Header, Payload และ Signature
จากขั้นตอนก่อนหน้านำ HTTP Request ไปที่ Repeater เพื่อนำ HTTP Request มาใช้ซ้ำ จากนั้นแก้ไขค่าจาก “wiener” เป็น “administrator” จากนั้นกดส่ง
จาก HTTP Response ในขั้นตอนก่อนหน้ากดคลิกขวา จากนั้นกดที่ “Show response in browser” พบว่าสามารถเข้าสู่บัญชีของ “administrator” ได้ หรือทำการนำ JWT ใหม่มาแทนของเดิมในแอปพลิเคชันก็สามารถใช้งานบัญชี “administrator” ได้
Lab 2 Brute-forcing secret keys
Lab ต่อมานี้มีการใช้ Algorithms คือ “HS256” ซึ่ง Algorithms นี้เป็นการเข้ารหัสแบบ “Symmetric key” ซึ่งจะใช้ Secret key ในการ Sign โดย Secret key นั้นเป็นเพียงค่า String แค่ 1 ชุดเท่านั้น หมายความว่าถ้าใครก็ตามที่สามารถรู้ค่า Secret key หรือสามารถ Brute-force หา Secret key นั้นก็จะสามารถปลอมแปลงตัว JWT นี้ได้
ขั้นตอนแรกหลังจากที่ Log in เข้าสู่ระบบ เหมือนกับ Lab ที่แล้ว สังเกตใน “Burp Suite” พบว่าผมได้ JWT มา 1 ชุดเหมือนเดิม
หลังจากนั้นผมลองทำแบบ Lab ที่แล้วคือเปลี่ยนชื่อจาก “wiener” เป็น “administrator” พบว่า ถูก Redirect กลับมาที่หน้า Log in เนื่องจาก แอปพลิเคชันพบว่า JWT นี้ถูกแก้ไข
แต่ผมสังเกตในส่วน Header ของ JWT พบว่า “alg” ที่ใช้เป็น “HS256” ดังนั้นผมจึงต้องหาค่า Secret ที่ใช้ในการ Sign ตัว JWT นี้ โดยผมจะใช้ “john the ripper” ที่มีอยู่ใน kali ซึ่งผมจะนำ JWT บันทึกลงไปในไฟล์ชื่อ “jwt.txt” จากนั้นใช้คำสั่ง
john jwt.txt — wordlist=/usr/share/wordlists/rockyou.txt — format=HMAC-SHA256
จากนั้นก็รู้แล้วว่าค่า Secret key ก็คือ “secret1” ดังรูป โดยผมจะเข้าไปที่เว็บไซต์ https://jwt.io/ นำ JWT ปัจจุบันในแอปพลิเคชันมาวางในช่อง “Encoded” จะเห็นว่าในส่วนของ Payload ยังเป็นข้อมูล ของ wiener อยู่
แก้ไขค่าจาก “wiener” เป็น “administrator” แล้วใส่ค่า Secret key ลงไปในช่อง “Signature” คือ “secret1”
จากนั้นนำ Token JWT จากขั้นตอนที่แล้วมาใส่แทนที่ Token JWT เดิมที่อยู่ในช่อง “Cookie” จากนั้นกด F5 เพื่อ “Refresh” หน้าเว็บอีกครั้ง
จะพบว่าสามารถล็อกอินเป็นบัญชีของ “administrator” ได้
จาก Lab ทั้ง 2 ตัวอย่างนี้เป็นเพียงตัวอย่างคร่าว ๆ ที่ทำให้ผู้อ่านนั้นได้พอเห็นภาพ แต่ถ้าผู้อ่านอยากศึกษาการโจมตี JWT เพิ่มเติม ผมขอแนะนำให้เข้าไปลองเล่น Lab หัวข้ออื่น ๆ ใน “PortSwigger” นะครับ และผมหวังว่าบทความนี้จะเป็นประโยชน์ต่อผู้อ่านไม่มากก็น้อยนะครับ ขอบคุณที่อ่านมาจนจบครับ สวัสดีครับ