EC2 서버에 Google Authenticator로 SSH 2차 인증 구축하기 — .pem 키 탈취 방어 실전

AWS EC2 운영 중 .pem 키가 유출되면 서버에 그대로 뚫린다는 사실, 알고 계셨나요?
이 글에서는 실제로 제가 운영 중인 암호화폐 거래봇 서버에 Google Authenticator 기반 2차 인증을 적용한 전 과정을 정리합니다.
문제 인식 → 실제 해킹 시나리오 → 해결 방법 → 검증까지 단계별로 다룹니다.


🚨 문제점 — .pem 키 단일 인증의 한계

AWS EC2의 기본 SSH 접속 방식은 공개키 인증 하나만으로 이루어집니다. .pem 파일만 있으면 누구나 서버에 접속할 수 있다는 뜻이죠.

현재 제 서버 구조의 취약점

제가 운영 중인 서비스는 Spring Boot 기반 백엔드가 EC2에서 돌아가고, 배포 시 환경변수로 중요 설정값(DB 접속정보, API 인증키, 서명 키 등)을 주입합니다. 문제는 이 환경변수들이 실행 중인 프로세스의 메모리 공간에 평문으로 존재한다는 점입니다.

만약 공격자가 .pem 키를 탈취하면:

# 1. SSH 접속
ssh -i stolen-key.pem ubuntu@{서버주소}

# 2. 애플리케이션 PID 확인
ps aux | grep java

# 3. 프로세스 환경변수 덤프
sudo cat /proc/{PID}/environ | tr '\0' '\n'

이 세 줄이면 서비스의 모든 민감 정보가 평문으로 노출됩니다. DB 비밀번호부터 외부 서비스 API 키까지 전부요.

.pem 키는 생각보다 쉽게 유출됩니다

  • 💾 개발자 노트북 분실/도난
  • 🔓 .pem 파일을 깜빡하고 Git 저장소에 커밋
  • 📧 이메일이나 메신저로 팀원과 키 공유
  • ☁️ 클라우드 드라이브 동기화 폴더에 보관
  • 👤 퇴사자의 로컬 백업에 남아있는 키
    그래서 .pem 키 유출을 "가능한 시나리오"가 아니라 "언젠가 발생할 사건"으로 가정하고 방어선을 추가해야 합니다.

💀 실제 해킹 시나리오 — 한 줄씩 따라가 보기

실제로 제 서비스가 .pem 키 유출로 공격받는다면 어떤 일이 벌어질지, 공격자 관점에서 재구성해보겠습니다.

Stage 0 — .pem 키 획득

공격자 김해커 씨가 팀원 A의 노트북을 카페에서 잠시 훔쳐봤거나, A가 실수로 GitHub에 올린 .pem 키를 자동 스캐너로 수집했다고 가정합시다. 이 시점에서 공격자는 이미 정당한 사용자와 구별 불가능한 접근권한을 손에 넣었습니다.

Stage 1 — SSH 접속 성공

$ ssh -i stolen.pem ubuntu@ec2-xx-xx-xx-xx.ap-northeast-2.compute.amazonaws.com
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.14.0-aws aarch64)

ubuntu@ip-xxx-xx-xx-xx:~$ 

추가 인증 없이 바로 쉘 프롬프트가 뜹니다. 현재 구조에서는 이 순간이 사실상 게임 오버입니다.

Stage 2 — 실행 중인 프로세스 확인

ubuntu@ip-xxx:~$ ps aux | grep java
ubuntu    12345  2.1 15.3 {...} java -jar -Dserver.port=8080 /opt/app/app.jar

Spring Boot 프로세스의 PID를 찾아냈습니다.

Stage 3 — 환경변수 덤프로 시크릿 탈취

ubuntu@ip-xxx:~$ sudo cat /proc/12345/environ | tr '\0' '\n'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
DB_USERNAME=admin
DB_PASSWORD=MyStr0ngP@ssw0rd!
JWT_SECRET=my-super-secret-signing-key-do-not-share
EXTERNAL_API_KEY=sk-live-xxxxxxxxxxxxxxxx
EXTERNAL_API_SECRET=xxxxxxxxxxxxxxxxxxxxxx
...

모든 민감 정보가 평문으로 덤프됩니다. 이제 공격자는:

Stage 4 — 2차 피해 확산

  • DB 직접 접속: DB_PASSWORD로 데이터베이스에 접속해서 사용자 데이터 전체 유출
  • 서비스 사칭: JWT_SECRET으로 임의 사용자의 토큰을 위조 → 관리자 권한으로 로그인
  • 외부 API 남용: EXTERNAL_API_KEY로 유료 서비스 크레딧 소진, 비용 청구서 폭발
  • 흔적 삭제: sudo 권한으로 로그 파일 조작 → 침해 사실 은폐Stage 5 — 완전 침해 (최악의 경우)

