# 모든 VPS 또는 클라우드 제공자에서 첫 SSH 연결의 MITM 막기

> Clean Markdown view of GeekNews topic #29318. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29318](https://news.hada.io/topic?id=29318)
- GeekNews Markdown: [https://news.hada.io/topic/29318.md](https://news.hada.io/topic/29318.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-09T15:02:06+09:00
- Updated: 2026-05-09T15:02:06+09:00
- Original source: [joachimschipper.nl](https://www.joachimschipper.nl/Stop%20MITM%20on%20the%20first%20SSH%20connection,%20on%20any%20VPS%20or%20cloud%20provider.html)
- Points: 3
- Comments: 1

## Topic Body

- [ssh-init-vm](https://github.com/JoachimSchipper/ssh-init-vm/blob/main/ssh-init-vm)은 새 VM의 첫 SSH 접속에서 **중간자 공격**을 막기 위해 cloud-init으로 임시 SSH 호스트 개인키를 주입하고, 장기 호스트 키를 생성·가져오는 동안만 신뢰하게 함
- Hetzner Cloud처럼 전용 접속 보호 기능이 없는 VPS나 클라우드에서도 동작하며, 필요한 것은 널리 지원되는 **cloud-init**뿐임
- 일반적인 **Trust On First Use**에서 `ssh`의 “The authenticity of host [...] can't be established” 질문에 `yes`를 입력하면, 공격자가 트래픽을 프록시하거나 사용자의 VM처럼 보이는 머신을 제공할 수 있음
- 장기 SSH 호스트 개인키를 cloud-init userdata에 직접 넣으면 첫 접속 인증에는 도움이 되지만, 메타데이터 서비스·SSRF·클라우드 제공자 시스템·관리자 워크스테이션을 통해 **민감한 키 자료**가 노출될 수 있음
- ssh-init-vm은 임시 키를 임시 디렉터리에 두고 `~/.ssh/known_hosts`에 넣지 않으며, VM 출력물을 그대로 저장하지 않고 OpenSSH의 **호스트 키 순환**에 의존해 장기 키를 기록함

---

### cloud-init userdata 노출 문제
- cloud-init으로 장기 SSH 호스트 개인키를 주입하면 공개키를 `~/.ssh/known_hosts`에 넣어 첫 접속을 인증할 수 있지만, 개인키가 여러 경로로 유출될 수 있음
- VM 내부의 임의 프로세스가 일반적으로 읽을 수 있는 **메타데이터 서비스**에서 userdata를 얻을 수 있으며, Hetzner VM에서는 `http://169.254.169.254/hetzner/v1/userdata`로 cloud-init 내용이 보일 수 있음
- 공격자는 [SSRF](https://en.wikipedia.org/wiki/Server-side_request_forgery)를 통해 프로세스가 메타데이터를 누설하도록 만들 수 있고, 이런 차단은 전용 보호 기능이 있는 환경에서도 [항상 적용되는 것은 아님](https://aws.amazon.com/blogs/security/get-the-full-benefits-of-imdsv2-and-disable-imdsv1-across-your-aws-infrastructure/)
- 클라우드 제공자의 다른 시스템에서도 userdata가 노출될 수 있으며, Hetzner는 서버 생성 API 문서에서 [“passwords or other sensitive information”을 저장하지 말라고 명시](https://docs.hetzner.cloud/reference/cloud#tag/servers/create_server:~:text=don%E2%80%99t%20use%20it%20to%20store%20passwords%20or%20other%20sensitive%20information)함
- 관리자 워크스테이션도 cloud-init userdata가 남거나 지나가는 위치가 될 수 있으므로, 장기 개인키를 넣는 방식은 키가 유효한 동안 노출될 위험을 만듦

### 보안 분석과 위협 모델
- 전제는 **OpenSSH 프로토콜과 구현**을 신뢰하며, 관리자가 공격을 탐지하는 능력에는 의존하지 않는다는 것임
- ## 네트워크 공격자에 대한 보호
  - 보호 대상은 관리자 워크스테이션의 무결성과 VM임
  - 공격자는 네트워크를 완전히 제어하는 중간자이며, 스크립트가 성공하거나 실패해 종료된 뒤 어느 시점에 cloud-init userdata를 알게 될 수 있음
  - 공격자가 키 자료를 가치가 남아 있는 시점에 알지 못하기 때문에 보호가 성립함
  - [스크립트](https://github.com/JoachimSchipper/ssh-init-vm/blob/main/ssh-init-vm)는 임시 SSH 호스트 키의 우발적 사용을 막기 위해 이를 임시 디렉터리에 보관하며, 임시 SSH 호스트 키를 `~/.ssh/known_hosts`에 넣지 않음
- ## 관리자 워크스테이션이 침해된 경우
  - 보호 대상은 VM과 VM의 장기 SSH 호스트 개인키에 한정됨
  - 공격자는 네트워크와 관리자 워크스테이션을 완전히 제어하지만, 실제 VM에는 접속하지 않는다고 가정함
  - 장기 SSH 호스트 개인키는 관리자 워크스테이션에 있었던 적이 없고, 공격자가 실제 VM에 접속하지 않기 때문에 VM의 장기 호스트 키를 얻지 못함
  - 공격자가 실제 VM에 접속하면 `ssh root@&lt;VM&gt; cat /etc/ssh/ssh_host_*` 같은 방식으로 SSH 호스트 키를 알 수 있을 가능성이 큼
- ## VM 또는 제공자가 침해된 경우
  - 보호 대상은 관리자 워크스테이션의 무결성에 한정됨
  - 공격자는 네트워크를 완전히 제어하고 VM이나 제공자도 완전히 제어할 수 있음
  - 이 경우에도 OpenSSH가 안전하다는 전제 때문에 관리자 워크스테이션의 무결성을 보호함
  - 추가 방어로 [스크립트](https://github.com/JoachimSchipper/ssh-init-vm/blob/main/ssh-init-vm)는 VM 출력물을 그대로 `~/.ssh/known_hosts`에 쓰지 않고, [OpenSSH](https://man.openbsd.org/ssh_config#UpdateHostKeys)의 [키](https://blog.djm.net.au/2015/02/key-rotation-in-openssh-68.html) [순환](https://blog.djm.net.au/2015/02/hostkey-rotation-redux.html)에 의존해 장기 SSH 호스트 키를 넣음
  - 이 방식은 침해된 호스트가 `known_hosts` 파서에 악성 데이터를 먹이는 것을 막고, VM이 실제로 [제어하는 키](https://blog.djm.net.au/2015/02/hostkey-rotation-redux.html)만 `~/.ssh/known_hosts`에 기록되게 함
  - [`HashKnownHosts`](https://man.openbsd.org/ssh_config#HashKnownHosts) 같은 OpenSSH 옵션과 향후 관련 옵션도 올바르게 처리할 수 있음

### 실제 중간자 공격이 성립하는 조건
- 중간자 공격의 성공 여부는 사용자가 모든 접속이 처음부터 잘못된 머신으로 향하고 있음을 실제로 감지하는지, 비밀번호 입력을 거부하는지, `ssh`의 agent 또는 X11 전달을 설정하는지에 따라 달라짐
- [ssh-mitm](https://docs.ssh-mitm.at/) 기준의 단순화된 조건으로는, 공격자가 진짜 대상 호스트가 아니라 공격자 제어 머신에 접근 권한을 제공해 사용자를 속일 수 있으면 성공 가능성이 큼
- 공격자가 사용자를 속여 진짜 호스트에 로그인할 수 있는 정보를 얻어도 성공함
  - 사용자가 공격자 머신에 **비밀번호**로 로그인하면 공격자가 성공할 수 있음
  - 사용자가 어떤 인증 방식으로든 로그인한 뒤 프롬프트에서 비밀번호를 입력하면 공격자가 성공할 수 있음
  - 사용자가 어떤 인증 방식으로든 로그인하고 **ssh-agent 연결**을 전달하면 공격자가 성공할 수 있음
- 위 조건이 없으면 공격자는 사용자를 속이기 위해 진짜 호스트 접근이 필요하지만, 사용자의 입력만으로는 진짜 호스트에 로그인할 수 없어 실패할 가능성이 큼
- 사용자가 X11 연결을 전달하면 공격자는 인증 방식과 별개로 관리자 워크스테이션을 공격하는 데 성공할 수도 있음

## Comments



### Comment 57110

- Author: neo
- Created: 2026-05-09T15:02:07+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/q5bds7/stop_mitm_on_first_ssh_connection_on_any) 
- 자동화할 수 있어서 멋짐. 수동 방식으로는 클라우드 제공자 콘솔에서 서버의 **SSH 지문**을 별도 채널로 확인해 왔음  
  관리하는 클라우드 인스턴스가 많지 않아서, 프로비저닝에 수동 단계가 몇 개 있어도 괜찮음
  - 그 방식도 동작함. 다만 사용하던 제공자가 그 부분을 연결해 두지 않았고, 동시에 설정 자동화 스크립트를 만들고 있던 상황이었음

- **DNS 영역**을 자동화해 뒀다면 다른 접근도 가능함: 범위가 매우 좁은 일회용 토큰을 만들고, 예를 들어 `my-server-hostname.example.net`에 레코드 하나 생성만 허용하게 함  
  그 토큰을 `cloud-init`으로 서버에 전달하고, 서버가 공개 SSH 키를 DNS의 SSHFP 레코드로 올리게 함. 이후 SSH 클라이언트가 SSHFP 레코드를 자동 검증하게 할 수 있으며, DNS 영역은 **DNSSEC 서명**이 되어 있어야 함  
  이 흐름은 서버가 비공개 SSH 호스트 키를 유지하면서도 키 회전을 피할 수 있게 해줌. 대부분의 DNS 제공자는 이런 세밀한 일회용 접근 토큰을 지원하지 않지만, 토큰을 검증한 뒤 영구적이고 범위 제한 없는 토큰으로 대신 API 호출을 해주는 간단한 내부 웹 서비스를 둘 수 있음. SSH 서버는 그 영구 토큰에 접근하지 못함
  - 창의적인 방식이고, **커스텀 서비스용 일회용 토큰**이면 꽤 유연해서 잘 동작할 듯함  
    다만 DNSSEC 도메인에 쓰기보다는 SSH 인증서를 생성하는 쪽을 더 선호할 것 같고, 그 지점부터는 어떤 해법이 특정 환경에 더 잘 맞는지의 문제가 됨  
    이렇게 유연한 토큰을 생성할 수 있는 소프트웨어나 제공자를 알고 있는지, 아니면 어느 정도 직접 개발이 필요한지 궁금함

- 꽤 깔끔함. 그런데 글의 날짜에서 **월과 일**이 뒤바뀐 것 같음
  - OpenSSH로 작업하는 건 늘 즐겁고, **월/일 표기**는 고쳐 둠

- 예전에 같은 **SSH 닭과 달걀 문제**를 탐색하려고 실험적인 서비스를 만든 적이 있음  
  요청 시 SSHFP DNS 레코드를 생성해 주며, 관심 있으면 https://github.com/tedb/sshfp 를 보면 됨

- VM 제공자들 사이에서 어느 정도 일관적인 **169.254.169.254 메타데이터 서비스**를 다뤄줘서 반가움. [`cloud-init` 소스](https://github.com/canonical/cloud-init/blob/9c7d85d82c16c2379b1f004623c9d43507f31d3e/cloudinit/sources/DataSourceHetzner.py#L39)의 여러 `cloudinit/source/DataSource*.py` 항목을 보면 확인할 수 있음  
  개인적으로는 `cloud-init`의 설계와 한계 때문에 점점 피로감을 느낌. 로컬 QEMU 가상머신, 원격 머신, 컨테이너, 물리 하드웨어 전반에서 시스템 설정을 통합하는 데 관심이 있음  
  [arch-boxes project](https://gitlab.archlinux.org/archlinux/arch-boxes)는 ArchLinux [cloud-init image](https://wiki.archlinux.org/title/Arch_Linux_on_a_VPS)가 어떻게 만들어지는지 보여주며, 매우 단순한 셸 스크립트 묶음임. 이 방법을 [`guestfish`](https://libguestfs.org/)나 [µvm](https://www.qemu.org/docs/master/system/i386/microvm.html)으로 응용하면, OCI 호환 이미지, QEMU나 클라우드 제공자용 디스크 이미지, 새 물리 머신 프로비저닝에 **완전히 같은 스크립트**를 쓸 수 있음  
  몇 가지 QEMU 플래그와 함께라면 `cloud-init` 의존 없이 같은 접근을 재현할 수 있음. 아는 한 [`systemd.system-credentials`](https://man.archlinux.org/man/systemd.system-credentials.7)로 임시 호스트 키를 전달할 수는 없고, `ssh.authorized_keys.root` 같은 `~/.ssh/authorized_keys`용 자격 증명만 있음  
  대신 initrd 단계에서 실행되거나 `systemd-firstboot.service`와 함께 실행되는 unit 파일을 만들 수 있음. 이 unit 파일은 이미지에 미리 넣거나 `systemd.extra-unit.*` 자격 증명으로 임시 주입하고, `systemd.wants=…` 커널 명령줄 옵션으로 활성화할 수 있음. QEMU에서는 `-netdev user,id=metadata,net=169.254.0.0/16,dhcpstart=169.254.0.15,guestfwd=tcp:169.254.169.254:80-cmd:…` 항목으로 메타데이터 서비스 존재를 흉내낼 수 있음. 다만 생성된 인터페이스를 활성화해야 할 가능성이 크고, 이 역시 임시 unit 파일로 처리하는 편이 나을 수 있음  
  이렇게 하면 여러 종류의 “머신”에서 일관된 시스템 설정을 수행할 때, 비교적 낮은 복잡도로 **상당한 유연성**을 얻음. 실제로 이 작업만 놓고 보면, 이미지 생성 도구가 고정 호스트 키가 들어간 머신 이미지를 만들고, 첫 재부팅이나 종료 때 실행되는 커스텀 호스트 키 회전 스크립트를 SystemD 서비스로 설치하는 방식이 가장 좋아 보임
  - ArchLinux에서는 `/etc/mkinitcpio.conf`에서 `systemd` `HOOK`을 활성화하면, initrd에서 수행할 작업을 위해 **SystemD unit 파일**을 작성할 수 있고 사실상 그래야 함  
    실제로 써보면 `{/etc,/usr/lib}/initcpio/hooks`를 작성하는 것보다 아주 약간 더 번거로운 정도임  
    하지만 initrd에서 `systemd-networking`과 `systemd-resolved`를 켜는 건 꽤 쉽기 때문에, initrd가 시스템 시작 책임을 맡고 루트 파일시스템으로 전환하기 전에 작업을 예약할 수 있음  
    물론 노트북 같은 물리 하드웨어에서는 Wi-Fi 연결에 `NetworkManager` 같은 것이 필요해 잘 안 맞을 수 있지만, QEMU VM과 호스팅 VM에는 잘 맞고 많은 시스템 시작 작업이 이 공간에 자연스럽게 들어감  
    목표는 `cloud-init`에 의존하지 않고, 특정 클라우드 제공자 하나에 묶이지 않으며, 물리 머신·컨테이너·로컬 VM·호스팅 VM 전반의 일관성을 얻고, 의존성을 사실상 SystemD 정도로 줄이는 것임
  - 원하는 기능만 확장하면 되는 아주 작은 도구로는 https://github.com/the-maldridge/shinit/ 같은 것도 가능하지 않을까 싶음
