본문으로 건너뛰기
effidevFlutter · Cloudflare 엣지 · 클라우드 비용 최적화

Alpine SSH 구멍, 비번 없는 계정이 열린다

effidev

Alpine SSH 구멍 비번 없는 계정이 열린다 257자 넘는 비번 하나로

서버에 로그인하려고 비밀번호 칸을 열고, 아무 글자나 257개 넘게 붙여 넣는다. 그 계정에는 비밀번호가 아예 없다. 그런데 로그인이 된다. 알파인(Alpine) 리눅스의 SSH 서버에 실제로 있었던 일이다.

11월 5일 OpenSSH 프로젝트가 고친 판(10.6p2)을 냈고, Alpine이 같은 날 보안 공지를 올렸다. 이 글은 2026년 11월 6일 아침 기준으로 두 문서와 발견자의 설명을 읽고 정리했다.

이 글은 세 부류에게 필요하다. Alpine을 가상 서버나 클라우드 이미지로 쓰면서 SSH 포트를 열어 둔 사람, 라우터나 임베디드 장비용 리눅스 이미지를 직접 만드는 사람, 그리고 “도커에서 알파인을 쓰는데 나도 해당되나”가 궁금한 사람이다.

먼저 결론이다. Alpine SSH 구멍은 조건 다섯 개가 한꺼번에 맞아야 열린다. 하나만 어긋나도 열리지 않는다. 컨테이너 안에서 sshd(서버 쪽 SSH 프로그램)를 돌리지 않으면 해당 없다고 Alpine이 공지에 적었다.

해당되면 apk upgrade -U -a로 패키지를 올린 뒤 rc-service sshd restart로 sshd를 다시 켜야 끝난다. 올리기 전까지 버티는 방법은 비밀번호 로그인을 끄는 것이다.

용어 셋을 먼저 짚는다. SSH는 멀리 있는 서버에 로그인하는 방식이고, sshd는 서버에서 그 로그인을 받는 프로그램이다. musl은 리눅스 프로그램들이 공통으로 부르는 기본 함수 모음(C 라이브러리) 가운데 가벼운 쪽으로, Alpine이 쓴다. 우분투·데비안·페도라는 다른 모음(glibc)을 쓴다.

PAM은 리눅스의 로그인 확인 부품이다. sshd가 PAM을 거치도록 만들어져 있으면 이번 구멍에서 비껴난다. Alpine의 기본 openssh-server 패키지는 PAM 없이 만들어져 있다.

11월 5일 하루에 나란히 나온 문서 셋

구멍이 알려진 경로는 문서 셋이고, 날짜가 모두 11월 5일이다.

문서 낸 곳 이 글이 옮긴 핵심
OpenSSH 10.6p2 릴리스 노트 OpenSSH 프로젝트 musl 리눅스에서 PAM을 끈 sshd에 영향을 주는 “중대한 보안 우회책”을 담았다. 원인은 sshd 밖에 있다고 보지만, sshd에서 피할 수 있게 고쳤다
보안 공지 Alpine Linux sshd를 돌린다면 지금 openssh를 올리고 sshd를 다시 켜라. 글을 쓸 당시 CVE 번호는 배정되지 않았다
sshhole.com과 GitHub 저장소 발견자 Özgün Kültekin(Wakefield) 구멍의 역사, 브라우저 시연, 내 서버 점검 스크립트

발견자 이름은 OpenSSH 노트와 Alpine 공지에 같이 적혀 있다. OpenSSH 노트는 뿌리가 sshd 밖에 있다고 썼고, Alpine 공지는 musl의 crypt() 동작을 원인으로 설명한다. 두 문서가 같은 곳을 가리킨다.

해커뉴스에서 Alpine 공지 글은 이 글을 쓰는 시점에 534점, 댓글 105개였다.

비밀번호 칸에 257자를 넣으면 생기는 일