공격자가 얻은 시크릿을 바탕으로 정상적인 사용자로 위장하여 지속 공격을 수행합니다. .pem 키만 있으면 서버에 자유롭게 드나들 수 있으니, 백도어 설치 → 장기 잠복 → 적절한 타이밍에 대규모 피해 실행의 수순이죠.

이 모든 과정이 몇 분 안에 끝납니다. 그리고 서버 로그에는 "정상적인 ubuntu 사용자의 접속"으로만 찍힙니다.


🛡️ 해결 방법 — Google Authenticator로 2차 방어선 구축

핵심 아이디어는 간단합니다. .pem 키(Something you HAVE) 에 더해, OTP 코드(Something you KNOW at this moment) 라는 완전히 다른 차원의 인증 요소를 요구하는 것입니다.

기존: .pem 키 통과 → 쉘 접속 ✅
개선: .pem 키 통과 → OTP 6자리 통과 → 쉘 접속 ✅

공격자가 .pem 키를 얻어도 제 핸드폰의 OTP 앱이 없으면 접속이 불가능해집니다.

Step 1 — Google Authenticator PAM 모듈 설치

Ubuntu 기준:

sudo apt update && sudo apt install -y libpam-google-authenticator

Amazon Linux 2 / 2023이라면:

sudo dnf install -y google-authenticator

Step 2 — 사용자 계정에서 OTP 시크릿 생성

SSH로 접속한 상태에서 본인 사용자 계정으로 실행합니다:

google-authenticator

대화형 질문이 7개 정도 나옵니다. 권장 답변과 그 이유를 정리하면:

질문 이유
Time-based tokens? y 대부분의 OTP 앱이 표준으로 지원하는 TOTP 방식
Update .google_authenticator? y 설정을 파일에 저장해서 재부팅 후에도 유지
Disallow multiple uses? y 같은 코드 재사용 금지 → 네트워크 도청 공격(MITM) 방어
Increase window to 4:00? n 기본 1:30이면 충분. 창이 넓을수록 공격 기회 증가
Rate-limit (3 tries/30s)? y 브루트포스 공격 차단

터미널에 표시되는 QR 코드를 스마트폰의 Google Authenticator / Authy / 1Password 앱으로 스캔하세요. 그리고 Scratch Code(비상 복구 코드) 5개는 반드시 비밀번호 관리자나 안전한 오프라인 장소에 보관합니다. 핸드폰을 잃어버리면 이게 유일한 복구 수단입니다.

Step 3 — PAM 설정 수정

sudo nano /etc/pam.d/sshd

파일 맨 위에 추가:

auth required pam_google_authenticator.so nullok

그리고 같은 파일에서 아래 줄을 찾아 주석 처리합니다 (Ubuntu 기준):

#@include common-auth

common-auth가 살아있으면 비밀번호 인증이 먼저 통과되어 OTP 단계까지 가지 않을 수 있습니다.

nullok 옵션은 .google_authenticator 파일이 없는 사용자는 OTP 없이 통과시킵니다. 모든 사용자가 설정을 마쳤다면 이 옵션을 제거해 강제하는 것이 더 안전합니다.

Step 4 — SSH 설정 수정

sudo nano /etc/ssh/sshd_config

또는 Ubuntu라면 보통 아래 방식이 더 안전합니다 (sshd_config.d/ 디렉터리의 다른 설정 파일과 충돌 방지):

sudo nano /etc/ssh/sshd_config.d/99-2fa.conf

아래 내용 추가:

UsePAM yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
PasswordAuthentication no
PermitRootLogin no

핵심은 AuthenticationMethods publickey,keyboard-interactive입니다. 쉼표가 AND의 의미이므로 "공개키 통과 AND OTP 통과" 둘 다 요구하게 됩니다.

Step 5 — 문법 검사 후 SSH 재시작

⚠️ 설정을 잘못 저장하고 재시작하면 .pem 키로도 접속이 불가능해집니다. 반드시 문법 검사부터 하세요.

# 문법 검사 (아무 출력 없으면 OK)
sudo sshd -t

# 적용된 설정 확인
sudo sshd -T | grep -iE "authenticationmethods|kbdinter|passwordauth"

기대 출력:

passwordauthentication no
kbdinteractiveauthentication yes
authenticationmethods publickey,keyboard-interactive

문제 없으면 재시작:

# Ubuntu
sudo systemctl restart ssh

# Amazon Linux 등
sudo systemctl restart sshd

