- Published on
SSH Protocol Deep Dive — Transport, Authentication, Channels, Certificates, Terrapin 완전 정복 (2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
TL;DR
- SSH는 3-레이어 프로토콜: Transport (암호화/무결성/키 교환), User Authentication (사용자 인증), Connection (멀티플렉싱 채널).
- 역사: 1995년 Tatu Ylönen의 SSH1, 1996년 상용화, 2006년 SSH2 표준(RFC 4251-4254), 현대는 OpenSSH가 de facto 표준.
- Transport layer: Diffie-Hellman(또는 ECDH, Curve25519) 키 교환 → 세션 키 도출 → 대칭 암호 + MAC.
- User Authentication: password, publickey, keyboard-interactive, GSSAPI.
publickey+ Ed25519가 현재 권장. - Connection layer: Multiplexed channel. 한 TCP 연결 위에 shell, SFTP, port forward 여러 개.
- Host key: 서버의 영구 ID. TOFU (Trust On First Use)로
~/.ssh/known_hosts에 저장. - ProxyJump (
-J): 중간 서버 통과.ssh -J bastion target. - ControlMaster: 연결 재사용. 두 번째
ssh부터 즉시. - SSH CA: Host/user key를 인증서로 서명.
known_hosts지옥 해결. - 포트 포워딩: Local (
-L), Remote (-R), Dynamic SOCKS (-D). - 2023 Terrapin 공격 (CVE-2023-48795): 암호 프로토콜 truncation 공격. 방어 옵션 제공됨.
1. SSH의 역사
1.1 1995년 — Tatu Ylönen
핀란드 헬싱키 대학의 Tatu Ylönen이 1995년에 SSH를 만들었다. 계기: 자신의 학교에 password sniffer 공격 — 네트워크를 스니핑해서 평문 비밀번호 수집.
당시 원격 로그인:
- Telnet: 모든 것이 평문.
- rlogin / rsh / rcp: "Berkeley r-tools". 더 나쁨 — IP 기반 신뢰 인증, 암호화 없음.
Ylönen의 목표: "Secure replacement for r-tools". Telnet + rlogin + rsh + rcp의 안전한 대체재.
1995년 7월 첫 공개 (SSH1). 순식간에 퍼졌다.
1.2 상업화와 분기
1995년 말: Ylönen이 SSH Communications Security(SSH.COM) 설립. 상용 버전 유지.
1999년: OpenBSD 팀이 마지막 free SSH1 코드베이스를 포크 → OpenSSH. 버그 수정, 보안 강화, 무료 + BSD 라이선스.
OpenSSH가 모든 주류 OS에 기본 설치되며 사실상 SSH = OpenSSH가 됐다.
1.3 SSH1 → SSH2
SSH1의 문제:
- CRC-32 체크섬 → insertion attack 가능.
- 약한 key exchange.
- 한 가지 인증 방법만.
- 단일 채널.
SSH2 (RFC 4251-4254, 2006):
- 강화된 암호학.
- 모듈형 3-레이어.
- 멀티플렉싱 채널.
- 다양한 인증 메커니즘.
2001년 이후 SSH1은 deprecated. OpenSSH 7.6 (2017)부터 SSH1 지원 완전 제거.
1.4 2025년 현황
- OpenSSH: 거의 유일한 구현. macOS, Linux, Windows(OpenSSH-Win32, 2018+) 모두 내장.
- libssh, libssh2: 라이브러리.
- Paramiko, go-crypto/ssh: 언어별.
- Dropbear: 임베디드용 경량 SSH.
- TeraTerm, PuTTY: Windows의 전통 클라이언트.
2023년 Terrapin 공격 (CVE-2023-48795) 이후 OpenSSH 9.6이 방어 도입. 주요 업데이트가 여전히 진행 중.
2. 3-레이어 구조
2.1 개요
SSH는 세 개의 논리적 레이어로 나뉜다:
┌──────────────────────────────────┐
│ SSH Connection Protocol │
│ (channels, shell, SFTP, ...) │
├──────────────────────────────────┤
│ SSH User Authentication │
│ (password, publickey, ...) │
├──────────────────────────────────┤
│ SSH Transport Layer │
│ (encryption, MAC, key exchange) │
├──────────────────────────────────┤
│ TCP (port 22) │
└──────────────────────────────────┘
각 레이어는 독립적. Transport는 암호화된 파이프, Auth는 그 위에 인증, Connection은 멀티플렉싱.
2.2 왜 이렇게 나누었나
대안: TLS처럼 "인증 + 암호화를 한 레이어에".
SSH가 분리한 이유:
- 유연성: 인증 방법을 쉽게 추가 (password, publickey, GSSAPI, ...).
- 재사용: Connection 레이어를 다른 서비스에도 (Git protocol over SSH, SFTP, ...).
- 멀티플렉싱: 한 연결에 여러 채널.
TLS는 "데이터 스트림 하나"에 최적화, SSH는 "다양한 응용"에 최적화.
3. Transport Layer
3.1 목표
TCP 위에 암호화 + 무결성 + 서버 인증 제공:
- 키 교환으로 세션 키 도출.
- 대칭 암호로 데이터 암호화.
- MAC으로 무결성.
- 서버의 host key로 인증.
3.2 Packet Format
SSH packet:
packet_length (4 bytes) # 전체 길이
padding_length (1 byte) # 패딩 길이
payload # 실제 내용
random padding # block size 맞춤
mac # 메시지 인증 코드
암호화 후: packet_length 제외하고 모두 암호화. MAC은 평문 길이 + 암호화된 데이터에 대해.
3.3 Banner Exchange
TCP 연결 후 가장 먼저:
Server: SSH-2.0-OpenSSH_9.4\r\n
Client: SSH-2.0-OpenSSH_9.4\r\n
평문. 서로의 SSH 버전을 확인. 이후 모든 것은 암호화.
이것이 telnet처럼 banner grabbing 가능한 이유 — 서버 버전이 노출됨.
3.4 Algorithm Negotiation
Banner 교환 후 KEXINIT 메시지 교환. 지원하는 알고리즘 목록:
KEXINIT {
cookie (16 bytes random)
kex_algorithms: ["curve25519-sha256", "diffie-hellman-group14-sha256", ...]
server_host_key_algorithms: ["ssh-ed25519", "rsa-sha2-512", ...]
encryption_algorithms_client_to_server: ["chacha20-poly1305@openssh.com", "aes256-gcm@openssh.com", ...]
encryption_algorithms_server_to_client: [...]
mac_algorithms_client_to_server: ["hmac-sha2-256-etm@openssh.com", ...]
mac_algorithms_server_to_client: [...]
compression_algorithms_client_to_server: ["none", "zlib@openssh.com"]
compression_algorithms_server_to_client: [...]
languages_client_to_server: [""]
languages_server_to_client: [""]
first_kex_packet_follows: false
reserved: 0
}
양측의 목록에서 첫 번째 일치가 선택됨. 클라이언트 선호도 우선.
3.5 Key Exchange
DH (Diffie-Hellman) 기반:
- 양측이 공유 파라미터 사전 합의 (
p,g). - 각자 랜덤
a,b생성. - 클라이언트:
A = g^a mod p송신. - 서버:
B = g^b mod p송신. - 양측:
K = B^a = A^b = g^(ab) mod p계산.
두 측이 같은 K를 가지지만, 도청자는 A, B만 보고 K를 유도할 수 없음.
현대 알고리즘:
- curve25519-sha256: Curve25519 ECDH. 현재 기본.
- diffie-hellman-group14-sha256: 2048-bit DH. 레거시 호환.
- ecdh-sha2-nistp256: NIST P-256. 중간.
Curve25519는 크기가 작고 빠르며 안전. DJB (Daniel J. Bernstein)의 작품.
3.6 Host Key 서명
KEX 중 서버가 host key로 서명한 값을 전송 → 클라이언트가 "이 서버가 진짜인지" 확인.
Server: K_S (host public key)
signature(K_S || K_client || ... || shared_secret K)
클라이언트가 검증:
K_S의 서명을 검증.K_S가known_hosts에 있는가?- 있으면 OK, 없으면 prompt → TOFU.
3.7 Session Key Derivation
공유 비밀 K에서 여러 키를 도출:
IV_C2S = HASH(K || H || "A" || session_id)
IV_S2C = HASH(K || H || "B" || session_id)
Key_C2S = HASH(K || H || "C" || session_id)
Key_S2C = HASH(K || H || "D" || session_id)
MAC_C2S = HASH(K || H || "E" || session_id)
MAC_S2C = HASH(K || H || "F" || session_id)
H = exchange hash (KEX 중 계산).
session_id = 첫 번째 H. 세션 내내 유지.
각 방향에 독립된 키 → 더 안전.
3.8 Cipher Suites
AEAD(Authenticated Encryption with Associated Data):
- chacha20-poly1305@openssh.com (OpenSSH 기본): 매우 빠름, constant-time.
- aes256-gcm@openssh.com: 하드웨어 가속 CPU에서.
- aes128-gcm@openssh.com: 같은 원리 AES-128.
Legacy:
- CBC 모드:
aes256-cbc, etc. 일부 공격 가능 → 권장 X. - RC4: 완전 deprecated.
MAC (AEAD 아닌 경우):
hmac-sha2-256-etm@openssh.com: Encrypt-then-MAC (권장).hmac-sha2-512-etm@openssh.com.- Legacy
hmac-sha1,hmac-md5는 약함.
3.9 Re-keying
일정 시간/데이터 후 rekey — 새 DH 교환, 새 세션 키. 장수명 연결의 보안 강화.
기본: 1 GB 또는 1 시간 후. RekeyLimit 1G 1h.
4. User Authentication
4.1 시작
Transport 레이어 완료 후, 클라이언트가 서비스 요청:
Client: SSH_MSG_SERVICE_REQUEST("ssh-userauth")
Server: SSH_MSG_SERVICE_ACCEPT("ssh-userauth")
이후 인증 레이어 활성화.
4.2 방법들
none: 인증 없이 시도 → 서버가 "이런 방법 쓸 수 있어요" 응답.
password: 비밀번호 전송. 암호화된 채널로.
publickey: 공개 키 기반. 권장.
keyboard-interactive: 서버가 질문을 보내고 클라이언트가 답변. MFA, OTP.
gssapi-with-mic: Kerberos 통합.
hostbased: 호스트 자체 ID 기반 (rlogin 스타일). 거의 안 씀.
4.3 publickey 상세
가장 일반적. 원리:
- 클라이언트가 키 쌍(private + public) 보유.
- 서버는
~/.ssh/authorized_keys에 공개 키 리스트 보유. - 클라이언트가 공개 키 제시:
Client: SSH_MSG_USERAUTH_REQUEST(user, "publickey", pubkey) - 서버: "이 키 허용됨" 응답.
- 클라이언트가 서명 생성 (session_id + 요청 정보를 개인 키로 서명):
Client: SSH_MSG_USERAUTH_REQUEST(user, "publickey", pubkey, signature) - 서버가 공개 키로 서명 검증.
개인 키는 네트워크에 전송 안 됨. 서명만 전송.
4.4 Key 형식
Private key (~/.ssh/id_ed25519):
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
c2gtZWQyNTUxOQAAACCDrIqbCn/K0a+zNVFcQ7NQsA5dgT9VV6PBZhFJF5uQvQAA
AJAXPlLvFz5S7wAAAAtzc2gtZWQyNTUxOQAAACCDrIqbCn/K0a+zNVFcQ7NQsA5d
...
-----END OPENSSH PRIVATE KEY-----
OpenSSH의 독자 형식 (RFC 아님). 선택적 passphrase 암호화.
Public key (~/.ssh/id_ed25519.pub):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIOsipsKf8rRr7M1UVxDs1CwDl2BP1VXo8FmEUkXm5C9 user@host
한 줄. 첫 부분: 알고리즘. 중간: base64 공개 키. 마지막: comment (보통 user@host).
4.5 Key 알고리즘
Ed25519 (권장):
- Curve25519 기반.
- 256-bit, 빠름, 안전.
- OpenSSH 6.5+ 지원.
ECDSA:
- NIST 곡선(P-256, P-384).
- Ed25519보다 약간 느림.
- 구현 버그 위험 (nonce 재사용 시 key 노출).
RSA:
- 3072-bit+ 권장.
- 레거시 호환.
- 큰 키 크기.
DSA:
- 1024-bit만 가능 → 폐기.
현대 권장: Ed25519 우선, RSA-3072 차선.
4.6 agent
매번 passphrase 입력은 불편. ssh-agent가 키를 메모리에 로드:
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519
# Passphrase once
ssh user@host # 이후 passphrase 없이
Agent가 서명 작업 대행. 개인 키는 agent 밖으로 안 나감.
4.7 Agent Forwarding
ssh -A user@hostA
# hostA에서:
ssh user@hostB # 원격에서도 내 키 사용
-A 옵션이 agent를 forward. hostA의 SSH 프로세스가 local agent로 프록시.
보안 주의: hostA의 root는 당신의 agent를 악용할 수 있다. 신뢰 없는 호스트에서 금지. ProxyJump가 안전한 대안.
5. Connection Layer
5.1 Channel 개념
인증 완료 후 클라이언트가 channel 열기:
Client: SSH_MSG_CHANNEL_OPEN("session")
Server: SSH_MSG_CHANNEL_OPEN_CONFIRMATION(channel_id)
각 channel이 독립 스트림 — 한 SSH 연결에 여러 개 가능.
5.2 Channel Type
session: shell 또는 명령 실행.
SSH_MSG_CHANNEL_REQUEST("pty-req") # PTY 할당
SSH_MSG_CHANNEL_REQUEST("shell") # 셸 시작
SSH_MSG_CHANNEL_REQUEST("exec", cmd) # 명령 실행
SSH_MSG_CHANNEL_REQUEST("subsystem", "sftp") # SFTP
direct-tcpip: Local port forward. forwarded-tcpip: Remote port forward. x11: X11 forwarding.
5.3 Flow Control
각 channel은 window (TCP-like):
- 채널 open 시 window 크기 선언.
- 데이터를 보내면 window 감소.
- 수신자가
SSH_MSG_CHANNEL_WINDOW_ADJUST로 window 증가.
한 채널이 다른 채널을 굶기지 못하게 하는 중요한 메커니즘.
5.4 Shell Session 흐름
Client: CHANNEL_OPEN("session")
Server: CHANNEL_OPEN_CONFIRMATION(id=0)
Client: CHANNEL_REQUEST(id=0, "pty-req", TERM, w, h, ...)
Client: CHANNEL_REQUEST(id=0, "env", KEY, VALUE)
Client: CHANNEL_REQUEST(id=0, "shell")
Server: CHANNEL_SUCCESS
Client: CHANNEL_DATA(id=0, "ls -la\n")
Server: CHANNEL_DATA(id=0, "total 64\n...")
...
Client: CHANNEL_CLOSE
Server: CHANNEL_CLOSE
5.5 Exec Mode
ssh user@host 'ls -la'
Shell을 시작하지 않고 단일 명령 실행. stdout과 stderr를 반환.
내부:
CHANNEL_REQUEST(id, "exec", "ls -la")
Shell보다 빠름 (시작 오버헤드 없음). 자동화에 유용.
5.6 SFTP Subsystem
sftp user@host
내부: SSH channel을 열고 sftp subsystem을 요청.
CHANNEL_REQUEST(id, "subsystem", "sftp")
Subsystem은 서버가 실행하는 프로그램 (일반적으로 sftp-server). 이 프로그램이 SFTP 프로토콜을 구현.
SFTP는 SSH와 별개의 프로토콜 — 파일 시스템 연산(open, read, write, mkdir, ...)을 정의. SSH는 단지 전송 수단.
6. Port Forwarding
6.1 Local Port Forwarding (-L)
ssh -L 8080:internal-server:80 user@bastion
내 컴퓨터:8080 → SSH → bastion → internal-server:80
내 localhost:8080으로 오는 연결이 bastion을 통해 internal-server:80으로 전달된다.
사용 예:
- 방화벽 뒤의 DB 접근.
- 원격 웹 인터페이스.
6.2 Remote Port Forwarding (-R)
ssh -R 9000:localhost:3000 user@remote
remote:9000 → SSH → 내 컴퓨터 → localhost:3000
remote 서버의 9000 포트로 오는 연결이 내 컴퓨터의 3000으로 전달.
사용 예:
- NAT 뒤의 서비스를 외부에 노출.
- 임시 웹훅 URL 제공.
6.3 Dynamic Port Forwarding (-D)
ssh -D 1080 user@host
내 컴퓨터:1080 (SOCKS5 proxy) → SSH → host → anywhere
내 컴퓨터에 SOCKS5 프록시가 생긴다. 브라우저 등을 이 프록시로 설정하면 모든 트래픽이 host를 거쳐.
사용 예:
- 제한된 네트워크에서 우회.
- 원격 위치로 "VPN" 역할.
6.4 구현
내부적으로 모두 channel을 쓴다:
Local (-L): 로컬에서 listen. 연결 오면 SSH에 direct-tcpip channel 요청 → 서버가 타겟에 연결.
Remote (-R): 클라이언트가 서버에 "이 포트로 listen해줘" 요청 → 서버가 받은 연결마다 forwarded-tcpip channel 열기.
Dynamic (-D): SOCKS5 파싱을 로컬에서 → 각 연결마다 direct-tcpip.
6.5 GatewayPorts
기본 -L과 -D는 localhost만 listen. 다른 머신에서 접근 불가.
ssh -L '*:8080:internal:80' user@host
# 또는 config에 GatewayPorts yes
*:8080으로 모든 인터페이스. 보안 주의.
7. Host Key와 known_hosts
7.1 Host Key
서버에는 영구 key pair가 있다. /etc/ssh/:
ssh_host_ed25519_key
ssh_host_ed25519_key.pub
ssh_host_rsa_key
ssh_host_rsa_key.pub
여러 타입을 동시 보유 — 클라이언트가 선호 알고리즘 사용.
7.2 TOFU (Trust On First Use)
첫 연결:
$ ssh user@new-server
The authenticity of host 'new-server' can't be established.
ED25519 key fingerprint is SHA256:abc123...
Are you sure you want to continue connecting (yes/no/[fingerprint])?
"이 키가 맞나요?" — 사용자가 결정.
yes 입력 시 ~/.ssh/known_hosts에 추가:
new-server ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
다음부터 자동 검증.
7.3 known_hosts 변경 경고
서버 키가 바뀌면:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
The ECDSA host key for new-server has changed,
and the key for the corresponding IP address 1.2.3.4
is unknown. This could be either a MITM attack!
Please contact your system administrator.
가능성:
- MITM 공격 — 누군가 가로채는 중.
- 서버 재설치 — 합법적 key 교체.
- IP 재사용 — 클라우드에서 IP가 다른 VM에.
수동 확인 필요. 합법이면 ssh-keygen -R host로 이전 키 삭제.
7.4 hostname 해싱
HashKnownHosts yes
known_hosts의 hostname을 해시로 저장:
|1|base64-salt|base64-hash ssh-ed25519 ...
공격자가 파일을 훔쳐도 어떤 서버를 방문했는지 알기 어려움.
OpenSSH 기본 활성화 (Ubuntu 등).
7.5 StrictHostKeyChecking
StrictHostKeyChecking yes # 항상 prompt
StrictHostKeyChecking ask # 기본, prompt
StrictHostKeyChecking no # 자동 수락 (위험)
StrictHostKeyChecking accept-new # 새 호스트만 자동 수락 (비교적 안전)
**accept-new**는 2018+ OpenSSH의 좋은 기본 — 알려진 호스트의 키 변경은 여전히 경고, 새 호스트는 자동.
8. SSH Certificates
8.1 known_hosts 지옥
대규모 인프라:
- 수백 ~ 수만 서버.
- 각자 host key.
- 새 서버 생기면 각 사용자가 TOFU 또는 중앙
known_hosts배포. - 서버 재설치 시 전부 업데이트.
확장 불가.
8.2 SSH CA
SSH CA (Certificate Authority)가 해결:
- 조직이 CA key 하나 생성.
- 각 클라이언트가 CA 공개 키를 한 번만 신뢰.
- CA가 host key들을 서명 → host certificate.
- 서버가 certificate를 제시.
- 클라이언트가 CA 서명 검증 → 자동 인정.
TOFU 필요 없음. 새 서버 생기면 CA가 서명만.
8.3 생성
CA 생성:
ssh-keygen -t ed25519 -f ca-host-key -C 'Host CA'
Host key 서명:
ssh-keygen -s ca-host-key -I 'host-001' -h \
-n 'host.example.com,192.0.2.10' \
-V '+52w' \
ssh_host_ed25519_key.pub
-h: host certificate (user certificate 아님).
-I: identity (로그용).
-n: 허용 principals.
-V: 유효 기간.
결과: ssh_host_ed25519_key-cert.pub.
8.4 Client 설정
~/.ssh/known_hosts 또는 /etc/ssh/ssh_known_hosts:
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... Host CA
"*.example.com에 접속 시 이 CA로 서명된 host cert면 자동 신뢰".
8.5 Server 설정
/etc/ssh/sshd_config:
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
서버가 host cert를 제시.
8.6 User Certificate
같은 원리를 user key에도:
# User CA 생성
ssh-keygen -t ed25519 -f ca-user-key -C 'User CA'
# User key 서명
ssh-keygen -s ca-user-key -I 'alice' \
-n 'alice,admin' \
-V '+8h' \
id_ed25519.pub
서버의 sshd_config:
TrustedUserCAKeys /etc/ssh/user_ca.pub
사용자가 ~/.ssh/id_ed25519-cert.pub를 가지고 있으면 자동 인증.
장점:
- Short-lived (8시간 등) — 키 노출 영향 제한.
- 중앙 관리 — 퇴사자 즉시 차단.
- Principal 기반 authorization.
8.7 Teleport / Step-ca / Vault SSH CA
운영 가능한 구현:
- Teleport: 전체 SSH + Kubernetes + DB 액세스 플랫폼.
- smallstep/step-ca: 오픈소스 CA.
- HashiCorp Vault: SSH secret engine으로 dynamic signing.
대규모 엔터프라이즈는 대부분 이 중 하나 사용.
9. ControlMaster와 연결 재사용
9.1 문제
매 ssh host가 전체 handshake:
- TCP 연결 (~50ms)
- SSH banner + KEXINIT (~200ms)
- Key exchange (~200ms)
- Authentication (~100ms)
- 총 500ms+ 대기.
자주 SSH하면 누적 시간 낭비.
9.2 ControlMaster
첫 연결이 master가 되고, 이후 연결이 같은 소켓을 공유:
~/.ssh/config:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p
ControlPersist 10m
- ControlMaster auto: 자동 master 생성/사용.
- ControlPath: 유닉스 소켓 경로.
%r=user, %h=host, %p=port. - ControlPersist 10m: 마지막 사용 후 10분 유지.
9.3 동작
$ ssh host # 500ms, master 생성
$ ssh host # 5ms, master 재사용
$ ssh host cmd # 5ms
$ sftp host # 5ms
$ scp file host: # 5ms
두 번째 이후는 채널만 열기 — 전체 handshake 스킵.
9.4 제약
- Master가 죽으면 모든 연결 종료.
- 일부 옵션 (예:
-L포트 포워딩)은 master와 달라야 함. - NFS 같은 공유 파일시스템에 ControlPath 두면 문제.
대부분 경우 매우 편리.
10. ProxyJump와 Bastion
10.1 Bastion 패턴
보안 설계:
- Private 서버는 public IP 없음.
- Bastion (jump host)만 public.
- 사용자가 bastion을 거쳐 private로.
[Internet]
↓
[Bastion]
↓
[Private Servers]
10.2 예전 방법 — ProxyCommand
Host internal
HostName internal.private.network
ProxyCommand ssh bastion nc %h %p
ssh internal이 실제로는 "bastion에서 nc internal 22"를 실행해 SSH 프로토콜을 tunneling.
복잡하고 버그 많음 (nc 동작 차이 등).
10.3 ProxyJump (-J)
OpenSSH 7.3+ (2016):
ssh -J bastion internal
또는 config:
Host internal
HostName internal.private.network
ProxyJump bastion
진짜 SSH 네이티브 — ssh 프로토콜을 bastion을 통해 forward.
장점:
- 더 안전 (bastion에서
nc불필요). - 더 빠름.
- Multi-hop:
-J host1,host2,host3로 여러 단계.
10.4 인증 흐름
Client → bastion
(authenticate with bastion)
Client → bastion → internal
(authenticate with internal, bastion은 tunneling만)
- bastion에 한 번 인증.
- internal에 또 한 번 인증 (같은 또는 다른 키).
- bastion은 internal의 credential을 보지 못함.
Agent forwarding 대체. 훨씬 안전.
10.5 Agent Forwarding vs ProxyJump
Agent Forwarding:
Client (agent) → hostA → hostB
↑
hostA가 agent 접근 가능 → root 탈취 시 위험
ProxyJump:
Client → hostA (tunnel) → hostB
↑______________↑
hostA는 tunneling만, 내 key 접근 불가
현대 권장: ProxyJump.
11. SFTP와 SCP
11.1 SCP
scp local.txt user@host:/remote/
scp user@host:/remote.txt ./
scp -r dir user@host:/remote/
Simple. 하지만 오래된 프로토콜:
- SSH에 1990년대 RCP 스크립트를 재활용.
- 보안 이슈 여러 건 (CVE-2020-15778 등).
- OpenSSH 9.0+ (2022)부터 내부적으로 SFTP 사용.
11.2 SFTP
Standalone 프로토콜 — SSH File Transfer Protocol. SSH와 별개지만 보통 SSH subsystem으로.
더 풍부한 API:
- 디렉토리 나열.
- Attribute (permissions, timestamps).
- Resume (partial transfer).
- Random access.
sftp user@host
> ls
> get file.txt
> put local.txt
> mkdir newdir
> exit
프로그래밍 언어 라이브러리 풍부: Python paramiko, Node ssh2-sftp-client, Go pkg/sftp.
11.3 rsync + SSH
파일 동기화의 표준:
rsync -avz --progress src/ user@host:/dest/
- Delta transfer: 변경된 블록만 전송.
- Compression.
- SSH로 tunneling.
백업, 배포에 필수.
12. 2023 Terrapin 공격
12.1 CVE-2023-48795
보훔 대학(Ruhr University Bochum)의 2023년 12월 발표. SSH 프로토콜 디자인 취약점.
12.2 취약점 원리
SSH 핸드셰이크 초반 일부 메시지는 암호화되지 않고 순서 번호만 증가. 공격자가 이 순서 번호를 조작 가능.
구체적으로:
- KEXINIT 이후 NEWKEYS 메시지.
- 공격자가 중간에 패킷을 삽입/삭제.
- 이후 실제 암호화된 메시지의 sequence number가 mismatch.
- 어떤 ChaCha20-Poly1305와 CBC+EtM 조합에서 이 mismatch를 악용 가능.
결과: 초기 프로토콜 확장 메시지 truncation — 보안 기능 downgrade 가능.
12.3 실제 영향
- 메시지 내용 직접 탈취는 아님.
- 다만 알고리즘 협상 조작 가능.
- 예: "strict KEX" 옵션이 협상되는 것을 막아 더 약한 모드로.
현실적 공격 난이도는 높지만 이론적 가능성 만으로도 수정 필요.
12.4 방어
OpenSSH 9.6 (2023년 12월): "strict KEX" 도입.
kex-strict-c-v00@openssh.com
kex-strict-s-v00@openssh.com
이 확장을 지원하는 양쪽이 서로에 알림 → sequence number를 reset + 초기에 non-KEX 메시지 거부.
12.5 업그레이드 권장
ssh -V
# OpenSSH_9.6p1 이상
서버/클라이언트 모두 업그레이드. 서버(sshd)와 클라이언트(ssh)가 각자 지원해야 효과.
12.6 교훈
SSH처럼 잘 설계된 프로토콜도 25년 후에 취약점 발견. 보안은 지속적 검토 필요.
Terrapin은 TLS의 여러 다운그레이드 공격 (BEAST, CRIME, FREAK 등)과 맥락 비슷.
13. 고급 기능
13.1 PAM 통합
서버가 PAM (Pluggable Authentication Modules) 사용:
/etc/pam.d/sshd
- 2FA (Google Authenticator, Duo, YubiKey).
- Account lockout after N failures.
- Time-based access.
13.2 Chroot SFTP
사용자를 특정 디렉토리에 가둠:
Match User sftpuser
ChrootDirectory /home/sftpuser
ForceCommand internal-sftp
AllowTcpForwarding no
Chroot 환경 내에서만 파일 접근. SSH shell 불가.
13.3 AllowUsers / AllowGroups
AllowUsers alice bob
AllowGroups sshusers
DenyUsers root
접근 제어.
13.4 Rate Limiting / Ban
외부 도구 조합:
- fail2ban: 실패한 로그인 반복 시 iptables로 차단.
- sshguard: 유사.
- iptables
limit: 연결 빈도 제한.
Public SSH 서버는 반드시 사용.
13.5 X11 Forwarding
ssh -X user@host
# 원격에서 GUI 앱 실행 → 내 디스플레이에
-Y는 trusted (모든 권한), -X는 untrusted (X 보안 확장).
Linux에서는 여전히 유용, Mac/Windows는 X 서버 설치 필요 (XQuartz, Xming).
13.6 Environment Variables
ssh -o SendEnv=LANG LC_* user@host
특정 env var를 원격으로 전달. 서버의 AcceptEnv 설정도 필요.
13.7 LocalForward in Config
Host devserver
HostName dev.internal
User alice
LocalForward 3000 localhost:3000
LocalForward 5432 db.internal:5432
ssh devserver 한 번에 여러 포트 포워드 자동 설정.
14. SSH의 경쟁자와 보완
14.1 Mosh
Mobile Shell. SSH의 문제:
- Roaming 안 됨 (IP 바뀌면 연결 끊김).
- Laggy (지연 시 echo 느림).
Mosh:
- UDP 기반 custom 프로토콜.
- Client-side echo (즉시 표시, 서버 동기화).
- Roaming (IP 변경 OK).
- SSH로 초기 인증 후 Mosh 프로토콜로 전환.
원격 개발, 모바일 환경에 유용.
14.2 Teleport
완전한 "infrastructure access" 플랫폼:
- SSH + Kubernetes + DB + Windows RDP.
- Short-lived certificate 발급.
- Audit logging (녹화된 세션).
- RBAC.
- PAM-like hooks.
엔터프라이즈 환경에.
14.3 Tailscale SSH
VPN + SSH 결합:
- Tailscale 네트워크 내에서 자동 인증.
- WireGuard 암호화.
- 별도 key 관리 불필요.
- SSH 서버 설정 거의 없음.
홈 서버, 작은 팀에 편리.
14.4 AWS Systems Manager Session Manager
AWS 관리 SSH 대체:
- SSH port 열기 불필요.
- IAM 기반 인증.
- Session audit / 녹화.
- 웹 콘솔에서도.
AWS 환경에서 bastion 대체로 인기.
14.5 Cloudflare Access for SSH
Zero Trust 네트워크. Cloudflare가 브라우저 기반 SSH proxy. SSH 서버에 직접 port를 열 필요 없이 Cloudflare Tunnel로.
15. 보안 모범 사례
15.1 sshd_config
권장 설정:
# /etc/ssh/sshd_config
# 기본 port는 22. 변경하면 스캔은 줄지만 보안은 약간만 향상.
#Port 22
# SSH2만
Protocol 2
# Root 직접 로그인 금지
PermitRootLogin no
# Password 인증 끔, publickey만
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
# 키 타입
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# 사용자 제한
AllowUsers alice bob
# Idle timeout
ClientAliveInterval 300
ClientAliveCountMax 0
# Max auth attempts
MaxAuthTries 3
# Log level
LogLevel VERBOSE
# Banner
Banner /etc/issue.net
15.2 알고리즘 강화
오래된 알고리즘 비활성화:
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512
Mozilla의 "OpenSSH Guidelines" 참고.
15.3 Client Config
~/.ssh/config:
Host *
HashKnownHosts yes
StrictHostKeyChecking accept-new
VerifyHostKeyDNS yes
VisualHostKey yes
Protocol 2
ServerAliveInterval 300
ServerAliveCountMax 2
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p
ControlPersist 10m
15.4 Key Management
- Passphrase 필수: 개인 키 도난 시 보호.
- ssh-agent 사용: 매번 입력 불편 해결.
- Hardware key: YubiKey, Nitrokey의 FIDO2 SSH 지원 (
sk-ed25519). - 주기적 교체: 매년 또는 더 자주.
15.5 Public SSH 서버
인터넷에 노출된 SSH는:
- fail2ban 필수.
- 비밀번호 비활성.
- Port knocking 옵션.
- Login 모니터링.
- CrowdSec 같은 커뮤니티 기반 차단 리스트.
가능하면 VPN/Tailscale/Zero Trust 뒤로.
16. 실전 문제와 해결
16.1 "Connection Refused"
- 포트가 맞나?
- sshd 실행 중?
- 방화벽?
ss -tlnp | grep 22
systemctl status sshd
ufw status
16.2 "Permission Denied"
- 공개 키가
authorized_keys에? - 파일 권한? (
chmod 600 ~/.ssh/authorized_keys,chmod 700 ~/.ssh) - SELinux/AppArmor?
sshd_config의 사용자 제한?
ssh -vvv user@host # 상세 로그
16.3 "Connection Closed"
- Banner grab 후 즉시 닫힘 → IP/사용자 차단 가능.
MaxStartups초과 (동시 연결 제한).AllowUsers제외.
서버 로그: journalctl -u sshd 또는 /var/log/auth.log.
16.4 Slow Login
- DNS 역조회 timeout:
UseDNS no - GSSAPI:
GSSAPIAuthentication no
16.5 Hanging After Login
X11 forward 시도 실패, motd script 느림 등.
16.6 ssh-copy-id
초기 키 배포:
ssh-copy-id user@host
자동으로 ~/.ssh/id_ed25519.pub를 원격의 authorized_keys에 추가. Passphrase 한 번 입력.
17. 학습 리소스
공식:
- RFC 4251-4254 (SSH-2).
- OpenSSH 공식 문서 (
man sshd_config,man ssh_config).
책:
- "SSH Mastery" — Michael W. Lucas. SSH의 성경.
- "SSH, The Secure Shell" — Barrett, Silverman, Byrnes.
블로그:
- Teleport 블로그 (엔터프라이즈 SSH 관점).
- Smallstep 블로그 (SSH CA, certificate).
도구:
ssh -v,-vv,-vvv: 디버그.ssh-audit: 서버 설정 자동 감사.ssh-keyscan: 공개 키 가져오기.ssh-keygen: 키 생성/관리/서명.
18. 요약 — 한 장 정리
┌─────────────────────────────────────────────────────┐
│ SSH Cheat Sheet │
├─────────────────────────────────────────────────────┤
│ 3-레이어: │
│ Transport (암호화, MAC, key exchange) │
│ User Authentication (password/publickey/...) │
│ Connection (multiplexed channels) │
│ │
│ Transport: │
│ DH/ECDH key exchange (curve25519) │
│ Host key로 서버 인증 │
│ 세션 키 도출 │
│ Cipher: chacha20-poly1305, aes256-gcm │
│ │
│ User Auth: │
│ publickey (권장, Ed25519) │
│ password (약함) │
│ keyboard-interactive (MFA) │
│ gssapi-with-mic (Kerberos) │
│ │
│ Connection: │
│ session (shell, exec, subsystem) │
│ direct-tcpip (local port forward) │
│ forwarded-tcpip (remote port forward) │
│ Flow control with windows │
│ │
│ Host Key: │
│ TOFU (Trust On First Use) │
│ ~/.ssh/known_hosts │
│ HashKnownHosts yes │
│ StrictHostKeyChecking accept-new │
│ │
│ Port Forwarding: │
│ -L: local → remote │
│ -R: remote → local │
│ -D: dynamic SOCKS proxy │
│ │
│ Agent: │
│ ssh-agent + ssh-add │
│ Agent forwarding (-A, 위험) │
│ │
│ ProxyJump: │
│ ssh -J bastion internal │
│ Multi-hop: -J h1,h2,h3 │
│ Agent forwarding 대체 │
│ │
│ ControlMaster: │
│ 연결 재사용 │
│ 두 번째 ssh부터 즉시 │
│ Config로 활성화 │
│ │
│ SSH CA: │
│ Host + user 인증서 │
│ known_hosts 지옥 해결 │
│ Short-lived │
│ Teleport, step-ca, Vault │
│ │
│ 파일 전송: │
│ sftp (정식 프로토콜) │
│ scp (legacy, 9.0+은 SFTP 사용) │
│ rsync (delta + compress) │
│ │
│ 보안: │
│ Ed25519 키 │
│ Password auth 비활성 │
│ fail2ban │
│ SSH CA 사용 │
│ Strict KEX (Terrapin 방어) │
│ │
│ 2023 Terrapin: │
│ CVE-2023-48795 │
│ Initial message truncation │
│ OpenSSH 9.6+에서 strict KEX │
│ │
│ 대안: │
│ Mosh (모바일, UDP) │
│ Teleport (엔터프라이즈) │
│ Tailscale SSH (zero-config) │
│ AWS SSM Session Manager │
└─────────────────────────────────────────────────────┘
19. 퀴즈
Q1. SSH의 3-레이어 구조가 주는 이점은?
A. 유연성과 재사용성. Transport layer가 암호화/무결성/서버 인증을 제공하면, User Authentication layer가 "누가 로그인하는가"에 집중할 수 있고, Connection layer가 "무엇을 하는가"(shell, SFTP, port forward)를 담당. 각 레이어가 독립적으로 진화 가능: 새 인증 방법 추가(keyboard-interactive, GSSAPI, U2F)는 transport 수정 없이, 새 서비스 추가(X11 forwarding, agent forwarding, SFTP, subsystem)는 auth 수정 없이 가능. TLS는 "보안 데이터 스트림 하나"에 최적화된 반면 SSH는 "다양한 응용"에 최적화 — 같은 SSH 연결 하나로 shell + SFTP + 여러 port forward가 동시에. Git protocol, Ansible, rsync 등이 SSH의 connection layer를 재사용하는 이유.
Q2. TOFU (Trust On First Use)의 장단점은?
A. 장점: (1) CA 인프라 불필요 — SSH는 PKI가 없던 1995년에 설계됐고, 단순 파일(known_hosts) 하나로 작동, (2) 사용자 통제 — 서버 키를 직접 확인 후 수락, (3) 분산 설계 — 중앙 신뢰 기관 없이 작동. 단점: (1) 첫 연결의 취약성 — 첫 번째 연결 시 실제로 공격자의 키를 수락할 수 있음, (2) 확장성 없음 — 수백 서버에 각자 TOFU는 실제로는 "yes, yes, yes" 클릭만 됨, (3) Key rotation 지옥 — 서버 키 바꿀 때마다 모든 클라이언트가 warning, (4) 클라우드에서 IP 재사용 — 다른 VM에 같은 IP가 할당되면 "MITM!" 오류. 대규모 인프라에서는 SSH Certificate(CA 기반)이 해결책 — "CA 한 번만 신뢰하면 자동으로 모든 서명된 호스트 신뢰". Teleport, Smallstep, Vault 같은 도구가 이것을 관리형으로 제공.
Q3. ProxyJump가 Agent Forwarding보다 안전한 이유는?
A. Agent의 권한 격리. Agent Forwarding (-A): 클라이언트가 SSH 연결을 맺은 후 원격 호스트에게 로컬 agent를 사용할 권한을 부여. 원격 호스트의 root(또는 손상된 사용자)가 SSH_AUTH_SOCK을 통해 당신의 개인 키 서명 요청을 임의로 만들 수 있음. 즉 원격 호스트에 로그인한 동안, 그 호스트의 root가 당신 키로 다른 서버에 접속 가능. ProxyJump (-J): bastion은 TCP 트래픽만 tunneling. 원본 SSH 연결은 client에서 bastion을 거쳐 최종 목적지까지 — bastion은 중간 내용을 볼 수 없다(이미 암호화). 클라이언트가 최종 목적지와 독립적으로 인증 — bastion은 인증 정보를 모름. Bastion이 손상돼도 당신의 키는 안전. 현대 권장: ProxyJump 기본, Agent Forwarding 절대 금지(신뢰할 수 있는 개인 머신 사이에서만 예외).
Q4. ControlMaster가 왜 그렇게 큰 성능 향상을 주는가?
A. 핸드셰이크 제거. SSH 연결 설정 비용: (1) TCP 3-way handshake ~50ms, (2) SSH banner exchange ~50ms, (3) KEXINIT + Diffie-Hellman ~200ms, (4) 공개 키 인증 ~100ms — 총 ~500ms. 이것은 모든 단계가 한 번만 필요한 작업이지만 매 ssh host 호출마다 반복됨. ControlMaster: 첫 연결이 master socket을 만들고, 이후 연결들은 같은 소켓을 재사용. 두 번째 연결부터는 단지 "새 channel 열기" — 수 ms. 개발자가 자주 SSH하는 환경(원격 개발, CI/CD 스크립트, Ansible)에서 극적 차이. Ansible의 "SSH multiplexing"이 ControlMaster를 활용 — 수백 서버에 대한 연속 작업이 10배 빨라짐. 설정: ~/.ssh/config에 ControlMaster auto + ControlPath + ControlPersist. 대부분 실질적으로 완전 투명 — 속도만 빨라짐.
Q5. SSH Certificate가 known_hosts 문제를 어떻게 해결하는가?
A. "한 번만 신뢰"를 가능하게 한다. TOFU 모델의 한계: 10,000 서버 환경에서 각 사용자가 각 서버에 대한 host key를 받아야 함. 서버 재설치, 키 교체, 새 서버 프로비저닝 시마다 known_hosts 업데이트 — 불가능. SSH CA: 조직이 CA key 하나 생성, 모든 클라이언트가 이 CA 공개 키를 known_hosts에 @cert-authority prefix와 함께 등록(한 번만). CA가 각 서버의 host key를 서명해서 host certificate 생성(-h 옵션). 서버가 연결 시 이 certificate를 제시 — 클라이언트가 CA 서명 검증 → 자동 신뢰. TOFU prompt 없음. 새 서버 프로비저닝: CA로 host key 서명만 → 모든 클라이언트가 즉시 신뢰. 사용자 키도 유사: CA가 단기(8시간) user certificate 발급 → 키 노출 영향 제한 + 퇴사자 처리 간단. 현대 대규모 인프라(Teleport, Uber, Facebook)의 표준.
Q6. Terrapin 공격(CVE-2023-48795)이 드러낸 SSH의 취약점은?
A. 프로토콜 초반 메시지의 암호화 전 sequence number 조작. SSH handshake 구조에서 초기 KEXINIT 이후, NEWKEYS 이전에는 일부 메시지가 sequence number가 증가하지만 암호화는 아직 미완. 공격자가 MITM 위치에서 이 구간의 메시지를 삽입/삭제하면 이후의 암호화된 메시지의 sequence number가 mismatch. 특정 암호 스위트(ChaCha20-Poly1305, CBC-EtM)에서 이 mismatch를 악용해 첫 몇 개 암호화된 메시지를 truncate 가능. 실제 영향: 알고리즘 협상 downgrade, 보안 확장 disable 등 — 평문 복구는 아니지만 보안 기능이 약화됨. 방어 (OpenSSH 9.6+): "strict KEX" 확장 — 양쪽이 sequence number reset + 초기 단계에서 KEX 외 메시지 거부. 교훈: 25년 된 잘 설계된 프로토콜도 새 분석 기법으로 취약점 발견 가능. "Never solved, only patched"라는 암호학 커뮤니티의 격언. TLS도 BEAST, CRIME, FREAK, POODLE 등 여러 downgrade 공격을 겪었다.
Q7. 2025년 현재 SSH의 모범 키 관리 설정은?
A. Ed25519 + CA + hardware + short-lived. (1) Key algorithm: Ed25519 (256-bit, 빠르고 안전, 구현 단순). RSA는 3072-bit 이상만, ECDSA는 구현 버그 위험(nonce 재사용 시 key 노출). DSA는 완전 폐기. (2) Passphrase: 개인 키는 반드시 passphrase로 암호화. 도난 시 보호. (3) ssh-agent: 매번 passphrase 입력 대신 agent에 로드. 메모리에만 존재. (4) Hardware key: YubiKey, Nitrokey 같은 FIDO2 기기가 sk-ed25519 지원 — 개인 키가 하드웨어 밖으로 절대 안 나감. 분실 시 즉시 무효화. (5) Agent forwarding 금지: ProxyJump 사용. (6) SSH CA: 가능하면 CA 기반 certificate로 장기 키 대체 (8시간 수명). Teleport/step-ca/Vault. (7) Key rotation: 장기 키는 매년 교체. (8) 서버 설정: PasswordAuthentication no, PermitRootLogin no, AllowUsers 제한. (9) 업데이트: OpenSSH 9.6+ (Terrapin 방어). (10) public SSH 서버: fail2ban + port 변경 + VPN/Zero-Trust 뒤로. 이 조합이 수 분의 공격 surface에서 수 시간의 공격 surface로 → 방어자에게 충분한 시간.
이 글이 도움이 됐다면 다음 포스트도 확인해 보세요:
- "TLS/SSL Deep Dive" — 또 다른 보안 전송 프로토콜.
- "OAuth 2.0 & OIDC Deep Dive" — 인증 프로토콜 설계 비교.
- "WebAuthn & Passkeys Deep Dive" — 공개 키 인증의 다른 맥락.
- "DNS Deep Dive" — Host name resolution의 기반.