cat readme.txt도 안전하지 않다, iTerm2를 사용한다면

3 hours ago 1
  • SSH integration은 원격 셸과 통신하기 위해 터미널 escape sequence를 사용하며, 이 구조 때문에 일반 터미널 출력도 conductor 프로토콜처럼 해석될 수 있는 상태
  • 핵심 문제는 신뢰 실패로, 실제 원격 conductor가 아닌 악성 파일·배너·MOTD·서버 응답도 위조된 DCS 2000p와 OSC 135를 통해 conductor처럼 동작 가능한 점
  • cat readme.txt 실행만으로도 가짜 conductor transcript가 렌더링되면 iTerm2가 getshell·pythonversion·run(...) 흐름을 스스로 진행하고, 공격 출력은 응답만 가장하면 되는 구조
  • 익스플로잇은 PTY에 기록된 base64 명령이 실제 SSH conductor가 없을 때 로컬 셸 평문 입력으로 떨어지는 혼선을 이용하며, 마지막 청크가 ace/c+aliFIo 경로로 해석될 때 실행 가능 상태
  • 수정은 3월 31일 커밋 a9e745993c2e2cbb30b884a16617cd5495899f86 에 반영됐지만 공개 시점 기준 stable release 미포함 상태였고, 패치 보급 전 공개로 보호 공백 구간이 발생한 상태

iTerm2의 SSH 통합 배경

  • iTerm2 SSH integration은 원격 세션을 더 풍부하게 이해하기 위한 기능이며, 원격 셸에 작은 헬퍼 스크립트인 conductor를 올려 동작하는 구조
    • it2ssh를 통해 SSH 통합 시작
    • 기존 SSH 세션을 통해 원격 부트스트랩 스크립트인 conductor 전송
    • 이 원격 스크립트가 iTerm2 프로토콜의 상대 역할 수행
  • iTerm2와 원격 conductor는 일반적인 네트워크 서비스가 아니라 터미널 I/O 위에서 escape sequence를 주고받는 방식
    • 로그인 셸 탐지
    • Python 존재 여부 확인
    • 디렉터리 변경
    • 파일 업로드
    • 명령 실행

PTY 동작 방식

  • 현대의 터미널 에뮬레이터는 과거 하드웨어 터미널의 소프트웨어 버전이며, 화면 출력·키보드 입력·터미널 제어 시퀀스 해석 담당
  • 셸과 커맨드라인 프로그램은 여전히 실제 터미널처럼 보이는 장치를 기대하므로 OS가 PTY를 제공하는 구조
    • PTY는 터미널 에뮬레이터와 포그라운드 프로세스 사이에 위치하는 pseudoterminal
  • 일반적인 SSH 세션에서는 iTerm2가 바이트를 PTY에 기록하고, 포그라운드 프로세스인 ssh 가 이를 원격 머신으로 전달하며, 원격 conductor가 stdin으로 읽는 흐름
  • iTerm2가 원격 conductor에 명령을 보낼 때 로컬에서는 결국 PTY에 바이트를 기록하는 방식

conductor 프로토콜

  • SSH 통합 프로토콜의 전송 수단은 터미널 escape sequence 사용
  • 핵심 요소는 두 가지
    • DCS 2000p 는 SSH conductor를 hook하는 용도
    • OSC 135 는 pre-framer conductor 메시지 용도
  • 소스 수준에서 DCS 2000p는 iTerm2가 conductor parser를 생성하게 만들고, 이후 parser가 OSC 135 메시지를 처리하는 구조
    • begin <id>
    • command output lines
    • end <id> <status> r
    • unhook
  • 정상적인 원격 conductor는 터미널 출력만으로 iTerm2와 통신 가능한 상태

핵심 취약점

  • 취약점의 본질은 신뢰 실패이며, 실제 신뢰된 conductor 세션이 아닌 터미널 출력도 iTerm2가 SSH conductor 프로토콜로 받아들이는 점
  • 그 결과 신뢰되지 않은 터미널 출력이 원격 conductor를 가장할 수 있는 상태
    • 악성 파일
    • 서버 응답
    • 배너
    • MOTD
  • 공격 입력은 위조된 DCS 2000p hook과 위조된 OSC 135 응답을 출력할 수 있으며, 이 경우 iTerm2는 실제 SSH integration 교환이 진행 중인 것처럼 동작

익스플로잇 동작 방식

  • 익스플로잇 파일은 가짜 conductor transcript를 포함하는 형태
  • 사용자가 cat readme.txt를 실행하면 iTerm2는 파일을 렌더링하지만, 파일에는 단순 텍스트가 아니라 다음 요소가 포함된 상태
    • 가짜 conductor 세션을 알리는 가짜 DCS 2000p 라인
    • iTerm2 요청에 응답하는 가짜 OSC 135 메시지
  • hook이 수락되면 iTerm2는 정상 conductor 워크플로를 시작하며, 상류 소스에서 Conductor.start()는 즉시 getshell() 전송 후 성공하면 pythonversion() 전송
  • 공격은 이 요청들을 주입할 필요가 없고, iTerm2가 스스로 요청을 발행하며 악성 출력은 응답만 가장하면 되는 구조

