1980년, 유닉스 개발자 빌 조이(Bill Joy)는 아무도 로그인해서는 안 되는 daemon 계정의 비밀번호 자리에 별표(*)를 넣었습니다. 당시에는 비밀번호 해시도 누구나 읽을 수 있는 /etc/passwd에 저장되었는데, *는 해시 암호가 아니므로 어떤 비밀번호와도 일치할 수 없어 로그인을 원천 차단하는 장치로 기능했습니다. 같은 항목의 이름(real name) 필드에는 'the devil himself'라고 적혀 있었습니다 1.

그런데 2026년, 이 *가 오히려 '257자에서 1024자 사이의 아무 글자나 입력하면 통과'시키는 열쇠로 변했습니다. 이를 발견한 연구자가 공개한 'sshhole' 취약점은, 서버가 응답 속도 차이로 계정 정보를 숨기려는 타이밍 공격 방어 로직과 리눅스 라이브러리 musl의 비밀번호 길이 제한이 우연히 겹칠 때 발생합니다 2. 특히 PAM(플러그인 인증 모듈)을 사용하지 않는 Alpine 리눅스나 OpenWrt 같은 가벼운 환경에서 나타납니다. OpenSSH 10.6p2 버전(2026년 11월 5일 배포)에서 이 문제는 수정되었습니다 3.

1980년 '아무도 못 들어오게' 막은 *가 46년 뒤 열쇠가 된 사연

*가 열쇠가 되는 과정: 세 가지 규칙의 충돌

sshhole 취약점은 서로 다른 시점과 목적에서 만들어진 세 가지 보안 장치가 한 줄의 비교 연산에서 만나며 발생합니다. 각 장치는 그 자체로 타당했지만, 결합되었을 때 인증 우회를 허용했습니다.

  1. musl 라이브러리의 실패 반환값 변경 (2012년): C 표준 라이브러리 중 하나인 musl은 crypt() 함수(비밀번호 해시를 계산하는 함수)가 실패했을 때 NULL 대신 *를 반환하도록 수정했습니다. 이는 개발자가 NULL 값을 처리하지 않아 프로그램이 꺼지는 것을 방지하기 위한 조치였습니다. 정상적인 비밀번호 해시는 $6$salt$hash 같은 형태이므로, 개발자들은 *가 실제 비밀번호와 일치하지 않을 것이라고 가정했습니다 2.

  2. 256자 길이 제한 도입 (2012년): 같은 시기 musl은 SHA-512 해시 계산에 256바이트라는 길이 제한을 걸었습니다. 비밀번호가 길수록 계산 시간이 늘어나므로, 매우 긴 입력으로 서버가 오래 점유되어 다른 접속에 영향을 주는 것을 막기 위함입니다. 입력이 256자를 넘으면 계산조차 하지 않고 즉시 실패값인 *를 돌려보냅니다 2.

  3. OpenSSH의 타이밍 공격 방어 (2016년): OpenSSH는 '존재하지 않는 계정'으로 접속할 때 응답 시간이 짧아져 해커가 계정 정보를 알아내는 타이밍 공격을 막기 위해 로직을 바꿨습니다(CVE-2016-6210). 원래는 해시가 없는 계정(* 등)에 로그인 시도가 들어오면 즉시 거부했지만, 수정 후에는 실제 해시를 계산하듯 '다른 계정의 솔트(보안 난수)'를 빌려와 가상으로 해시를 계산한 뒤 실패하도록 만든 것입니다 2.

충돌 지점: *로 표시된 계정에 257자 이상(1024자 이하)의 비밀번호로 접속하면 다음과 같은 흐름으로 인증이 통과됩니다. 단, 이 우회가 작동하려면 먼저 일반적인 길이의 잘못된 비밀번호로 한 번 실패한 이력이 있어야 합니다 2.

  1. OpenSSH는 저장된 값이 *이므로 실제 해시 계산이 불가능하다고 판단합니다.
  2. 타이밍 일관성을 유지하기 위해 다른 계정의 솔트를 빌려, 사용자가 입력한 257자 비밀번호로 해시를 계산하려 합니다.
  3. 서버가 musl 라이브러리를 사용 중이므로, 256자를 넘긴 입력에 대해 계산 결과로 *를 반환합니다.
  4. 서버는 '계산된 결과(*)'와 '저장된 비밀번호 필드(*)'가 문자열로 일치하므로 인증 성공으로 처리합니다 2.

