1 คะแนน โดย GN⁺ 2 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • CVE-2026-31431 Copy Fail ทำให้ผู้ใช้ภายในเครื่องที่ไม่มีสิทธิ์สามารถได้เชลล์ root และยังสามารถยกระดับสิทธิ์เป็น root ภายในคอนเทนเนอร์ Podman แบบ rootless ได้ด้วย
  • คอนเทนเนอร์ Podman แบบ rootless ใช้การผสานกันของ user namespace, การแยก UID และ Linux capabilities เพื่อแมป root ภายในคอนเทนเนอร์ให้เป็นผู้ใช้บนโฮสต์ที่ไม่มีสิทธิ์ และจำกัดสิทธิ์บนโฮสต์
  • ในการทดสอบ ผู้ใช้ foo ในคอนเทนเนอร์ rootless non-root สามารถกลายเป็น root ภายในคอนเทนเนอร์ได้หลังรัน Copy Fail แต่สิทธิ์ยังถูกจำกัดอยู่ในขอบเขตที่ผู้ใช้บนโฮสต์ที่ไม่มีสิทธิ์อย่าง bar ทำได้ และไม่สามารถอ่านไฟล์ที่โฮสต์ root เป็นเจ้าของได้
  • หากใช้ --security-opt=no-new-privileges หรือ --cap-drop=all หลังรัน Copy Fail แล้ว เชลล์จะยังคงเป็น foo และมี capabilities เป็น none ทำให้ป้องกันการได้เชลล์ root ทันทีและการยกระดับ capability ได้
  • ผลของ Copy Fail อาจคงอยู่ต่อไปเกินกว่าวงจรชีวิตของคอนเทนเนอร์ จึงต้องแพตช์เคอร์เนลและรีบูต รวมถึงควรใช้ defense in depth ร่วมกัน เช่น root filesystem แบบอ่านอย่างเดียว, การจำกัดทรัพยากรด้วย cgroups, อิมเมจรันไทม์แบบบาง และไฟร์วอลล์

ขอบเขตการได้รับผลกระทบของ Copy Fail และคอนเทนเนอร์ Podman แบบ rootless

  • CVE-2026-31431 ถูกเปิดเผยเมื่อวันที่ 29 เมษายนที่ copy.fail และเมื่อรัน สคริปต์ Python ที่เผยแพร่ไว้ ผู้ใช้ภายในเครื่องที่ไม่มีสิทธิ์ก็สามารถได้เชลล์ root ได้
  • Copy Fail สามารถถูกใช้โจมตีได้แม้ภายในคอนเทนเนอร์ Linux และสามารถได้เชลล์ root ภายในคอนเทนเนอร์ Podman แบบ rootless ได้เช่นกัน
  • ในการทดสอบ root ของคอนเทนเนอร์ถูกจำกัดไว้ในขอบเขตสิทธิ์ของผู้ใช้บนโฮสต์ที่ไม่มีสิทธิ์ชื่อ bar ซึ่งเป็นผู้ที่รันคอนเทนเนอร์ในระดับโฮสต์
  • การทำงานแบบ rootless ของ Podman ใช้ user namespace, การแยก UID และ Linux capabilities ร่วมกันเพื่อจำกัดสิทธิ์บนโฮสต์ของโปรเซสในคอนเทนเนอร์
  • Copy Fail แสดงให้เห็นว่าคอนเทนเนอร์ rootless ไม่ได้มีภูมิคุ้มกันต่อช่องโหว่นี้ แต่สามารถลดขอบเขตผลกระทบหลังถูกเจาะได้ด้วยการตั้งค่า Podman

