iTerm2를 사용한다면, "cat readme.txt"도 안전하지 않다
(blog.calif.io)- 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> runhook
- 정상적인 원격 conductor는 터미널 출력만으로 iTerm2와 통신 가능한 상태
핵심 취약점
- 취약점의 본질은 신뢰 실패이며, 실제 신뢰된 conductor 세션이 아닌 터미널 출력도 iTerm2가 SSH conductor 프로토콜로 받아들이는 점
- 그 결과 신뢰되지 않은 터미널 출력이 원격 conductor를 가장할 수 있는 상태
- 악성 파일
- 서버 응답
- 배너
- MOTD
- 공격 입력은 위조된
DCS 2000phook과 위조된OSC 135응답을 출력할 수 있으며, 이 경우 iTerm2는 실제 SSH integration 교환이 진행 중인 것처럼 동작
익스플로잇 동작 방식
- 익스플로잇 파일은 가짜 conductor transcript를 포함하는 형태
- 사용자가
cat readme.txt를 실행하면 iTerm2는 파일을 렌더링하지만, 파일에는 단순 텍스트가 아니라 다음 요소가 포함된 상태- 가짜 conductor 세션을 알리는 가짜
DCS 2000p라인 - iTerm2 요청에 응답하는 가짜
OSC 135메시지
- 가짜 conductor 세션을 알리는 가짜
- hook이 수락되면 iTerm2는 정상 conductor 워크플로를 시작하며, 상류 소스에서
Conductor.start()는 즉시getshell()전송 후 성공하면pythonversion()전송 - 공격은 이 요청들을 주입할 필요가 없고, iTerm2가 스스로 요청을 발행하며 악성 출력은 응답만 가장하면 되는 구조
상태 머신 진행 과정
- 가짜
OSC 135메시지는 최소한이지만 정확한 순서로 구성된 상태getshell에 대한 command body 시작- 셸 탐지 출력처럼 보이는 라인 반환
- 해당 명령 성공 종료
pythonversion에 대한 command body 시작- 해당 명령 실패 종료
unhook
- 이 흐름만으로도 iTerm2는 정상적인 fallback 경로로 진입하며, 이후 SSH integration 워크플로가 충분히 완료되었다고 판단하고 다음 단계로 진행
- 다음 단계는
run(...)명령을 구성해 전송하는 과정
sshargs의 역할
- 위조된
DCS 2000phook에는 여러 필드가 포함되며, 그중 공격자가 제어하는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.pyunzip poc.zipcat 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주는 의미 있는 보급을 기대하기에도 짧고, 조기 공개로 대응을 강제해야 한다고 정당화하기에도 짧은 기간
- 결과적으로 취약점은 널리 알려졌지만 수정판은 현실적으로 필요한 사용자에게 아직 제공되지 않은 공개 공백 구간 형성
- 더 나은 선택지로는 수정판이 실제 사용자 손에 들어갈 때까지 기다리거나, 조기 노출이 왜 필요했는지 분명한 근거 제시 가능했지만 둘 다 충족되지 않은 상태
댓글과 토론
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를 쓰는 건 비교적 최근 습관일 수 있음. 예전에는 **@**가 line-kill, **#**가 erase였고, 요즘은 시스템마다 키 동작이 꽤 다르게 느껴짐
-
6년 전에도 거의 같은 iTerm2 보안 이슈가 있었음
- 그래서 아무것도 배우지 못한 것처럼 보였음
-
나는 iTerm2 작성자임. 이 문제는 익스플로잇 체인의 한 고리로는 쓰일 수 있지만, 제목처럼 단독으로 엄청난 위험인 것처럼 말하는 건 과장이라고 봄. 지금은 가족 여행 중이고 돌아가면 수정판을 릴리스할 예정임
- 나는 취약점 발견자가 아니라 블로그 작성자임. 수정판을 내주기로 해서 고마움. 이 버그가 평범하고 무해해 보이는 워크플로에도 영향을 주는데 공식 릴리스가 없었던 점이 놀라웠고, 패치 커밋이 문제를 가설적이라고 표현해서 그렇지 않다는 점을 보여주고 싶었음. 수정판을 내기로 한 점은 반가움
- iTerm2를 고맙게 쓰고 있음. 응답해줘서 감사하고 휴가 잘 보내길 바람
- iTerm2를 정말 좋아해서 감사함
-
bootstrap 스크립트, 원격 conductor 에이전트, escape sequence 등을 활용하는 복잡한 시스템에서 미묘한 버그가 난 건 놀랍지 않았음. 원래 의도되지 않은 방식으로 구성요소를 활용하면 이런 문제가 생기기 쉬움. 텍스트 파일이나 서버 배너처럼 화면에 출력되는 신뢰되지 않은 출력에 특수 코드가 들어 있으면, 출처를 검증하지 않고 처리해버리는 구조로 이해됨
-
이게 전에 본 이야기처럼 느껴졌음. iTerm2의 SSH integration이 CVE 원인이었던 적이 있었고, CVE-2025-22275도 떠오름. 이전 사례들도 있었고, 이번 스레드에서 언급된 예전 이슈는 tmux integration 쪽이었음. 이런 통합 기능은 조금 덜 공격적으로 넣는 게 낫지 않을까 싶음
- ghostty의 SSH integration 방식도 비슷한 우려가 있음. 차라리 upstream ncurses와 협력해서 terminfo를 개선하는 쪽이 더 낫다고 봄
- 이런 일이 여러 번 반복돼 왔음
-
제목이 너무 자극적임. 문제는 고양이가 아니라 iTerm의 SSH integration이고, 데이터 스트림과 분리되지 않은 제어 채널 구조가 위험해 보임. 이 기능을 쓰지 않고 일반 SSH만 쓰면 대체로 괜찮다고 봄
- 그래서 HN 제목을 조금 완화된 표현으로 수정함
-
예전 터미널 에뮬레이터는 escape code로 키보드 재바인딩까지 허용했음. 그래서 신뢰할 수 없는 파일은
cat하지 말고less같은 도구로 열라는 게 거의 상식이었음- 어떤 터미널은 escape sequence만으로 파일 쓰기나 프로그램 실행까지 가능했던 기억이 있음. 지금도 임의 바이트를 터미널 스트림에 그대로 흘려보내지 않는 건 충분히 합리적인 조언이라고 봄
-
글의 표현이 부정확함. 둘째 문단이 "iTerm2를 쓰면 안전하지 않다"처럼 읽히는데, 정확히는 선택적인 Shell Integration 기능을 쓸 때 문제가 생길 수 있다는 정도라고 봄. 이 기능이 기본 비활성화라면 영향 범위는 제한적이라고 이해함. 틀렸다면 정정받고 싶음
- 한 문장의 과장 때문에 글 전체를 형편없다고 하는 건 과하다고 느낌
- 그 기능은 기본 활성화 상태였고 직접 확인 가능함