Alpine 공지와 발견자 설명을 합치면 순서는 이렇다.

  1. 리눅스는 계정마다 비밀번호의 지문(해시)을 /etc/shadow 파일에 적어 둔다. 로그인하면 sshd가 입력값으로 같은 지문을 만들어 비교한다. 지문 앞에는 $6$abc…처럼 방식($6$은 SHA-512)과 소금(salt, 지문을 섞는 임의의 문자열)이 붙는다.
  2. 비밀번호가 아예 없는 계정은 지문 칸에 * 한 글자를 적는다. *은 지문으로 나올 수 없는 값이라, 어떤 비밀번호도 맞을 수 없다는 표시다. Alpine은 3.16 무렵부터 root를 이렇게 둔다.
  3. PAM 없이 만든 sshd는 crypt()(비밀번호를 지문으로 바꾸는 C 라이브러리 함수)를 직접 부른다. *만 적힌 계정에는 소금이 없으니, sshd는 다른 계정의 소금을 빌려 계산한다.
  4. musl의 crypt()는 비밀번호가 256바이트를 넘으면 계산을 포기하고, 오류 표시로 문자열 *을 돌려준다.
  5. 저장된 칸도 *이고 계산 결과도 *이니 “같다”로 읽혀 로그인이 통과한다. 공지는 열리는 길이를 257~1024바이트로 적었다.

한 문장으로 줄이면 이렇다. 오류를 알리는 표시와 비밀번호가 없다는 표시가 같은 글자 *이었다.

4번은 musl 소스에서 직접 확인했다. src/crypt/crypt_sha512.c에 #define KEY_MAX 256이 있고, 파일 머리 주석에 on error "*" is returned라고 적혀 있다. 오류일 때 "*"을 돌려주는 줄도 있다.

아래는 발견자가 정리한 연표다. 내가 소스로 확인한 건 위의 musl 코드 두 줄뿐이고, 나머지는 발견자의 설명을 옮긴 것이다.

시기 발견자가 적은 일
2012년 8월 musl이 crypt() 실패 때 NULL 대신 *을 돌려주게 바꾸고, 20일 뒤 SHA-512 방식에 256바이트 상한을 넣었다
2016년 sshd가 없는 계정을 응답 시간 차이로 가려내는 문제(CVE-2016-6210)를 고치면서, 다른 계정의 소금을 빌려 계산하게 했다
2022년 Alpine 3.16이 root의 비밀번호 칸을 !에서 *로 바꿨다. !는 키 로그인까지 막아 클라우드 이미지가 쓸 수 없었다
2026년 11월 5일 OpenSSH 10.6p2와 Alpine 고친 패키지

발견자는 이 구멍이 2016년부터 있었다고 적는다. musl의 2012년 동작과 sshd의 2016년 수정이 만난 시점부터라는 뜻이다.

Alpine SSH 구멍이 열리는 다섯 칸

Alpine 공지가 “영향을 받는 조건”으로 적은 것을 칸으로 나눴다. 다섯 칸이 모두 “예”여야 열린다.

칸 조건 덧붙임
1 PAM 없는 sshd Alpine 기본 openssh-server, 또는 openssh-server-pam에 UsePAM no를 준 경우. UsePAM yes면 해당 없다
2 PasswordAuthentication yes OpenSSH 기본값이다. 직접 no로 바꾸지 않았다면 켜져 있다
3 비밀번호 칸이 *인 계정이 있다 root(3.16 무렵부터), 키로만 쓰는 계정, musl-nscd로 붙인 디렉터리 서비스 계정, 공식 tiny-cloud 이미지의 alpine 사용자
4 $5$나 $6$ 지문을 가진 계정이 하나 이상 있다 누군가 한 번이라도 passwd로 비밀번호를 정했다면 해당한다
5 공격자가 SSH 포트에 닿고, 3번 계정의 이름을 안다 root나 alpine처럼 흔한 이름이면 어렵지 않다

4번이 의외로 낮은 문턱이다. 비밀번호가 있는 계정이 딱 하나만 있어도, 그 한 개가 나머지 비밀번호 없는 계정을 전부 열어 준다고 공지는 적었다.

