
macOS SSH 클라이언트의 로직 취약점 분석으로 인해 로컬 공격자에게 클라이언트 패스프레이즈 노출
이 문서는 macOS ssh 바이너리에서 식별된 로직 취약점(제 첫 번째 소프트웨어 취약점입니다!)에 대한 분석과 Apple이 어떻게 이를 수정했는지 보여주는 패치 분석을 제공합니다. 저는 2022년 말 Apple Security Bounty 프로그램을 통해 이 문제를 Apple에 보고했으며, 이후 macOS Ventura 13.5에서 수정되어 CVE-2023-42829(🎉)가 할당되었습니다. 이 문제로 인해 macOS 사용자 로컬 'Login' 키체인(com.apple.ssh.passphrases 접근 그룹)에 저장된 SSH 패스프레이즈가 로컬 공격자에게 평문으로 노출됩니다.
면책 조항:
본 보고서는 교육 목적으로만 제공되며, 책임 있는 공개 절차를 준수하여 작성되었습니다. 분석은 있는 그대로 제공되며, 이 정보의 추가 게시 또는 사용은 책임 있는 공개 지침을 따라야 합니다.
패치 후 상위 수준 흐름
패치 전 상위 수준 흐름
| 하드웨어 | OS 소프트웨어 |
|---|---|
| MacBook Pro M1 2021 | /usr/bin/ssh @ macOS Ventura 빌드 13.0.1 (22A400) |
다음 개념 증명은 취약점의 비교적 간단한 악용 가능성을 보여주며, 동적 라이브러리를 ssh 바이너리의 -I 플래그에 전달합니다:
jamesd@local build % ssh -I /Users/jamesd/rome.dylib [email protected]
SSH Private Key Passphrase -> 'SuperSecret!!@@$'
이것이 어떻게 발견되었고 왜 발생했는지에 대해 이야기해 보겠습니다!
예전에 저는 무해한 작업을 위해 ssh 바이너리를 사용하다가 -i 플래그(SSH ID 파일을 전달하는 데 사용)를 -I 플래그로 잘못 입력했고, 다음과 같은 표준 출력을 보게 되었습니다:
jamesd@local build % ssh -I /Users/jamesd/.ssh/id_rsa something@somewhere
dlopen /Users/jamesd/.ssh/id_rsa failed: dlopen(/Users/jamesd/.ssh/id_rsa, 0x0002): tried: '/Users/jamesd/.ssh/id_rsa' (not a mach-o file), ...
...
ssh 바이너리의 권한을 확인한 후...
jamesd@local build % ldid -e /usr/bin/ssh # /usr/bin/ssh 바이너리의 권한 덤프
<key>com.apple.private.security.clear-library-validation</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>com.apple.ssh.passphrases</string>
</array>
...제 관심이 끌렸습니다. 바이너리가 보호된 키체인 접근 그룹(com.apple.ssh.passphrases)에서 읽을 수 있는 권한을 가지고 있고, com.apple.private.security.clear-library-validation 권한을 보유하고 있으며, ssh가 제 개인 키를 dlopen()하려고 시도하고 있었습니다?
알고 보니 ssh는 pkcs11이라는 것을 통해 원격 시스템에 인증하는 것을 지원합니다. pkcs11은 하드웨어 보안 모듈(HSM)에서 암호화 작업을 수행하기 위한 표준입니다 (https://docs.aws.amazon.com/cloudhsm/latest/userguide/pkcs11-library.html).
이 글의 목적상 pkcs11의 세부 사항에 대해 걱정할 필요는 없습니다. 클라이언트가 ssh -I에 pkcs11(동적 라이브러리) 라이브러리를 제공한다는 사실만 알면 됩니다.
com.apple.private.security.clear-library-validation 권한개념적으로는 동일하지만, com.apple.private.security.clear-library-validation은 이전의 동등한 권한(com.apple.security.cs.disable-library-validation)과 다릅니다. com.apple.private.security.clear-library-validation은 CS_OPS_CLEAR_LV를 전달하여 csops() 시스템 콜을 호출하여 라이브러리 검증을 활성화/비활성화해야 하기 때문에 런타임에 프로세스 무결성을 더 잘 제어할 수 있습니다 (com.apple.private.security.clear-library-validation은 런타임 제어 없이 모든 라이브러리를 로드할 수 있게 해줄 것입니다). (https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/kern_proc.c) (https://theevilbit.github.io/posts/com.apple.private.security.clear-library-validation/)

csops(CS_OPS_CLEAR_LV)가 라이브러리를 dlopen()하기 전에 호출되므로, 우리의 라이브러리는 그냥 로드되고 생성자가 실행되어, pkcs11 라이브러리로 위장한 악성 동적 라이브러리를 제공하고 /usr/bin/ssh의 컨텍스트 내에서 코드를 실행하며 keychain-access-groups 권한을 활용할 수 있습니다.
아마도 패치는 csops()를 호출하기 전에 바이너리에 대해 특정 신뢰할 수 있는 서명 ID를 확인하는 검사를 수행하는 것일까요?
아닙니다!
패치 릴리스(22G74) 이후에 pkcs11_add_provider()(dlopen() 호출이 이루어지는 메서드)의 안전하지 않은 구현을 비교했을 때, 패치 릴리스와 동일해 보였습니다. 하지만 /usr/bin/ssh에 권한이 하나 빠져 있었습니다?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>com.apple.ssh.passphrases</string>
</array>
</dict>
</plist>
하지만 Apple이 SSH에서 pkcs11 지원을 제거했을까요? PoC를 사용하여 취약점을 다시 악용해 보았지만, 라이브러리가 로드되었음에도 더 이상 키체인 접근 그룹에서 읽을 수 없었고, 이전에 보지 못했던 바이너리인 /usr/libexec/ssh-apple-pkcs11의 컨텍스트에서 실행되고 있는 것으로 보였습니다. 이 바이너리는 다음과 같은 권한을 가지고 있습니다:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.private.security.clear-library-validation</key>
<true/>
</dict>
</plist>
CS_OPS_CLEAR_LV를 위한 csops() 시스템 콜의 주석(https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/kern_proc.c)은 CS_OPS_CLEAR_LV와 함께 사용하는 대신 라이브러리 검증 없이 바이너리로 재실행하는 대안을 언급합니다. 그렇다면 왜 논리적으로 결함이 있는 루틴이 여전히 /usr/bin/ssh의 pkcs11_add_provider()에 존재할까요? 그리고 패치에서 구현이 변경되지 않은 것처럼 보이는 이유는 무엇일까요?
알고 보니 패치는 도우미 바이너리를 추가하는 것이 아니었고, macOS에 두 개의 ssh 바이너리(/usr/bin/ssh와 /usr/libexec/ssh-apple-pkcs11)를 배포하는 것이었습니다. 이 두 바이너리는 권한을 제외하고는 동일합니다(Diaphora를 사용하여 확인했습니다):

패치된 ssh 바이너리에 대한 동적 분석을 수행한 결과, ssh의 (상당히 큰) start() 루틴에 추가 검사가 추가되었습니다. 이 검사는 pkcs11 기능을 사용할 때 바이너리가 /usr/bin/ssh의 컨텍스트에서 실행 중인지 아니면 /usr/libexec/ssh-apple-pkcs11의 컨텍스트에서 실행 중인지 판단하는 데 사용됩니다:

녹색 블록에서 SecTaskCopyValueForEntitlement() 호출을 볼 수 있습니다(전달된 값은 "com.apple.private.security.clear-library-validation"). 이 값은 주황색 블록에서 평가되어, 현재 작업/프로세스에 "com.apple.private.security.clear-library-validation" 권한이 없으면(빨간색 블록) 재실행 루틴에 대한 조건부 호출을 발생시킵니다.
이로 인해 사용자가 제공한 pkcs11 라이브러리를 로드할 때 권한이 적은 ssh-apple-pkcs11 바이너리가 사용되고(사실상 ssh의 키체인 관련 기능을 비활성화), 신뢰할 수 없는 라이브러리를 로드하지 않아야 하는 경우 ssh가 사용됩니다(키체인 관련 기능을 활성화).
패치된 ssh 바이너리(macOS 22G74에서 배포)를 디버깅하고 execv()에 중단점을 설정하면, pkcs11 기능을 사용하지 않고 ssh를 실행할 때는 발생하지 않는 execv() 호출을 -I 플래그를 제공할 때 실제로 발견할 수 있습니다:

저는 2022년 말 Apple Security Bounty 프로그램을 통해 이 문제를 Apple에 보고했으며, 이후 macOS Ventura 13.5에서 수정되어 CVE-2023-42829(🎉)가 할당되었습니다.
이 글은 독립적인 게시물이며, Apple Inc.의 승인, 후원 또는 기타 승인을 받지 않았습니다. macOS, iOS, iWork는 Apple Inc.의 상표입니다.