วิธีการทำงานของคอนเทนเนอร์ rootless

  • ตัวอย่างพื้นฐาน: ผู้ใช้ bar ที่ไม่มีสิทธิ์รัน HTTP server

    • สภาพแวดล้อมตัวอย่างคือผู้ใช้ bar ที่ไม่มีสิทธิ์และมี UID 1001 สร้างอิมเมจบนฐาน ubuntu:latest ด้วย Podman และรัน python3 -m http.server
    • เมื่อดูจากโฮสต์ด้วย ps จะเห็นว่าโปรเซส python3 รันอยู่ภายใต้ผู้ใช้ bar
    • Podman ใช้โมเดล fork/exec ดังนั้นโปรเซสของคอนเทนเนอร์จึงเป็นลูกหลานของโปรเซส podman run และสามารถแยกโปรเซสคอนเทนเนอร์ออกจาก root ของโฮสต์หรือผู้ใช้อื่นได้ด้วยการแยก UID ตามปกติ
    • ในการตั้งค่า Docker ทั่วไป แม้ผู้ใช้ที่ไม่มีสิทธิ์จะรัน docker run แต่ Docker client จะสื่อสารกับเดมอนที่มีสิทธิ์ root และเดมอนจะเป็นผู้สร้างโปรเซสคอนเทนเนอร์ในท้ายที่สุด ทำให้บนโฮสต์อาจเห็นโปรเซสคอนเทนเนอร์เป็น root
  • rootful แบบ rootless

    • โดยทั่วไป หากอิมเมจคอนเทนเนอร์ไม่มีคำสั่ง USER หรือแฟลก --user อย่างชัดเจน คำสั่งในคอนเทนเนอร์จะถูกรันในฐานะ root ภายในคอนเทนเนอร์
    • ในผลลัพธ์ของ podman top โปรเซส HTTP server จะถูกแมปเป็นผู้ใช้โฮสต์ 1001 แต่รันเป็นผู้ใช้ root ภายในคอนเทนเนอร์
    • การตั้งค่านี้คือสถานะ rootless rootful ซึ่งหมายถึงรันเป็นผู้ใช้ไม่มีสิทธิ์บนโฮสต์ แต่เป็น root ภายในคอนเทนเนอร์
  • user namespace

    • คอนเทนเนอร์ Podman แบบ rootless ใช้ user namespace เพื่อแมป UID/GID ภายในและภายนอกคอนเทนเนอร์ให้ต่างกัน
    • ในตัวอย่าง root ที่มี UID 0 ภายในคอนเทนเนอร์จะถูกแมปเป็น bar ที่มี UID 1001 บนโฮสต์
    • การตั้งค่า bar:165536:65536 ใน /etc/subuid กำหนดช่วง UID ที่สามารถจัดสรรให้กับโปรเซส namespace ของ bar ได้
    • ในตัวอย่าง นอกจาก UID 1001 ของ bar แล้ว UID ตั้งแต่ 165536 ถึง 231072 ยังสามารถถูกจัดสรรให้กับโปรเซสของ bar ได้ด้วย
    • หากรัน sleep ด้วยผู้ใช้ www-data ภายในคอนเทนเนอร์ ภายในจะเป็น www-data แต่บนโฮสต์จะแสดงเป็น 165568
    • หากเข้าไปยัง user namespace ด้วย podman unshare โฮมไดเรกทอรีที่บนโฮสต์เป็นของ bar:bar จะมองเห็นเป็น root:root ภายใน namespace
    • Docker ก็รองรับ user namespace เช่นกัน แต่ต้องตั้งค่าแยกต่างหากและอนุญาตได้เพียง user namespace เดียว ขณะที่ Podman จะรันคอนเทนเนอร์ rootless ของผู้ใช้ UNIX แต่ละรายภายใน user namespace ของผู้ใช้นั้น
  • งานด้านสิทธิ์และ Linux capabilities

    • Podman ใช้ Linux capabilities เพื่อมอบสิทธิ์ระดับ root แบบละเอียดให้กับโปรเซสในคอนเทนเนอร์
    • ระหว่างการ build อิมเมจ งานอย่าง apt install ทำได้ด้วยชุด capability เช่น chown, dac_override, fowner, setgid, setuid, net_bind_service, sys_chroot
    • หากลบ capability ทั้งหมดด้วย podman build --cap-drop=all แล้ว apt จะล้มเหลวกับ setgroups, setegid, seteuid, chown และอื่น ๆ ทำให้การ build อิมเมจไม่สำเร็จ
    • สามารถใช้วิธีเพิ่มเฉพาะ capability ที่จำเป็นได้ และในตัวอย่างมีการเพิ่ม CAP_SETUID,CAP_SETGID,CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER เพื่อทำการติดตั้งแพ็กเกจ
    • HTTP server ในสถานะรันปกติจะทำงานเป็น root ภายในคอนเทนเนอร์ และมี effective capabilities จำนวนมาก เช่น CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT
    • แต่ HTTP server ไม่จำเป็นต้องใช้สิทธิ์เหล่านี้ จึงสามารถลบ capability ทั้งหมดได้ด้วย podman run --cap-drop=all และในกรณีนี้ podman top จะแสดง effective capabilities เป็น none
  • rootless non-root

    • หากต้องการรัน HTTP server เป็นผู้ใช้ที่ไม่มีสิทธิ์แม้ภายในคอนเทนเนอร์ ก็สามารถใช้ผู้ใช้ที่มีอยู่ใน /etc/passwd เช่น www-data หรือสร้างผู้ใช้เฉพาะระหว่างการ build อิมเมจได้
    • ในตัวอย่างมีการสร้างผู้ใช้และกลุ่ม foo ที่มี UID 1002 ให้สิทธิ์อ่านกับ /var/www/html แล้วตั้งค่า USER foo:foo
    • เมื่อรันอิมเมจนี้ด้วย --cap-drop=all โปรเซสจะอยู่ในสถานะเป็น foo ภายในคอนเทนเนอร์, โฮสต์ UID 166537 และ effective capabilities เป็น none
    • โปรเซสของคอนเทนเนอร์ควรรันด้วยสิทธิ์ขั้นต่ำที่จำเป็น และตัวอย่างเช่น หาก foo ต้อง bind กับ privileged port 80 ก็ต้องเพิ่ม --cap-add=CAP_NET_BIND_SERVICE
    • วิธีรันคอนเทนเนอร์แบ่งได้เป็น 4 แบบ
      • ผู้ใช้โฮสต์ root + คอนเทนเนอร์ root: root rootful
      • ผู้ใช้โฮสต์ root + ผู้ใช้คอนเทนเนอร์ที่ไม่มีสิทธิ์: root non-root
      • ผู้ใช้โฮสต์ที่ไม่มีสิทธิ์ + คอนเทนเนอร์ root: rootless rootful
      • ผู้ใช้โฮสต์ที่ไม่มีสิทธิ์ + ผู้ใช้คอนเทนเนอร์ที่ไม่มีสิทธิ์: rootless non-root
    • Podman ทำให้การรันคอนเทนเนอร์ rootless rootful ทำได้ง่าย และหากสามารถรันโปรเซสคอนเทนเนอร์เป็นผู้ใช้ที่ไม่มีสิทธิ์ได้ การตั้งค่าแบบ rootless non-root ก็ทำได้ค่อนข้างง่ายเช่นกัน