Step 6 — 반드시 현재 세션을 유지한 채 새 터미널에서 테스트

기존 SSH 세션은 절대 닫지 마세요. 만약 설정에 문제가 있어서 새 접속이 막히면, 기존 세션에서만 롤백이 가능합니다.

새 터미널 창에서:

ssh -i your-key.pem ubuntu@{서버주소}

정상 흐름:

Authenticated with partial success.
Verification code: ______   ← 여기서 OTP 앱의 6자리 입력
Last login: ...
ubuntu@ip-xxx:~$

Verification code: 프롬프트가 뜨고, 올바른 OTP를 입력했을 때 쉘이 열리면 성공입니다. 이걸 확인한 후에야 기존 세션을 닫으세요.


🔍 해킹 시나리오 재검증 — 같은 공격을 다시 시도하면?

2차 인증 적용 후 공격자 입장에서 같은 공격을 시도해봅시다:

$ ssh -i stolen.pem ubuntu@ec2-xx-xx-xx-xx.ap-northeast-2.compute.amazonaws.com
Authenticated with partial success.
Verification code: 

공격자는 여기서 멈춥니다. OTP 6자리를 모르니까요. 100만 가지 경우의 수를 시도해도:

  • 30초마다 코드가 바뀌므로 브루트포스 불가능
  • Rate limit(30초당 3회)로 자동화 공격 차단
  • 코드 재사용 금지로 가로챈 코드 재사용 불가
    공격자가 접속하려면 제 핸드폰까지 물리적으로 탈취해야 합니다. 난이도가 차원이 다르게 올라가죠.

방어 효과 비교

공격자가 가진 것 이전 이후
.pem 키만 ❌ 즉시 침해 ✅ OTP 단계에서 차단
OTP만 (코드 유출) ✅ publickey 단계에서 차단
.pem + OTP 앱 핸드폰 ✅ 접속 가능 (정상 사용자)

⚠️ 주의사항 — 꼭 챙겨야 할 것들

1. Scratch Code 백업은 생명줄입니다

google-authenticator 실행 시 출력된 8자리 복구 코드 5개를 지금 당장 안전한 곳에 저장하세요. 핸드폰 분실/초기화/OTP 앱 삭제 시 이것 없이는 영영 접속 불가입니다.

저장 위치 추천:

  • 1Password, Bitwarden 등 비밀번호 관리자의 Secure Note
  • 물리적 종이로 안전한 서랍/금고
    권장하지 않는 위치:
  • 이메일 (유출 시 위험)
  • 평문 파일 (해킹 시 같이 노출)
  • 클라우드 드라이브 (동기화 사고 가능)2. 백업 접속 경로 확보

3. 서버 시간 동기화 확인

TOTP는 서버와 클라이언트의 시각이 일치해야 동작합니다. 시간이 30초 이상 어긋나면 OTP가 계속 거부됩니다:

timedatectl status
# System clock synchronized: yes 가 떠야 함

 

4. 여러 서버가 있다면 각각 설정

EC2 인스턴스별로 독립적인 리눅스 환경입니다. Staging과 Production이 분리되어 있다면 두 서버 모두 같은 작업을 반복해야 합니다. OTP 앱에는 서버별로 별개 항목으로 등록하는 것을 추천합니다.


📝 최종 정리

적용 전후 변화

항목 적용 전 적용 후
SSH 인증 요소 1개 (.pem 키) 2개 (.pem + OTP)
.pem 탈취 시 즉시 완전 침해 OTP 단계에서 차단
브루트포스 공격 키 파일만 얻으면 시도 불필요 Rate limit으로 차단
네트워크 도청 OTP 재사용 가능 한 번 쓴 코드 즉시 무효화
공격 난이도 중 (키 하나만 털면 됨) 높음 (핸드폰까지 털어야)

 

  1.  

마치며

보안은 하나의 완벽한 방어막이 아니라 여러 겹의 방어선을 쌓는 일입니다. SSH 2FA는 그 중 가장 효과적이고 가성비 좋은 한 겹입니다. 30분의 설정 시간으로 .pem 키 유출의 파급력을 극적으로 줄일 수 있으니, 아직 적용하지 않으셨다면 오늘 바로 적용해보세요.

특히 서비스에 금전적 가치가 걸려 있거나, 사용자 데이터를 다루는 서버라면 선택이 아닌 필수입니다. .pem 키는 언젠가 새어 나간다고 가정해야 하고, 그때 2차 방어선이 있는지 없는지가 회사의 존망을 가를 수 있습니다.


🏷️ 태그: #AWS #EC2 #SSH #2FA #GoogleAuthenticator #보안 #DevOps #Ubuntu #서버보안 #PAM

+ Recent posts