รู้จัก “Dirty Sock” ช่องโหว่ยกระดับสิทธิ์ (Privilege Escalation) บน Linux (CVE-2019-7304)

สรุปย่อ

เมื่อช่วงกลางเดือนกุมภาพันธ์ที่ผ่านมา Canonical บริษัทผู้พัฒนา Ubuntu ได้ปล่อยแพตช์เพื่อแก้ไขช่องโหว่ที่ได้รับชื่อเรียกว่า Dirty Sock ค้นพบโดย Chris Moberly นักวิจัยจาก Shenanigans Labs ช่องโหว่ไม่ได้เป็นปัญหาของระบบปฏิบัติการ Linux โดยตรง แต่เป็นปัญหาในส่วนของ service ที่มีชื่อว่า Snapd ซึ่งถูกติดตั้งเป็น service พื้นฐานบนระบบปฎิบัติการ Linux หลายตัว เช่น Ubuntu, Debian, Arch Linux, OpenSUSE. Solus และ Fedora ถูกใช้เพื่อจัดการเกี่ยวกับการดาวโหลดและติดตั้งไฟล์ที่เป็น snaps (.snap) แพ็กเกจ ส่งผลให้ผู้ไม่หวังดีสามารถสร้างบัญชีที่มีสิทธิ์ระดับ root (Privilege Escalation) บนเครื่องได้ ผ่านช่องโหว่ใน API ของ Snapd

