1P by GN⁺ | ★ favorite | 댓글 1개
  • xzbot은 xz 백도어(CVE-2024-3094)를 탐색하기 위한 저장소로, 허니팟, ED448 공개키 패치, 백도어 페이로드 형식, 원격 코드 실행 데모를 함께 제공함
  • 허니팟은 OpenSSH에 간단한 패치를 적용해 백도어 형식과 맞는 공개키 N 값으로 들어오는 연결 시도를 sshd 로그에 남김
  • ED448 패치는 백도어가 서명 검증과 페이로드 복호화에 쓰는 하드코딩 공개키를 사용자가 생성한 키로 바꿔 백도어를 트리거할 수 있게 함
  • 백도어 페이로드는 SSH 인증서의 CA 서명키 N 값에 들어가며, a * b + c로 요청 타입을 만들고 Type 2는 null 종료 명령을 system()으로 실행함
  • 데모 CLI는 취약 SSH 서버에 접속해 명령을 실행하는 흐름을 보여주며, 성공한 악용은 INFO 이상 로그를 생성하지 않음

xzbot의 범위와 구성

  • xzbot은 xz 백도어인 CVE-2024-3094를 탐색하기 위한 프로젝트임
  • 포함 항목은 네 가지임
    • honeypot: 익스플로잇 시도를 탐지하기 위한 가짜 취약 서버
    • ed448 patch: 자체 ED448 공개키를 쓰도록 liblzma.so를 패치
    • backdoor format: 백도어 페이로드 형식 정리
    • backdoor demo: ED448 개인키를 알고 있다는 가정에서 RCE를 트리거하는 CLI

허니팟: OpenSSH 패치로 시도 탐지

  • openssh.patch는 백도어 형식과 맞는 공개키 N 값으로 들어오는 연결 시도를 기록하도록 OpenSSH에 적용하는 간단한 패치임
  • 흐름은 openssh-portable을 클론한 뒤 패치를 적용하고 autoreconf, configure, make를 실행하는 방식임
  • 연결 시도는 sshd 로그에 xzbot: magic 1 [preauth]와 페이로드 바이트열 형태로 나타남

ED448 공개키 패치

  • 백도어는 서명 검증과 페이로드 복호화에 하드코딩된 ED448 공개키를 사용함
  • 이 공개키를 사용자가 가진 키로 교체하면 백도어를 트리거할 수 있음
  • 저장소는 공격자 ED448 공개키와 seed=0으로 생성한 대체 공개키 값을 함께 제시함
  • 시작 단계는 백도어가 포함된 liblzma.so.5.6.1 공유 객체를 준비하는 것임
    • 예시 출처로 xz-utils 5.6.1-1 Debian snapshot이 제시됨
    • pwntools 설치 후 patch.py liblzma.so.5.6.1를 실행하면 liblzma.so.5.6.1.patch가 생성됨
    • 이후 수정된 liblzma.so.5.6.1.patch 공유 객체를 사용해 sshd를 실행함
  • assets에는 예시가 있음

백도어 페이로드 형식

  • 백도어는 SSH 인증서로 연결할 때 CA 서명키 N 값 안에 페이로드를 넣어 트리거할 수 있음
  • 이 페이로드는 공격자의 ED448 키로 암호화 및 서명되어야 함
  • 바깥 구조는 다음 필드로 구성됨
    • a: 32비트
    • b: 32비트
    • c: 64비트
    • ciphertext: 240바이트
  • 요청 타입은 a * b + c로 파생됨
    • 값이 3보다 크면 백도어는 처리를 건너뜀
    • Type 1: 용도 미상이며 0바이트를 기대함
    • Type 2: null 종료 페이로드를 system()으로 실행함
    • Type 3: 용도 미상이며 48바이트 서명 데이터를 기대함
  • ciphertext는 ED448 공개키의 처음 32바이트를 대칭키로 사용해 chacha20으로 암호화됨
    • 이 때문에 익스플로잇 시도는 제시된 32바이트 키로 복호화할 수 있음
  • 복호화된 ciphertext 내부에는 114바이트 서명, xy 1비트 필드, 길이, 명령 문자열 등이 들어감
    • x 또는 y를 설정하면 약간 다른 코드 경로로 이어짐
  • 서명은 RFC-8032 ED448 서명임
    • 입력에는 32비트 magic 값, 명령 전 5바이트 필드, 선택적으로 명령의 length 바이트, 서버 hostkey의 SHA-256 해시 앞 32바이트가 포함됨