bind mount และการแยก UID

  • เมื่อนำไดเรกทอรีของโฮสต์มาเมานต์ในคอนเทนเนอร์ การเข้าถึงไฟล์ที่เป็นเจ้าของโดยโฮสต์ root, โฮสต์ bar และเนมสเปซ foo จะต่างกันไปตามการแมป UID
  • ในตัวอย่าง มีการสร้างไฟล์ root.txt ที่โฮสต์ root เป็นเจ้าของ และ bar.txt ที่โฮสต์ bar เป็นเจ้าของ ในไดเรกทอรี /var/lib/bar/test แล้วเมานต์แบบอ่าน/เขียนเป็น /test ภายในคอนเทนเนอร์
  • เมื่อรันคอนเทนเนอร์ด้วย foo ไฟล์ที่โฮสต์ bar เป็นเจ้าของจะมองเห็นเป็น root:root ภายในคอนเทนเนอร์ ส่วนไฟล์ที่โฮสต์ root เป็นเจ้าของจะไม่ถูกแมปในเนมสเปซ จึงมองเห็นเป็น nobody:nogroup
  • foo ภายในคอนเทนเนอร์ไม่สามารถอ่าน bar.txt และ root.txt ได้ และ rootless non-root ให้การแยกเพิ่มเติมมากกว่า rootless rootful
  • foo.txt ที่ foo สร้างในไดเรกทอรีที่เมานต์ไว้จะแสดงบนโฮสต์ว่าเป็นของ UID 166537 และผู้ใช้โฮสต์ bar ไม่สามารถอ่านเนื้อหาไฟล์นั้นได้
  • หากรันคอนเทนเนอร์เป็น root ภายใน เนมสเปซ root จะสามารถอ่านไฟล์ที่โฮสต์ bar เป็นเจ้าของและไฟล์ของ foo ได้ แต่ยังอ่านไฟล์ root.txt ที่โฮสต์ root เป็นเจ้าของไม่ได้
  • หากรันเป็น root ภายในพร้อมใช้ --cap-drop=all จะอ่านไฟล์ของ foo ไม่ได้เช่นกัน และอ่านได้เฉพาะไฟล์ที่โฮสต์ bar เป็นเจ้าของ

