---
title: "sshole คืออะไร? ทำไมบัญชีที่ใช้ SSH key ถึงถูกข้ามการยืนยันตัวตนได้"
description: "สรุปช่องโหว่ sshole หรือ CVE-2026-123158 ใน OpenSSH บนระบบที่ใช้ musl เงื่อนไขที่ทำให้บัญชี SSH key ได้รับผลกระทบ พร้อมวิธีแก้ไขและตั้งค่า SSH"
canonical_url: "https://iamthana.com/blogs/2026-11-06-sshole"
---

> Index of every page on this site: [llms.txt](https://iamthana.com/llms.txt)

เมื่อวันที่ 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](https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_sha512.c) ลองหา `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 เป็นค่าเริ่มต้นอยู่แล้ว จึงไม่จำเป็นต้องเห็นสองบรรทัดนี้เขียนอยู่ในไฟล์เสมอไป:

```text [/etc/ssh/sshd_config]
UsePAM no
PasswordAuthentication yes
```

ส่วนบัญชีใน `/etc/shadow` อาจมีหน้าตาประมาณนี้ โดยค่า hash ในตัวอย่างย่อไว้เพื่อให้อ่านง่าย:

```text [/etc/shadow]
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` ไม่ได้รับผลกระทบผ่านช่องทางนี้

<note>

Container ที่ใช้ Alpine เป็น base image แต่ไม่ได้รัน `sshd` อยู่ข้างใน ไม่ได้รับผลกระทบจากช่องทางนี้ เห็น Alpine ใน Dockerfile อย่างเดียวยังสรุปไม่ได้ครับ

</note>

## ใช้ 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 และเวอร์ชันแพ็กเกจที่ติดตั้งก่อน:

```sh
cat /etc/alpine-release
apk list -I "openssh-server*"
```

แล้วเทียบกับ branch และ repository ที่เครื่องใช้อยู่:

<accordion>
<accordion-item label="เวอร์ชัน openssh ที่แก้แล้วในแต่ละ branch ของ Alpine">
<table>
<thead>
  <tr>
    <th>
      Branch
    </th>
    
    <th>
      เวอร์ชันที่แก้แล้ว
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      edge
    </td>
    
    <td>
      10.6_p2-r0
    </td>
  </tr>
  
  <tr>
    <td>
      3.24
    </td>
    
    <td>
      10.3_p1-r2
    </td>
  </tr>
  
  <tr>
    <td>
      3.23
    </td>
    
    <td>
      10.2_p1-r1
    </td>
  </tr>
  
  <tr>
    <td>
      3.22
    </td>
    
    <td>
      10.0_p1-r11
    </td>
  </tr>
  
  <tr>
    <td>
      3.21
    </td>
    
    <td>
      9.9_p2-r1
    </td>
  </tr>
  
  <tr>
    <td>
      3.20
    </td>
    
    <td>
      9.7_p1-r6
    </td>
  </tr>
  
  <tr>
    <td>
      3.19
    </td>
    
    <td>
      9.6_p1-r3
    </td>
  </tr>
  
  <tr>
    <td>
      3.18
    </td>
    
    <td>
      9.3_p2-r4
    </td>
  </tr>
  
  <tr>
    <td>
      3.17
    </td>
    
    <td>
      9.1_p1-r7
    </td>
  </tr>
  
  <tr>
    <td>
      3.16
    </td>
    
    <td>
      9.0_p1-r6
    </td>
  </tr>
</tbody>
</table>
</accordion-item>
</accordion>

คำสั่งอัปเดตตามประกาศของ Alpine คือ:

```sh
apk upgrade -U -a
rc-service sshd restart
```

รันด้วยสิทธิ์ root ครับ บรรทัดแรกอัปเดตแพ็กเกจทั้งเครื่องตาม repository ที่ตั้งไว้ ส่วนบรรทัดที่สองต้องทำด้วย เพราะอัปเดตแพ็กเกจอย่างเดียวไม่ได้แทนที่ process ของ `sshd` ที่กำลังรันอยู่

ถ้ายังอัปเดตไม่ได้ ให้ปิดรับ password ไปก่อน:

```text
PasswordAuthentication no
```

<caution>

ก่อนเปลี่ยนค่า ให้ทดสอบว่า SSH key เข้าเครื่องได้ และเปิด session เดิมค้างไว้ ตรวจ configuration ด้วย `sshd -t` ก่อน restart แล้วลองเปิด session ใหม่ให้เข้าได้ก่อนปิดอันเดิม

</caution>

ตรวจค่าที่ `sshd` อ่านจาก configuration ได้ด้วย:

```sh
sshd -T | grep -i passwordauthentication
```

ควรได้ `passwordauthentication no` แต่ถ้ามี `Match` block ต้องตรวจตามเงื่อนไขของ connection ด้วย ตัวอย่างนี้ให้แทนชื่อบัญชีและ IP ของเครื่องที่ใช้เชื่อมต่อด้วยค่าจริง:

```sh
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 อยู่ ต้องปรับให้เข้ากับระบบเดิมก่อน

<steps level="3">

### สร้างและติดตั้ง SSH key

ถ้ายังไม่มี key ให้สร้างบนเครื่องที่เราใช้ดูแล server และตั้ง passphrase ไว้ด้วย:

