- พบกรณีที่ มีการส่งแพ็กเก็ตนับร้อยชุดเมื่อกดคีย์เพียงครั้งเดียว ในเซสชัน SSH และได้ไล่หาสาเหตุของปัญหานี้
- ผลการวิเคราะห์ด้วย
tcpdump พบว่าแพ็กเก็ตส่วนใหญ่เป็น ข้อความซ้ำขนาด 36 ไบต์ และเกิดขึ้นห่างกันราว 20ms
- สาเหตุมาจากฟีเจอร์ “keystroke timing obfuscation” ที่ถูกเพิ่มเข้ามาใน SSH เมื่อปี 2023 โดยจะส่ง แพ็กเก็ต ‘chaff’ (SSH2_MSG_PING) จำนวนมากเพื่อซ่อนจังหวะการพิมพ์ของผู้ใช้
- หากปิดฟีเจอร์นี้ หรือแก้ไม่ให้เซิร์ฟเวอร์โฆษณาส่วนขยาย
[email protected] จะทำให้ การใช้ CPU และแบนด์วิดท์ลดลงเหลือน้อยกว่าครึ่ง
- เป็นกรณีตัวอย่างที่แสดงให้เห็นว่าฟีเจอร์ความปลอดภัยของ SSH อาจกลายเป็นภาระหนักใน แอปพลิเคชันที่ต้องการประสิทธิภาพแบบเรียลไทม์สูง (เช่น เกม)
การค้นพบปัญหา
- ระหว่างทดสอบ TUI ของเกมประสิทธิภาพสูง ที่รันผ่าน SSH พบว่าการกดคีย์เพียงครั้งเดียวทำให้เกิด แพ็กเก็ต 270 ชุด
- จากผล
tcpdump พบว่า 66% เป็นข้อความขนาด 36 ไบต์, 33% เป็น TCP ACK และที่เหลือเป็นข้อมูลอื่นเพียงเล็กน้อย
- มีการส่งข้อมูลเฉลี่ย 90 แพ็กเก็ต/วินาที หรือทุก ๆ ประมาณ 11ms
- ระหว่างการทดสอบ มีการตั้งค่าเซิร์ฟเวอร์ผิดพลาดจนส่งเพียงข้อความ “your screen is too small” เท่านั้น และในตอนนั้น การใช้ CPU และแบนด์วิดท์ลดลงครึ่งหนึ่ง
- แม้ไม่ควรมีการส่งข้อมูลของเกมแล้ว การใช้ CPU ก็ควรใกล้ 0% แต่ยังคงอยู่ราว 50%
- ทำให้เกิดข้อสงสัยว่าอาจเป็น โอเวอร์เฮดด้านการสื่อสารของ SSH เอง
กระบวนการสืบสวน
- ใช้
tcpdump เปรียบเทียบทราฟฟิก SSH ระหว่างสถานะปกติกับสถานะผิดพลาด
- แม้อยู่ในสถานะผิดพลาดก็ยังพบว่า แพ็กเก็ตขนาด 36 ไบต์ยังคงถูกส่งต่อเนื่องทุก ๆ 20ms
- ตรวจพบแพตเทิร์นเดียวกันใน SSH client มาตรฐานของ MacOS ด้วย
- เมื่อนำไฟล์ pcap ไปวิเคราะห์ด้วย Claude Code พบว่า
- จากแพ็กเก็ตทั้งหมด 413,703 ชุด มี 66% ที่ขนาด 36 ไบต์ และ 34% เป็น ACK ขนาด 0 ไบต์
- แพ็กเก็ตเหล่านี้ถูกสร้างขึ้นโดยฝั่ง SSH client เป็นหลัก
สาเหตุที่แท้จริง
วิธีแก้ไข
- ฝั่งไคลเอนต์สามารถปิดฟีเจอร์นี้ได้ด้วยตัวเลือก
ObscureKeystrokeTiming=no
- หลังใช้งานแล้ว การใช้ CPU และแบนด์วิดท์ลดลงอย่างมาก ขณะที่การส่งข้อมูลยังทำงานเป็นปกติ
- สำหรับฝั่งเซิร์ฟเวอร์ มีการแก้โดยนำการโฆษณาส่วนขยาย
[email protected] ออกจากไลบรารี SSH ของ Go
- หลังย้อนคอมมิตที่เกี่ยวข้องแล้วทดสอบ พบว่า
- การใช้ CPU 29.9% → 11.6% ,
system call 3.10s → 0.66s,
การประมวลผลเข้ารหัส 1.6s → 0.11s,
แบนด์วิดท์ 6.5Mbit/s → 3Mbit/s
- ประสิทธิภาพดีขึ้น มากกว่า 50%
ประสบการณ์ดีบักด้วย LLM
- ใช้ Claude Code ช่วยทำงานวิเคราะห์
tcpdump และ tshark แบบอัตโนมัติ ทำให้จำกัดวงหาสาเหตุของปัญหาได้รวดเร็ว
- สามารถเฝ้าดูขั้นตอนการรันคำสั่งแบบเรียลไทม์และ รักษา mental model ของปัญหาไว้ได้
- ส่วน ChatGPT เคยตัดสินพฤติกรรมของ SSH ว่า “ปกติ” อย่างผิดพลาด ซึ่งสะท้อนให้เห็น ความแตกต่างระหว่างโมเดล
- แม้ LLM จะยังไม่สามารถแทนที่กระบวนการแก้ปัญหาได้ทั้งหมด แต่ก็แสดงให้เห็นถึง ประสิทธิภาพสูงในฐานะเครื่องมือวิเคราะห์เสริม
- เป็นตัวอย่างของการผสาน การให้เหตุผลของมนุษย์กับการวิเคราะห์ของ LLM เพื่อแก้ปัญหาประสิทธิภาพเครือข่ายที่ซับซ้อน
1 ความคิดเห็น
ความคิดเห็นใน Hacker News
รู้สึกน่ากลัวนิดหน่อยที่จะ fork ไลบรารี crypto ของ go
กำลังคิดอยู่ว่าจะรักษาแพตช์เล็ก ๆ ของตัวเองให้ปลอดภัยได้อย่างไร
จริง ๆ แล้วคิดว่าฟีเจอร์แบบนี้ควรถูก upstream เป็นออปชันของไลบรารี ssh
ในสภาพแวดล้อมที่ไม่น่าเชื่อถือ การส่งแพ็กเก็ต chaff (แพ็กเก็ตรบกวน) เป็นค่าเริ่มต้นก็ดี แต่หลายกรณีก็อยากประหยัดแบนด์วิดท์
ทางออกที่ถูกต้องคือเพิ่มออปชันให้เซิร์ฟเวอร์ส่งสัญญาณไปยังไคลเอนต์ได้ว่า “ไม่จำเป็น” แล้วให้ไคลเอนต์เลือกรับหรือเตือนได้
ใช้กับเฉพาะ TTY session และไคลเอนต์สามารถปิดได้
เพียงแต่กรณีนี้เป็นสถานการณ์พิเศษที่เซิร์ฟเวอร์รู้ล่วงหน้าว่าการเชื่อมต่อนี้ไม่สำคัญ
โดยส่วนใหญ่แล้วไคลเอนต์คาดหวังให้การตั้งค่า ObscureKeystrokeTiming ถูกคงไว้
ไลบรารี crypto เป็น codebase ที่มีจุดยืนชัดมาก จนถึงขั้นไม่เปิดให้เปลี่ยนลำดับ TLS cipher suite
นี่ดูเป็นกรณีใช้งานของ SSH ที่ เฉพาะทางมาก
ถ้าเปิดให้ใช้กว้างเกินไปก็อาจเกิดสถานการณ์แบบ “ตั้งค่าแล้วลืม” จนทำให้ความปลอดภัยอ่อนลงแทน
เคยสื่อสารผ่านโมเด็ม 1200bps ด้วย และ 56K modem ก็แทบจะเป็นคำโฆษณาเกินจริง
ราวปี 1994 ตอนทำงานที่วิทยาลัยทหารในอังกฤษได้เจอ WWW ครั้งแรก และตอนนั้นคิดว่า “ก็ไม่ค่อยน่าประทับใจนะ”
พอมองย้อนกลับไปก็ทึ่งกับ ความเปลี่ยนแปลงของยุคสมัย จริง ๆ
เพิ่งเคยได้ยิน ฟีเจอร์ obfuscation นี้ น่าสนใจดี
เวลาดีบักพฤติกรรมของ ssh การแพตช์ cipher แบบ “None” เพื่อดูเนื้อหาแพ็กเก็ตตรง ๆ ก็เป็นวิธีที่ดี
ถ้าเป็น เกมบนเทอร์มินัล ที่ความปลอดภัยไม่สำคัญแต่ประสิทธิภาพสำคัญ ก็อาจพิจารณาใช้ telnet ไปเลย
ไม่เคยรู้มาก่อนว่า SSH ทำแบบนี้ด้วย
เข้าใจได้ว่าทำไมถึงเปิดเป็นค่าเริ่มต้น แต่ในสภาพแวดล้อมของผม/ฉันน่าจะเหมาะกับการปิด
เลยกำลังจะตั้งค่า
ObscureKeystrokeTiming=noมีเหตุผลอะไรไหมที่ไม่ควรทำแบบนั้น?(1) ไม่ใช่ว่าจะบอกได้เสมอว่ามนุษย์กำลังป้อนความลับเมื่อไร และกิจกรรมทั้งหมดอาจถูก วิเคราะห์จากรูปแบบ ได้
(2) นี่เป็นการโจมตีที่ทำได้แม้ในระดับห้องแล็บมหาวิทยาลัย — ดู งานวิจัย USENIX และ กรณีศึกษา
(3) ในอินเทอร์เน็ตที่ทราฟฟิกวิดีโอครองสัดส่วนหลัก การยอมลดความปลอดภัยเพื่อประหยัดข้อมูลคีย์สโตรกไม่กี่ไบต์นั้นไม่มีความหมาย
ถ้าผู้โจมตีวิเคราะห์จังหวะการพิมพ์ได้ ก็อาจเดารูปแบบของคำสั่งและรหัสผ่านที่เข้ารหัสไว้ได้
แน่นอนว่าการถอดรหัสยังยากเพราะ session key เปลี่ยนทุกครั้ง แต่ก็ยังมีความเป็นไปได้
ผม/ฉันเองก็ copy จาก password manager มาวางสำหรับรหัสผ่านส่วนใหญ่
คนส่วนใหญ่รู้สึกว่าไม่มีปัญหาอะไรแม้จะ ปิดฟีเจอร์ความปลอดภัย ของ SSH แต่ที่จริงอาจแค่โชคดี
ถ้าต้องการประสิทธิภาพจริง ๆ ใช้ Telnet ไปเลย ถ้าต้องการความปลอดภัยจริง ๆ ใช้ ContainerSSH + OAuth2 จะดีกว่า
เมื่อปี 2004 เคยทำวิจัยเรื่อง การวิเคราะห์ความหน่วงระหว่างคีย์สโตรก ของ SSH session เพื่อเดาคำสั่ง
ดู เอกสารการวิเคราะห์ในตอนนั้น
แพตช์ปี 2023 ก็เท่ากับมาแก้ปัญหานั้นได้ในที่สุด
เอกสารนำเสนอ
เวลาผ่านไปเร็วจริง ๆ
ไม่ค่อยแน่ใจว่า Claude ช่วยในการดีบักจริงแค่ไหน
ผู้เขียนดูเหมือนจะรู้อยู่แล้วว่าควรไปทางไหน และ Claude ก็เหมือนแค่คอยเห็นด้วย
เวลาที่ Claude พูดอะไรอย่าง “Holy Cow!” มัน ชวนรำคาญ นิดหน่อย
เวลาผม/ฉันใช้ Claude ดีบักการทำงานของระบบ ถึงจะไม่ได้คำตอบตรง ๆ แต่ก็ช่วย จัดความเข้าใจและรักษาแรงจูงใจ ได้
Rubber Duck Debugging บนวิกิ
จากที่ผู้เขียนชอบปฏิกิริยาแบบ “holy cow” แล้วเอาไปใส่ในบล็อก ก็ดูเหมือน Claude อ่านบรรยากาศได้ดี
ถ้าใช้ TCP_CORK ก็ลดจำนวนแพ็กเก็ตได้โดยไม่ต้องเพิ่มความหน่วง
การปิด TCP_NODELAY ก็เป็นวิธีหนึ่ง แต่ต้องแลกกับ latency ที่เพิ่มขึ้น
เมื่อ cork socket แล้ว kernel จะบัฟเฟอร์ข้อมูลไว้จนกว่าจะ uncork หรือถึง MSS แล้วค่อยส่ง
กล่าวคือเป็นการ รวมแพ็กเก็ตแล้วส่ง
ข้อมูลอ้างอิง
น่าจะยังรับ ping ได้เหมือนเดิม แต่ลดจำนวนครั้งในการส่ง pong ได้
เคยใช้ TCP_NODELAY แล้ว แต่ latency สูงขึ้น เลยไม่เหมาะกับเกมของผม/ฉัน
โพสต์ HN ก่อนหน้า
เพื่อจุดประสงค์ด้าน obfuscation แล้ว การ coalescing ความหน่วง น่าจะทำไม่ได้
วลี “The smoking gun!” ตลกดี
ผม/ฉันไม่ใช่เจ้าของภาษาอังกฤษ แต่ Claude ใช้บ่อยจนได้เรียนรู้คำนี้ครั้งแรก
ตอนนี้มันกำลัง แพร่เหมือนคำฮิต ไปแล้วจริง ๆ
รู้สึกเสียดายที่พึ่ง LLM
เรื่องนี้แค่จับแพ็กเก็ตด้วย Wireshark ก็น่าจะแก้ได้เร็วกว่า
SSH dissector ก็ค่อนข้างสมบูรณ์แล้ว
แค่จับหนึ่งคีย์สโตรกด้วย tcpdump ก็ได้แพ็กเก็ตเข้ารหัสเป็นร้อยแล้ว
สุดท้ายผู้เขียนก็ได้เรียนรู้อะไรน่าสนใจและเอามาแชร์เพราะ LLM แบบนี้ก็มีความหมาย
หลังข้อความ NEWKEYS แล้วจะ parse ต่อไม่ได้ และถึงจะแพตช์ให้ใช้การเข้ารหัส
noneก็ยังตีความ flow ได้ไม่ครบยังมีช่องให้ปรับปรุง
การใช้เครื่องมือเพื่อเรียนรู้ก็มีคุณค่าเพียงพอ
การจับแพ็กเก็ตอย่างเดียวจึงยากจะได้ข้อมูลที่มีนัยสำคัญ
ไม่เห็นว่าทำไมถึงต้องมองว่าน่าเสียดาย
ในปี 2023 SSH ได้เพิ่มฟีเจอร์ การทำให้จังหวะคีย์สโตรกสับสน
เพราะสามารถเดาตัวอักษรจากความเร็วในการพิมพ์ได้ SSH จึงผสม chaff packet เข้าไปเพื่อให้ผู้โจมตีแยกไม่ออก
แต่เรื่องนี้ดูเหมือนเป็นแนวทางที่ผิด
ถ้าอยากทำจริง ๆ ก็แค่ส่งทุกคีย์สโตรกที่ ช่วง 50ms ก็พอ
ตอนนี้การทำงานจริงคือรวมเป็นชุดทุก 20ms และถ้าไม่มีการพิมพ์ต่อสักพักก็หยุดส่ง chaff
แก่นของ SSH คือ ความปลอดภัย แต่ถ้าไม่ต้องการความปลอดภัยก็สงสัยว่าจะใช้ SSH ไปทำไม
อย่างเช่น netcat(nc) ก็มีติดตั้งมาเป็นค่าเริ่มต้นในแทบทุกแพลตฟอร์ม
SSH ยังมี ปัจจัยอื่น อย่างประสิทธิภาพและความสะดวกด้วย
ผู้เขียนแค่บอกว่าไม่ต้องการฟีเจอร์ การทำให้คีย์สโตรกสับสน (privacy) เท่านั้น
เขา/เธออาจยังต้องการการเข้ารหัสหรือการรับประกันความถูกต้องครบถ้วนอยู่
กล่าวคือยังคงฟีเจอร์ความปลอดภัยส่วนใหญ่ของ SSH เอาไว้ และปิดเพียงบางส่วน