root 계정은 기본 설정(PermitRootLogin prohibit-password)이 비밀번호 로그인을 막아 주므로, PermitRootLogin yes로 바꿔 둔 경우가 아니면 root로는 안 열린다. 하지만 Alpine 공지는 이게 위안이 못 된다고 적었다. 로그인되는 다른 계정이 doas(권한을 올리는 명령) 같은 길로 root가 될 수 있으면 결과가 같기 때문이다.

공지가 “해당 없음”으로 꼽은 경우도 정리해 둔다.

발견자 문서는 glibc를 쓰는 우분투·데비안·페도라, PAM을 거치는 서버, dropbear, OpenBSD도 “문제 없음” 쪽에 넣었다.

내 서버가 해당하는지 보는 명령, 순서대로

root 권한으로 서버 안에서 돌린다. ①~③은 위 표의 1·2칸, ④는 3·4칸이다.

# ① musl 인가? (Alpine이면 musl이라 건너뛰어도 된다) — 첫 줄에 musl이 찍히면 musl이다
ldd --version 2>&1 | head -1

# ② sshd 패키지가 PAM판인가 — 이름 끝에 -pam이 붙은 줄이 있으면 PAM판이다
apk list -I 2>/dev/null | grep openssh-server

# ③ 비밀번호 로그인이 켜져 있나
sshd -T | grep -i '^passwordauthentication'

# ④ 비밀번호 칸이 * 인 계정 / $5$·$6$ 지문이 있는 계정
grep -E '^[^:]+:\*:' /etc/shadow | cut -d: -f1
grep -E '^[^:]+:\$[56]\$' /etc/shadow | cut -d: -f1

읽는 법은 간단하다. ①이 musl이고, ②에 -pam이 없고(또는 PAM판인데 UsePAM no), ③이 yes이고, ④의 두 목록이 모두 비어 있지 않으면 Alpine SSH 구멍이 열려 있는 상태다.

④의 첫 목록에만 계정이 있고 둘째 목록이 비었다면 지금은 안 열린다. 하지만 누군가 passwd를 한 번 치는 순간 열린다. 공식 클라우드 이미지가 정확히 이 상태로 나온다(아래 절).

공지는 musl-nscd로 붙인 디렉터리 서비스 계정(nslcd·winbind·sssd)도 3번 부류로 센다. 이들은 shadow 항목이 없다고 공지가 적었으니 ④로는 잡히지 않는다. 그런 구성이면 ①~③이 걸리는 순간 해당으로 보는 편이 안전하다.

발견자는 저장소에 점검 스크립트(check.sh)를 올려 뒀다. 나는 그 스크립트를 열어 읽지 않았다. 쓸 사람은 root로 바로 돌리지 말고 먼저 내용을 읽어 보길 권한다.

컨테이너는 왜 대부분 비껴가나

Alpine 공지는 “컨테이너에서 sshd를 돌리지 않는 도커 등 사용자는 해당 없음”이라고 적었다. 반면 발견자 페이지 첫머리는 “당신이 받아 둔 도커 이미지 상당수”를 musl 계열의 예로 든다.

두 문장은 같은 말로 읽을 수 있다. 그 이미지들이 musl 위에서 만들어졌다는 말과, 그 안에서 sshd가 실제로 도는 경우만 구멍이 된다는 말은 충돌하지 않는다. 이건 내 해석이다.

내 컨테이너가 해당되는지는 세 가지로 가른다.

셋 모두 “예”면 컨테이너 안에서 위의 다섯 칸을 똑같이 확인한다. 하나라도 “아니오”면 이번 구멍과 무관하다.

해커뉴스 댓글에는 도커 생태계에서 베이스 이미지가 늦게 갱신되는 일이 많아 영향이 번질 거라는 걱정이 달렸다. 숫자나 근거는 없는 우려라서 참고만 한다.

클라우드 이미지의 alpine 사용자는 곧 root였다

Alpine 공지에서 가장 조심스럽게 읽을 부분이다. 공식 tiny-cloud 이미지(qcow2·VHD·AMI)는 3.18.11부터 3.24.3까지 alpine 사용자를 넣고 나온다. 이 사용자의 비밀번호 칸은 *이고, 비밀번호 없이 doas를 쓸 수 있다.

