WorstFit: เปิดเผย Transformers ที่ซ่อนอยู่ใน Windows ANSI
(blog.orange.tw)- การแปลงอักขระแบบ Best-Fit ของ Windows จะแทนที่สตริง UTF-16 ด้วยอักขระที่ดูคล้ายกันระหว่างแปลงเป็น ANSI code page และพฤติกรรมนี้กลายเป็นพื้นผิวโจมตี WorstFit ที่นำไปสู่ Path Traversal, Argument Injection และ RCE
- ปัญหาเกิดจากโครงสร้างที่ ANSI API, C/C++ runtime, startup code ที่คอมไพเลอร์แทรกให้ และการที่นักพัฒนาใช้ API อักขระแบบไม่ใช่ wide ซ้อนทับกัน โดยเส้นทางอย่าง
GetCommandLineA,GetEnvironmentVariableA,getenv,int main()ได้รับผลกระทบ - CVE-2024-4577 ทำให้ U+00AD ถูกแปลงเป็น
-ใน code page ภาษาจีน/ญี่ปุ่น จึงเลี่ยงแพตช์ของ PHP-CGI ได้ ส่วน Filename Smuggling ทำให้¥,₩และ fullwidth slash ถูกแปลงเป็น/หรือ\จนเกิดความสับสนของพาธ - Argument Splitting สามารถใช้อักขระอย่าง fullwidth double quote หรือสัญลักษณ์ Yen/Won เพื่อสร้างอักขระสำหรับ parsing command line และฉีดอาร์กิวเมนต์เข้าไปในเครื่องมือ CLI เช่น
wget.exe,tar.exe,openssl.exe,java.exeได้ โดยการ escape อาร์กิวเมนต์ทั่วไปของ PHP, Python, Node.js, Rust เพียงอย่างเดียวป้องกันได้ยาก - แนวทางบรรเทาคือเปิดตัวเลือก UTF-8 ของ Windows หรือให้นักพัฒนาใช้ Wide Character API และเส้นทางอักขระแบบ wide เช่น
_wgetcwd,_wgetenv,wmain()และจนกว่า Microsoft จะเปิด UTF-8 เป็นค่าเริ่มต้นใน Windows ทุกเอดิชัน ปัญหาคล้ายกันอาจเกิดซ้ำได้
โครงสร้าง encoding ของ Windows และ Best-Fit
- Windows ในยุคแรกใช้ ANSI code page และ code page จะแตกต่างกันตามกลุ่มภาษา เช่น 1252, 932, 936, 949, 950
- ACP (ANSI Code Page) ใช้กับแอปพลิเคชันและการตั้งค่าระบบส่วนใหญ่ เช่น งานไฟล์และตัวแปรสภาพแวดล้อม
- OEMCP (Original Equipment Manufacturer Code Page) ใช้หลัก ๆ กับการสื่อสารกับอุปกรณ์ เช่น การอ่าน/เขียนคอนโซล
chcpแสดง OEMCP ไม่ใช่ ACP จึงไม่ใช่วิธีตรวจสอบ ACP ซึ่งเป็นประเด็นหลักของงานวิจัยนี้
- Windows เปลี่ยนไปใช้ Unicode ในช่วงกลางทศวรรษ 1990 และ API หลักในปัจจุบันใช้ wide character บนพื้นฐาน UTF-16
- API หลักอย่างระบบไฟล์ ข้อมูลระบบ และการประมวลผลข้อความ ถูกเปลี่ยนไปเป็น wide character API
- แม้จะมีฟังก์ชัน UTF-8 อยู่ แต่ในภาษาส่วนใหญ่ไม่ได้เปิดเป็นค่าเริ่มต้น และบทความอธิบายว่ายังอยู่ในขั้น beta
- ด้วยเหตุผลด้านความเข้ากันได้ย้อนหลัง Windows API จึงมีทั้งเวอร์ชัน ANSI และ Unicode
- ANSI API มี suffix
Aเช่นGetEnvironmentVariableA - Unicode API มี suffix
Wเช่นGetEnvironmentVariableW - เมื่อมีการเรียก ANSI API Windows จะแปลงสตริง UTF-16 ภายในเป็นสตริง ANSI ด้วย
RtlUnicodeStringToAnsiStringหรือWideCharToMultiByte
- ANSI API มี suffix
Best-Fit กลายเป็น WorstFit ได้อย่างไร
- Best-Fit คือพฤติกรรมที่แมปอักขระ UTF-16 ไปเป็นอักขระที่ดูคล้ายหรือให้ความรู้สึกคล้ายกัน เมื่อไม่สามารถแทนอักขระนั้นใน ANSI code page ปลายทางได้อย่างถูกต้อง
- ตัวอย่างเช่น ใน Windows-1252
∞U+221E จะถูกแมปเป็น8 - เมื่อ
√π⁷≤∞ผ่าน ANSI API อาจกลายเป็น"vp7=8"
- ตัวอย่างเช่น ใน Windows-1252
- การแมปทำงานแตกต่างกันไปในแต่ละ code page
¥U+00A5 ถูกแมปเป็น\ใน code page ญี่ปุ่น 932- ใน code page ยุโรปกลาง 1250 จะถูกแมปเป็น
Y - ใน code page อื่นส่วนใหญ่จะไม่เปลี่ยนแปลง
- การแปลงแบบเดียวกันเกิดขึ้นไม่เพียงกับการเรียก Windows API โดยตรง แต่ยังเกิดในฟังก์ชัน CRT และเส้นทางของฟังก์ชัน
mainทั่วไปด้วย- ฟังก์ชัน CRT แบบ non-wide เช่น
getenvจะถูกใช้การแปลง Best-Fit - กรณีรับอาร์กิวเมนต์และตัวแปรสภาพแวดล้อมในรูปแบบ
int main(int argc, char* argv[], char* envp[])ก็มีการแปลงเข้ามาเกี่ยวข้องเช่นกัน - เพราะ startup code ของ CRT ที่คอมไพเลอร์แทรกให้ทำงานร่วมกับการใช้ ANSI Windows API
- ฟังก์ชัน CRT แบบ non-wide เช่น
- ตรวจสอบการแมปได้จาก Best-fit Mapping Grepper และ ข้อมูลแมปดิบ WindowsBestFit ของ Unicode.org
กรณี WorstFit แรก: PHP-CGI CVE-2024-4577
- CVE-2024-4577 เป็นกรณีโจมตี WorstFit ที่ทำให้เซิร์ฟเวอร์ PHP-CGI ซึ่งตั้งค่าเป็น code page ภาษาจีน/ญี่ปุ่นถูกเจาะได้ด้วยคำขอเพียง
?%ADs- code page ที่ได้รับผลกระทบคือ 932 (ญี่ปุ่น), 936 (จีนตัวย่อ), 950 (จีนตัวเต็ม)
- อักขระที่เป็นภัยคือ
U+00AD
- ช่องโหว่ PHP-CGI ปี 2012 เป็น argument injection ที่เกิดจาก Apache ประมวลผล query string เป็นอาร์กิวเมนต์แรกของโปรแกรม CGI โดยอัตโนมัติ
- หากเติม
?-sจะทำให้รั่วไหลซอร์สโค้ดของหน้าเว็บและทำ RCE ได้ - แพตช์ของ PHP ใช้วิธีหยุด parsing อาร์กิวเมนต์เมื่อ query string เริ่มต้นด้วย dash
- หากเติม
- เพราะ Best-Fit ทำให้ soft hyphen U+00AD ถูกแปลงเป็น
-ใน code page ภาษาจีน/ญี่ปุ่น จึงเลี่ยงแพตช์เดิมได้?%ADsอาจทำงานเหมือน-sในมุมมองของ PHP-CGI- กรณีนี้ทำให้ทีมวิจัยได้พบคำว่า Best-Fit เป็นครั้งแรก
Filename Smuggling: ปัญหาที่อักขระพาธถูกแปลง
- Filename Smuggling คือการโจมตีที่อักขระ Unicode ในชื่อไฟล์ถูกแปลงเป็น
/หรือ\บนเส้นทาง ANSI API จนอาจสร้าง path traversal ได้- API ที่เกี่ยวข้องได้แก่
GetCurrentDirectoryA,getcwd,FindFirstFileA,findfirst*,GetFullPathNameAเป็นต้น - code page ที่ได้รับผลกระทบคือ 874, 125x, 932 (JP), 949 (KR)
- อักขระที่เป็นภัยคือ
/U+FF0F,\U+FF3C,¥U+00A5 (JP),₩U+20A9 (KR)
- API ที่เกี่ยวข้องได้แก่
d8.exeซึ่งเป็น Developer Shell ของ Chrome V8 ใช้GetCurrentDirectoryA()ในการดึงไดเรกทอรีทำงานปัจจุบันในการทำงานภายใน- หากสามารถสร้างไดเรกทอรีทำงานที่มีอักขระ Unicode อันตรายได้ เมื่อเข้าถึงผ่าน ANSI API จะถูกแปลงเป็น payload สำหรับ path traversal
- ตัวอย่างเช่นสามารถเข้าถึง
C:/windows/win.iniโดยไม่ตั้งใจได้
- การใช้งาน Windows ของ
Dir.getwd()ใน mruby พึ่งพาฟังก์ชัน ANSI CRT_getcwd()- ค่าที่ส่งคืนอาจถูกปนเปื้อน และนำไปสู่ Path Traversal ได้
Cuckoo Sandbox: จาก Path Traversal ไปจนถึง RCE
- การเข้าถึงระบบไฟล์ Windows ของ Python เคยสามารถใช้ wide API หรือ ANSI API ได้ ขึ้นอยู่กับว่าสตริงเป็น wide หรือ narrow
- หลังจาก PEP 529 การเข้ารหัสระบบไฟล์ Windows ถูกทำให้เป็นมาตรฐานเป็น UTF-8
- Python 2 และ Python 3 ก่อนเวอร์ชัน 3.6 ยังคงอยู่ในสถานะที่เสี่ยงต่อการโจมตี WorstFit
- Cuckoo Sandbox เป็นแพลตฟอร์มวิเคราะห์มัลแวร์แบบอัตโนมัติ และเวอร์ชันทางการล่าสุดยังพึ่งพา Python 2.7
- Cuckoo ประกอบด้วย Cuckoo Host และ VM Cluster
- ตัวอย่างที่อัปโหลดจะถูกเรียกใช้งานแบบแยก isolated ใน VM และซิงก์แพ็กเก็ตเครือข่าย ไฟล์ที่ถูก drop และ log ด้วยกลไกของตัวเอง
- หากมัลแวร์สร้างไฟล์ drop ที่มีชื่อไฟล์เป็น Unicode อาจเกิด Path Traversal ในการประมวลผล path ของ Python บน Cuckoo Host ได้
- ตัวอย่าง PoC สร้าง path
AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf - หลังการวิเคราะห์จบลง เมื่อผู้ใช้กดปุ่มดาวน์โหลดในเว็บอินเทอร์เฟซ จะ trigger การทำงานกับไฟล์ของ Python
- Cuckoo Host อาจประมวลผล path ที่ผ่านการแปลงและมี
../อยู่ ทำให้ส่งข้อมูลอ่อนไหวให้ผู้โจมตีได้
- ตัวอย่าง PoC สร้าง path
- ผู้โจมตีสามารถดาวน์โหลด
cuckoo.confและรวบรวมข้อมูลอ่อนไหวที่จำเป็นต่อการคำนวณ Flask PIN เพื่อทำ RCE บน Sandbox Host ได้- วิดีโอสาธิตมีให้ดูที่ Video 11
Argument Splitting: Best-Fit ที่เปลี่ยนการ parse บรรทัดคำสั่ง
- Argument Splitting คือการโจมตีที่สตริงบรรทัดคำสั่งถูกเปลี่ยนจนทำให้อาร์กิวเมนต์ถูกแยกออก ในเส้นทางของเอาต์พุต
GetCommandLineAหรือ non-Unicodeint main()- API และเส้นทางที่เกี่ยวข้องคือ
GetCommandLineA,int main() - code page ที่ได้รับผลกระทบคือ 874, 125x, 932(JP), 949(KR)
- อักขระที่เป็นภัยคือ
"U+FF02,\U+FF3C,¥U+00A5(JP),₩U+20A9(KR)
- API และเส้นทางที่เกี่ยวข้องคือ
- ตัวอย่างโค้ด PHP ใช้
escapeshellarg()ครอบ URL อย่างปลอดภัยแล้วเรียกwget.exe -qแต่ด้วยอินพุต" --use-askpass=calc "สามารถทำให้calc.exeรันได้- อินพุตเดียวกันนี้ แม้เปลี่ยนเป็น Node.js, Rust, Python ก็ไม่สามารถป้องกันได้
- ใช้ได้แม้ในตัวอย่าง
subprocess.run(["wget", "-q", ...])ของ Python เวอร์ชันล่าสุด
- Windows ส่งบรรทัดคำสั่งทั้งหมดให้โปรเซสใหม่เป็นสตริงเดียว และไฟล์ executable จะ parse เองโดยตรง
- ไม่ใช่โครงสร้างที่ส่งอาร์เรย์ของอาร์กิวเมนต์เสมอเหมือนระบบตระกูล UNIX
- API
CreateProcessรับพารามิเตอร์lpCommandLineโดยตรง
- อักขระสำคัญในการ parse บรรทัดคำสั่งทั่วไปคือ whitespace และ tab, double quote, backslash
- whitespace และ tab จะแยกอาร์กิวเมนต์เมื่อไม่ได้อยู่ใน quote mode
"ใช้สลับ quote mode\ใช้ escape double quote และ backslash ในลำดับบางแบบ
- standard library ของภาษาส่วนใหญ่จะ escape อาร์กิวเมนต์ผู้ใช้ให้ตรงตามกฎนี้ แต่การ escape สิ้นสุดก่อนการแปลง Best-Fit
- PHP
escapeshellargเปลี่ยน double quote เป็น whitespace ครอบอาร์กิวเมนต์ด้วย quote และจัดการ backslash - Python
subprocessใช้list2cmdlineเพื่อ escape ตามกฎการ parse บรรทัดคำสั่งของ Microsoft CRT - หลังจากนั้น หากในการแปลง ANSI
"U+FF02 ถูกเปลี่ยนเป็น"U+0022 โครงสร้างบรรทัดคำสั่งเดิมก็จะเปลี่ยนไป
- PHP
- โปรแกรมที่ใช้เพียง
int main()ก็อาจมีช่องโหว่ได้- compiler สร้าง
mainCRTStartupไว้ใน binary และฟังก์ชันเริ่มต้นนี้เชื่อมกับ CRT library - หากภายใน CRT ใช้ ANSI API เพื่อดึงบรรทัดคำสั่งมา parse การแปลง Best-Fit จะเข้ามาเกี่ยวข้อง
- ด้วยพฤติกรรมนี้ จึงยากที่จะป้องกันการโจมตีได้สมบูรณ์ด้วย standard library ของภาษาโปรแกรมบางภาษาเพียงอย่างเดียว
- compiler สร้าง
กรณีจริงของ Argument Splitting
- ElFinder เป็นตัวจัดการไฟล์บนเว็บแบบโอเพนซอร์สที่ใช้แบ็กเอนด์ PHP และรองรับ Windows server รวมถึงการสร้าง/แตกไฟล์บีบอัดเป็นค่าเริ่มต้น
- การจัดการ archive ถูก implement ด้วยการรัน shell command และอาร์กิวเมนต์ถูก escape ด้วย
escapeshellarg - การจัดการรูปแบบ tar ใช้
tar.exeที่มาพร้อม Windows - สามารถฉีดอาร์กิวเมนต์
--use-compress-programด้วยชื่อไฟล์ tar เช่นaaa" "--use-compress-program=calc" "bbb.tarเพื่อรันคำสั่งตามอำเภอใจได้ - เดโมอิงตาม Windows server ที่ตั้งค่าเป็นภาษาอังกฤษและ Code Page 1252 และสรุปว่าควรใช้ได้กับ code page 125x และ Code Page 874 ด้วย
- วิดีโอสาธิตมีให้ดูที่ Video 12
- การจัดการ archive ถูก implement ด้วยการรัน shell command และอาร์กิวเมนต์ถูก escape ด้วย
- กรณี
plink.exeที่ถูกปรับแก้ซึ่งใช้ใน TortoiseGit สามารถ trigger การรันโค้ดได้เมื่อใส่ URI อันตรายในอินพุต clone- ดูรายละเอียดได้ใน curated list
- วิดีโอสาธิตคือ Video 13
- RStudio รองรับการควบคุมเวอร์ชัน SVN และหากมีโปรเจกต์ SVN อยู่ในโฟลเดอร์ที่สร้างขึ้นอย่างประสงค์ร้าย ก็สามารถรันเครื่องคิดเลขได้ด้วยการคลิกเพียงครั้งเดียว
- ดูรายละเอียดได้ใน curated list
- วิดีโอสาธิตคือ Video 14
- กรณี Microsoft Excel คือ CVE-2024-49026 ซึ่งผสาน Argument Splitting กับฟีเจอร์ “Open-With” ของ Windows
- Windows เก็บ handler table แยกตามนามสกุลไฟล์ และสามารถตรวจสอบได้ด้วย
ftypeและassoc - ชื่อไฟล์จะกลายเป็นส่วนหนึ่งของอาร์กิวเมนต์ของโปรแกรม handler จึงสามารถใช้ชื่อไฟล์เพื่อโจมตีได้
- ทำให้เกิด argument injection ต่อ
Excel.exeด้วยชื่อไฟล์ที่เปลี่ยน dot, slash, backslash, double quote เป็นรูปแบบ fullwidth - ตัว Excel เองไม่มีอาร์กิวเมนต์ที่เหมาะต่อการ exploit เพิ่มเติม จึงใช้ NTLM Relay และ RBCD/ADCS ร่วมกันเพื่อทำ RCE
- วิดีโอสาธิตมีให้ดูที่ Video 15
- Windows เก็บ handler table แยกตามนามสกุลไฟล์ และสามารถตรวจสอบได้ด้วย
ความสับสนของ Environment Variable
- Environment Variable Confusion เกิดขึ้นเมื่อ
GetEnvironmentVariableA,GetEnvironmentStringsA,char *getenv()ส่งคืน environment variable ในเวอร์ชันที่ผ่านการแปลงแบบ Best-Fit- ไม่ได้ระบุ code page ที่ได้รับผลกระทบและอักขระที่เป็นภัยคุกคามอย่างเฉพาะเจาะจง
- ในกรณีของ Apache HTTPd เกี่ยวข้องกับ 0x00-0xFF
- เพื่อให้การโจมตีนี้เกิดขึ้นได้ environment variable ต้องถูกควบคุมโดยผู้ใช้ได้
- รวมถึงกรณีที่โปรเซสแม่ส่งข้อมูลไปยังโปรเซสลูกที่สร้างขึ้น
- ใน CGI ข้อมูลจำนวนมากของคำขอ HTTP เช่น query string และ HTTP header จะถูกส่งต่อเป็น environment variable
- ตัวอย่างการหลบเลี่ยง WAF กล่าวถึงสถานการณ์ที่ CGI script ทำงานเหมือน routing service
- การตั้งค่า Apache มี rule ที่ปฏิเสธ
REQUEST_URIที่มี/adminเพื่อป้องกันการเข้าถึงระยะไกลไปยัง/cgi.pl/admin - เนื่องจากพฤติกรรม WorstFit ของ Windows Perl หากเปลี่ยนบางส่วนของ
adminให้เป็น Best-Fit equivalent ก็สามารถหลบเลี่ยงได้ - ใน Code Page 1250 อักขระ
àU+00E0 จะถูกแปลงเป็นaระหว่างการแปลง ANSI - คำขอ
/cgi.pl/%E0dminจะถูกมองเป็น path อื่นใน rule ฝั่งเซิร์ฟเวอร์ แต่เมื่อ Perl CGI script อ่านPATH_INFOผ่าน ANSI API จะถูกประมวลผลเป็น/admin
- การตั้งค่า Apache มี rule ที่ปฏิเสธ
- ใน PHP-CGI บน Windows พบ file existence oracle และ LFI ที่อาจเกิดขึ้นได้ในบางการตั้งค่า
- สาเหตุคือวิธีจัดการ
PATH_INFOและ environment variable อื่น ๆ ที่เกี่ยวข้องกับ path - คำขอ
/index.php/foo/barจะถูกส่งต่อเป็น environment variable เช่นREDIRECT_URL,REQUEST_URI,PATH_INFO,PATH_TRANSLATEDตามมุมมองของ Apache - ด้วยข้อมูลเพียงเท่านี้ ยากที่จะแยกขอบเขตระหว่างชื่อไฟล์ PHP กับ
PATH_INFOเพิ่มเติมได้อย่างชัดเจน และphp-cgi.exeจะเป็นผู้ตีความสิ่งนี้
- สาเหตุคือวิธีจัดการ
- หากใช้
¥ใน Japanese code page การตีความ path ของเว็บเซิร์ฟเวอร์กับ PHP-CGI จะแตกต่างกัน- เว็บเซิร์ฟเวอร์จะประมวลผล
/..¥..¥windows/win.ini/fooทั้งหมดเป็นPATH_INFOเพิ่มเติม - PHP-CGI ได้รับค่าที่ถูกแปลงมา เช่น
REQUEST_URI=/index.php/..\..\windows/win.ini/fooและเกิดความสับสนในขั้นตอนแยกไฟล์ PHP จริงออกจากPATH_INFO - ใน Apache สามารถทำ file existence oracle ได้จากความแตกต่างของ response ระหว่างไฟล์ที่ไม่มีอยู่กับไฟล์ที่มีอยู่
- ใน IIS หากตั้งค่า directive
doc_rootไว้ สามารถทำ LFI เพื่อ include และอ่านC:\Windows\win.iniผ่าน path อย่าง/index.php/..¥..¥..¥windows/win.ini/ได้ - หากไฟล์ที่ถูก include สามารถรันได้หรือมีโค้ดที่ผู้ใช้ควบคุมได้ อาจนำไปสู่ RCE ได้ แต่ scenario ดังกล่าวถูกจัดว่าใกล้เคียงกับ bug ที่พบได้ยากในแอปพลิเคชันจริง
- เว็บเซิร์ฟเวอร์จะประมวลผล
ความยากลำบากในกระบวนการเปิดเผยและแก้ไข
- ทีมวิจัยรายงานปัญหาหลายรายการในภาษาโปรแกรม, โครงการโอเพนซอร์ส และโปรแกรม CLI ที่มากับ Windows ไปยัง upstream maintainer แต่ละราย
- ประเด็นที่ถกเถียงกันมากที่สุดเกิดขึ้นใน Argument Splitting
- vendor บางรายมองว่าการส่ง user input เข้า command line เองเป็นช่องโหว่
- ความไม่ชัดเจนเรื่องผู้รับผิดชอบก็เป็นปัญหาเช่นกัน
- โค้ดที่เป็นปัญหาครอบคลุมทั้ง
mainCRTStartup()ที่ถูกแทรกอัตโนมัติระหว่างการ compile และการเรียก ANSI API ภายใน MSVCRT/UCRT - แยกได้ยากว่าเป็นปัญหาที่ developer ไม่ใช้
wmain()หรือเป็นปัญหาที่ CRT แบ่ง command line ผิดและส่ง argument ที่ผิดไปยังmain() - บางโครงการให้เฉพาะ source code ส่วน Windows prebuilt executable นั้นเผยแพร่โดยอาสาสมัครบุคคลที่สามบนอินเทอร์เน็ต
- โค้ดที่เป็นปัญหาครอบคลุมทั้ง
- การแก้ไขไม่ใช่แค่เปลี่ยน
main()เป็นเวอร์ชัน wide-character เท่านั้น- เมื่อ function signature เปลี่ยน ต้องเขียนนิยามตัวแปรและตรรกะการ parse argument ใหม่จากฐาน
char *เป็นwchar_t * - กระบวนการนี้ยุ่งยากและเกิดข้อผิดพลาดได้ง่าย
- เมื่อ function signature เปลี่ยน ต้องเขียนนิยามตัวแปรและตรรกะการ parse argument ใหม่จากฐาน
- Curl ตอบว่าเป็นฟีเจอร์ของ Windows และไม่มีแผนแก้ไข ส่วน Curl ที่ Microsoft port มาได้แก้ entry เป็น
wmain()แล้ว ทำให้curl.exeที่มากับ Windows ไม่ได้รับผลกระทบ- binary official build ของ Curl ได้รับผลกระทบจากการโจมตี Argument Splitting
- รายงานฉบับเต็มเผยแพร่ที่ HackerOne
- OpenSSL สามารถประมวลผล argument ในรูปแบบ wide character ได้ด้วย environment variable
OPENSSL_WIN32_UTF8- เดิมมีจุดประสงค์เพื่อแก้ปัญหาการแสดงผล UTF-8 ใน UI แต่ก็ช่วยบรรเทาการโจมตี Argument Splitting ด้วย
- ในการใช้งาน OpenSSL ทั่วไป developer มักไม่รู้ว่าต้องตั้งค่า environment variable นี้ และสามารถเกิดการรันโค้ดตามอำเภอใจผ่าน argument
-engineได้
- distribution ทางการของ Perl ไม่ได้ให้ Windows prebuilt executable และมักใช้ installer จาก third-party เช่น Strawberry Perl และ ActiveState Perl
- ทั้งสอง distribution ได้รับผลกระทบจากการโจมตี Argument Splitting
- หลังหารือกับ Perl maintainer ได้ข้อสรุปว่า “ใกล้เคียงกับ bug ของ Microsoft มากกว่า bug ของ Perl” และปัจจุบันยังไม่ได้รับการแก้ไข
- มีรายงานถึง Microsoft ผ่าน MSRC 3 รายการ และทั้งหมดถูกปฏิเสธในตอนแรกเพราะไม่ถึงเกณฑ์ความรุนแรง
- หลังเปิดเคสซ้ำหลายครั้ง มีเพียงกรณี Excel ที่ได้รับการยอมรับหลังลองครั้งที่สาม
- กรณีอื่น ๆ ยังไม่ได้รับการแก้ไขจนถึงปัจจุบัน
- MSRC ตอบว่าเป็นการพึ่งพาช่องโหว่ที่แอปพลิเคชันแยกต่างหากนำ input ที่ไม่น่าเชื่อถือใส่ใน command line แล้วรัน และ technique ที่ทำให้การโจมตีนี้เป็นไปได้เองไม่เข้าเงื่อนไขของช่องโหว่
- ได้ขอความช่วยเหลือจาก CERT/CC ด้วย และไม่กี่เดือนต่อมา Microsoft ได้เพิ่ม คำเตือนด้านความปลอดภัย ในเอกสาร
GetCommandLineA- คำเตือนถูกเพิ่มเฉพาะใน
GetCommandLineAเท่านั้น และยังมี ANSI API อื่น ๆ ที่ต้องระวังเหลืออยู่
- คำเตือนถูกเพิ่มเฉพาะใน
เป้าหมายที่ได้รับรายงานว่ามีผลกระทบและสถานะ
- รายการที่ได้รับการยืนยันและรายงานระหว่างกระบวนการเปิดเผยมีดังนี้
- 2024/05/07: PHP
php-cgi.exe— CVE-2024-4577 - 2024/06/13: Curl Official Build — Won’t Fix
- 2024/06/13: Apache Subversion
svn.exe— CVE-2024-45720 - 2024/06/16: Microsoft Tar
tar.exe— Won’t Fix - 2024/06/19: Microsoft Excel
excel.exe— CVE-2024-49026 - 2024/06/19: Microsoft PhoneBook
rasphone.exe— Won’t Fix - 2024/06/19: Oracle Java
java.exe— Pending Fix - 2024/06/19: Perl
perl.exe— Won’t Fix - 2024/07/15: Perforce
p4.exe— CVE-2024-8067 - 2024/08/05: PostgreSQL
psql.exe— Won’t Fix - 2024/08/08: Putty
plink.exe— Fixed - 2024/08/19: OpenSSL
openssl.exe— Other - 2024/08/19: wkhtmltopdf
wkhtmltopdf.exe— EOL - 2024/08/19: GNU Wget — No Reply
- 2024/05/07: PHP
มาตรการบรรเทาและพื้นผิวการโจมตีที่ยังเหลืออยู่
- การโจมตี WorstFit เป็นปัญหาในระดับระบบปฏิบัติการ ดังนั้นจนกว่า Microsoft จะเปิดใช้ UTF-8 เป็นค่าเริ่มต้นใน Windows ทุกเอดิชัน ปัญหาลักษณะเดียวกันอาจยังกลับมาเกิดซ้ำได้
- สิ่งที่ผู้ใช้ทำได้คือ ตรวจสอบและเปิดใช้ ตัวเลือก UTF-8 ของ Windows
- ฟีเจอร์นี้ยังแสดงว่าอยู่ในขั้น beta และยังไม่แน่ชัดว่าจะมีผลข้างเคียงหรือไม่
- นักพัฒนาควรใช้ Wide Character API ให้มากที่สุดเท่าที่เป็นไปได้
- CRT ก็มีเวอร์ชัน wide character เช่น
_wgetcwd,_wgetenvให้ใช้งานเช่นกัน - หากยังใช้เส้นทางแบบ non-wide ต่อไป การทำงานภายในอาจเรียก ANSI API และอาจเปิดให้ถูกโจมตีแบบ WorstFit ได้
- CRT ก็มีเวอร์ชัน wide character เช่น
- เนื่องจากความเข้ากันได้ย้อนหลังของ Windows อาจยังมีจุดอื่นที่ซ่อนการใช้ ANSI API อยู่
- ตัวอย่างเช่น การ query Windows Registry อย่าง
RegQueryValueAอาจได้รับผลกระทบ แต่จำเป็นต้องค้นหาสถานการณ์ที่เป็นช่องโหว่ - ทีมวิจัยยังสังเกตเห็นพฤติกรรม Best-Fit ใน Active Directory ด้วย
- ตัวอย่างเช่น การ query Windows Registry อย่าง
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
นี่เป็นปัญหาที่ค่อนข้างยุ่งยาก การแมปโค้ดแบบ “best fit” ของ Microsoft เป็นตัวแมปที่เปลี่ยน Unicode ช่วงกว้างให้เป็น ASCII ซึ่งเปิดเผยต่อสาธารณะ แต่โดยพื้นฐานแล้วเป็นการแมปแบบ “ตามความรู้สึก” และฝังอยู่ทั่วทั้งระบบ
ตัวแมปนี้ถูกลิงก์เป็นค่าเริ่มต้นในที่จำนวนมาก และจากมุมมองของ Microsoft เรื่องความเข้ากันได้ย้อนหลัง ก็ดูเหมือนว่าจะต้องรวมไว้ต่อไป การโจมตีส่วนใหญ่มักเกิดจาก code point แปลก ๆ ที่ถูกแมป “ตามความรู้สึก” ไปเป็นสแลช ไฮเฟน หรือเครื่องหมายอัญประกาศ ภายในภาษาโปรแกรมสมัยใหม่จะถูกตรวจสอบเป็น Unicode ที่ถูกต้อง แต่เมื่อส่งต่อไปยังคำสั่งเชลล์หรือ Win32 API หลังจากส่งมอบการควบคุมไปแล้ว มันจะถูก ย่อแปลงในอีกแบบหนึ่ง อย่างที่ผู้ดูแล curl พูดไว้ ตรงนี้ “curl เป็นเหยื่อ” แต่คำถามคือผู้ร้ายคือใคร ถ้าเซิร์ฟเวอร์บดขยี้อินพุตผู้ใช้ไม่เหมือนกันระหว่างตอนตรวจสอบกับตอนส่งให้ไลบรารีระบบ สุดท้ายก็จะเกิดปัญหา ตัวเลือกให้ปิดการแปลงแบบ best fit ฝั่ง Win32 อาจเป็นทางแก้ได้ แต่ผมไม่ใช่ผู้เชี่ยวชาญ Windows จึงเป็นแค่การคาดเดา ถึงทำแบบนั้น ก็ยังต้องมีปฏิสัมพันธ์กับ API ทางการหรือซอฟต์แวร์ที่ยังไม่ได้ปิดอยู่ดี
"w"ไม่ใช่"a"วิธีนี้ถ้าใส่คำนำหน้า"\\?\"หรือกำหนด manifest ให้ถูกต้อง ก็จะแก้ปัญหา path ยาวเกิน 260 ตัวอักษรไปพร้อมกันด้วย ทำได้มาตั้งแต่ Windows XP และถูกแนะนำมาโดยตลอดผมไม่ค่อยเข้าใจว่าทำไม API ที่ไม่ใช่ Unicode ยังถูกใช้กันแพร่หลายขนาดนี้ นึกภาพไม่ออกว่าเป็นเพราะอยากรองรับ Windows 98 หรือ Windows 2000
อีกอย่างที่ต้องมีคือ linting แบบหนึ่ง โดยปกติแล้วแอปพลิเคชันสมัยใหม่ไม่มีเหตุผลต้องเรียกฟังก์ชัน ANSI WinAPI อาจมีแนวทางตั้งค่า locale เป็น UTF-8 แล้วใช้เฉพาะฟังก์ชัน 8 บิต แต่ไม่รู้ว่าทำงานได้ดีแค่ไหน เท่าที่รู้ยังมีการตั้งค่าและ header บางอย่างที่ทำให้
argv,printf,std::coutทำงานเป็น UTF-8 และใช้เฉพาะฟังก์ชันแปลง UTF-8/UTF-16 สำหรับ WinAPI โดยไม่ผ่านการแปลงประหลาด ๆ Microsoft ควรจัดทำเอกสารขั้นตอนแบบนี้ไว้ในที่เดียวเรื่องนี้คาดเดาได้ในระดับหนึ่ง แต่ถึงผมจะเคยทำงานพัฒนา Windows และแฮ็ก Wine API อยู่ราว 10 ปีในช่วงที่ความสับสนระหว่าง W/A เกิดขึ้น มันก็ยังเป็นเรื่องใหม่สำหรับผม
Windows เหมือนเกมไพ่ Munchkin ตรงที่เมื่อฟีเจอร์หลายอย่างบังเอิญเข้ามาประกอบกัน ก็อาจรวมตัวเป็น exploit ที่สุ่มและทรงพลังอย่างไม่น่าเชื่อได้ เป็นเรื่องน่ายินดีที่กำลังเปลี่ยนซับซิสเต็ม ANSI ให้เป็น UTF-8 และในทางทฤษฎีก็น่าจะบรรเทาปัญหาแบบนี้ได้มาก สงสัยเหมือนกันว่าทีม Rust จะต้องแก้ไข อีกครั้ง ใน API สำหรับสร้างโปรเซสหรือไม่
แน่นอนว่า Rust ไม่สามารถควบคุมสิ่งที่เกิดขึ้นข้ามขอบเขตโปรเซสได้ ถ้าแอปพลิเคชันที่ Rust รันใช้ ANSI API ปัญหาก็จะเกิดฝั่งนั้น แต่นั่นเป็นความรับผิดชอบของแอปพลิเคชันนั้น
“การค่อย ๆ เลิกใช้ ANSI และแนะนำให้ใช้ Wide Character API” ถ้าจำไม่ผิด เป็นจุดยืนทางการของ Microsoft มาตั้งแต่ NT 3.5
น่าเสียดายที่อุปสรรคใหญ่ข้อหนึ่งคือวิธีที่ไลบรารี runtime C/C++ ของ Microsoft อย่าง
msvcrt.dllถูก implement ฟังก์ชัน wide ที่ไม่ใช่มาตรฐานอย่าง_wfopen(),_wgetenv()จะใช้ฟังก์ชัน W ของ Win API ภายใน แต่ฟังก์ชัน narrow มาตรฐานอย่างfopen(),getenv()กลับใช้ฟังก์ชัน A โดยตรง แทนที่จะแปลงไปใช้เวอร์ชัน wide และฟังก์ชัน A มักไม่รายงานความล้มเหลวในการแปลง Unicode แต่กลบด้วยวิธี best-fit คนที่พอร์ตซอฟต์แวร์ที่เขียนด้วย C มายัง Windows มักไม่อยากเปลี่ยนการใช้ฟังก์ชันมาตรฐานทั้งหมดไปเป็นฟังก์ชันของ Microsoft ที่พกพาไม่ได้ จากจุดนั้นก็แทบเท่ากับต้องเขียนใหม่ทั้งระบบactiveCodePageเป็น UTF-8 ใน manifest ของแอปพลิเคชัน แล้วใช้เฉพาะฟังก์ชัน “ANSI”#defineฟังก์ชันมาตรฐานอย่างmainและfopenให้เป็นฟังก์ชันคู่ wideแบบนี้จะใช้
char*และ string literal แบบไม่ตกแต่งตรง ๆ ไม่ได้ จึงกำหนดชนิดtcharซึ่งเป็นcharบน Linux และเป็นwchar_tบน Windows รวมถึงมาโคร_T()สำหรับ string literal โดยรวมแล้วมันทำงานได้ดีโดยไม่ต้องคิดมาก-Aขึ้นมาก่อนเสมอ ไม่ใช่ตัวแปร-Wไม่รู้ว่าในrobots.txtมีอะไรแปลก ๆ หรือเปล่า แต่ API ที่แนะนำให้ใช้ตัวแปร-Wสำหรับโค้ดใหม่ กลับคืนค่า API legacy เป็นค่าเริ่มต้นนี่มันแปลกmsvcrt.dllถูกแทนที่ด้วย Universal C Runtime (UCRT)[1] แล้ว และ UCRT ก็สอดคล้องกับ C99มีสองวิธีในการบังคับให้ code page “Ansi” เป็น UTF-8 จริง ๆ ในแอปพลิเคชันที่เขียนเองหรือ EXE ที่แพตช์แล้ว
วิธีหนึ่งคือใช้ไฟล์ manifest ซึ่งทำงานได้ตั้งแต่บาง build ของ Windows 10 เป็นต้นมา สามารถใช้กับ EXE ใด ๆ หลัง build ได้ด้วย จึงยัดการรองรับ UTF-8 เข้าไปในโปรแกรมได้ เหมาะเป็นพิเศษกับโปรแกรมโหมดคอนโซล อีกวิธีคือใช้แฮ็กแบบที่เครื่องมือแนว “App Locale” ใช้ วิธีหนึ่งเกี่ยวข้องกับการเรียกฟังก์ชันที่ไม่ได้มีเอกสารของ NTDLL ไม่แน่ใจว่าต้องใช้ฟังก์ชันใดกันแน่ แต่
RtlInitNlsTablesและRtlResetRtlTranslationsอาจเกี่ยวข้องไม่แน่ใจว่า Microsoft จะเปิดใช้ UTF-8 เป็นค่าเริ่มต้นใน Windows ทุกเอดิชันหรือไม่ เพราะมีแอปพลิเคชันเก่าจำนวนมากที่สมมติว่าใช้ code page เฉพาะ หรือ 1 ไบต์ต่ออักขระ ซึ่งอาจพังได้
ที่ละเอียดอ่อนกว่านั้นคือมีแอปพลิเคชันที่นำบัฟเฟอร์เดิมกลับมาใช้ซ้ำ โดยสมมติว่าเมื่อแปลงจาก wide character เป็น ANSI แล้วจำนวนไบต์จะไม่เพิ่มขึ้น ใน UTF-8 ไม่เป็นเช่นนั้น และใน code page เดิมส่วนใหญ่โดยทั่วไปสมมตินี้เคยถูกต้อง จึงอาจทำให้เกิดช่องโหว่ใหม่ได้ น่าจะพังน้อยกว่ามากถ้าเอา Best-Fit logic ออกจาก Win32
xxxAAPI แล้วแทนที่อักขระที่แมปไม่ได้ด้วยอักขระอย่างxที่ไม่มีความหมายเมตาร่วมกัน[0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
เมื่อดูปัญหาที่ Best-Fit mapping ก่อขึ้น การทำให้เป็นค่าเริ่มต้นก็สมเหตุสมผลเช่นกัน แต่ Microsoft ควรช่วยให้ผู้ใช้หาวิธีรันโค้ดเก่าได้ง่าย วิธีที่สมเหตุสมผลน้อยกว่าคือเอา mapping ทั้งหมดที่ไปยังอักขระ ASCII “พิเศษ” ออกจาก Best-Fit mapping แต่ก็ไม่ช่วยแอปที่ลิงก์ CRT แบบ static และไม่ได้แก้ช่องโหว่ด้วย จึงไม่ใช่ทางออกที่ดี บางครั้งช่องโหว่ด้านความปลอดภัยก็เป็นแรงผลักดันให้เกิดการ ทำลายความเข้ากันได้ย้อนหลัง
Microsoft รู้เรื่องปัญหานี้มาอย่างน้อย 1 ปีแล้ว เพราะได้ออกกฎวิเคราะห์โค้ดพิเศษชื่อ CA2101[1] ซึ่งระบุชัดว่าไม่แนะนำให้ใช้ best-fit mapping
คำอธิบายกฎพูดถึงช่องโหว่ด้านความปลอดภัย แต่จงใจทำให้รายละเอียดคลุมเครือ
[1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
ไม่จำเป็นต้องเปลี่ยนทุกอย่างจาก
char *เป็นwchar *แค่แปลง wide character ที่ได้รับมาเป็น UTF-8 หรือถ้าต้องการรองรับลำดับที่ไม่ถูกต้องอย่าง surrogate ที่ไม่มีคู่ด้วย ก็แปลงเป็นอะไรทำนอง WTF-8 ของ Rust แล้วใช้charต่อไปได้แน่นอนว่าต้องระวังไม่ให้สตริง ANSI หรือ OEMCP ปนกับสตริง UTF-8 แต่ถ้าใช้แค่ UTF-8 ก็ง่าย แนวทางนี้เป็นวิธีที่เว็บไซต์คลาสสิก https://utf8everywhere.org/ แนะนำ
บนคอมพิวเตอร์ Windows ส่วนตัว ผมเปิดโหมด UTF-8 ไว้มาหลายปีแล้ว เลยบังเอิญหลีกเลี่ยงบั๊กนี้ได้ เป็นการตั้งค่าที่อยู่ท้ายบทความ
ผมเปิดไว้เพราะเกมต่างประเทศเก่า ๆ แสดงตัวอักษรเพี้ยน และแม้จะมีป้ายว่า “Beta” แต่ก็ไม่รู้สึกว่ามีบั๊กหรือผลข้างเคียง
ผมสงสัยว่า checkbox เบต้านั้นเหมือนกับการตั้ง
ActiveCodePageเป็น UTF-8 ใน manifest หรือไม่ แต่ดูจากเอกสาร[0] แล้วระบุชัดว่า GDI ไม่ทำตาม code page รายโปรเซส แต่ทำตาม code page ส่วนกลางเพียงค่าเดียว ที่ checkbox ตั้งไว้เท่านั้นน่าเสียดายอยู่บ้างที่ไม่สามารถ opt-in ให้
*AAPI ในแอปของตัวเองเป็น UTF-8 ได้อย่างสมบูรณ์ ถึงอย่างนั้นสำหรับปัญหาที่บทความเน้น ผมก็ยังมองว่ามันเป็น workaround หรือมาตรการ defense-in-depth ที่ใช้ได้[0] https://learn.microsoft.com/en-us/windows/apps/design/global...
ให้ตายเถอะ ผมรู้ว่า Windows API มีการแปลงแบบ best-fit ประเภทนั้นให้ใช้ แต่ไม่รู้ว่ามันเป็นพฤติกรรมเริ่มต้นของฟังก์ชัน ANSI หลายตัวใน code page เริ่มต้นของผมคือ 949[1]
มาถึงจุดนี้ก็ควรแบนไปเลยเหมือน
gets[1] ผมรู้ว่ามี UTF-8 code page 65001 อยู่ มันเคยอยู่ในระดับที่แทบใช้จริงไม่ได้มานาน และตอนนี้ก็ยังมีปัญหาความเข้ากันได้อยู่