1P by GN⁺ 3시간전 | ★ favorite | 댓글 1개
  • SSH integration은 원격 셸과 통신하기 위해 터미널 escape sequence를 사용하며, 이 구조 때문에 일반 터미널 출력도 conductor 프로토콜처럼 해석될 수 있는 상태
  • 핵심 문제는 신뢰 실패로, 실제 원격 conductor가 아닌 악성 파일·배너·MOTD·서버 응답도 위조된 DCS 2000pOSC 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 2000pOSC 135 시퀀스를 포함한 readme.txt
  • 첫 번째 파일은 iTerm2가 가짜 conductor와 통신하도록 유도하고, 두 번째 파일은 마지막 청크 도착 시 셸이 실제로 실행할 대상을 제공
  • 익스플로잇이 성공하려면 cat readme.txtace/c+aliFIo가 있는 디렉터리에서 실행해야 하며, 그래야 마지막 공격자 형상 청크가 실제 실행 가능한 경로로 해석되는 조건

공개 및 패치 일정

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

공개 시점에 대한 문제 제기

  • 수정 사항이 stable release에 도달하기 전에 공개가 이뤄지며 대다수 사용자가 실질적으로 보호받기 어려운 상태에서 취약점이 알려지는 창구 형성
  • 이런 공개 시점의 상충 관계에는 명확한 정당화 필요
  • 2주는 의미 있는 보급을 기대하기에도 짧고, 조기 공개로 대응을 강제해야 한다고 정당화하기에도 짧은 기간
  • 결과적으로 취약점은 널리 알려졌지만 수정판은 현실적으로 필요한 사용자에게 아직 제공되지 않은 공개 공백 구간 형성
  • 더 나은 선택지로는 수정판이 실제 사용자 손에 들어갈 때까지 기다리거나, 조기 노출이 왜 필요했는지 분명한 근거 제시 가능했지만 둘 다 충족되지 않은 상태
