TECH 으로 돌아가기
TECH HACKER NEWS 7분 읽기 20 READS

잠긴 계정에 로그인되던 musl 리눅스 sshd, 'sshole' 결함의 전말

잠긴 계정에 로그인되던 musl 리눅스 sshd, 'sshole' 결함의 전말
SOURCE IMAGE · HACKER NEWS

유닉스 로그인은 생각보다 단순한 비교 연산이다. 사용자가 비밀번호를 입력하면 sshd는 저장된 salt를 꺼내 같은 방식으로 해시한 뒤, 그 결과가 /etc/shadow에 저장된 값과 글자 하나까지 동일한지 확인한다. 동일하면 통과, 아니면 거부다. 과거에는 이 해시가 누구나 읽을 수 있는 /etc/passwd에 그대로 놓여 있었고, 지금은 $6$salt$hash 형태로 shadow 파일에 들어간다. 여기서 $6$은 sha512를 뜻한다.

이 구조에는 오래된 관례가 하나 있다. 로그인시키면 안 되는 계정에는 해시 자리에 별표()를 넣는 것이다. 1980년 빌 조이가 daemon 계정에 처음 넣은 것으로 전해지는 이 표식은 해시가 아니기 때문에 어떤 비밀번호를 해시해도 결코 일치할 수 없다. 즉 하나로 계정을 '잠그는' 셈이다. 바로 이 안전장치가 특정 조건에서 뒤집힌다는 것이 이번에 공개된 'sshole' 문제의 핵심이다.

crypt()가 실패할 때 무엇을 돌려주는가

문제의 출발점은 C 표준 함수 crypt()의 실패 처리다. crypt()는 시스템이 지원하지 않는 알고리즘을 요구받는 등 계산이 불가능하면 원래 NULL을 반환한다. 그런데 많은 프로그램이 이 NULL을 점검하지 않고 그대로 저장된 필드와 비교하다가 크래시를 일으킨다. 알파인 리눅스가 쓰는 libc인 musl은 이 문제를 피하려고 다른 선택을 했다. 실패 시 NULL 대신 '절대 비밀번호와 일치할 수 없는 한 글자'를 돌려주기로 한 것이다. 그리고 그 한 글자로 하필 별표(*)를 골랐다.

여기에 또 하나의 조건이 겹친다. sha512crypt는 비밀번호가 길수록 계산이 느려져서, 공격자가 아주 긴 비밀번호 하나로 서버를 오래 붙잡아둘 수 있다. musl은 이를 막으려고 비밀번호 길이를 256바이트로 제한하고 그보다 길면 거부한다. 그런데 이 거부 역시 crypt()의 '실패'이고, 실패하면 앞서 정한 대로 *를 반환한다.

잠금 표식과 실패 표식이 같아지는 순간

두 설계를 나란히 놓으면 결론이 드러난다. 잠긴 계정의 저장값도 이고, 257바이트 이상 비밀번호를 던졌을 때 crypt()가 돌려주는 값도 다. sshd가 이 둘을 글자 단위로 비교하면 == , 즉 '일치'가 되어 로그인이 허용된다. 정상적으로는 아무도 들어갈 수 없어야 할 잠긴 계정에, 충분히 긴 아무 문자열이나 넣으면 통과하는 상황이 벌어진다. 발견자는 glibc·musl, pam 사용·미사용, shadow 유무 등 가능한 조합으로 sshd를 빌드해 약 1만 7,600건의 비정상 로그인을 던졌고, 오직 'musl에 pam 없는' 빌드만 들어오면 안 되는 요청에 예라고 답했다고 밝혔다. 대부분의 배포판은 pam을 거치는데, pam은 *인 계정에 대해 아예 crypt()를 호출하지 않아 이 경로를 타지 않는다.

2016년부터 쌓여온 신호들

이 결함은 2016년부터 존재했지만 여러 사건 속에서 매번 비켜갔다. 같은 crypt 동작은 CVE-2016-6210의 배경이기도 했다. 존재하지 않는 사용자는 해시할 것이 없어 즉시 거부되고 실제 사용자는 몇 밀리초 더 걸리는 차이를 이용해 계정 존재 여부를 알아내는 타이밍 공격이었다. sshd는 가짜 사용자에게도 다른 계정의 salt로 비밀번호를 해시해 동일한 시간을 소모시키는 식으로 대응했는데, 이 '무효 사용자' 처리에 를 가진 실제 계정도 사실상 함께 묶였다. 알파인의 루트 계정 이야기도 얽혀 있다. 2019년 도커 이미지의 루트에 비밀번호가 아예 없던 문제(CVE-2019-5021)는 느낌표(!)로 잠가 해결했지만, pam이 없으면 !가 ssh 키 로그인까지 막아 클라우드 이미지가 불능이 됐고, 2022년 알파인 3.16은 루트를 다시 로 바꿔 키 로그인을 되살렸다. 공교롭게도 그 * 복귀가 이 경로를 다시 열어둔 셈이다.

그렇다면 왜 아무도 알아채지 못했을까. 비밀번호가 설정되지 않은 새 시스템은 영향이 없고, shadow에 가 있어야 하며 틀린 시도가 한 번 전제돼야 재현된다. openssh는 openbsd와 glibc 환경에서 테스트하고 musl 쪽은 sshd를 테스트하지 않으며, 대부분 배포판은 pam을 거친다. 데비안 개발자가 glibc에 musl의 방식을 요청했다가 6년 뒤 libxcrypt로 옮기며 *0으로 닫은 일, 바쁜 쓰레드에서 busybox passwd가 257자 비밀번호에 깨진 보고가 오간 일처럼 같은 메커니즘이 여러 번 스쳐갔지만 누구도 'sshd'를 입에 올리지 않았다. 심지어 pick_salt()를 손본 보안 리뷰는 네 줄 아래의 이 버그를 지나쳤다.

실무자가 봐야 할 지점

정리하면 이 결함은 모든 환경이 아니라 musl 기반 + pam 미사용 + 로 잠긴 계정이라는 좁은 교집합에서 성립한다. 알파인을 베이스 이미지로 쓰는 컨테이너, 경량 임베디드·클라우드 이미지처럼 pam 없이 shadow를 직접 읽는 sshd 구성이 점검 대상이다. 운영 환경에서는 외부로 sshd를 노출한 musl 기반 호스트가 있는지, 잠금 표식으로 를 쓰는지, pam 경유 여부가 어떻게 되는지를 확인하고 libc·openssh 패치 상태를 점검하는 것이 현실적인 출발점이다. 동시에 이 사례는 암호학 그 자체보다 libc 경계처럼 아무도 들여다보지 않는 접합부에서 사고가 난다는 점을 보여준다. 다만 공개된 내용만으로는 구체적 패치 버전이나 실제 피해 사례가 특정되지 않았고, 영향 범위도 특정 조합에 국한되므로 과도한 공포보다는 자기 환경이 그 조합에 해당하는지부터 차분히 확인하는 편이 맞다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://sshhole.com
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...