(ที่มา: https://www.cyberkendra.com)

รายละเอียดการทดสอบช่องโหว่

การทำงานโดยปกติของ Snapd service และอีกหลายๆ บริการบน Linux จะมีการสร้าง socket ขึ้นมาเพื่อใช้ในการติดต่อกับระหว่างโปรเซสอื่นๆ ภายในเครื่อง (AF_UNIX Socket) หรือใช้สำหรับติดต่อกับเครื่องอื่นๆ (AF_INET Socket) Chris Moberly ได้ทำการทดสอบช่องโหว่ดังกล่าวบน Ubuntu จากตำแหน่งของ Snapd service (/lib/systemd/system/snapd.service) บนเครื่อง พบว่ามีการเรียกใช้งาน socket ทั้งหมด 2 รายการด้วยกัน คือ /run/snapd.socket และ /run/snapd-snap.socket ซึ่งทั้ง 2 รายการได้สิทธิ์เป็น root บนระบบปฏิบัติการ

Chris ได้ทดลองเรียก socket ตัวหนึ่ง โดยใช้เครื่องมือที่ชื่อว่า "netcat" หรือ "nc" และพบว่าสามารถเรียกได้แต่ไม่สำเร็จ โดยมีผลลัพธ์ตอบกลับมาเป็น HTTP ตามภาพด้านล่าง ซึ่งเชื่อว่าน่าจะสามารถถูกนำไปใช้ในการยกระดับสิทธิ์ (Privilege escalation) ได้

รูปที่ 1: แสดงการเรียก getUcred สำหรับดูสิทธิ์ของผู้เรียกใช้งาน socket

จากการศึกษา API ของ Snapd ทำให้พบ API บางตัวที่สามารถใช้ในการสร้าง local user บนเครื่องได้ (POST /v2/create-use) แต่คำสั่งดังกล่าวจำเป็นต้องใช้สิทธิ์ระดับ root ในการเรียกเท่านั้น

รูปที่ 2: แสดง Snapd API ที่สามารถใช้สร้าง user ผู้ใช้งานได้

จากการศึกษา source code ของ Snapd ซึ่งถูกเขียนขึ้นมาด้วยภาษา Go ทำให้รู้ได้ว่าตัว service มีการตรวจสอบสิทธิ์ของผู้เรียก socket ผ่าน function ที่มีชื่อว่า "getUcred" ซึ่งจะมีการเรียกใช้งาน Standard library ของ Go (sys.GetsockoptUcred)

รูปที่ 3: แสดงการเรียก getUcred สำหรับดูสิทธิ์ของผู้เรียกใช้งาน socket

 

รูปที่ 4: แสดง GetsockoptUcred ที่เป็น standard library ใน Go

จากการใช้ Go Debugger ที่มีชื่อว่า "delve" ทำให้นักวิจัยทราบได้ว่าค่าที่ส่งกลับมา (return) จะอยู่ในรูปแบบ {Pid: 5388, Uid: 1000, Gid: 1000} โดยค่าที่เป็นตัวบ่งชี้ว่าผู้เรียกใช้งานมีสิทธิ์อะไร และสามารถเรียกใช้ API ใดๆ ได้หรือไม่คือ Uid

ใน GetsockoptUcred  ยังมีการเรียก Standard library อีกตัวที่ชื่อว่า "RemoteAddr" ที่มีการส่งค่าเกี่ยวกับการเชื่อมต่อ (connection) กลับมา จากนั้นค่าดังกล่าวจะถูกนำไปต่อ (concatenate) กับค่า Pid และ Uid ที่ได้มา กลายเป็นค่า string ชุดหนึ่งที่มีตัวอักษร ";" เป็นตัวแบ่งค่า (delimiter) ในรูปแบบ "pid=5127;uid=1000;socket=/run/snapd.socket;@"

นอกเหนือจาก Pid และ Uid ค่าที่เพิ่มมา ประกอบด้วย 2 ค่าด้วยกันคือ

  1. "socket=/run/snapd.socket" : เป็นตำแหน่งแสดงค่า local "network address" ของ socket ที่มีการใช้งาน (ระบุถึงตำแหน่งของ File (File path) ที่ service มีการเรียกใช้) ซึ่งไม่สามารถแก้ไขเพื่อให้ Snapd ไปเรียก socket จากที่อื่นได้
  2. "@" : เป็นตำแหน่งแสดงค่า "remote address" ของ socket ที่มีการใช้งาน โดย "@" ถูกเรียกว่า "abstract namespace" เป็นค่า default (แสดงว่าไม่มีการระบุตำแหน่งเจาะจงของ File Path ที่ผูกกับ socket) ซึ่งค่าในส่วนนี้สามารถถูกแก้ไขได้

รูปที่ 5: แสดงการเรียก RemoteAddr สำหรับเก็บข้อมูล connection และต่อค่า string

 

รูปที่ 6: แสดง RemoteAddr ที่เป็น standard library ใน golang

จากนั้นจะมีการเรียก function ที่ชื่อ "ucrednetGet" เพื่อทำการตัด string โดยใช้ตัวอักษร ";" อีกครั้ง ทำให้ได้ผลลัพธ์เป็นค่าต่างๆ 4 ค่า

ยกตัวอย่างเช่น

หาก string ที่ได้จากการต่อ (concatenate) =  " pid=5127;uid=1000;socket=/run/snapd.socket;@ "

ผลลัพธ์จากการตัด (Parse) ด้วยตัวอักษร ";"  = (1) pid=5127 (2) uid=1000  (3) socket=/run/snapd.socket (4) @

นักวิจัยได้ทำการเขียนสคริปต์ Python เพื่อใช้ในการสร้างไฟล์บนเครื่องโดยให้มีชื่อไฟล์ต่อท้ายด้วยคำว่า " ;uid=0; " จากนั้นระบุ Path ของไฟล์ดังกล่าวกับ socket ของ Snapd ที่ใช้เรียก API

รูปที่ 7: แสดง Python code ที่ใช้สร้างไฟล์ และทดสอบเรียกใช้งาน Snapd socket

จากการใช้ debugger เพื่อดูค่าของตัวแปร RemoteAddr ที่ได้มาใหม่หลังจากทำการเปลี่ยนค่าของ socket ในส่วนของ remote address เป็น Path ของไฟล์ที่สร้างขึ้นมา จะได้ผลลัพธ์เป็น

string ที่ได้จากการต่อ (concatenate) =  " pid=5275;uid=1000;socket=/run/snapd.socket;/tmp/sock;uid=0; "

ผลลัพธ์จากการตัด (Parse) ด้วยตัวอักษร ";"  = (1) pid=5127 (2) uid=1000 (3) socket=/run/snapd.socket (4) /tmp/sock (5) uid=0

จะเห็นได้ว่าค่า Uid จะมี 2 ค่าด้วยกัน คือ uid=1000 (สิทธิ์ที่แท้จริงของผู้เรียกใช้งาน socket) และ uid=0 (ค่าที่ได้มาจากชื่อไฟล์ที่ใช้สคริปต์ Python ในการสร้างขึ้นมา) จากที่ได้ระบุไปก่อนหน้านี้ว่า ค่าของ Uid จะเป็นตัวบ่งชี้ว่าผู้เรียกใช้งานมีสิทธิ์ที่จะเรียกใช้ API ได้หรือไม่ โดยสิ่งที่ service มองหาเพื่อตรวจสอบ คือ ค่าๆ หนึ่งที่ขึ้นต้นด้วย "uid="

ดังนั้นเมื่อมีค่า "uid=" มากกว่า 1 ค่า จึงส่งผลให้ service จะดึงค่า uid ที่เป็นตัวสุดท้ายมาใช้ในการตรวจสอบสิทธิ์ของผู้เรียกใช้งาน เช่น จากตัวอย่างก่อนหน้าที่มีค่า uid 2 ค่าด้วยกัน คือ uid=1000 และ uid=0 ส่งผลให้แทนที่จะใช้ค่า uid จริงๆ ของผู้เรียกใช้งาน (uid=1000) กลับกลายเป็นว่าค่าที่ถูกนำไปใช้ตรวจสอบสิทธิ์ผู้ที่เรียกใช้งานเป็นค่าที่ถูกสร้างขึ้นมา (uid=0) ซึ่งมีสิทธิ์เป็นระดับ root ของเครื่องแทน จึงทำให้สามารถเรียกใช้ API บางตัวที่ต้องการสิทธิ์ผู้เรียกใช้ระดับ root ได้ อย่างเช่น POST /v2/create-use เป็นต้น ทั้งนี้มีการเผยแพร่ PoC บน GitHub สำหรับทดสอบช่องโหว่ดังกล่าว 2 เวอร์ชั่นด้วยกัน

ผลกระทบจากช่องโหว่

ช่องโหว่ดังกล่าวมีผลกระทบกับระบบปฏิบัติการ Linux ทุก distro ที่รองรับการใช้งาน snaps แพ็กเกจ (.snap) ยกตัวอย่างเช่น Ubuntu, Debian, Arch Linux, OpenSUSE. Solus และ Fedora ส่งผลให้ผู้ไม่หวังดีสามารถใช้ช่องโหว่นี้ในการเพิ่มสิทธิ์ตนเอง (Privilege Escalation) ให้เป็น root บนเครื่องของเหยื่อได้

คำแนะนำ

  1. ทำการแพตช์ระบบปฏิบัติการที่ตนเองใช้งานอยู่ให้เป็นรุ่นล่าสุด (ข้อมูลเพิ่มเติม)
  2. ไม่เปิดช่องทางการเข้าถึงเครื่องของตนเองจากภายนอก (remote) หรือเปิดให้สามารถเข้าถึงได้เพียงเท่าที่จำเป็นเท่านั้น
  3. ทำการแพตช์ซอฟต์แวร์ที่ใช้งานอยู่บนเครื่องให้เป็นเวอร์ชั่นล่าสุด เนื่องจากช่องโหว่ของซอฟต์แวร์อาจถูกใช้เป็นช่องทางในการเข้าถึงเครื่องได้
  4. ติดตามข่าวสารเกี่ยวกับภัยคุกคามทางไซเบอร์อย่างสม่ำเสมอ

แหล่งอ้างอิง

  1. https://shenaniganslabs.io/2019/02/13/Dirty-Sock.html
  2. https://github.com/snapcore/snapd/blob/4533d900f6f02c9a04a59e49e913f04a485ae104/daemon/ucrednet.go
  3. https://github.com/snapcore/snapd/wiki/REST-API
  4. https://golang.org/pkg
  5. https://www.cyberkendra.com/2019/02/dirty-sock-bug-gives-root-access-to.html
  6. https://www.zdnet.com/article/dirty-sock-vulnerability-lets-attackers-gain-root-access-on-linux-systems