การทดสอบ Copy Fail

  • เงื่อนไขการทดสอบ

    • การทดสอบ Copy Fail ใช้เวอร์ชันเอ็กซ์พลอยต์จากคอมมิตที่เปิดเผยเดิม 8e918b5
    • อิมเมจคอนเทนเนอร์ตัวอย่างเพิ่ม curl เข้าไปในอิมเมจ HTTP server เดิม เพื่อให้ดาวน์โหลดสคริปต์เอ็กซ์พลอยต์จากภายในคอนเทนเนอร์ได้
    • อิมเมจถูก build ด้วยชื่อ copyfail
    • เคอร์เนลที่ใช้ทดสอบคือ 6.12.74+deb13+1-amd64 ของ Debian และตามเกณฑ์ของ Debian เวอร์ชันล่าสุดที่ต่ำกว่า 6.12.85 ถือว่ายังเป็นเคอร์เนลที่ไม่ถูกแพตช์และนำมาใช้ทดสอบได้
    • โดยทั่วไปเมื่อผู้ใช้ที่ไม่มีสิทธิ์ foo เรียก su จะต้องใส่รหัสผ่านของ root
    • ในแต่ละการทดสอบ ผู้ใช้คอนเทนเนอร์จะดาวน์โหลดสคริปต์ Copy Fail ไปไว้ที่ /tmp แล้วรัน จากนั้นหากได้เชลล์ root ก็จะเรียก sleep
    • Copy Fail คงอยู่ข้ามวงจรชีวิตของคอนเทนเนอร์ ดังนั้นจึงรีบูต VM ก่อนการทดสอบแต่ละครั้ง
  • ผลลัพธ์ใน rootless rootful

    • เมื่อรันคอนเทนเนอร์ด้วย --user=root โปรเซสภายในคอนเทนเนอร์จะเป็น root อยู่แล้ว
    • ในสถานะนี้ หากรันสคริปต์ Copy Fail แล้วเรียก su จะได้เชลล์ uid=0(root) แต่เดิมผู้ใช้ root ก็สามารถเปิด root shell อื่นด้วย su โดยไม่ต้องใช้รหัสผ่านอยู่แล้ว จึงแทบไม่มีสิ่งที่ Copy Fail เพิ่มให้จริง
    • ใน podman top จะเห็นว่า /bin/bash, python3 copy_fail_exp.py, su, sleep ทั้งหมดแสดงเป็น root ภายในคอนเทนเนอร์ และผู้ใช้โฮสต์ 1001
    • ชุด capability เดิมยังคงอยู่ และเห็น CHOWN,DAC_OVERRIDE,FOWNER,FSETID,KILL,NET_BIND_SERVICE,SETFCAP,SETGID,SETPCAP,SETUID,SYS_CHROOT
    • root ภายในสามารถอ่าน bar.txt และ foo.txt ใน /test ที่เมานต์ไว้ได้ แต่ยังอ่าน root.txt ที่โฮสต์ root เป็นเจ้าของไม่ได้
  • ผลลัพธ์ใน rootless non-root

    • หลังจากรันคอนเทนเนอร์ด้วย foo แล้วรันสคริปต์ Copy Fail และเรียก su จะเกิดการยกระดับสิทธิ์เป็น root ภายในคอนเทนเนอร์
    • id ของเชลล์ที่ได้จะแสดงเป็น uid=0(root) gid=1002(foo) groups=1002(foo)
    • ใน podman top จะเห็นว่า /bin/bash เริ่มต้น, โปรเซสที่รันเอ็กซ์พลอยต์ และการเรียก su แสดงเป็น UID โฮสต์ 166537, ผู้ใช้คอนเทนเนอร์ foo และ capabilities เป็น none
    • หลังยกระดับสิทธิ์แล้ว [sh] และ sleep จะแสดงเป็นผู้ใช้โฮสต์ 1001, ผู้ใช้คอนเทนเนอร์ root และได้รับชุด capability เดียวกับ rootless rootful
    • แม้แต่ root ของคอนเทนเนอร์ที่ยกระดับสิทธิ์แล้วก็ยังอ่าน root.txt ที่โฮสต์ root เป็นเจ้าของไม่ได้
    • ในสถานะนี้แม้คอนเทนเนอร์จะถูกเจาะได้ แต่ขอบเขตการโจมตียังถูกจำกัดอยู่ในขอบเขตที่คอนเทนเนอร์และผู้ใช้โฮสต์ที่ไม่มีสิทธิ์ bar ทำได้
  • ผลลัพธ์เมื่อใช้ no-new-privileges

    • Podman สามารถใช้ --security-opt=no-new-privileges เพื่อไม่ให้โปรเซสในคอนเทนเนอร์ได้รับสิทธิ์มากกว่าตอนเริ่มต้น
    • เมื่อใช้ตัวเลือกนี้กับคอนเทนเนอร์ rootless non-root แล้วรัน Copy Fail จะเปิดเชลล์ได้ แต่ยังคงเป็น uid=1002(foo)
    • ใน podman top เช่นกัน ทุกโปรเซสยังคงเป็น UID โฮสต์ 166537, ผู้ใช้คอนเทนเนอร์ foo และ capabilities none
    • ที่ /test ซึ่งถูกเมานต์ไว้ foo ก็ยังอ่านได้เฉพาะไฟล์ของตัวเอง และไม่สามารถอ่าน bar.txt กับ root.txt ได้
    • แม้คอนเทนเนอร์จะถูกเจาะ แต่ก็ยังถูกจำกัดไว้ที่ผู้ใช้ภายในที่ไม่มีสิทธิ์ foo และสถานะที่ไม่มี capability
  • ผลลัพธ์เมื่อใช้ --cap-drop=all

    • แม้จะรันคอนเทนเนอร์ rootless non-root ด้วย --cap-drop=all แต่เดิม foo ก็ไม่มี capability อยู่แล้ว
    • ในสถานะนี้ หากรัน Copy Fail และเรียก su เชลล์ที่เปิดขึ้นจะยังคงเป็น uid=1002(foo)
    • ใน podman top เช่นกัน /bin/bash, การรันเอ็กซ์พลอยต์, su, เชลล์ และ sleep ทั้งหมดจะยังเป็น foo และ capabilities none
    • เอ็กซ์พลอยต์ไม่สามารถยึด root shell ได้ และ foo อ่านได้เฉพาะไฟล์ของตัวเองใน /test
    • ผลลัพธ์นี้คล้ายกับการทดสอบ no-new-privileges และสามารถใช้ทั้งสองมาตรการร่วมกันเพื่อลดการเปิดเผย capability ได้อย่างมีประสิทธิภาพ
  • ความคงอยู่ของเอ็กซ์พลอยต์

    • แม้การได้ root shell และ capability ทันทีจะถูกป้องกันได้ด้วย no-new-privileges หรือ --cap-drop=all แต่ผลของเอ็กซ์พลอยต์เองยังคงอยู่
    • หลังจากนั้น หากรันคอนเทนเนอร์ใหม่โดยไม่มีการจำกัด capability ผู้ใช้คอนเทนเนอร์ที่ไม่มีสิทธิ์ foo ก็อาจกลายเป็น root ของคอนเทนเนอร์ได้เพียงแค่เรียก su
    • ดังนั้นจึงยังจำเป็นต้องแพตช์เคอร์เนลและรีบูต