Hacker News 의견들
  • 안정판에 패치가 아직 안 나왔는데 왜 지금 공개했는지 궁금했음. 업스트림에 보고된 지 18일밖에 안 됐고, 공개된 커밋보다 블로그 글이 훨씬 자세해서 실제 악용 가능성을 높인다고 느꼈음. 다만 작성자가 업스트림 커밋만으로도 LLM을 써서 익스플로잇을 만들 수 있었다는 점은 확인했지만, 그래도 이 글이 취약점의 가시성을 더 키웠다고 봤음

    • 나는 취약점을 발견한 사람은 아니고 블로그 작성자였음. 맞음, 업스트림 커밋만 보고도 익스플로잇을 만들 수 있었고, iTerm2 커밋을 지켜보는 누구라도 비슷하게 할 수 있다고 봤음. 그리고 맞음, 나는 이 취약점의 가시성을 높이려 했고 실제로 그렇게 됐음. iTerm2 작성자는 처음엔 긴급 릴리스가 필요할 만큼 심각하다고 보지 않았지만, 지금은 재고한 듯했음
    • 실제 활성 악용이 의심되거나, 수정 내용이 git 커밋처럼 이미 공개돼 빠른 익스플로잇 제작이 가능한 경우엔 공개 유예의 예외가 있음. 이런 상황에서는 커뮤니티가 오히려 취약점을 공개하는 쪽을 선호한다고 봤음
    • 커밋이 공개된 순간 이미 비밀은 끝난 셈이라고 생각했음. 괜히 말을 아끼는 건 공격자만 돕고, 방어하는 쪽의 보안만 약하게 만든다고 봤음
    • 전통적인 공개 유예 기간이 AI 때문에 점점 의미를 잃어갈 것 같았음. 저렴한 공개형 모델도 취약점을 찾아낼 수 있다면, 공격자도 이미 같은 방식으로 찾아냈다고 가정하는 게 자연스럽다고 봤음
    • 이 버그가 내 업데이트 윈도우 단축 주장에 힘을 실어준다고 느꼈음. 아주 난해한 버그는 Claude 같은 강한 모델이 먼저 찾아도, 일단 패치가 git에 올라가면 더 작은 모델도 훨씬 쉽게 재발견하게 됨. 앞으로 1~2년 안에 커밋 공개부터 실제 포트 스캔까지의 간격이 몇 시간, 심지어 몇 분으로 줄어도 놀랍지 않을 것 같음. 이 점에서는 폐쇄형 SaaS가 유리한데, 변경 내역이 안 보이고 배포 후에는 알아도 실익이 거의 없기 때문이었음
  • 이 작업은 멋지지만 아주 놀랍지는 않았음. 기능이 풍부한 터미널 앱에서 반복적으로 나오는 문제였고, 지난 15년 동안 비슷한 취약점이 여러 번 공개됐음. less나 vim 같은 도구도 예외가 아니었고, 이런 문제 중 상당수는 메모리 안전성보다 로직 버그에 가까워서 Rust로 다시 써도 자동으로 해결되진 않는다고 봤음. 한편으로는 OS 수준 도구가 단순하고 예측 가능하길 바라면서도, 다른 한편으로는 예쁜 색상과 애니메이션, 끝없는 커스터마이징을 원한다는 긴장이 있다고 느꼈음. 이제는 AI 에이전트까지 들어오니, 악성 텍스트 파일이 "이전 지시를 무시하라" 같은 문구만 담아도 되는 시대가 온 셈이었음

    • iTerm2 문제, 프롬프트 인젝션, SQL injection, XSS는 결국 같은 부류의 실수라고 봤음. 인밴드 데이터와 아웃오브밴드 제어 데이터를 같은 스트림에 섞어 넣는 게 핵심 문제였음. 사람들이나 에이전트가 이런 패턴을 위험 신호로 인식하게 되면, 사용자 콘텐츠 옆에 제어 명령을 무심코 넣는 일이 줄어들 것 같았음
    • 문제의 일부는 낡은 인터페이스에 있다고 봤음. 지금 필요한 건 인밴드 명령 시퀀스에 기대지 않는 현대적 터미널 API이고, GUI처럼 프로그래밍 가능하면서도 예전처럼 단순한 원격 터미널 사용성은 유지하는 방향이 더 맞다고 느꼈음
    • Claude Code 같은 풍부한 터미널 UI도 비슷한 취약점이 있는지 궁금했음. 색상, 애니메이션, 상호작용 기능을 텍스트 기반 터미널 프로토콜 위에 억지로 얹기보다, 처음부터 타입과 의미론이 명확한 GUI 프로토콜로 설계하는 게 해법이라고 봤음. 그래야 원격 인터페이스가 사용자 데이터와 핵심 UI 코드를 섞어 해석하는 일을 막을 수 있음. 다만 현실의 소프트웨어 개발과 경제성은 대개 새 프로토콜을 보급하는 것보다, 이미 널리 쓰이는 걸 조금 더 보기 좋게 개조하는 쪽을 택하게 만든다고 느꼈음
    • 나는 "미안하지만 데이브, 그건 허용할 수 없음" 같은 HAL 9000 분위기의 농담이 떠올랐음
    • 예전에 xterm도 창 제목 escape code를 악용해서 비슷하게 공격할 수 있었던 걸로 기억했음
  • PDP-10 시절 얘기가 떠올랐음. 어떤 동료가 백스페이스를 충분히 많이 누르면 터미널 핸들러가 버퍼 앞쪽 문자까지 계속 지운다는 걸 알아냈음. 거기서 더 나아가 한 줄 전체를 지우는 escape 문자를 쓰면 운영체제가 그대로 날아가버렸음

    • 이 얘기를 들으니 Real Life Tron on an Apple IIgs가 떠올랐음. 시스템 메모리가 잘못 해석될 때만 나오는 묘한 매력이 있다고 느꼈음
    • line-kill에 control+u를 쓰는 건 비교적 최근 습관일 수 있다고 봤음. 1984년 책 The Unix Programming Environment에서는 @ 가 line-kill이고 # 가 erase로 나오는데, 요즘 키보드 동작은 시스템마다 제법 다르게 느껴졌음
  • 6년 전에도 거의 같은 iTerm2 보안 이슈가 보고됐음을 상기했음

    • 그래서 나는 아무것도 배우지 못한 것처럼 보였음
  • 나는 iTerm2 작성자였음. 이 문제는 익스플로잇 체인의 한 고리로는 쓰일 수 있어도, 제목처럼 단독으로 엄청난 위험인 것처럼 말하는 건 과장이라고 봤음. 지금은 가족 여행 중이지만 돌아가면 수정판을 릴리스하겠다고 했음

    • 나는 취약점 발견자는 아니고 블로그 작성자였음. 수정판을 내주기로 해서 고마웠음. 이 버그가 평범하고 무해해 보이는 워크플로에도 영향을 주는데 공식 릴리스가 없었던 점이 놀라웠고, 패치 커밋(링크)이 문제를 가설적이라고 표현해서, 블로그 글로 그렇지 않다는 점을 보여주고 싶었음. 이제 수정판을 내기로 한 점은 반가웠음
    • iTerm2를 고맙게 쓰고 있고, 응답해줘서 감사하며 휴가 잘 보내길 바랐음
    • 나는 iTerm2를 정말 좋아해서 감사를 전하고 싶었음
  • 내가 너무 박하게 보는 걸 수도 있지만, bootstrap 스크립트, 원격 conductor 에이전트, 그리고 특수 escape sequence로 터미널 연결을 가로채는 식의 복잡한 시스템에서 미묘한 버그가 난 건 놀랍지 않았음. 원래 의도되지 않은 방식으로 기본 구성요소를 활용하면 이런 종류의 문제가 생기기 쉽다고 봤음. 내가 이해한 바로는, 텍스트 파일이나 서버 배너처럼 화면에 출력되는 신뢰되지 않은 출력에 iTerm2와 원격 conductor가 쓰는 특수 코드가 들어 있으면, 그 출처를 검증하지 않은 채 처리해버린다는 뜻이었고, 내가 오해한 거면 바로잡아주길 바랐음

  • 이게 전에 본 이야기처럼 느껴졌음. iTerm2의 SSH integration이 이미 눈에 띄는 CVE 원인이었던 걸로 기억했고, CVE-2025-22275도 떠올랐음. 게다가 더 이전 사례도 있었고, 이번 스레드에서 언급된 예전 이슈는 tmux integration 쪽이었음. 그래서 iTerm2는 이런 통합 기능을 조금 덜 공격적으로 넣는 편이 낫지 않을까 싶었음

    • 그래서 ghostty가 SSH 접속 시 자체 terminfo를 주입하겠다는 integration 방식도 경계하게 됐음. 차라리 upstream ncurses와 협력해서 terminfo 데이터베이스를 갱신하는 쪽이 더 낫다고 봤음
    • 나는 이런 일이 여러 번 있었다고 짚고 싶었음
  • 제목은 너무 자극적이라고 느꼈음. 문제는 고양이가 아니라 iTerm의 SSH integration이었고, 데이터 스트림과 분리되지 않은 제어 채널을 넣는 구조상 위험해 보였음. 이 기능을 쓰지 말고 일반 SSH만 쓰면 대체로 괜찮다고 봤음

    • 그래서 HN 제목에 기사 속 단서 조항을 반영해 조금 완화된 표현으로 바꿨다고 했음
  • 예전 터미널 에뮬레이터는 escape code로 키보드 재바인딩까지 허용하곤 했음. 그래서 신뢰할 수 없는 파일을 cat하지 말고, less 같은 pager나 텍스트 편집기로 열라는 게 거의 상식이었음

    • 어떤 터미널 에뮬레이터는 더 심해서 escape sequence만으로 임의 파일 쓰기나 프로그램 실행까지 가능했던 걸로 기억했음. 그래서 지금도 임의 바이트를 터미널 스트림에 그대로 흘려보내지 않는 건 충분히 합리적인 조언이라고 봤음. 최소한 터미널 상태가 망가지는 건 막을 수 있기 때문이었음
  • 나는 이 글이 형편없이 쓰였다고 느꼈음. 둘째 문단 전체가 "iTerm2를 쓰면 안전하지 않다"는 식으로 읽히는데, 실제로 맞는 표현은 "iTerm2와 선택적인 Shell Integration 기능을 쓰면 안전하지 않을 수 있다" 정도라고 봤음. 내가 알기로 이 기능은 완전히 선택 사항이고 기본 비활성화라서, 켜지 않았다면 문제는 없다고 이해했음. 내가 틀렸다면 기꺼이 정정받고 싶었음

    • 그 한 문장의 과장 때문에 글 전체를 형편없다고 하는 건 너무 나간 평가처럼 느껴졌음
    • 그 기능은 기본 활성화 상태였고, 직접 테스트해볼 수 있다고 반박했음