17,600번의 테스트로 확인된 조건

이 취약점은 모든 서버에서 나타나는 것이 아닙니다. sshhole.com을 운영하며 이 문제를 공개한 연구자는 glibc와 musl 라이브러리, PAM 사용 여부, shadow 파일 설정을 조합해 여러 개의 OpenSSH 빌드를 제작했습니다. 여기서 약 17,600건의 비정상적인 로그인 시도를 수행한 결과, 'musl 라이브러리를 사용하면서 PAM을 비활성화한 빌드'에서만 이 우회 인증이 성공함을 확인했습니다 2.

대부분의 일반 리눅스 배포판(Debian, Ubuntu, Fedora 등)은 PAM 인증 모듈을 사용합니다. PAM은 비밀번호 필드가 *인 경우 아예 해시 계산 함수(crypt())를 호출하지 않고 인증을 거부하기 때문에, 일반적인 환경에서는 이 취약점에 노출되지 않습니다. OpenSSH 프로젝트 자체의 테스트 환경도 OpenBSD와 glibc에 집중되어 있어, 이 특수한 'musl + PAM 비활성화' 조합이 오랫동안 검증되지 않았습니다 2.

Alpine 클라우드 이미지의 현실적 위협

sshhole이 특히 우려되는 이유는 Alpine 리눅스의 공식 클라우드 이미지(AWS, Azure 등)가 이 조건을 기본값으로 갖추고 있기 때문입니다.

Alpine은 2019년 이전 버전에선 root 계정의 비밀번호가 비어 있어 빈 비밀번호로 로그인할 수 있는 문제를 겪었습니다(CVE-2019-5021). 이를 막기 위해 비밀번호 필드를 !로 변경했으나, PAM을 사용하지 않는 환경에서는 !로 설정하면 SSH 키 기반 인증까지 함께 차단되는 부작용이 발생했습니다. 결국 Alpine 3.16 버전(2022년)부터는 키 로그인을 살리기 위해 비밀번호 필드를 다시 *로 되돌렸습니다 2, 4.

기본 상태의 Alpine 공식 클라우드 이미지는 PasswordAuthentication yes와 UsePAM no가 설정되어 있습니다. 게다가 기본 생성되는 alpine 계정은 doas(명령어 권한 상승 도구)를 비밀번호 없이 사용할 수 있도록 설정되어 있습니다. 따라서 공격자는 257~1024자의 임의 문자열로 alpine 계정에 로그인한 후, doas 명령을 통해 별도의 인증 없이 즉시 root 권한을 획득할 수 있습니다 2.

즉각적인 대응 및 점검 방법

즉각적인 대응 및 점검 방법

sshhole 취약점은 OpenSSH 10.6p2 버전(2026년 11월 5일 배포)에서 공식적으로 수정되었습니다 3.

1. 업데이트 가장 안전한 조치는 OpenSSH를 10.6p2 이상으로 업데이트하는 것입니다. Alpine 리눅스에서는 다음 명령어로 업데이트할 수 있습니다.

bash
apk upgrade openssh

2. 설정 변경 업데이트가 불가능한 환경이라면 비밀번호 인증 자체를 비활성화해야 합니다. /etc/ssh/sshd_config.d/00-no-passwords.conf와 같은 설정 파일에 다음 내용을 추가합니다.

text
PasswordAuthentication no

설정 변경 후 Alpine 리눅스에서는 rc-service sshd restart 명령어로 SSH 서비스를 재시작해야 적용됩니다 2.

3. 침입 여부 확인 이미 서버가 침해되었는지 점검하려면 서버 로그를 확인해야 합니다. 비밀번호 필드가 정확히 *인 계정이 '비밀번호 인증 방식'으로 로그인에 성공한 기록이 남아 있다면 이 취약점이 악용되었을 가능성을 검토해야 합니다 2.

참고자료

  1. *sshole · sshhole.com · sshhole.com
    ↩
  2. ↩
  3. OpenSSH · openssh.org
    ↩
  4. ↩