상태 머신 진행 과정

  • 가짜 OSC 135 메시지는 최소한이지만 정확한 순서로 구성된 상태
    • getshell에 대한 command body 시작
    • 셸 탐지 출력처럼 보이는 라인 반환
    • 해당 명령 성공 종료
    • pythonversion에 대한 command body 시작
    • 해당 명령 실패 종료
    • unhook
  • 이 흐름만으로도 iTerm2는 정상적인 fallback 경로로 진입하며, 이후 SSH integration 워크플로가 충분히 완료되었다고 판단하고 다음 단계로 진행
  • 다음 단계는 run(...) 명령을 구성해 전송하는 과정

sshargs의 역할

  • 위조된 DCS 2000p hook에는 여러 필드가 포함되며, 그중 공격자가 제어하는 sshargs 존재
  • 이 값은 이후 iTerm2가 conductor의 run ... 요청을 구성할 때 명령 재료로 사용되는 값
  • 익스플로잇은 iTerm2가 다음 데이터를 base64 인코딩할 때
    • run <padding><magic-bytes>
  • 마지막 128바이트 청크가 ace/c+aliFIo 가 되도록 sshargs 선택
  • 이 문자열은 임의값이 아니라 다음 두 조건을 동시에 만족하도록 선택된 값
    • conductor 인코딩 경로의 유효한 출력
    • 유효한 상대 경로명

익스플로잇을 가능하게 하는 PTY 혼선

  • 정상적인 SSH integration 세션에서는 iTerm2가 base64로 인코딩된 conductor 명령을 PTY에 기록하고, ssh 가 이를 원격 conductor로 전달하는 구조
  • 익스플로잇 상황에서도 iTerm2는 동일하게 PTY에 명령을 기록하지만, 실제 SSH conductor가 없으므로 로컬 셸이 이를 평문 입력으로 수신하는 차이
  • 기록된 세션에서는 다음과 같은 형태 관찰
    • getshell이 base64 형태로 나타남
    • pythonversion이 base64 형태로 나타남
    • 이어서 긴 base64 인코딩된 run ... payload 등장
    • 마지막 청크는 ace/c+aliFIo
  • 앞선 청크들은 의미 없는 명령으로 실패하고, 마지막 청크는 해당 경로가 로컬에 존재하며 실행 가능할 경우 동작하는 구조

재현 절차

  • 원래 파일 기반 PoC는 genpoc.py 로 재현 가능
    • python3 genpoc.py
    • unzip poc.zip
    • cat readme.txt
  • 이 절차로 다음 두 파일 생성
    • ace/c+aliFIo 라는 실행 가능한 헬퍼 스크립트
    • 악성 DCS 2000p 및 OSC 135 시퀀스를 포함한 readme.txt
  • 첫 번째 파일은 iTerm2가 가짜 conductor와 통신하도록 유도하고, 두 번째 파일은 마지막 청크 도착 시 셸이 실제로 실행할 대상을 제공
  • 익스플로잇이 성공하려면 cat readme.txt를 ace/c+aliFIo가 있는 디렉터리에서 실행해야 하며, 그래야 마지막 공격자 형상 청크가 실제 실행 가능한 경로로 해석되는 조건

공개 및 패치 일정

  • 3월 30일 iTerm2에 버그 보고
  • 3월 31일 커밋 a9e745993c2e2cbb30b884a16617cd5495899f86 에서 수정 완료
  • 작성 시점 기준 수정 사항은 아직 stable release에 포함되지 않은 상태
  • 패치 커밋이 반영된 뒤 패치만을 기반으로 익스플로잇을 처음부터 다시 구성하는 시도 진행
    • 해당 과정의 프롬프트는 prompts.md
    • 결과물은 genpoc2.py
    • genpoc.py와 매우 유사하게 동작

공개 시점에 대한 문제 제기

  • 수정 사항이 stable release에 도달하기 전에 공개가 이뤄지며 대다수 사용자가 실질적으로 보호받기 어려운 상태에서 취약점이 알려지는 창구 형성
  • 이런 공개 시점의 상충 관계에는 명확한 정당화 필요
  • 2주는 의미 있는 보급을 기대하기에도 짧고, 조기 공개로 대응을 강제해야 한다고 정당화하기에도 짧은 기간
  • 결과적으로 취약점은 널리 알려졌지만 수정판은 현실적으로 필요한 사용자에게 아직 제공되지 않은 공개 공백 구간 형성
  • 더 나은 선택지로는 수정판이 실제 사용자 손에 들어갈 때까지 기다리거나, 조기 노출이 왜 필요했는지 분명한 근거 제시 가능했지만 둘 다 충족되지 않은 상태
Read Entire Article