첫 부팅 때는 안전하다. 비밀번호 있는 계정이 없으니 4번 칸이 비어 있기 때문이다. 하지만 관리자가 콘솔 비밀번호를 정하거나, user-data로 hashed_passwd 계정을 추가하는 순간 4번 칸이 채워진다. 그러면 공격자는 alpine 사용자로 셸을 얻고, doas로 root가 된다.

고친 패키지가 들어간 이미지는 3.21.10, 3.22.8, 3.23.8, 3.24.4 이후에 빌드된 것이다. 이미 떠 있는 인스턴스는 이미지를 바꾼다고 고쳐지지 않으니, 실행 중인 서버마다 openssh를 올리거나 비밀번호 로그인을 꺼야 한다.

고치는 법: 패키지를 올린 다음 sshd를 다시 켜야 끝난다

Alpine SSH 구멍을 막는 가장 확실한 길은 업데이트다. Alpine 공지의 명령 그대로다.

apk upgrade -U -a
rc-service sshd restart

둘째 줄을 빼면 안 된다. 패키지만 올리면 이미 떠 있는 sshd는 옛 프로그램 그대로 돈다고 공지가 따로 강조했다. 고친 openssh 버전은 아래와 같다.

브랜치 고쳐진 openssh
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

3.21은 11월 1일 지원이 끝난 판인데도 고친 패키지가 나왔다. 공지는 3.16까지 거슬러 올라가 모든 판에 패키지를 냈다고 적었다.

지금 올릴 수 없으면 비밀번호 로그인부터 끈다. 키로 로그인되는지 먼저 확인하고, 지금 연결한 세션은 닫지 말고 새 창으로 접속을 시험해 보는 순서다.

mkdir -p /etc/ssh/sshd_config.d
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00-no-passwords.conf
sshd -t && rc-service sshd restart
sshd -T | grep -i passwordauthentication

마지막 줄이 passwordauthentication no를 찍어야 성공이다. 이 임시 방벽은 3.19부터 기본 설정이 sshd_config.d 폴더를 읽는다는 전제다. sshd_config 안에 PasswordAuthentication yes를 직접 적어 둔 서버를 위해 공지에는 sed를 쓰는 더 긴 명령이 따로 실려 있으니, 마지막 줄이 yes를 찍으면 공지 원문을 열어 그 명령을 쓴다.

OpenSSH를 직접 빌드해 쓰는 사람은 10.6p2로 올리면 된다. Alpine 말고 musl을 쓰는 다른 배포판의 패키지 공지는 이 글이 확인하지 못했다. 발견자 저장소는 OpenWrt·Buildroot·Yocto·postmarketOS·Void musl·Chimera를 musl 계열로 꼽았다.

이미 들어왔는지 확인하는 순서

Alpine 공지가 안내한 순서를 따랐다.

  1. 로그를 본다. 기본 로그 파일은 /var/log/messages다.
grep 'Accepted password for' /var/log/messages

비밀번호가 없는 계정(alpine, root, 키로만 쓰는 계정) 이름 옆에 Accepted password가 찍혀 있으면 이상 신호다. 비밀번호가 있는 일반 사용자의 정상 로그인도 같은 문구로 찍히니, 위 ④에서 뽑은 * 계정 이름과 맞춰 본다.

  1. doas를 쓴 기록, 최근 만들어진 사용자와 파일, authorized_keys에 낯선 열쇠가 들어 있는지 본다. 열쇠 파일을 확인하는 방식은 지난 글 클로드 코드 확장 백도어 11시간의 “낯선 열쇠가 있는지 보는 두 줄” 절에 적었다. 그 글의 svc-telemetry 이름표는 이번 건과 무관하다. 파일을 연다는 방식만 참고하면 된다.

  2. 로그인이 없었다고 단정할 수 없으면 그 서버는 침해된 것으로 본다는 것이 Alpine 공지의 입장이다.

