sshole คืออะไร? ทำไมบัญชีที่ใช้ SSH key ถึงถูกข้ามการยืนยันตัวตนได้
สรุปช่องโหว่ sshole หรือ CVE-2026-123158 ใน OpenSSH บนระบบที่ใช้ musl เงื่อนไขที่ทำให้บัญชี SSH key ได้รับผลกระทบ พร้อมวิธีแก้ไขและตั้งค่า SSH
เมื่อวันที่ 5 พฤศจิกายน 2026 มีการเปิดเผยช่องโหว่ชื่อ sshole หรือที่ผู้ค้นพบเขียนว่า *sshole รหัส CVE-2026-123158 ตัวนี้ทำให้ผู้โจมตี SSH เข้าบัญชีบางประเภทได้โดยไม่ต้องรู้รหัสผ่าน รวมถึงบัญชีที่เจ้าของเครื่องตั้งใจให้เข้าได้ด้วย SSH key เท่านั้น
ช่องโหว่เกิดจากการทำงานร่วมกันของ OpenSSH กับ musl ซึ่งเป็น C library ที่ใช้ใน Linux บางค่าย เช่น Alpine Linux โดยต้องเป็น OpenSSH ที่ทำงานโดยไม่ใช้ PAM และยังเปิดรับการ login ด้วย password อยู่ด้วย ส่วน OpenSSH ที่ใช้ glibc ตามปกติใน Debian, Ubuntu หรือ RHEL ไม่เข้าเงื่อนไขนี้
ที่น่าสนใจคือ บัญชีที่โดนไม่ได้มีรหัสผ่านให้เดาด้วยซ้ำ แต่ค่าที่ระบบใช้แทน password hash กลับไปตรงกับค่าที่ฟังก์ชันคืนมาตอนทำงานผิดพลาดครับ
ทำไมรหัสผ่านผิดถึง login ได้?
ปกติ password hash ของแต่ละบัญชีจะอยู่ใน /etc/shadow แต่บางบัญชีจะมีค่า * อยู่แทน เช่น บัญชีที่ตั้งใจให้เข้าด้วย SSH key อย่างเดียว ค่า * ไม่ใช่ password hash ที่ใช้งานได้ จึงไม่ควรมีรหัสผ่านไหนนำมาเทียบแล้วตรงกัน
ทีนี้ crypt() ของ musl ก็ใช้ * เป็นค่าที่คืนกลับมาเมื่อทำงานไม่สำเร็จเหมือนกัน และในเส้นทาง SHA-256/SHA-512 crypt มีการจำกัดความยาวรหัสผ่านไว้ที่ 256 bytes เพื่อไม่ให้รหัสผ่านที่ยาวเกินไปกินทรัพยากรของเครื่อง
พอส่งรหัสผ่านยาว 257–1,024 bytes เข้าไปในระบบที่เข้าเงื่อนไข crypt() จึงคืนค่า * กลับมา OpenSSH เอาไปเทียบกับ * ของบัญชี เห็นว่าตรงกัน ก็ยอมให้ login ผ่าน
ถ้าอยากดูโค้ด ส่วนนี้อยู่ใน crypt_sha512.c ของ musl ลองหา KEY_MAX จะเจอค่าจำกัดความยาว และส่วนที่คืน * เมื่อทำงานไม่สำเร็จ
แต่ยังมีอีกเงื่อนไขหนึ่งครับ ในเครื่องต้องมีบัญชีอื่นที่ใช้ password hash แบบ $5$ หรือ $6$ อยู่ด้วย เพราะเมื่อบัญชีเป้าหมายไม่มี hash ที่ใช้ได้ OpenSSH จะหยิบ salt ของบัญชีอื่นมาใช้แทน
พฤติกรรมนี้เพิ่มเข้ามาตอนแก้ CVE-2016-6210 เมื่อปี 2016 เพื่อไม่ให้ผู้โจมตีเดาได้ว่าบัญชีไหนมีอยู่จริงจากเวลาที่ server ใช้ตอบกลับ พอมาเจอกับการคืนค่า * ของ musl เลยเกิดปัญหานี้ขึ้น
ระบบแบบไหนได้รับผลกระทบ?
ประกาศของ Alpine ระบุเงื่อนไขหลักไว้ดังนี้ โดยต้องเข้าเงื่อนไขร่วมกัน:
- ใช้
openssh-serverที่ไม่มี PAM หรือใช้openssh-server-pamแต่ตั้งUsePAM no - เปิด
PasswordAuthentication yes - มีบัญชีเป้าหมายที่ช่อง password เป็น
*และนโยบายของsshdอนุญาตให้บัญชีนั้น login ได้ - มีบัญชีในเครื่องอย่างน้อยหนึ่งบัญชีที่ใช้ hash แบบ
$5$หรือ$6$
สำหรับแพ็กเกจ openssh-server ปกติของ Alpine การไม่ใช้ PAM และการเปิดรับ password เป็นค่าเริ่มต้นอยู่แล้ว จึงไม่จำเป็นต้องเห็นสองบรรทัดนี้เขียนอยู่ในไฟล์เสมอไป:
UsePAM no
PasswordAuthentication yes
ส่วนบัญชีใน /etc/shadow อาจมีหน้าตาประมาณนี้ โดยค่า hash ในตัวอย่างย่อไว้เพื่อให้อ่านง่าย:
root:*:20000:0:::::
deploy:*:20000:0:::::
admin:$6$rounds=5000$x7Kq2m9L$Wn3...:20000:0:99999:7:::
ในตัวอย่างนี้ เราตั้งใจให้ deploy เข้าด้วย SSH key แต่เมื่อมี hash แบบ $6$ ของ admin อยู่ในเครื่องด้วย ก็ครบเงื่อนไขที่ทำให้ผู้โจมตีใช้รหัสผ่านยาว ๆ ผ่านการยืนยันตัวตนของ deploy ได้
ส่วนจะได้ root เลยไหม ขึ้นอยู่กับบัญชีและ configuration ถ้าจะเข้า root โดยตรงต้องเปิด PermitRootLogin yes ด้วย หากเข้าบัญชีทั่วไปก็จะได้สิทธิ์ของบัญชีนั้นก่อน แต่ถ้าบัญชีนั้นเรียกคำสั่งเป็น root ผ่าน doas หรือ sudo ได้โดยไม่ถามรหัสผ่าน ก็อาจใช้สิทธิ์นั้นต่อได้
เครื่องที่ปิดด้วย PasswordAuthentication no หรือใช้ openssh-server-pam พร้อม UsePAM yes ไม่ได้รับผลกระทบผ่านช่องทางนี้
sshd อยู่ข้างใน ไม่ได้รับผลกระทบจากช่องทางนี้ เห็น Alpine ใน Dockerfile อย่างเดียวยังสรุปไม่ได้ครับใช้ SSH key อยู่แล้ว ทำไมยังต้องตรวจ?
เวลาเราใช้ SSH key เข้าเครื่องทุกวัน อาจไม่ได้กลับไปดูว่า server ยังเปิดรับ password อยู่หรือเปล่า การที่เราไม่เคยพิมพ์รหัสผ่านไม่ได้หมายความว่า server ปิดวิธีนี้ให้แล้ว
ถ้า PasswordAuthentication ยังเป็น yes บัญชีมีค่า * และเข้าเงื่อนไขอื่นครบ บัญชีที่เราใช้ SSH key อยู่ก็ได้รับผลกระทบได้เหมือนกัน
Wakefield ผู้ค้นพบช่องโหว่ยกตัวอย่าง Alpine cloud image บางรุ่นบน AWS และ Azure ที่มีบัญชีเริ่มต้นชื่อ alpine เป็นลักษณะนี้ และใช้ doas เป็น root ได้โดยไม่ถามรหัสผ่าน
ตอน boot ครั้งแรก image กลุ่มนี้ยังไม่เข้าเงื่อนไขการโจมตี แต่ถ้าภายหลังมีใครตั้งรหัสผ่านจนเกิด hash แบบ $6$ ในเครื่อง เงื่อนไขก็ครบขึ้นมาได้ ทั้งที่วิธี SSH เข้าเครื่องของผู้ดูแลไม่ได้เปลี่ยนไปเลย
แก้ไขอย่างไร?
OpenSSH ออก 10.6p2 เมื่อวันที่ 5 พฤศจิกายน 2026 เพื่อป้องกันปัญหานี้ในฝั่ง sshd โดยทีม OpenSSH อธิบายว่ามองต้นเหตุอยู่นอก sshd แต่สามารถเพิ่มการป้องกันจากฝั่ง OpenSSH ได้
ส่วน Alpine มีการ backport แพตช์กลับไปใส่แพ็กเกจรุ่นเดิมแล้ว หมายเลขเวอร์ชันจึงไม่จำเป็นต้องเป็น 10.6p2 ทุกเครื่อง ให้ดูรุ่น Alpine และเวอร์ชันแพ็กเกจที่ติดตั้งก่อน:
cat /etc/alpine-release
apk list -I "openssh-server*"
แล้วเทียบกับ branch และ repository ที่เครื่องใช้อยู่:
| Branch | เวอร์ชันที่แก้แล้ว |
|---|---|
| edge | 10.6_p2-r0 |
| 3.24 | 10.3_p1-r2 |
| 3.23 | 10.2_p1-r1 |
| 3.22 | 10.0_p1-r11 |
| 3.21 | 9.9_p2-r1 |
| 3.20 | 9.7_p1-r6 |
| 3.19 | 9.6_p1-r3 |
| 3.18 | 9.3_p2-r4 |
| 3.17 | 9.1_p1-r7 |
| 3.16 | 9.0_p1-r6 |
คำสั่งอัปเดตตามประกาศของ Alpine คือ:
apk upgrade -U -a
rc-service sshd restart
รันด้วยสิทธิ์ root ครับ บรรทัดแรกอัปเดตแพ็กเกจทั้งเครื่องตาม repository ที่ตั้งไว้ ส่วนบรรทัดที่สองต้องทำด้วย เพราะอัปเดตแพ็กเกจอย่างเดียวไม่ได้แทนที่ process ของ sshd ที่กำลังรันอยู่
ถ้ายังอัปเดตไม่ได้ ให้ปิดรับ password ไปก่อน:
PasswordAuthentication no
sshd -t ก่อน restart แล้วลองเปิด session ใหม่ให้เข้าได้ก่อนปิดอันเดิมตรวจค่าที่ sshd อ่านจาก configuration ได้ด้วย:
sshd -T | grep -i passwordauthentication
ควรได้ passwordauthentication no แต่ถ้ามี Match block ต้องตรวจตามเงื่อนไขของ connection ด้วย ตัวอย่างนี้ให้แทนชื่อบัญชีและ IP ของเครื่องที่ใช้เชื่อมต่อด้วยค่าจริง:
sshd -T -C user=your-user,addr=CLIENT_IP,host=CLIENT_HOSTNAME \
| grep -i passwordauthentication
หากมีเงื่อนไขอื่น เช่น IP หรือ port ฝั่ง server ให้ระบุค่าที่เกี่ยวข้องเพิ่มด้วย การใช้ sshd -T อย่างเดียวไม่ได้ตรวจแทนทุก Match block
ตั้งค่า SSH ต่ออย่างไรดี?
ถ้าเครื่องเราใช้ SSH key อยู่แล้วและไม่มีงานที่ต้องใช้ password ก็ควรปิด password authentication ให้ชัดเจนไปเลยครับ ส่วนบัญชีที่เข้าได้และสิทธิ์หลัง login ก็ควรกำหนดให้ตรงกับการใช้งาน
ตัวอย่างด้านล่างเป็นแนวทางสำหรับ Alpine ที่ต้องการใช้ SSH key อย่างเดียว ถ้าเครื่องใช้ OTP หรือ MFA ผ่าน keyboard-interactive อยู่ ต้องปรับให้เข้ากับระบบเดิมก่อน
สร้างและติดตั้ง SSH key
ถ้ายังไม่มี key ให้สร้างบนเครื่องที่เราใช้ดูแล server และตั้ง passphrase ไว้ด้วย:
ssh-keygen -t ed25519 -a 100 -C "your-user@laptop"
ssh-copy-id your-user@SERVER_IP
แทน your-user กับ SERVER_IP ด้วยค่าจริง แล้วลองเข้าเครื่อง:
ssh your-user@SERVER_IP
ต้องเข้าได้โดยไม่ถามรหัสผ่านของบัญชีบน server ก่อนค่อยไปต่อ ส่วนการถาม passphrase ของ SSH key ยังเป็นเรื่องปกติ
แยก configuration ที่เราตั้งเอง
เก็บค่าที่ปรับเองไว้ใน sshd_config.d จะช่วยให้กลับมาดูภายหลังได้ง่ายขึ้น:
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
PermitRootLogin no
AllowGroups ssh-users
MaxAuthTries 3
LoginGraceTime 30
ตัวอย่างนี้ปิดการเข้า root ผ่าน SSH ให้เข้าเป็นบัญชีปกติแล้วใช้ sudo หรือ doas เมื่อต้องใช้สิทธิ์เพิ่ม จึงควรทดสอบสิทธิ์ของบัญชีนั้นให้เรียบร้อยก่อน
ส่วน AllowGroups ssh-users จะอนุญาตเฉพาะบัญชีที่อยู่ในกลุ่มนี้ บน Alpine สร้างกลุ่มและเพิ่มบัญชีด้วย:
addgroup ssh-users
addgroup your-user ssh-users
ถ้ามีบัญชีสำหรับ deploy หรือ backup ที่ต้องเข้า SSH ด้วย อย่าลืมเพิ่มบัญชีเหล่านั้นก่อนเปิดใช้ AllowGroups
ไฟล์แยกจะมีผลก็ต่อเมื่อ /etc/ssh/sshd_config อ่านมันด้วย ตรวจว่ามีบรรทัดนี้อยู่ในส่วน global ก่อน option ที่ต้องการตั้งค่า:
Include /etc/ssh/sshd_config.d/*.conf
โดยทั่วไป sshd ใช้ค่าที่อ่านเจอก่อนสำหรับแต่ละ option จึงต้องดูทั้งลำดับไฟล์และค่าที่มีอยู่เดิม ไม่ใช่เพิ่มไฟล์ใหม่แล้วจะทับค่าเก่าเสมอไป ส่วน Match block ให้ตรวจแยกตาม connection ตามตัวอย่างก่อนหน้า
MaxAuthTries 3 และ LoginGraceTime 30 เป็นค่าตัวอย่างสำหรับจำกัดจำนวนครั้งและเวลายืนยันตัวตนต่อ connection ถ้า SSH agent มีหลาย key แล้วเจอ Too many authentication failures ให้ระบุ key ที่ต้องการใช้ หรือปรับค่าให้เหมาะกับเครื่องนั้น
ตรวจแล้วค่อย restart
บน Alpine ใช้:
sshd -t && rc-service sshd restart
sshd -T | grep -iE 'passwordauthentication|kbdinteractive|permitrootlogin|allowgroups'
การใช้ && ทำให้ restart ต่อเมื่อ configuration ผ่านการตรวจแล้ว จากนั้นเปิด terminal อีกหน้าต่างลอง SSH เข้าใหม่ โดยยังเก็บ session เดิมไว้จนกว่าจะมั่นใจว่าบัญชีที่ต้องใช้เข้าได้ครบ
หากนำแนวทางนี้ไปใช้บน Ubuntu หรือ Debian คำสั่งตรวจและ restart โดยทั่วไปจะเป็น:
sudo /usr/sbin/sshd -t && sudo systemctl restart ssh
ตรวจบัญชีและ key ที่ยังใช้อยู่
ดูชื่อบัญชีที่มีค่า * และบัญชีที่ใช้ hash แบบ $5$ หรือ $6$ ได้ด้วยคำสั่งต่อไปนี้ โดยรันด้วยสิทธิ์ root:
awk -F: '$2 == "*" { print $1 }' /etc/shadow
awk -F: '$2 ~ /^\$(5|6)\$/ { print $1 }' /etc/shadow
สองคำสั่งนี้ช่วยตรวจเงื่อนไขด้านบัญชี แต่ยังต้องดูแพ็กเกจและ configuration ประกอบด้วย
จากนั้นตรวจ authorized_keys ของแต่ละบัญชีที่อนุญาตให้เข้า SSH หากใช้ตำแหน่งมาตรฐาน คำสั่งนี้จะแสดง key ของบัญชีที่กำลังใช้งานอยู่:
cat ~/.ssh/authorized_keys
key ของคนหรือเครื่องที่เลิกใช้งานแล้วก็ควรถอดออก ส่วนบัญชี service ให้ตรวจว่าจำเป็นต้องมี shell หรือเข้า SSH หรือไม่ ก่อนเปลี่ยนค่า เพราะบางงานอาจยังใช้บัญชีนั้นสำหรับ deploy, backup หรือ SFTP
ถ้าเปิด SSH ออก internet ตรง ๆ อาจเพิ่ม fail2ban หรือ rate limit เพื่อลดการลอง login ซ้ำ ส่วนการย้ายออกจาก port 22 ช่วยลด bot บางส่วนและทำให้ Logs เงียบลงได้ แต่การแก้ sshole ยังต้องอัปเดตแพ็กเกจหรือปิด password authentication ตามข้างต้นครับ
แหล่งอ้างอิง
ข้อมูลช่องโหว่ตรวจสอบ ณ วันที่ 6 พฤศจิกายน 2026
- OpenSSH 10.6p2 release notes
- Red Hat: CVE-2026-123158
- *sshole: รายละเอียดทางเทคนิคจากผู้ค้นพบ
- Alpine Linux: openssh-server password authentication bypass
- Wakefield: OpenSSH Password Authentication Bypass on musl Linux
- musl: src/crypt/crypt_sha512.c
- OpenSSH: sshd manual
- OpenSSH: sshd_config manual
- Alpine Linux: Setting up a new user