설명
이 취약점은 SSH 클라이언트가 서버의 host key(서버 공개키)를 검증하지 않고 그냥 신뢰할 때 발생합니다. Go에서 ssh.InsecureIgnoreHostKey() 를 사용하면, 클라이언트는 접속하는 서버가 진짜 서버인지 확인하지 않게 됩니다.
공격자는 네트워크 중간에서 위조된 SSH 서버를 띄우고 사용자의 트래픽을 가로채는 Man-in-the-Middle(MitM) 공격을 할 수 있습니다. 사용자는 정상 서버에 접속한다고 생각하지만, 실제로는 공격자 서버에 접속하게 되고, 비밀번호 인증에 사용한 값과 그 연결로 전송되는 중요 데이터가 노출될 수 있습니다. 공개키 인증만으로 클라이언트의 개인키 자체가 서버에 전송되는 것은 아닙니다.
잠재적 영향
- 중간자 공격(MitM, Man-in-the-Middle): 서버의 진위를 확인하지 않아 공격자가 가짜 SSH 서버로 트래픽을 가로채서 세션 내용을 엿보거나 변조할 수 있음
- 자격 증명 탈취: SSH 비밀번호, 토큰, 명령 실행 결과 등의 민감 정보가 공격자에게 노출될 수 있음
- 명령·응답 노출 및 조작: 악성 연결 상대가 클라이언트가 보낸 명령을 수신하고 반환하는 응답을 위조할 수 있음
해결 방법
- 운영 코드에서
ssh.InsecureIgnoreHostKey()를 사용하지 마세요. golang.org/x/crypto/ssh/knownhosts의knownhosts.New로 신뢰한 OpenSSHknown_hosts파일을 읽고HostKeyCallback에 설정하세요.- 제한된 환경에서는
ssh.FixedHostKey()로 미리 신뢰한 서버 공개키를 고정하세요. known_hosts나 고정 키를 안전하게 배포·관리하고, 서버 키 변경 시 신뢰한 절차로 새 키를 확인하세요.- 테스트에서도 검증을 생략하지 말고 테스트용
known_hosts나 고정 키를 사용하세요.
예시
호스트 키 검증만 비교합니다. 예시 비밀번호를 운영에 사용하지 말고 보호된 설정에서 값을 공급하세요. known_hosts는 신뢰한 경로로 배포하고, 홈 디렉터리 조회 오류도 실제 코드에서 처리해야 합니다.
변경 전
go
package main
import (
"log"
"golang.org/x/crypto/ssh"
)
func newSSHClientInsecure(addr, user, password string) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
// ❌ 서버 host key를 전혀 검증하지 않음
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return client, nil
}
func main() {
client, err := newSSHClientInsecure("example.com:22", "deploy", "secret-password")
if err != nil {
log.Fatal(err)
}
defer client.Close()
// ... 중요 작업 수행 ...
}
변경 후
go
package main
import (
"log"
"os"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
func newSSHClientSecure(addr, user, password, knownHostsPath string) (*ssh.Client, error) {
// ✅ known_hosts 파일을 사용한 host key 검증 설정
hostKeyCallback, err := knownhosts.New(knownHostsPath)
if err != nil {
return nil, err
}
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(password),
},
HostKeyCallback: hostKeyCallback,
}
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return client, nil
}
func main() {
home, _ := os.UserHomeDir()
knownHosts := home + "/.ssh/known_hosts"
client, err := newSSHClientSecure("example.com:22", "deploy", "secret-password", knownHosts)
if err != nil {
log.Fatal(err)
}
defer client.Close()
// ... 안전하게 SSH 작업 수행 ...
}
설명:
- 변경 전:
HostKeyCallback: ssh.InsecureIgnoreHostKey()설정은 SSH 서버의 host key를 전혀 검증하지 않습니다. 따라서 공격자가 네트워크 중간에 가짜 SSH 서버를 띄워도 클라이언트는 이를 정상 서버로 믿고 접속합니다. 이 과정에서 비밀번호, 명령, 결과 값 등 민감 정보가 모두 공격자에게 노출될 수 있고, 세션이 완전히 장악될 수 있습니다. - 변경 후:
knownhosts.New를 이용해 OpenSSH 형식의known_hosts파일을 읽고,HostKeyCallback으로 설정함으로써 서버 host key가 신뢰된 값인지 자동 검증합니다. 서버 키가 다를 경우 SSH 연결이 실패하므로, 중간자 공격이나 가짜 서버에 대한 접속이 차단됩니다. 이렇게 서버의 엔티티(정체)를 인증함으로써 키 교환과 통신 채널의 기밀성과 무결성을 보호할 수 있습니다.