```sh
ssh-keygen -t ed25519 -a 100 -C "your-user@laptop"
ssh-copy-id your-user@SERVER_IP
```

แทน `your-user` กับ `SERVER_IP` ด้วยค่าจริง แล้วลองเข้าเครื่อง:

```sh
ssh your-user@SERVER_IP
```

ต้องเข้าได้โดยไม่ถามรหัสผ่านของบัญชีบน server ก่อนค่อยไปต่อ ส่วนการถาม passphrase ของ SSH key ยังเป็นเรื่องปกติ

### แยก configuration ที่เราตั้งเอง

เก็บค่าที่ปรับเองไว้ใน `sshd_config.d` จะช่วยให้กลับมาดูภายหลังได้ง่ายขึ้น:

```text [/etc/ssh/sshd_config.d/10-hardening.conf]
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 สร้างกลุ่มและเพิ่มบัญชีด้วย:

```sh
addgroup ssh-users
addgroup your-user ssh-users
```

ถ้ามีบัญชีสำหรับ deploy หรือ backup ที่ต้องเข้า SSH ด้วย อย่าลืมเพิ่มบัญชีเหล่านั้นก่อนเปิดใช้ `AllowGroups`

ไฟล์แยกจะมีผลก็ต่อเมื่อ `/etc/ssh/sshd_config` อ่านมันด้วย ตรวจว่ามีบรรทัดนี้อยู่ในส่วน global ก่อน option ที่ต้องการตั้งค่า:

```text
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 ใช้:

```sh
sshd -t && rc-service sshd restart
sshd -T | grep -iE 'passwordauthentication|kbdinteractive|permitrootlogin|allowgroups'
```

การใช้ `&&` ทำให้ restart ต่อเมื่อ configuration ผ่านการตรวจแล้ว จากนั้นเปิด terminal อีกหน้าต่างลอง SSH เข้าใหม่ โดยยังเก็บ session เดิมไว้จนกว่าจะมั่นใจว่าบัญชีที่ต้องใช้เข้าได้ครบ

หากนำแนวทางนี้ไปใช้บน Ubuntu หรือ Debian คำสั่งตรวจและ restart โดยทั่วไปจะเป็น:

```sh
sudo /usr/sbin/sshd -t && sudo systemctl restart ssh
```

### ตรวจบัญชีและ key ที่ยังใช้อยู่

ดูชื่อบัญชีที่มีค่า `*` และบัญชีที่ใช้ hash แบบ `$5$` หรือ `$6$` ได้ด้วยคำสั่งต่อไปนี้ โดยรันด้วยสิทธิ์ root:

```sh
awk -F: '$2 == "*" { print $1 }' /etc/shadow
awk -F: '$2 ~ /^\$(5|6)\$/ { print $1 }' /etc/shadow
```

สองคำสั่งนี้ช่วยตรวจเงื่อนไขด้านบัญชี แต่ยังต้องดูแพ็กเกจและ configuration ประกอบด้วย

จากนั้นตรวจ `authorized_keys` ของแต่ละบัญชีที่อนุญาตให้เข้า SSH หากใช้ตำแหน่งมาตรฐาน คำสั่งนี้จะแสดง key ของบัญชีที่กำลังใช้งานอยู่:

```sh
cat ~/.ssh/authorized_keys
```

key ของคนหรือเครื่องที่เลิกใช้งานแล้วก็ควรถอดออก ส่วนบัญชี service ให้ตรวจว่าจำเป็นต้องมี shell หรือเข้า SSH หรือไม่ ก่อนเปลี่ยนค่า เพราะบางงานอาจยังใช้บัญชีนั้นสำหรับ deploy, backup หรือ SFTP

</steps>

ถ้าเปิด SSH ออก internet ตรง ๆ อาจเพิ่ม fail2ban หรือ rate limit เพื่อลดการลอง login ซ้ำ ส่วนการย้ายออกจาก port 22 ช่วยลด bot บางส่วนและทำให้ Logs เงียบลงได้ แต่การแก้ sshole ยังต้องอัปเดตแพ็กเกจหรือปิด password authentication ตามข้างต้นครับ

## แหล่งอ้างอิง

ข้อมูลช่องโหว่ตรวจสอบ ณ วันที่ 6 พฤศจิกายน 2026

- [OpenSSH 10.6p2 release notes](https://www.openssh.org/txt/release-10.6p2)
- [Red Hat: CVE-2026-123158](https://access.redhat.com/security/cve/cve-2026-123158)
- [*sshole: รายละเอียดทางเทคนิคจากผู้ค้นพบ](https://sshhole.com/)
- [Alpine Linux: openssh-server password authentication bypass](https://www.alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/)
- [Wakefield: OpenSSH Password Authentication Bypass on musl Linux](https://wakefield.co/research/openssh-musl-authentication-bypass)
- [musl: src/crypt/crypt_sha512.c](https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_sha512.c)
- [OpenSSH: sshd manual](https://man.openbsd.org/sshd.8)
- [OpenSSH: sshd_config manual](https://man.openbsd.org/sshd_config.5)
- [Alpine Linux: Setting up a new user](https://wiki.alpinelinux.org/wiki/Setting_up_a_new_user)

## Sitemap

See the full [sitemap](https://iamthana.com/sitemap.md) for all pages.