กลยุทธ์การป้องกันเชิงลึก

  • อิมเมจแบบอ่านอย่างเดียว

    • การเพิ่ม --read-only ให้กับ podman run จะเมานต์รูทไฟล์ซิสเต็มของคอนเทนเนอร์เป็นแบบอ่านอย่างเดียว
    • โดยปกติ Podman จะเมานต์บางไดเรกทอรีอย่าง /tmp, /run, /var/tmp ให้เขียนได้อยู่แล้ว ดังนั้นหากต้องการให้เป็นแบบอ่านอย่างเดียวทั้งหมด ต้องเพิ่ม --read-only-tmpfs=false ด้วย
    • หากคอนเทนเนอร์แบบอ่านอย่างเดียวถูกเจาะ ผู้โจมตีจะไม่สามารถเขียนลงระบบได้ จึงช่วยจำกัดการโจมตีบางส่วนหลังการเอ็กซ์พลอยต์
    • อย่างไรก็ตาม สามารถ pipe เอาต์พุตของ curl ไปยัง python3 ได้ ดังนั้นการตั้งค่าแบบอ่านอย่างเดียวเพียงอย่างเดียวไม่ได้ป้องกันการรันเอ็กซ์พลอยต์โดยตรง
    • python3 HTTP server ในตัวอย่างไม่ต้องเขียนลงไฟล์ซิสเต็ม จึงสามารถใช้ตัวเลือกนี้ได้อย่างปลอดภัย
    • อิมเมจที่พรีบิลด์ไว้จำนวนมากตั้งสมมติฐานว่าจะต้องเขียนลงบางไดเรกทอรีได้ จึงอาจทำงานได้ไม่ถูกต้องบนรูทไฟล์ซิสเต็มแบบอ่านอย่างเดียว
    • รูทไฟล์ซิสเต็มแบบอ่านอย่างเดียวแยกจากวอลุ่มที่เขียนได้ซึ่งต่อเข้ากับคอนเทนเนอร์ และหากถูกเจาะก็ยังคงเขียนลงไดเรกทอรีเมานต์เหล่านั้นได้
  • การจำกัดทรัพยากร

    • Docker และ Podman สามารถใช้ cgroups เพื่อจำกัดทรัพยากรที่ให้กับคอนเทนเนอร์ได้
    • คอนเทนเนอร์ไม่จำเป็นต้องใช้หน่วยความจำ, CPU หรือ PID แบบไม่จำกัด
    • สามารถตรวจสอบการใช้ทรัพยากรของคอนเทนเนอร์ด้วย podman stats แล้วจึงกำหนดขีดจำกัดให้เหมาะสมได้
  • จำกัดไบนารีที่ใช้งานได้

    • ตัวอย่างนี้ใช้อิมเมจ ubuntu เพื่อความเรียบง่าย แต่อิมเมจ ubuntu มีไบนารีจำนวนมากที่ผู้โจมตีสามารถใช้ได้หากคอนเทนเนอร์ถูกเจาะ
    • การรัน HTTP server ไม่ได้ต้องใช้ไบนารีส่วนใหญ่เหล่านี้
    • อิมเมจรันไทม์ควรทำให้บางที่สุดเท่าที่เป็นไปได้
    • สามารถใช้ multi-stage build เพื่อแยกสภาพแวดล้อมตอน build ออกจากสภาพแวดล้อมตอนรันไทม์ได้
    • สามารถใช้อิมเมจเฉพาะงานอย่าง python3, เวอร์ชัน -slim ของ Debian หรือดิสทริบิวชันขนาดเล็กกว่าอย่าง alpine เป็นฐานได้
    • หากเข้ากันได้กับโปรเซสของคอนเทนเนอร์ สามารถใช้ distroless images หรือ scratch เพื่อสร้างรันไทม์ที่ไม่มีเชลล์, package manager และยูทิลิตีของระบบได้
  • ไฟร์วอลล์

    • สามารถใช้ iptables หรือ nftables เพื่อ จำกัดโปรเซสของคอนเทนเนอร์ด้วยไฟร์วอลล์ ได้
    • ควรอนุญาตเฉพาะการเชื่อมต่อขาเข้าและขาออกที่จำเป็นจริง ๆ สำหรับโปรเซสของคอนเทนเนอร์
    • ในตัวอย่าง HTTP server ไม่จำเป็นต้องเชื่อมต่อกับ DNS หรือเซิร์ฟเวอร์ภายใน/ภายนอก ดังนั้นจึงสามารถจำกัดให้ยอมรับเฉพาะแพ็กเก็ต tcp ที่มาจากการเชื่อมต่อขาเข้าที่สร้างขึ้นแล้วได้