익스플로잇 데모 CLI

  • CLI는 Go로 설치함
    • go install github.com/amlweems/xzbot@latest
  • xzbot -h는 세 가지 주요 옵션을 보여줌
    • -addr: SSH 서버 주소, 기본값 127.0.0.1:2222
    • -seed: xz 백도어 키와 맞아야 하는 ED448 seed, 기본값 0
    • -cmd: system()으로 실행할 명령, 기본값 id > /tmp/.xz
  • 예시는 127.0.0.1:2222의 취약 SSH 서버에 접속해 id > /tmp/.xz 명령을 실행함
  • 취약 서버에서 system() 호출 위치에 watchpoint를 설정하면 sshd 프로세스가 id > /tmp/.xz를 실행하는 모습을 볼 수 있음
  • 실행 후 /tmp/.xz에는 uid=0(root) gid=0(root) groups=0(root) 출력이 남는 예시가 제시됨

프로세스 트리와 로그 특성

  • 정상 SSH 접속의 프로세스 트리는 sshd의 사용자 세션과 셸로 이어짐
  • 백도어 실행 예시는 xzbot -cmd 'sleep 60' 이후 sshd: root [priv], sshd: root [net], sh -c sleep 60, sleep 60이 나타나는 형태임
  • 성공한 악용은 INFO 이상 로그 항목을 생성하지 않음

참고 자료

댓글과 토론

