- เป็น race condition ใน signal handler ของ OpenSSH เซิร์ฟเวอร์
sshdซึ่งทำให้ไคลเอนต์ที่ยังไม่ยืนยันตัวตนสามารถก่อให้เกิดการรันโค้ดจากระยะไกลบนเซิร์ฟเวอร์ที่ใช้ค่าตั้งต้นได้ ภายในเวลาจำกัดก่อนยืนยันตัวตนLoginGraceTime - ช่องโหว่นี้เป็น regression ของ CVE-2006-5051 และกลับมาอีกครั้งในช่วงตั้งแต่ 8.5p1 ขึ้นไปแต่ต่ำกว่า 9.8p1 หลังคอมมิต OpenSSH 8.5p1 เมื่อเดือนตุลาคม 2020 ได้ลบมาตรการป้องกันออกจาก
sigdie() - บน Linux ที่ใช้ glibc,
syslog()จะเรียก ฟังก์ชันที่ไม่ปลอดภัยต่อ async-signal เช่นmalloc()และfree()จึงอาจนำไปสู่ root RCE แบบไม่ต้องยืนยันตัวตนได้ใน privileged code ของsshdที่ไม่ได้อยู่ใน sandbox - การทดลองทำบนเครื่องเสมือน i386 และเครือข่ายที่ค่อนข้างเสถียรซึ่งมี packet jitter ราว 10ms โดยบน Debian 12.5.0 OpenSSH 9.2p1 ต้องลองเฉลี่ยราว 10,000 ครั้ง และภายใต้เงื่อนไข
MaxStartups=100,LoginGraceTime=120ใช้เวลาประมาณ 6–8 ชั่วโมงกว่าจะได้ root shell - OpenSSH แก้ไขแล้วด้วยคอมมิต
81c1099เมื่อ 6 มิถุนายน 2024 และหากอัปเดตหรือคอมไพล์ใหม่ได้ยาก การตั้งLoginGraceTime 0จะป้องกัน RCE ได้ แต่ยังคงมีความเสี่ยง DoS จากการทำให้การเชื่อมต่อMaxStartupsหมดลง
จุดที่ทำให้เกิดช่องโหว่
- ปัญหาของ OpenSSH
sshdเริ่มจาก SIGALRM handler ที่ทำงานก่อนการยืนยันตัวตน- หากไคลเอนต์ไม่ยืนยันตัวตนภายใน
LoginGraceTimeระบบจะเรียกSIGALRMhandler แบบอะซิงโครนัส - handler นี้เรียกฟังก์ชันที่ไม่ใช่ async-signal-safe เช่น
syslog() - ค่าเริ่มต้นคือ
LoginGraceTime=120วินาที และใน OpenSSH เวอร์ชันเก่าจะเป็น 600 วินาที
- หากไคลเอนต์ไม่ยืนยันตัวตนภายใน
- ช่องโหว่นี้เป็น regression ของ CVE-2006-5051
- CVE-2006-5051 คือ race condition ใน signal handler ของ OpenSSH ก่อน 4.4 ที่ Mark Dowd รายงานในปี 2006
- ในเดือนตุลาคม 2020 คอมมิต
752250cของ OpenSSH 8.5p1 ได้ลบ#ifdef DO_LOG_SAFE_IN_SIGHANDออกจากsigdie()โดยไม่ตั้งใจ
- ขอบเขตเวอร์ชันที่ได้รับผลกระทบแบ่งได้ชัดเจน
- ต่ำกว่า OpenSSH 4.4p1: มีช่องโหว่หากยังไม่ได้ backport แพตช์ที่เกี่ยวข้องกับ CVE-2006-5051 หรือ CVE-2008-4109
- OpenSSH 4.4p1 ขึ้นไปแต่ต่ำกว่า 8.5p1:
sigdie()ถูกเปลี่ยนให้เรียก_exit(1)อย่างปลอดภัย จึงไม่เปราะบางต่อ race condition นี้ - OpenSSH 8.5p1 ขึ้นไปแต่ต่ำกว่า 9.8p1: กลับมาเปราะบางอีกครั้งหลังลบมาตรการป้องกัน
สภาพแวดล้อมที่ได้รับผลกระทบและข้อยกเว้น
- เป้าหมายของการโจมตีจากระยะไกลคือ Linux ที่ใช้ glibc
syslog()ของ glibc เรียกฟังก์ชันที่ไม่ปลอดภัยต่อ async-signal ภายใน เช่นmalloc()และfree()- โค้ดที่มีช่องโหว่อยู่ใน privileged code ของ
sshdและรันโดยไม่อยู่ใน sandbox พร้อมสิทธิ์เต็ม - ส่งผลให้สามารถรันโค้ดระดับ root จากระยะไกลได้โดยไม่ต้องยืนยันตัวตน
- libc หรือระบบปฏิบัติการอื่นไม่อยู่ในขอบเขตการตรวจสอบนี้
- OpenBSD ไม่ได้รับผลกระทบ
SIGALRMhandler ของ OpenBSD เรียกsyslog_r()แทนsyslog()syslog_r()เป็นเวอร์ชันที่ปลอดภัยต่อ async-signal มากกว่าซึ่ง OpenBSD สร้างขึ้นในปี 2001
สมมติฐานของงานวิจัยการโจมตีจากระยะไกล
- การใช้ race condition นี้จากระยะไกลต้องแก้ปัญหา 3 อย่าง
- ต้องมี code path ที่ทำให้
sshdอยู่ในสถานะไม่สอดคล้องกันเมื่อSIGALRMแทรกเข้ามาในจังหวะที่เหมาะสม - ต้องเข้าถึง code path นั้นและเพิ่มโอกาสให้ถูกขัดจังหวะในเวลาที่ถูกต้อง
- ต้องสามารถจับจังหวะดังกล่าวได้แม้อยู่บนสภาพแวดล้อมเครือข่ายระยะไกล
- ต้องมี code path ที่ทำให้
- งานวิจัยเริ่มจากสภาพแวดล้อม i386 ของ OpenSSH รุ่นเก่าก่อน แล้วค่อยขยายไปยังเวอร์ชันใหม่ แทนที่จะชนกับกลไกป้องกันสมัยใหม่ทันที
- เงื่อนไขการทดลองมีข้อจำกัดชัดเจน
- ทดสอบกับ เครื่องเสมือน เท่านั้น ไม่ใช่ bare-metal server
- เครือข่ายเป็นลิงก์ที่ค่อนข้างเสถียร มี packet jitter ประมาณ 10ms
- หลายส่วนของ exploit ยังสามารถปรับปรุงได้อีก
- เริ่มงาน exploit สำหรับ amd64 แล้ว แต่ยากกว่ามากเพราะ ASLR แข็งแรงกว่า
การทดลองกับ OpenSSH รุ่นเก่า
-
Debian 3.0r6, OpenSSH 3.4p1
- เป้าหมายคือ
SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3ซึ่งเป็นสภาพแวดล้อมของ Debian 3.0r6 ในปี 2005 - Debian รุ่นนี้เป็นรุ่นแรกที่เปิดใช้ privilege separation เป็นค่าตั้งต้น และมีการใช้แพตช์ช่องโหว่หลักในยุคนั้นแล้ว
- การโจมตีอาศัยการขัดจังหวะ
free()และ สถานะ heap ที่ไม่สอดคล้องกัน- ขัดจังหวะการเรียก
free()ในโค้ด parsing ของ public key ด้วยSIGALRM - จากนั้นอาศัยสถานะ heap ที่ไม่สอดคล้องกันใน
free()อีกจุดหนึ่งภายในpacket_close()
- ขัดจังหวะการเรียก
- glibc 2.2.5 ยังไม่มีการเสริมความแข็งแกร่งต่อเทคนิค
unlink()ของ Solar Designer - การโจมตีเขียนทับ
__free_hookเพื่อเปลี่ยนทิศทางการทำงานไปยังตำแหน่ง shellcode บน heap - Debian รุ่นนี้ไม่มีทั้ง ASLR และ NX
- หลังปรับปรุงการจับจังหวะแล้ว ต้องลองเฉลี่ยราว 10,000 ครั้ง
- ภายใต้
MaxStartups=10,LoginGraceTime=600ใช้เวลาเฉลี่ยประมาณ 1 สัปดาห์กว่าจะได้ remote root shell
- เป้าหมายคือ
-
Ubuntu 6.06.1, OpenSSH 4.2p1
- เป้าหมายคือ
SSH-2.0-OpenSSH_4.2p1 Debian-7ubuntu3ซึ่งเป็นสภาพแวดล้อมของ Ubuntu 6.06.1 ในปี 2006 - เป็น Ubuntu เวอร์ชันสุดท้ายที่ยังคงเปราะบางต่อ CVE-2006-5051
- glibc 2.3.6 จะจับ mandatory lock เมื่อเข้าใช้ฟังก์ชันตระกูล malloc ทำให้วิธีขัดจังหวะ malloc แล้วไปโจมตีด้วยการเรียก malloc อีกครั้งจบลงที่ deadlock
- เส้นทางโจมตีสุดท้ายใช้ PAM
pam_start()ตั้งค่า pointer ส่วนกลางsshpam_handleของsshd- หาก
_pam_add_handler()ถูกขัดจังหวะ อาจเหลือฟิลด์nextที่ยังไม่ถูกกำหนดค่า - เมื่อ
pam_end()ถูกเรียกจากSIGALRMhandler ก็อาจส่ง arbitrary pointer ให้free()ได้
- เทคนิค
unlink()แบบเก่าของ glibc ถูกป้องกันแล้ว จึงใช้ House of Mind fastbin เวอร์ชันจาก Malloc Maleficarum - ชี้ fake arena ไปที่
.got.pltของsshdและเขียนทับ entry ของ_exit()ให้เป็นตำแหน่ง heap shellcode - heap ของ Ubuntu รุ่นนี้รันโค้ดได้ตามค่าตั้งต้น
- ต้องลองเฉลี่ยราว 10,000 ครั้ง
- ภายใต้
MaxStartups=10,LoginGraceTime=120ใช้เวลาเฉลี่ยประมาณ 1–2 วันกว่าจะได้ remote root shell - หากโชคร้าย ผู้โจมตีอาจทำให้การเชื่อมต่อ
MaxStartupsทั้ง 10 เส้น deadlock ก่อนจะได้ root shell
- เป้าหมายคือ
การทดลองบน Debian 12.5.0, OpenSSH 9.2p1
-
syslog()และเส้นทาง malloc ของ glibc- เป้าหมายคือ
SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2ซึ่งเป็นสภาพแวดล้อม current stable ของ Debian 12.5.0 ในปี 2024 - สภาพแวดล้อมนี้เปราะบางต่อ regression ของ CVE-2006-5051
SIGALRMhandler ของเวอร์ชันนี้ไม่เรียกpacket_close()หรือpam_end()แต่ไปตามเส้นทางsyslog()grace_alarm_handler()เรียกsigdie()sigdie()เรียกsyslog()ผ่านsshlogv()และdo_log()
syslog()ของ Debian glibc 2.36 จะเรียก malloc ในการเรียกครั้งแรก- มีการเรียก
__tzfile_read()ผ่านเส้นทาง__localtime64_r() fopen()เรียกmalloc(304)สำหรับโครงสร้าง FILE- และเรียก
malloc(4096)สำหรับ internal read buffer
- มีการเรียก
- glibc malloc ตั้งแต่หลังเดือนตุลาคม 2017 จะไม่จับ mandatory lock ในกรณี single-threaded
- จึงเกิดโอกาสใช้ประโยชน์จากการแข่งขันของ malloc ได้ในโปรเซสแบบ single-threaded อย่าง
sshd
- เป้าหมายคือ
-
เงื่อนไข ASLR และข้อจำกัดของ i386
- สภาพแวดล้อม Debian 12.5.0 i386 มี จุดอ่อนของ ASLR
- PIE, heap, ไลบรารีส่วนใหญ่ และ stack ของ
sshdโดยทั่วไปจะถูกสุ่มตำแหน่ง - แต่ glibc เองจะถูกแมปที่
0xb7200000หรือ0xb7400000เสมอ - จึงเดาตำแหน่ง glibc ได้ด้วยโอกาสครึ่งหนึ่ง
- exploit นี้สมมติว่า glibc ถูกแมปที่
0xb7400000 - เพราะที่อยู่นี้พบได้บ่อยกว่า
0xb7200000เล็กน้อย
-
สถานะ heap ที่ไม่สอดคล้องกันและการใช้ประโยชน์จากโครงสร้าง FILE
- เส้นทาง malloc ที่เลือกคือเส้นทาง split ซึ่งแบ่ง free chunk ขนาดใหญ่เป็นสองส่วน
- จะได้ chunk ที่ส่งคืนและ remainder chunk
- หาก
SIGALRMแทรกหลังจาก remainder chunk ถูกเชื่อมเข้า unsorted list แต่ก่อนกำหนดค่า size field จะเกิดความไม่สอดคล้องของ heap - ผู้โจมตีควบคุม size field ของ remainder chunk ได้ผ่านข้อมูลตกค้างจาก heap allocation ก่อนหน้า
- ทำให้ remainder chunk ดูใหญ่กว่าความจริงและซ้อนทับกับ heap chunk อื่นได้
- เมื่อ malloc ใน
SIGALRMhandler ใช้ chunk นี้ก็จะทำให้หน่วยความจำ heap เสียหาย - เป้าหมายคือ โครงสร้าง FILE ที่
fopen()ภายใน__tzfile_read()จัดสรรไว้บน heap- ด้วย heap corruption ที่จำกัด สามารถเขียนทับ
_vtable_offsetได้ 1 ไบต์ - ทำให้ฟังก์ชัน libio ของ glibc ไปอ่าน vtable pointer จาก offset อื่นแทนตำแหน่งปกติ
- ผู้โจมตีควบคุม fake vtable pointer และ
_codecvtpointer ที่ตำแหน่งนั้นได้ผ่านข้อมูลตกค้างจาก heap allocation ก่อนหน้า
- ด้วย heap corruption ที่จำกัด สามารถเขียนทับ
- บน i386 glibc เทคนิคนี้ทำให้เรียก arbitrary function pointer
__fctระหว่าง__fread_unlocked()ได้ - ดูเหมือน amd64 glibc จะไม่ใช้
_vtable_offset
-
heap layout และ race window 27 จุด
- race window ขนาดเล็กเพียงจุดเดียวไม่พอจะชนะ race condition นี้ จึงสร้าง คู่ large hole และ small hole 27 คู่
- ถ้าเป็น 28 คู่จะเกิน
PACKET_MAX_SIZE256KB - แพ็กเก็ตสุดท้ายจะบังคับลำดับ
malloc(~4KB), malloc(304), malloc(~4KB), malloc(304) - หากการ split large hole แต่ละจุดถูกขัดจังหวะในช่วงที่เหมาะสม
fopen()ในSIGALRMhandler จะนำ small hole ที่สอดคล้องกันไปจัดสรรเป็นโครงสร้าง FILE - ใช้โค้ด parsing ของ public key ใน
sshdเพื่อสร้าง heap layout- ผ่านเส้นทาง
cert_parse()และcert_free()เพื่อทำลำดับmalloc()และfree()ที่ใกล้เคียงกับอิสระ - เนื่องจากหา memory leak ไม่พบ จึงใช้ tcache chunk เป็น barrier chunk ชั่วคราว
- ผ่านเส้นทาง
- ส่ง public key packet 5 ประเภท
- a/ ทำให้ heap allocation ที่ควบคุมไม่ได้เข้าไปอยู่ใน tcache chunk
- b/ สร้าง large/small hole 27 คู่และ barrier chunk
- c/ เขียน fake header, fake footer, fake vtable, และ
_codecvtpointer ไว้ล่วงหน้า - d/ จัดสรรและคืนค่าสตริงขนาดใหญ่เกือบ 256KB เพื่อย้าย hole จาก unsorted list ไปยัง malloc bin ต่าง ๆ
- e/ บังคับลำดับ malloc สุดท้ายเพื่อเปิด small race window ทั้ง 27 จุด
-
กลยุทธ์จับจังหวะสำหรับเวอร์ชันใหม่
- กลยุทธ์ timing แบบอิง feedback ที่ใช้กับเวอร์ชันเก่า ใช้ไม่ได้กับ OpenSSH 9.2p1
- การ parse public key ชุดที่ห้าใช้เวลาประมาณ 10ms ทำให้ large race window กว้างเกินไป
user_specific_delay()ที่เพิ่มเข้ามาใน OpenSSH 7.8p1 ทำให้การตอบสนองล่าช้าได้สูงสุดราว 9ms จึงทำลาย feedback แบบเดิม- กลยุทธ์ใหม่เปรียบเทียบเวลาตอบกลับของ intentional error สองชนิด
- ส่ง packet ที่ทำให้เกิดข้อผิดพลาดก่อนเริ่ม parse public key
- ส่ง packet ที่ทำให้เกิดข้อผิดพลาดหลัง parse public key เสร็จ
- ใช้ส่วนต่างของเวลาตอบกลับทั้งสองเพื่อวัดเวลา parse public key ครั้งสุดท้าย
- ด้วยวิธีนี้สามารถชนะ race condition ได้ที่ค่าเฉลี่ยราว 10,000 ครั้ง
- ภายใต้
MaxStartups=100,LoginGraceTime=120ใช้เวลาเฉลี่ย 3–4 ชั่วโมงเพื่อชนะ race condition - และเนื่องจาก ASLR ต้องใช้เวลาเฉลี่ย 6–8 ชั่วโมงกว่าจะได้ remote root shell
ความคืบหน้าของ exploit บน amd64
- เลือก Rocky Linux 9 เป็นเป้าหมายสำหรับ amd64
- อิมเมจเป้าหมายคือ
Rocky-9.4-x86_64-minimal.iso - OpenSSH 8.7p1 เปราะบางต่อ race condition ใน signal handler นี้
- glibc ถูกแมปที่เป็นพหุคูณของ 2MB เพราะจุดอ่อนของ ASLR ทำให้ partial pointer overwrite มีพลังมากขึ้น
- อิมเมจเป้าหมายคือ
syslog()ของ glibc 2.34 บน Rocky Linux 9 เรียก__open_memstream()ภายใน- มีการ
malloc()โครงสร้าง FILE บน heap - และยังเรียก
calloc(),realloc(),free()เปิดช่องเพิ่มเติม
- มีการ
- จาก heap corruption primitive, โครงสร้าง FILE สองตัวที่ถูกจัดสรรบน heap และบิตคงที่ 21 บิตของที่อยู่ glibc จึงเชื่อว่าสามารถใช้ประโยชน์บน amd64 ได้เช่นกัน
- คาดว่าใช้เวลานานกว่า 6–8 ชั่วโมงของ i386 แต่ยังน่าจะน้อยกว่า 1 สัปดาห์
- ยังมีข้อสังเกตแยกสำหรับ Ubuntu 24.04
- Ubuntu 24.04 ไม่สุ่ม ASLR ใหม่ให้ child ของ
sshdทุกครั้ง แต่สุ่มเพียงครั้งเดียวตอนบูต - ต้นเหตุสืบไปถึง
systemd-socket-activation.patchที่ปิดrexec_flag - โดยทั่วไปถือว่าเป็นทางเลือกที่ไม่ดี แต่สำหรับช่องโหว่นี้กลับขัดขวางการโจมตี เพราะ
syslog()ในSIGALRMhandler ไม่ใช่การเรียกsyslog()ครั้งแรก จึงไม่เรียกฟังก์ชัน malloc - แพตช์ที่เกี่ยวข้อง: https://git.launchpad.net/ubuntu/+source/…
- Ubuntu 24.04 ไม่สุ่ม ASLR ใหม่ให้ child ของ
แพตช์และมาตรการบรรเทา
- OpenSSH แก้ race condition นี้แล้วด้วยคอมมิต
81c1099เมื่อ 6 มิถุนายน 2024- 81c1099: เพิ่มความสามารถให้
sshd(8)ลงโทษ client behavior ที่มีปัญหา - ย้ายโค้ดที่ไม่ปลอดภัยต่อ async-signal ออกจาก
SIGALRMhandler ของsshdไปยัง listener process เพื่อจัดการแบบ synchronous
- 81c1099: เพิ่มความสามารถให้
- การแก้ไขนี้พึ่งพาคอมมิตใหญ่
81c1099และคอมมิต defense-in-depth ที่ใหญ่กว่าอีกตัวคือ03e3de4จึงอาจ backport ได้ยาก - หาก backport ได้ยาก สามารถลบหรือคอมเมนต์โค้ด async-signal-unsafe ใน
sshsigdie()เพื่อให้เหลือเพียงการเรียก_exit(1) - หากอัปเดตหรือคอมไพล์ใหม่ไม่ได้ สามารถตั้งค่า
LoginGraceTimeเป็น0ในไฟล์คอนฟิกได้- การตั้งค่านี้ป้องกันการรันโค้ดจากระยะไกลตาม advisory นี้ได้
- แต่จะยังเปราะบางต่อ DoS จากการทำให้การเชื่อมต่อ
MaxStartupsหมดลง
กำหนดการเปิดเผย
- 2024-05-19: ติดต่อผู้พัฒนา OpenSSH และมีการวนแก้แพตช์กับรีวิวต่อจากนั้น
- 2024-06-20: ติดต่อ
distros@openwall - 2024-07-01: เปิดเผยตาม coordinated release date
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
น่าสนใจที่ดูเหมือนว่าแพตช์แก้ RCE จะถูก “ปะปน” เผยแพร่ต่อสาธารณะไปแล้วเกือบหนึ่งเดือนก่อน
เมื่อเปิดใช้ PerSourcePenalties, sshd(8) จะเฝ้าดูสถานะการจบการทำงานของโปรเซสเซสชันลูกก่อนการยืนยันตัวตน และบันทึกเงื่อนไขอย่างการยืนยันตัวตนล้มเหลวซ้ำ ๆ หรือ sshd แครช เป็นโทษต่อที่อยู่ไคลเอนต์เป็นระยะเวลาหนึ่ง
https://github.com/openssh/openssh-portable/commit/81c1099d2...
ดูค่อนข้างฉลาด เพราะแทนที่จะเป็นแพตช์ที่ทำวิศวกรรมย้อนกลับได้และบอกอะไรบางอย่างแก่ผู้โจมตี มันดูเหมือนมีผลข้างเคียงในการเปลี่ยนโครงสร้างไบนารีเพื่อตัดช่องโหว่เฉพาะตัวหนึ่งออก และยังช่วยบรรเทา ตระกูล exploit ทั้งหมดนั้นด้วย
การเปลี่ยนแปลงข้างต้นเป็นฟีเจอร์ที่เคยประกาศไว้เพื่อจัดการกับการเชื่อมต่อขยะ และเพียงแค่ช่วยบรรเทาช่องโหว่นี้ด้วยการทำให้ชนะ race condition ได้ยากขึ้น
การพูดคุยก่อนหน้านี้: https://news.ycombinator.com/item?id=40610621
สงสัยว่าผู้คนอ่านแค่คอมเมนต์แรกของเธรด กดโหวต แล้วจากไปพร้อมความเข้าใจผิดหรือเปล่า
มีข้อความตอนหนึ่งใน release notes ของ OpenSSH ที่น่าสนใจ
“มีการสาธิตการใช้ประโยชน์สำเร็จบนระบบ Linux/glibc 32 บิตที่เปิด ASLR แล้ว ภายใต้เงื่อนไขในห้องแล็บ การโจมตีต้องรักษาการเชื่อมต่อแบบต่อเนื่องให้ถึงค่าสูงสุดที่เซิร์ฟเวอร์อนุญาตโดยเฉลี่ย 6–8 ชั่วโมง เชื่อว่าเป็นไปได้บนระบบ 64 บิตด้วย แต่ยังไม่ได้พิสูจน์ การโจมตีลักษณะนี้มีแนวโน้มว่าจะถูกปรับปรุงให้ดีขึ้นได้”
https://www.openssh.com/releasenotes.html
เมื่อดู diff [1] ที่ทำให้บั๊กถูกใส่เข้ามา ตามการวิเคราะห์แล้ว ปัญหาคือ
sigdie()เดิมถูกครอบด้วย#ifdef DO_LOG_SAFE_IN_SIGHANDแต่เมื่อรีแฟกเตอร์เป็นsshsigdie()ที่เรียกsshlogv()โดยตรง #ifdef กลับหายไปอะไรจะช่วยป้องกันได้? ควรมีคนดู pull request มากกว่านี้หรือเปล่า? น่าทึ่งที่ซอฟต์แวร์ซึ่งทั้งโลกพึ่งพาเพื่อการเชื่อมต่อที่ปลอดภัย ดูเหมือนว่าถูกดูแลโดยคนสองคน [2] ในทางปฏิบัติ
[1] https://github.com/openssh/openssh-portable/commit/752250caa...
[2] https://github.com/openssh/openssh-portable/graphs/contribut...
ในกรณีนี้ หากมีคอมเมนต์อธิบายว่าทำไมต้องมี
#ifdefก็น่าจะช่วยได้ เช่น “โค้ดตรงนี้ต้อง ปลอดภัยต่อสัญญาณแบบอะซิงโครนัส และสถานะของล็อกอาจไม่แน่นอน” อะไรทำนองนั้นแต่พูดตามตรง
getrlimitก็ไม่ได้อยู่ในรายการนี้เช่นกัน: https://man7.org/linux/man-pages/man7/signal-safety.7.htmlถึงอย่างนั้น ถ้าโค้ดที่มีคอมเมนต์เกี่ยวกับความปลอดภัยต่อสัญญาณแบบอะซิงโครนัสถูกลบหรือแก้ไข ก็มีโอกาสที่จะสะดุดตาในการรีวิว โค้ดที่อ้างถึงมีเพียง
SAFE_IN_SIGHANDเท่านั้นที่พอจะบอกเป็นนัยว่าโค้ดนี้ต้องปลอดภัยเมื่ออยู่ใน signal handlersyslogที่ reentrant และปลอดภัยต่อสัญญาณแบบอะซิงโครนัสแล้ว ดังนั้นผู้เขียนโค้ดนี้อาจแค่สันนิษฐานว่าการเปลี่ยนแปลงนี้ปลอดภัยพวกเขาอาจลืมหรือไม่รู้ว่า บนแพลตฟอร์มอื่นที่นักพัฒนา OpenBSD ssh ไม่ได้อ้างว่าสนับสนุนจริง ๆ นั้น ยังคงใช้ ฟังก์ชันที่ไม่ปลอดภัยต่อสัญญาณแบบอะซิงโครนัส อยู่
คุณไม่มีสิทธิ์โดยชอบธรรมที่จะได้รับอะไรจากนักพัฒนาโอเพนซอร์ส พวกเขาก็ทำผิดพลาดได้ และจะมี maintainer หรือ reviewer กี่คนก็เป็นสิ่งที่พวกเขาตัดสินใจเองได้
https://gist.github.com/richhickey/1563cddea1002958f96e7ba95...
#ifdefโดยไม่มีเหตุผลรองรับเหมือนใน [1]บันทึกประจำรุ่นก็น่าอ่านเช่นกัน: https://www.openssh.com/releasenotes.html
จริง ๆ แล้วนี่เป็นรูปแบบหนึ่งของ บั๊ก race condition ของสัญญาณ ที่น่าสนใจ ตามรายงานช่องโหว่ระบุว่า “OpenBSD ไม่ได้มีช่องโหว่เป็นพิเศษ เพราะ handler ของ SIGALRM เรียก
syslog_r()ซึ่งเป็นเวอร์ชันของsyslog()ที่ปลอดภัยกว่าสำหรับสัญญาณแบบ asynchronous ที่ OpenBSD สร้างขึ้นในปี 2001”กล่าวคือ มาตรการลดผลกระทบด้านความปลอดภัยของสัญญาณทำให้นักพัฒนา OpenBSD ใส่โค้ดที่ไม่ใช่โค้ดเล็กน้อยไว้ใน signal handler และเมื่อโค้ดนั้นถูกพอร์ตไปยังระบบอื่นก็กลายเป็นไม่ปลอดภัย หากมีการ refactor เพื่อลดโค้ดใน signal handler ให้เหลือน้อยที่สุดตามภูมิปัญญาทั่วไปและธรรมเนียมของโค้ด Unix ก็น่าจะหลีกเลี่ยงบั๊กนี้ได้
เพราะเมื่อเวลาผ่านไป เป็นเรื่องง่ายเกินไปที่จะมีการเรียกที่ไม่ปลอดภัยต่อสัญญาณแบบ asynchronous ปะปนเข้ามาที่ใดที่หนึ่งใน transitively-called functions และก็ไม่ได้ชัดเจนเสมอไปว่าเส้นทางนั้นเข้าถึงได้จากบริบทของสัญญาณ
หลังจากอัปเกรดอินสแตนซ์ OpenSSH ของผมแล้ว ผมเห็นว่ามันลิงก์กับ musl ไม่ใช่ glibc เลยลองดูว่า
syslog(3)ของ musl มีการ allocate ด้วยหรือไม่ และจึงถูก exploit ได้ง่ายในลักษณะเดียวกันหรือเปล่าดูเหมือนจะไม่ใช่: https://github.com/bminor/musl/blob/master/src/misc/syslog.c
สิ่งที่อยู่ในนั้นทั้งหมดคือสแตกหรือ static variables ที่ใช้ล็อกเพื่อป้องกันการ re-enter ส่วนการเรียก
{d,sn,vsn}printf()ใน musl ก็ไม่ได้ allocate แต่ใน glibc อาจทำได้ ผมพลาดอะไรไปไหม?ถึงอย่างนั้น หาก deadlock เกิดใน
sigalrmก็อาจขัดขวางการเคลียร์ connection และนำไปสู่ denial of service ได้มีแพตช์สำหรับ FreeBSD ออกมาแล้ว
ยังไม่ชัดเจนว่าได้รับผลกระทบหรือไม่ การ exploit ที่เป็นที่รู้จักทำได้เฉพาะใน glibc และ FreeBSD ไม่ได้ใช้ glibc แต่เลือกทางปลอดภัยไว้ก่อนดีกว่า
https://www.freebsd.org/security/advisories/FreeBSD-SA-24:04...
ตามรายงาน หากไม่สามารถอัปเดตหรือคอมไพล์ sshd ใหม่ได้ การตั้งค่า
LoginGraceTimeเป็น 0 ในไฟล์คอนฟิกเพียงอย่างเดียวก็สามารถแก้ race condition ของ signal handler นี้ได้ในกรณีนี้ sshd จะเสี่ยงต่อ denial of service ที่ทำให้ connection ทั้งหมดตาม
MaxStartupsถูกใช้จนหมด แต่จะปลอดภัยจาก remote code execution ที่ระบุในคำแนะนำนี้ดังนั้นการตั้ง
LoginGraceTime 0ในsshd_configดูเหมือนจะเป็นการบรรเทาผลกระทบได้ถ้าอย่างนั้นไม่ยิ่งแย่กว่าเดิมหรือ?
มีแพตช์สำหรับ Debian 12 ออกมาแล้ว และ Debian 11 ไม่ได้รับผลกระทบ
https://security-tracker.debian.org/tracker/CVE-2024-6387
apt updateและupgradeบนเซิร์ฟเวอร์ Debian 12 และแพ็กเกจที่ถูกอัปเกรดมีเพียงแพ็กเกจ OpenSSH เท่านั้นเป็นการค้นพบที่ยอดเยี่ยมจริง ๆ
แม้ผมไม่ได้อยู่ในสถานะที่ทำงานด้านนี้โดยตรง แต่ในงานวิจัยความปลอดภัย มักรู้สึกได้ถึงบรรยากาศว่า ถ้าจะ “ชนะ” ไม่ใช่แค่หาปัญหาเดี่ยว ๆ แล้วแก้หรือรับรางวัล แต่ต้องหา ทั้งเชน ที่นำไปสู่การเข้าถึงจากระยะไกลได้
ผมรู้สึกว่าการเจอช่องโหว่เดี่ยว ๆ อย่างเช่น memory corruption หนึ่งจุด หรือ sandbox escape หนึ่งจุด ก็น่าจะเพียงพอแล้วไม่ใช่หรือ ตอนนี้มีปัญหาเล็ก ๆ มากเกินไป จนบางทีถ้าจะให้ผู้คนมองอย่างจริงจังหรือจ่าย bug bounty ก็อาจต้องสาธิตการแฮ็กที่ต่อไปได้จนสุดทาง
ตัวอย่างเช่น หากแอปหนึ่ง crash เมื่อรับอินพุตที่เชื่อถือผิดพลาด แต่โดยลักษณะของแอปนั้นไม่ได้ตั้งใจให้เปิดเผยต่อผู้ไม่หวังดี และในความเป็นจริงก็แทบไม่มีทางเกิดขึ้น คนส่วนใหญ่ก็จะมองว่าเป็นแค่บั๊ก ไม่ใช่บั๊กด้านความปลอดภัย แก้ได้ก็ดี แต่ไม่ใช่ระดับเดียวกัน และเรื่องแบบนี้ก็หาเจอได้ไม่ยากนัก
ดังนั้นจึงจำเป็นต้องแยกแยะระหว่างบั๊กความปลอดภัย “ของจริง” อย่างกรณีนี้ กับบั๊กที่ไม่มีผลกระทบด้านความปลอดภัย และการ พิสูจน์ว่าสามารถนำปัญหาไปใช้โจมตีได้ จึงสำคัญมาก
บั๊กที่ไม่มีผลกระทบด้านความปลอดภัยคงมีได้ไม่รู้จบ ดังนั้นข้อกำหนดให้พิสูจน์แบบนี้คงยังไม่หายไปในเร็ว ๆ นี้
นี่เป็นไปตามการออกแบบ และผู้ใช้สามารถ serialize และ deserialize อะไรก็ได้ รวมถึง lambda function ไลบรารีของผมตั้งใจให้ใช้ประมวลผลข้อมูลจากแหล่งที่เชื่อถือได้เท่านั้น
เท่าที่ผมรู้ ไม่มีใครใช้ไลบรารีนี้เพื่อประมวลผลข้อมูลที่ไม่น่าเชื่อถือ มีไลบรารียอดนิยมตัวหนึ่งใช้ไลบรารีของผมในการอ่านไฟล์ตั้งค่า แต่พวกเขามองว่าไฟล์ตั้งค่าเป็นข้อมูลที่เชื่อถือได้ และการไปควบคุมว่าคนอื่นใช้ไลบรารีของผมอย่างไรก็ไม่ใช่งานของผม
ในกรณีแบบนี้ ควรลงทะเบียน CVE ระดับความสำคัญสูงสุดโดยอ้างว่าโปรเจกต์ของผมมี ช่องโหว่ Remote Code Execution หรือไม่?
รางวัลมักจ่ายให้กับประเภทแรกเสมอ ส่วนรายงานประเภทที่สอง หากไม่มี proof of concept หรือการพิสูจน์ว่านำไปใช้โจมตีได้ บางครั้งกลับอาจทำลายชื่อเสียงหรือสัญญาณความน่าเชื่อถือด้วยซ้ำ
จุดอ่อนที่ยังไม่สามารถนำไปโจมตีได้จนกว่าจะมีเงื่อนไขบางอย่างครบถ้วน แทบจะมีอยู่เสมอ แม้แต่ในการแข่งขันอย่าง Pwn2Own ก็ยังมักเห็นการเชื่อมช่องโหว่หลายจุดเข้าด้วยกันจนยึดอุปกรณ์ได้ในท้ายที่สุด และหลายกรณีก็ยังคงไม่ถูกแพตช์อยู่นานหลายปี นักวิจัยบางคนก็เก็บจุดอ่อนเหล่านั้นไว้นานเพื่อเพิ่มผลกระทบให้มากที่สุด
น่าเศร้า แต่นี่คือความจริง
บันทึกการเผยแพร่ของ OpenSSH: https://www.openssh.com/txt/release-9.8
แพตช์ขั้นต่ำสำหรับผู้ที่ไม่สามารถหรือไม่ต้องการอัปเกรด: https://marc.info/?l=oss-security&m=171982317624594&w=2