ความหมายในเชิงปฏิบัติการ

  • คอนเทนเนอร์ Podman แบบ rootless มาตรฐานให้การแยกตัวที่ดีกว่าคอนฟิกคอนเทนเนอร์ Docker มาตรฐานโดยค่าเริ่มต้น
  • Docker ก็สามารถ รันแบบ rootless และ ใช้ user namespace แบบไม่มีสิทธิพิเศษ ได้เช่นกัน แต่ต้องตั้งค่ามากกว่า Podman และความแตกต่างด้านสถาปัตยกรรมก็มีผลด้วย
  • Docker ยังคงถูกใช้อย่างแพร่หลาย และเครื่องมือ self-hosting อย่าง Dokku, Kamal, Coolify, Dokploy ก็ใช้ Docker เป็นค่าเริ่มต้น
  • หากรันอิมเมจจาก Docker Hub โดยไม่ได้ตรวจสอบอย่างเพียงพอหรือไม่ได้ใช้มาตรการล็อกดาวน์ บริการอาจทำงานด้วยพื้นผิวการโจมตีที่กว้างเกินความจำเป็น
  • ต้องเข้าใจรายละเอียดการติดตั้งใช้งานของอิมเมจคอนเทนเนอร์
    • ต้องรู้ว่ามีผู้ใช้คนใดหรือผู้ใช้กลุ่มใดที่รันโปรเซสของคอนเทนเนอร์
    • ต้องรู้ว่าโปรเซสของคอนเทนเนอร์พึ่งพาไดเรกทอรีใดบ้างในรูทไฟล์ซิสเต็ม
    • ต้องแยกให้ออกว่า Linux capabilities ใดจำเป็น และ capabilities ใดไม่จำเป็น
  • การผสานหลายกลไกที่ Podman และคอนเทนเนอร์มีให้สามารถช่วย harden คอนเทนเนอร์และลด blast radius เมื่อถูกเจาะได้
  • ขึ้นอยู่กับลักษณะของเวิร์กโหลด ไม่ควรพึ่งคอนเทนเนอร์เป็นขอบเขตความปลอดภัยเพียงอย่างเดียว
  • การใช้คอนเทนเนอร์ร่วมกับเครื่องจริงหรือเครื่องเสมือนที่แยกต่างหากสามารถช่วยแยกส่วนได้อย่างมีประสิทธิภาพ
  • Podman มีวิธีแยกเวิร์กโหลดแต่ละตัวภายในโฮสต์เดียวกัน โดยให้รันด้วยผู้ใช้แบบไม่มีสิทธิพิเศษคนละรายและมี user namespace ของตัวเอง

แหล่งข้อมูลเพิ่มเติม

