OpenSSH 인증 우회 취약점(*sshole, CVE-2026-123158) 해부: 257바이트 비밀번호가 Alpine Linux를 뚫는 원리와 10.6p2 패치 점검하기
257바이트 이상의 긴 비밀번호로 Alpine Linux 등 musl 기반 OpenSSH의 비밀번호 미설정 계정(*) 인증을 우회하는 CVE-2026-123158(*sshole) 원리와 10.6p2 패치를 정리한다. 노출 조건 5가지와 컨테이너·클라우드 이미지 점검 절차를 살펴본다.
257바이트 이상의 긴 비밀번호 입력이 musl crypt()에서 ""로 변환되어 /etc/shadow의 "" 계정과 충돌하던 경로를 OpenSSH 10.6p2의 4글자 미만 해시 거부 방패가 차단하고 SSH 공개키 인증만 통과시키는 개념 일러스트다. 실제 콘솔 화면이 아니다.
2026년 11월 5일 OpenSSH 프로젝트와 Alpine Linux가 257~1,024바이트 길이의 임의 비밀번호를 입력하는 것만으로 비밀번호 로그인이 비활성화된 계정(/etc/shadow의 비밀번호 필드가 *인 계정)에 즉시 로그인할 수 있는 치명적인 인증 우회 취약점인 *sshole(CVE-2026-123158, CVSS 3.1 8.1 High)을 수정한 OpenSSH 10.6p2 및 Alpine 브랜치별 보안 패치를 긴급 배포했다. OpenSSH 10.6p2 릴리스 노트, OX Security 기술 분석, Cycode 권고문
결론부터 말하면 이 취약점은 암호학이나 메모리 손상 버그가 아니라, musl C 라이브러리의 crypt() 오류 반환값("*")과 Linux /etc/shadow의 비밀번호 미설정 표시자("*")가 PAM 없는 sshd에서 정면 충돌해 발생한 논리 결함이다. Debian·Ubuntu·RHEL 등 glibc 기반 배포판이나 UsePAM yes, PasswordAuthentication no 환경은 영향을 받지 않는다. 반면 Alpine Linux의 기본 openssh-server 패키지처럼 musl + 비-PAM + 비밀번호 인증 허용 조합을 쓰는 서버·컨테이너·클라우드 이미지는 단 한 번의 접속 시도로 뚫릴 수 있으므로, 즉시 패키지를 업그레이드한 뒤 실행 중인 sshd 데몬을 재시작해야 한다.
이 글은 OpenSSH 공식 릴리스 노트와 Cycode·OX Security·Konfirmity의 공개 분석 자료를 교차 검증해 정리한 대응 가이드다. 실제 상용 서버를 대상으로 공격을 수행하지 않았으며, 로컬 Python 검증 코드는 musl crypt() 센티널 충돌 원리와 설정 감사 로직만 시뮬레이션한다.
1. 세 가지 정상적인 설계가 만나 만든 10년 만의 인증 우회 원리
CVE-2026-123158은 단일 컴포넌트의 실수가 아니라, 서로 다른 시점에 도입된 세 가지 합리적인 설계가 PAM 없는 musl 환경에서 결합하며 발생했다.
| 구성 요소 | 개별 설계 의도 | 실제 동작 방식 | 세 요소가 결합했을 때 발생하는 충돌 |
| 1. musl libc의 crypt() 길이 제한 | 과도하게 긴 비밀번호 입력으로 인한 CPU 연산 고갈(DoS) 방지 | 비밀번호 입력이 256바이트를 초과(257~1,024바이트)하면 해싱을 거부하고 실패 표시로 문자열 "*"를 반환함 | 해싱 실패 반환값( "*")이 계정의 비밀번호 비활성화 마커("*")와 글자 그대로 동일해짐 |
| 2. OpenSSH의 계정 열거 방지 로직 (2016년~) | 타이밍 차이로 특정 사용자 계정의 존재 여부가 노출되는 것을 방지 | 대상 계정에 유효한 해시가 없더라도, 시스템 내 다른 계정의 정상 해시( $5$, $6$ 등)에서 솔트 설정을 빌려와 crypt()를 호출한 뒤 결과를 대상 계정의 /etc/shadow 필드와 비교함 | 비밀번호가 없는 계정에 대해서도 crypt(긴_비밀번호, 타_계정_솔트)가 실제로 실행되어 "*"가 반환됨 |
| 3. Alpine Linux의 /etc/shadow * 표기 (2022년 Alpine 3.16~) | 비밀번호 로그인은 막되 SSH 공개키 로그인이나 doas는 허용 | 키 전용 계정이나 공식 클라우드 이미지( 3.18.11~3.24.3)의 기본 alpine 계정 비밀번호 필드에 ! 대신 *를 기록함 | sshd가 crypt() 반환값("*")과 /etc/shadow 저장값("*")을 비교해 일치(True)로 판정하고 즉시 로그인을 승인함 |
OpenSSH 10.6p2는 이 연결고리를 끊기 위해 /etc/shadow에 저장된 비밀번호 해시 문자열의 길이가 4글자 미만이면 유효한 해시로 인정하지 않고 무조건 거부하도록 수정했다. 따라서 crypt()가 "*"(1글자)를 반환하더라도 절대 인증이 성립하지 않는다.
2. 내 서버와 컨테이너는 영향 대상인가: 노출 조건 5가지와 비영향 환경
버전 배너만 보고 과잉 대응하거나 반대로 컨테이너 내부의 sshd를 놓치지 않으려면, 아래 다섯 가지 교집합 조건을 정확히 대조해야 한다. 다섯 가지가 모두 참(True)일 때만 공격에 노출된다.
| 점검 항목 | 취약점 노출 조건 (5가지 모두 충족 시 취약) | 영향 받지 않는 안전 조건 (하나라도 해당하면 비영향) |
| 1. C 표준 라이브러리 및 SSH 서버 | musl libc 기반으로 빌드된 OpenSSH sshd (Alpine Linux, 또는 OpenSSH를 별도 설치한 OpenWrt·Buildroot·Yocto) | Debian, Ubuntu, RHEL, Fedora 등 glibc 기반 배포판, 또는 OpenWrt 기본값인 Dropbear SSH 사용 환경 |
| 2. PAM 사용 여부 | PAM 없이 동작 (Alpine 기본 openssh-server 패키지, 또는 openssh-server-pam에서 UsePAM no 설정) | UsePAM yes로 PAM 모듈을 통해 비밀번호 인증을 위임하는 환경 |
| 3. 비밀번호 인증 활성화 여부 | PasswordAuthentication yes (OpenSSH 기본값) | PasswordAuthentication no로 공개키 인증만 허용하는 환경 (Gitea, Gogs, Jenkins SSH 에이전트 기본 이미지 등) |
| 4. 대상 계정의 /etc/shadow 값 | 비밀번호 필드가 정확히 *인 계정 존재 (키 전용 계정, Alpine 공식 클라우드 이미지의 alpine 계정 등). 단, root는 기본 PermitRootLogin prohibit-password로 보호됨 | 비밀번호 비활성화 계정이 * 대신 !(또는 !*)로 잠겨 있는 경우 |
| 5. 솔트 공여(Donor) 계정 존재 여부 | 동일 시스템의 /etc/shadow 내에 실제 비밀번호 해시($5$, $6$ 등)를 가진 계정이 최소 1개 이상 존재 | 시스템 내 모든 계정에 비밀번호 해시가 전혀 없거나, glibc 전용 yescrypt($y$) 해시만 복사되어 있는 경우 |
특히 주의할 대상은 Alpine 공식 클라우드 이미지(3.18.11~3.24.3)다. 해당 이미지의 기본 사용자인 alpine 계정은 비밀번호 필드가 *이면서 비밀번호 없이 루트 권한 명령을 실행할 수 있는 doas 권한을 갖고 있어, 257바이트 비밀번호 한 번으로 로그인한 즉시 루트 권한 탈취로 이어진다.
3. Alpine Linux 브랜치별 수정 패키지 버전과 즉시 조치 명령어
Alpine Linux 보안팀은 현재 지원이 종료된 구버전 브랜치(3.16~3.19)까지 포함해 3.16부터 edge까지 모든 브랜치에 백포트 패치를 배포했다. 단, Alpine 3.15 이하 컨테이너 이미지는 수정 패키지가 제공되지 않으므로 반드시 베이스 이미지를 올리거나 PasswordAuthentication no를 강제해야 한다.
| Alpine 브랜치 | 수정된 openssh 패키지 버전 | 비고 및 조치 기준 |
| edge | 10.6_p2-r0 이상 | Portable OpenSSH 10.6p2 원문 반영 |
| 3.24 / 3.23 / 3.22 | 3.24: 10.3_p1-r2 / 3.23: 10.2_p1-r1 / 3.22: 10.0_p1-r11 | 4글자 미만 저장 해시 거부 패치 백포트 완료 |
| 3.21 / 3.20 / 3.19 | 3.21: 9.9_p2-r1 / 3.20: 9.7_p1-r6 / 3.19: 9.6_p1-r3 | 백포트 패치 적용 후 반드시 sshd 프로세스 재시작 필요 |
| 3.18 / 3.17 / 3.16 | 3.18: 9.3_p2-r4 / 3.17: 9.1_p1-r7 / 3.16: 9.0_p1-r6 | 3.15 이하는 패치 미제공 (즉시 PasswordAuthentication no 설정 또는 베이스 이미지 업그레이드 필수) |
운영 중인 Alpine 호스트나 컨테이너에서는 다음 3단계 명령으로 노출 여부 확인, 패치 및 데몬 재시작, 침해 흔적 점검을 순서대로 수행한다.
4. 로컬 Python으로 검증하는 musl crypt() 충돌 원리와 노출 감사기
왜 256바이트 이하의 비밀번호는 실패하고 257바이트 이상의 비밀번호만 * 계정을 뚫는지, 그리고 OpenSSH 10.6p2의 4글자 미만 해시 거부 패치가 이를 어떻게 차단하는지 표준 Python 코드로 명확히 재현해 볼 수 있다.
직접 실행해 확인한 범위
로컬 Python 환경에서 다섯 가지 경계 조건을 실행해 검증했다.
- 257바이트 비밀번호 입력 시 우회 발생:
target_shadow_field="*"이고 타 계정의$6$솔트가 존재할 때, 257바이트 길이의 비밀번호는vulnerable_sshd_verify()에서True를 반환한다.
- 256바이트 이하 일반 비밀번호 실패: 12바이트 비밀번호는 정상 해시 문자열로 변환되어
"*"와 일치하지 않으므로False가 된다.
- OpenSSH 10.6p2 패치(
len < 4차단): 동일한 257바이트 입력에 대해patched_10_6p2_sshd_verify()는target_shadow_field길이가 1글자이므로 해싱 비교 전에 즉시False로 차단한다.
!로 잠긴 계정의 비영향 확인:/etc/shadow필드가"!"인 계정은crypt()가"*"를 반환해도("*" != "!") 뚫리지 않는다.
- 호스트 설정 감사:
root: *,alpine: *,locked_svc: !,admin: $6$...가 공존하고PermitRootLogin prohibit-password인 기본 설정에서 실제로 외부 로그인에 노출되는 계정이["alpine"]임을 정확히 식별했다.
5. 실무 보안 점검 체크리스트
- 컨테이너 베이스 이미지 재빌드 및 재배포: 실행 중인 서버에서
apk upgrade를 수행하는 것만으로는 CI/CD 레지스트리에 저장된 Docker 이미지가 고쳐지지 않는다.FROM alpine:...으로openssh-server를 설치하고chpasswd로 다른 계정에 비밀번호를 부여하는 모든 Dockerfile을 찾아 재빌드·재배포한다.
- 비-PAM 환경에서 키 전용 계정은
*대신!로 잠금:/etc/shadow에서 비밀번호 로그인을 허용하지 않을 계정의 두 번째 필드를*대신passwd -l <user>(!)로 관리하면 설령 구버전 바이너리가 남아 있더라도 해시 실패 반환값(*)과 일치하지 않는다.
- AI 기반 취약점 재발견 속도에 맞춘 패치 주기 단축: OpenSSH 메인테이너들은 이번 10.6p2 릴리스 노트에서 AI 도구들이 제보된 버그를 수 시간 내에 재발견하기 때문에 더 이상 긴 비공개 유예 기간(Embargo)을 두지 않고 패치가 준비되는 즉시 배포하겠다고 밝혔다. 외부에 SSH 포트가 열린 인프라는 패치 공개 당일 반영할 수 있는 자동화 체계를 갖춰야 한다.
참고 자료
확인 기준일: November 7, 2026
English version:
OpenSSH Auth Bypass on Alpine Linux (*sshole, CVE-2026-123158): How 257-Byte Passwords Collide with musl crypt() and What 10.6p2 Fixes