댓글이 붙든 질문: 오류 표시가 정상 값과 같아도 되나

해커뉴스 댓글에서 눈에 띈 갈림은 세 가지다. 모두 댓글 쓴 사람의 의견이다.

첫째, 설계 질문이다. 한 사용자는 POSIX 표준이 crypt()의 성공 때 반환값을 어떻게 적었는지 인용하며, musl이 오류를 *로 알리는 건 이례적인 선택이 아니냐고 물었다. 발견자 페이지도 같은 쪽 이야기를 한다. 표준은 오류 때 NULL을 돌려준다고 읽히는데, 많은 프로그램이 NULL을 검사하지 않고 비교하다 죽기 때문에 musl이 NULL 대신 *을 골랐다는 것이다.

둘째, 언어 논쟁이다. 오류를 특수한 값으로 알리는 C의 관습 자체가 문제이고, 성공과 실패를 타입으로 가르는 방식(Rust의 Result 같은)이 낫다는 댓글들이 달렸다. 다른 댓글은 Alpine과 musl을 쓰지 말라고 늘 말하는데, 정적 링크가 이득이라는 답이 돌아온다고 썼다.

셋째, 범위 질문이다. 한 댓글은 공지의 4번 조건이 사실상 “비밀번호가 있는 다른 계정 아무나”라는 뜻인데 공지 문구만으로는 그게 잘 안 전해진다고 짚었다. 이 글의 표에서 4번 칸을 따로 뺀 이유다.

발견자 페이지에는 비껴간 이야기가 하나 더 있다. 데비안은 2019년에 libxcrypt라는 라이브러리로 옮겼는데, 이 라이브러리는 오류 때 *0을 돌려준다고 한다. *과 한 글자 차이라서 /etc/shadow의 *과 겹치지 않는다. 오류 표시는 정상 값으로 나올 수 있는 모양과 달라야 한다는 오래된 교훈이 이번에는 한 글자 차이로 갈렸다.

AI 에이전트 떼가 찾아낸 구멍과 OpenSSH가 밝힌 릴리스 방침

발견자 페이지에 따르면 구멍을 찾은 과정이 이렇다. glibc·musl, PAM 있음·없음, shadow 있음·없음을 조합해 sshd를 여러 방식으로 빌드하고, 이상한 지문·망가진 계정·너무 긴 비밀번호로 약 17,600번 로그인을 시도했다. 모든 빌드가 같은 답을 냈는데, musl에 PAM이 없는 빌드만 안 되어야 할 로그인을 받아 줬다.

발견자는 이 시도를 AI 에이전트 여럿(그의 표현으로 “agent swarms”)으로 돌렸다고 적었다. 사람이 흔히 안 보는 곳, 곧 암호 코드가 아니라 OpenSSH가 C 라이브러리와 대화하는 지루한 부분에서 나온 구멍이다.

OpenSSH 10.6p2 릴리스 노트는 이 흐름을 방침으로 적었다. AI로 찾은 보안 보고가 많이 들어오고, AI가 찾은 버그를 다른 연구자가 따로 또 찾는 일이 있었다. 그래서 OpenSSH 팀은 당분간 수정을 모았다 내지 않고 더 자주 릴리스한다고 했다. 노트에는 AI 보고 상당수가 현실적인 위협 모델에선 보안 영향이 없다고 판정된다는 문장도 같이 있다.

내 권고는 단순하다. 패치가 더 자주 나오니, 서버의 업데이트 확인 간격을 월 단위가 아니라 주 단위로 줄이는 쪽이 낫다. AI가 찾은 구멍이 곧바로 공격 코드로 이어진 사례는 앞서 두 줄로 고친 리눅스 원격 root 구멍 글에서 다뤘다.

재현은 못 했다, 대신 소스와 맞춰 본 것

이 글을 쓴 PC에는 도커도 Alpine 환경도 없다. 로그인을 직접 시험해 보지 못했다. 확인한 것과 못 한 것을 나눠 적는다.

확인한 것:

확인하지 못한 것:

읽은 원문과 날짜