1 ความคิดเห็น

 
GN⁺ 2 시간 전
ความเห็นจาก Lobste.rs
  • ควรโฟกัสที่ พฤติกรรมพื้นฐาน ที่ทำให้ช่องโหว่นี้เกิดขึ้นได้ มากกว่าตัวเอ็กซ์พลอยต์ที่ถูกเผยแพร่ออกมา
    ช่องโหว่นี้ทำให้สามารถเขียนลง page cache ได้ไม่ว่าจะเป็นแบบอ่านอย่างเดียวหรือไม่ก็ตาม ดังนั้นคอนเทนเนอร์ที่เป็นอันตรายจึงสามารถแก้ไขหน้าเพจที่เป็นของไฟล์ base image ของ overlayfs ได้ และผลกระทบอาจลามไปยังคอนเทนเนอร์อื่นได้ด้วย ขึ้นอยู่กับวิธีการ deploy คอนเทนเนอร์
    สำหรับการตั้งค่าแบบ rootless ในกรณีนี้ เป้าหมายจะเป็นคอนเทนเนอร์อื่นที่รันด้วยผู้ใช้คนเดียวกันบนระบบโฮสต์
    วิธีเอ็กซ์พลอยต์อีกแบบคือ รันหรือค้นหาคอนเทนเนอร์ที่อิงกับ base image ซึ่งรู้แน่ว่ากำลังถูกใช้งานอยู่ แล้วแก้ไข page cache ภายในคอนเทนเนอร์นั้น จากนั้นทำให้คอนเทนเนอร์อื่นที่ใช้ runtime เดียวกันและแชร์ข้อมูล overlayfs เดียวกันไปรันโค้ดนั้น
    rootless และ user namespace นั้นสำคัญ แต่ในกรณีนี้แทบไม่ช่วยอะไร และอย่างที่เว็บ copy.fail บอกไว้ ควรพิจารณาบล็อก system call socket(AF_ALG, ...) ด้วย seccomp ภายในคอนเทนเนอร์

    • ผมเองยังคิดไปไม่ถึงระดับ พฤติกรรมพื้นฐาน แบบนี้ และไปโฟกัสกับการจัดระเบียบ namespace และ capability ที่ rootless container มีให้ เพื่อประเมินขอบเขตการเปิดเผยเมื่อคอนเทนเนอร์ถูกเจาะมากกว่า
      ถ้าช่วยอธิบายเพิ่มได้จะดีว่า “ขึ้นอยู่กับวิธีการ deploy คอนเทนเนอร์” หมายถึงอะไรแบบเจาะจง
      ข้อดีของ rootless Podman คือขึ้นอยู่กับ workload ว่าไม่จำเป็นต้องรันคอนเทนเนอร์ด้วยผู้ใช้คนเดียวกันบนโฮสต์
      ถ้าหมายถึงกรณีที่รัน rootless container หลายตัวด้วยผู้ใช้หลักของเวิร์กสเตชัน ก็เห็นด้วย แต่บนเซิร์ฟเวอร์สามารถแยกแต่ละตัวเป็นคนละผู้ใช้ได้ และแม้จะเป็น container image เดียวกันก็ยังรันด้วยผู้ใช้ที่ไม่มีสิทธิ์ต่างกันได้
      มันต่างจากค่าเริ่มต้นของ Docker ที่มักรันเกือบทุกอย่างเป็น root มาก แต่ผมก็เขียนไว้ท้ายบทความแล้วว่านี่ไม่ใช่ security boundary ขั้นสุดท้าย และการกระจาย rootless container ไปให้ผู้ใช้ที่ไม่มีสิทธิ์หลายคนใช้นั้นเหมาะสมแค่ไหนก็ขึ้นกับงาน
      บาง workload ผมแยกไปใช้ VM อยู่แล้ว
      เลยสงสัยว่าที่บอกว่า rootless กับ user namespace ไม่ช่วยในที่นี้ หมายถึงไม่ช่วยป้องกันเอ็กซ์พลอยต์ใช่ไหม
      ผมยังไม่เคยเขียนนโยบาย seccomp แบบชัดเจนให้คอนเทนเนอร์เลย เลยไม่ได้พูดถึง แต่ก็นับเป็นจุดเริ่มต้นที่ดีให้ไปศึกษาต่อ
  • ผมชอบ Podman และ rootless container แต่พอเห็น CopyFail ก็ได้ข้อสรุปเหมือนคอมเมนต์ข้างเคียง
    ต่อให้ podman+rootless มีข้อดีด้านการควบคุมการเข้าถึงเพิ่มขึ้น สุดท้ายก็ยืนยันคำเตือนคลาสสิกอีกครั้งว่า คอนเทนเนอร์ไม่ใช่ security boundary และเอ็กซ์พลอยต์เคอร์เนลเพียงตัวเดียวก็พังได้ทั้งหมด
    ผมเป็นแค่คนดูแลระบบเป็นงานอดิเรก แต่แนวโน้มใหม่ในพื้นที่นี้ที่ผมเห็นคือ libkrun backend for crun with podman
    มันสัญญาว่ายังจัดการ workload ที่ถูกทำให้เป็นคอนเทนเนอร์ส่วนใหญ่ได้เหมือนเดิม แต่ภายในจริง ๆ จะไปรันใน MicroVM ที่มี guest kernel แยกต่างหาก แม้ผมจะยังไม่แน่ใจเรื่องความสุกงอม การพิสูจน์ในภาคสนาม หรือระดับการตรวจสอบด้านความปลอดภัย และบางส่วนก็ดูค่อนข้างล้ำมาก
    MicroVM กำลังถูกนำไปใช้เชิงรุกในเครื่องมือเขียนโค้ดสำหรับ LLM ดังนั้นสถานะนั้นอาจอยู่ต่อไปอีกนาน
    podman machine ก็ดูมีอนาคต แต่เสียดายที่ดูเหมือนออกแบบมาเพื่อเวิร์กสเตชันของนักพัฒนาเท่านั้น และเป็นโมเดลที่มี VM สำหรับรันคอนเทนเนอร์เพียงตัวเดียวต่อระบบโฮสต์หนึ่งเครื่อง
    ถึงอย่างนั้น ผมก็คิดว่าคำว่า “คอนเทนเนอร์ไม่ใช่ security boundary” มันง่ายเกินไป คอนเทนเนอร์เป็น security boundary อยู่แน่ เพียงแต่ไม่แข็งแรงเท่าที่เราอยากเชื่อ

    • นี่ก็เป็นเหตุผลเดียวกับที่การ deploy คอนเทนเนอร์บนคลาวด์ส่วนใหญ่ใช้ VM เพราะ VM เป็นขอบเขตที่ป้องกันได้
      สำหรับการ deploy แบบโลคัล เส้นแบ่งนี้จะพร่ามัวขึ้นหน่อย
      ในมุมมองฮาร์ดแวร์ VM ไม่ได้ปลอดภัยกว่าพรอเซสโดยเนื้อแท้ แต่มีสามเหตุผลที่ทำให้มันเป็นขอบเขตที่ป้องกันได้มากกว่า
      การหนีออกจาก VM เกิดได้น้อยกว่า system call จึงมีพื้นที่ให้ใส่มาตรการลด side channel เพิ่มได้โดยไม่กระทบประสิทธิภาพมาก
      อินเทอร์เฟซฝั่งโฮสต์ของ VM นั้นง่ายกว่ามาก อุปกรณ์บล็อกมีอินเทอร์เฟซอ่าน-เขียนเป็นบล็อก และอุปกรณ์เครือข่ายก็ส่งรับเฟรม
      คำสั่ง setsockopt ที่ Linux หรือ *BSD ให้กับ socket มีพื้นผิวการโจมตีใหญ่กว่าพวกไดรเวอร์จำลองหรือ paravirtualized ส่วนใหญ่เสียอีก และนั่นก็ยังเป็นเพียงส่วนเล็กมากของพื้นผิวโจมตีทั้งหมดของเคอร์เนล
      อินเทอร์เฟซของ VM ยังมี state น้อยกว่ามากด้วย มีเพียงทรานแซกชันที่กำลังดำเนินอยู่ใน ring แบบ request-response เป็นหลัก นอกนั้นแทบไม่มี
      สิ่งอย่าง credential, UID, GID, ตาราง file descriptor ล้วนเพิ่มความซับซ้อนเชิงสถานะให้กับเคอร์เนล และถ้ามีบั๊ก พรอเซสก็อาจใช้ประโยชน์จากมันได้
      ความยากของรูปแบบเวิร์กสเตชันก็คือ มันดึงความซับซ้อนเหล่านี้กลับเข้ามาอีกครั้ง
      ตัวอย่างเช่น base layer ของคอนเทนเนอร์อาจถูกเปิดให้เห็นเป็น block device ที่บรรจุไฟล์ซิสเต็มแบบ immutable แต่ volume และ shared folder ก็น่าจะถูกเมานต์ผ่าน 9pfs หรือ VirtIO-FS ซึ่งก็คือ 9p หรือ FUSE บน VirtIO
      แบบนั้นพื้นผิวการโจมตีก็จะเพิ่มขึ้นอีก
      ถ้าโชคดี อาจต้องใช้เอ็กซ์พลอยต์เป็นลูกโซ่
      ผมคุ้นกับฝั่ง FreeBSD มากกว่า ซึ่งโดยทั่วไปองค์ประกอบที่ให้บริการอุปกรณ์ paravirtualized หรือ emulated จะถูก sandbox ด้วย Capsicum ดังนั้นก่อนอื่นต้องยึดโปรเซสบนโฮสต์ให้ได้ และถ้าจะเข้าถึงสิ่งที่ VM เดิมไม่มีสิทธิ์เข้าถึงต่อ ก็ต้องทะลุไปถึงเคอร์เนลอีกชั้น
      แต่ถ้าไม่มี sandbox เพิ่มแบบนี้ การหนีจากคอนเทนเนอร์ก็จะกลับไปสู่โลกที่ทำได้ทุกอย่างที่ผู้ใช้คนนั้นทำได้ ซึ่งบนเดสก์ท็อปก็แทบไม่ได้ดีกว่าการที่ root ถูกเจาะเท่าไร
    • Kata Containers กับ Firecracker ก็เป็นเทคโนโลยีที่มีมานานพอสมควรแล้ว และเพราะ นักวิจัย ตรวจสอบกันมาแล้ว ผมจึงมองว่ามันสุกงอมในระดับที่สมเหตุสมผล
      ส่วนตัวผมชอบ gVisor มากกว่า แม้จะไม่ใช่ VMM runtime แต่ก็มีมาหลายปีแล้ว และมีบริษัทอย่าง Tencent ใช้งานอยู่ อีกทั้งยังเข้ากับสภาพแวดล้อมของผมที่รันคอนเทนเนอร์ทั้งหมดภายใน Proxmox VM อยู่แล้ว
      อีกตัวที่กำลังทดลองคือ syd-oci ซึ่งดูเหมือนได้รับความสนใจน้อยกว่าตัวเลือกพื้นฐานอย่าง MicroVM หรือ gVisor อยู่บ้าง
    • ถ้อยคำนี้ก็ตรงกับประสบการณ์ของผมเหมือนกัน และกระบวนการเขียนบทความนี้ก็เกือบเหมือนการ ฝึกยอมรับ ข้อเท็จจริงนั้น
      ขอบคุณสำหรับข้อมูลอ้างอิงเรื่อง libkrun ด้วย ดูเป็นความเป็นไปได้ที่น่าสนใจ
    • การนำไปใช้อย่างจริงจังในฝั่งเครื่องมือเขียนโค้ด LLM น่าจะทำให้ MicroVM สุกงอมขึ้น และมีแนวโน้มจะนำไปสู่การพิสูจน์ใช้งานจริงกับการ harden มากขึ้น
      ก็ดูมีโอกาสสูงที่จะตามมาด้วยการตรวจสอบด้านความปลอดภัยเช่นกัน