Hacker News 의견들
  • 누구나 악용할 수 있는 원격 코드 실행이 아니라 공격자의 개인키가 필요하게 만든 점이 꽤 흥미로움
    아이러니하게도 매우 보안 의식적인 취약점처럼 보임

    • 원래 의도는 백도어의 수명을 오래 유지하려는 것이었을 듯함
      누구나 들어갈 수 있게 크게 구멍을 내면 금방 발견되어 막혔을 테고, 성능 저하가 없었다면 널리 악용 가능하지 않다는 이유로 조용히 오래 살아남았을 가능성이 있음
    • 국가 지원 공격으로 보면 자국민도 쓸 수 있는 시스템에 안전한 취약점을 심어두려는 게 꽤 말이 됨
      xz에 대한 기여 전체가 결국 이 백도어를 넣기 위한 작업처럼 보이고, 예를 들어 악성 페이로드를 숨길 수 있는 테스트 프레임워크까지 만들었음
      xz 작업 전에는 BSD의 libarchive에도 기여했는데, 거기서 취약점이 생겼음
    • 정말 국가 지원 익스플로잇처럼 보임
      설계와 실행이 엄청 정교했고, 성능 문제가 발견의 계기가 된 건 거의 순전히 운이었던 느낌
    • 이건 NOBUS라고 부름: https://en.wikipedia.org/wiki/NOBUS
    • 페이로드 서명에 대상 SSH 호스트 키가 포함되는 것으로 읽히는데 맞는지 궁금함
      그렇다면 서버들에 마구 뿌릴 수 없고, 한 호스트에 보내는 것 자체가 계산 비용이 꽤 드는 구조임
  • 이 사건이 주말 내내 머릿속을 떠나지 않았음
    메커니즘은 흥미롭고 훌륭한 난독화 모음이며, 사회공학 부분은 오픈소스 유지관리자에게 부끄러울 정도로 익숙한 이야기임
    가장 흥미로운 건 공격 벡터로 나쁜 테스트 데이터를 골랐다는 점인데, 좋은 아카이브를 준비하고 구조적으로 조작한 뒤 퍼지 테스트용 나쁜 데이터로 쓰면 나머지 단계가 엄청 쉬워짐
    나중을 위해 말하면 이런 조작은 바이너리 패턴 그래프에 드러나야 할 것 같음
    나머지 기법은 페이로드가 정해진 뒤에는 대체로 평범한 난독화에 가까웠지만, 같은 패턴으로 다른 테스트 파일에 “패치”나 아예 새 백도어를 눈치채지 못하게 추가할 수 있었다는 점이 가장 뛰어난 수였음
    GitHub가 저장소를 숨기고 제거한 건 전혀 도움이 안 됐고, 이 사건을 분석하는 데 방해가 됨

    • 우리가 쓰는 오픈소스 도구의 빌드 시점 의존성이 의심스러워졌음
      그 의존성은 유독 xz를 선호하고, 설치되어 있지 않으면 편의 기능처럼 호스트 머신에 xz까지 설치한다는 걸 발견했음
      다른 의존성에는 그런 행동을 안 해서 좀 이상함
      이런 장기전은 무섭고, 실제로 “악”이 실행되기 전까지는 무엇이 악의적이고 무엇이 그냥 이상한지 알 수 없음
    • 핵심 원인 중 하나는 테스트 입력으로 쓰기 위해 저장소에 바이너리 파일을 추가한 것으로 보임
      특히 테스트 실패를 증명하기 위한 “바이너리 쓰레기” 파일이라면 악성 내용을 숨기기 너무 좋은 장소임
    • 저장소를 제거해버려서 결국 공격자만 코드와 노하우에 접근할 수 있게 됐음
  • 커뮤니티, 특히 amlweems가 이렇게 빨리 개념 증명 코드를 구현하고 문서화한 점이 매우 인상적임
    암호 기능이나 페이로드 로딩 기능에 추가 취약점이 없다면, 키가 깨지기 전까지는 다른 공격자 모두에게 보안 결함을 열어준 것도 아니었을 수 있음
    다음 단계는 취약한 배포판을 탐지하는 방법을 찾는 것인데 쉽지 않아 보이고, 하드코딩된 키로 누군가 SSH 서버를 능동적으로 탐색하는지 감시하는 방법을 upstream에 넣을 수도 있을 듯함

    • 그건 원본 익스플로잇이 아니라 키를 바꾼 버전에 대한 개념 증명임
      원본 버전에 대한 개념 증명에는 공개되지 않은 공격자의 개인키가 필요함
    • 공격자의 개인키 없이 네트워크로 취약한 배포를 탐지하는 건 어렵다기보다 불가능해 보임
      할 수 있는 최선은 더 미세한 벤치마킹 정도지만, 임의의 인터넷 호스트가 느린 이유가 취약해서인지, 멀리 있어서인지, 컴퓨터 자체가 느려서인지 알 수 없음
      과거에 그 호스트로 연결 시도가 얼마나 걸렸는지 접근할 수도 없고, 라우팅 변동도 있음
  • 이 개념 증명을 비정상 프로세스 동작 탐지 도구들에 돌려본 사람이 있는지 궁금함
    Carbon Black, AWS GuardDuty, Sysdig 같은 제품이 해당되며, 이게 실제로 배포됐을 때 누군가 비교적 빨리 알아챘을 가능성을 시험하기에 이 제품군에 딱 맞는 사례처럼 보임

    • 익스플로잇이 정상적인 압축 동작을 얼마나 비슷하게 흉내 내거나 그 안에 숨는지에 달렸다고 봄
      GuardDuty는 CrowdStrike나 Carbon Black 같은 EDR처럼 프로세스를 들여다보지 않으니 잡기 어려울 것 같고, Sysdig는 컨테이너와 클라우드 인프라를 보니 익스플로잇 자체를 잡기는 어려워 보임
      다만 권한 상승 이후 위협 행위자의 후속 행동에서 이상 징후를 잡을 가능성은 있음
      결국 익스플로잇 자체를 잡을 가능성이 가장 높은 건 엔드포인트 프로세스를 감시하는 EDR이나, upstream 자유·오픈소스 소프트웨어의 보안 문제를 감시하는 소프트웨어 공급망 평가일 듯함
      흥미롭게도 이건 더 큰 보안 주제로 이어짐
      개발팀은 성능 저하와 격리 조치 시 사용자 경험 문제 때문에 서버에 EDR 올리는 걸 싫어할 수 있고, 자유·오픈소스 소프트웨어 사용 제한 정책도 싫어할 수 있음
      이 익스플로잇은 조직 차원의 “취약점” 한복판을 찌르며, 위치에 따라 노출을 유지할 논리도 고칠 논리도 생김
    • Sysdig가 금요일에 블로그를 냈음
      “런타임 탐지의 한 방법은 SSHD가 악성 라이브러리를 로드하는지 감시하는 것이다. 이런 공유 라이브러리는 파일명에 버전을 포함하는 경우가 많다”라고 했음
      다른 보안 업체에서는 못 본 실제 탐지 규칙 내용도 블로그에 있음
      https://sysdig.com/blog/cve-2024-3094-detecting-the-sshd-bac...
    • 그 제품군 전체는 대부분 허풍 보안 제품에 가깝다고 봄
  • 아래 링크를 잘못 읽었고, 원래 내용은 기록 차원에서 남김
    같은 메일 스레드 아래를 보면 백도어를 커밋한 사람이 최근 커널 기여도 한 것처럼 보인다고 했는데, 원문 분석 자체는 정말 훌륭해서 이런 글을 읽는 게 좋음
    https://www.openwall.com/lists/oss-security/2024/03/29/10

    • 그 패치 시리즈는 Jia Tan이 아니라 Lasse가 올린 것임
      Lasse 본인도 전혀 긴급하지 않고 이번 병합 창에는 들어가지 않을 거라고 했으며, 정상적인 사람이라면 Lasse를 악의적 행위자로 몰고 있지 않음
    • 반증되기 전까지 Lasse Collin은 Jia Tan이 아님
    • 참고된 패치 시리즈는 아직 커널에 들어가지 않았음
    • 우연일 수도 있지만 JiaT75는 항공에서 납치를 뜻하는 트랜스폰더 코드 7500과 꽤 닮았음
  • 이 사건은 몇 년 전 Audacity 사건과 닮은 점이 터무니없이 많음
    Cookie guy는 자신이 칼에 찔렸고 연방 경찰이 사건에 관여했다고 주장했는데, 이건 사건이 4chan보다 훨씬 큰 행위자와 연결됐을 가능성을 시사함
    당시에는 Muse Group만 관련된 줄 아는 사람이 많았지만, 어쩌면 러시아 국가 행위자였을 수도 있음
    그 전에는 Audacity에 텔레메트리와 백도어가 많아서 포크한 뒤 첫 커밋에서 제거했다고 주장했음
    어쩌면 Audacity에도 실제로 백도어가 있을 수 있으니 소스 코드를 확인해봐야겠음

    • 조심해야 함, APT28은 꽤 위험함
      요즘은 APT29와 작전을 합치는 중이고, 나라면 cozy bear를 깨우지 않겠음
    • Cookie guy가 누구임?
  • 컴파일 시점의 허니팟 openssh.patch https://github.com/amlweems/xzbot/blob/main/openssh.patch 없이, 실제 익스플로잇은 런타임에 이걸 어떻게 했는지 궁금함
    체인은 opensshd -> systemd 알림 -> 일시적 의존성으로 포함된 xz였는데, liblzma.so.5.6.1이 메모리에 로드된 뒤 어떻게 openssh_RSA_verify까지 거슬러 올라가 훅이나 패치를 걸었는지 알고 싶음

    • liblzma를 로드할 때 ELF의 GOT에 악성 코드 주소를 패치함
      libcrypto보다 먼저 로드된 경우에는 심볼 감사 처리기를 등록하는데, 아마 glibc 전용 기능으로 보이며, libcrypto의 심볼이 해석될 때 알림을 받아 GOT 패치를 지연시킬 수 있음
    • ifunc
  • 이 익스플로잇이 SSH 연결이 들어왔을 때만 동작했는지 알고 있는지 궁금함
    GitHub에 있는 문자열 목록에 DISPLAYWAYLAND_DISPLAY가 포함되어 있음
    https://gist.github.com/q3k/af3d93b6a1f399de28fe194add452d01
    이것들은 SSH와 명확한 관련이 없으니, 연결이 없을 때도 뭔가 했을 가능성이 있음
    코드를 실행했지만 SSH 서버를 인터넷에 노출하지 않았으니 안전하다고 가정하는 사람들에게 중요할 수 있음

    • 아마 터미널이 열려 있거나 GUI 세션에서 실행 중이면 익스플로잇이 동작하지 않게 하는 킬 스위치일 가능성이 큼
      즉 누군가 탐지, 재현, 디버깅하려는 상황을 피하려는 장치임
    • X11 세션 포워딩과 관련 있을 수도 있음
      신뢰할 수 없는 머신에 접속할 때 끄지 않으면 접속자 쪽에서 흔한 보안 구멍이 됨
  • “성공적인 악용은 로그 항목을 생성하지 않는다”는 말은, 이 익스플로잇이 들키지 않았다면 공격자가 손상된 호스트에서 해당 “연결”에 대한 sshd 로그 하나 없이 root로 임의 명령을 실행할 수 있었다는 뜻인지 궁금함

    • 맞음
      원격 코드 실행은 로그가 남기 전인 연결 단계에서 발생함
    • 그래도 대응되는 로그인 없이 SSH 트래픽은 남았을 것임
      어떤 이상 탐지가 이걸 잡을 수 있었을지 궁금함
  • 테스트 파일을 별도 저장소로 분리해서 빌드 시점에 사용할 수 없게 했다면 이 공격을 더 어렵게 만들 수 있었을지 궁금함
    빌드에 관여할 수 있는 것은 무엇이든 사람이 읽을 수 있어야 한다는 논리임

    • “빌드에 관여할 수 있는 것은 사람이 읽을 수 있어야 한다”는 건 전반적으로 채택하기 좋은 원칙임
      이 공격을 항공기 사고처럼 다루고, 다시 성공할 가능성을 줄이는 새 규칙을 도입해야 함
      기여자 한 명 한 명을 모두 검증하지는 못하더라도, 시끄러운 테스트 데이터는 쉽게 분리할 수 있어야 함