들어가며
Config 규칙 점검에서 ec2-imdsv2-check 미준수 인스턴스가 나왔다. 설정 한 줄이면 끝나는 작업인데, 이런 항목은 늘 뒤로 밀린다. 지금 아무 문제도 일으키지 않기 때문이다.
그런데 이 항목은 성격이 조금 다르다. 평소에는 정말 아무 일도 없다가, 애플리케이션에 취약점 하나가 생기는 순간 그 서버의 문제가 아니라 계정의 문제가 된다. 그 연결 고리를 이해하고 나서 우선순위를 올렸다.
IMDS(Instance Metadata Service)란
EC2 인스턴스 내부에서 http://169.254.169.254/로 접근하면 인스턴스 자신의 메타데이터를 조회할 수 있는 서비스다.
조회할 수 있는 정보는 이렇다.
- 인스턴스 ID, 리전, AZ
- 보안 그룹, 네트워크 정보
- IAM Role의 임시 자격증명 (Access Key, Secret Key, Session Token)
- UserData (인스턴스 시작 스크립트)
세 번째 항목이 핵심이다. 우리가 Access Key에서 IAM Role 기반 인증으로 전환하면서 코드에서 키를 없앨 수 있었던 건, SDK가 이 주소에서 임시 자격증명을 받아오기 때문이다. 편리함과 위험이 같은 구조에서 나온다. 코드에 키가 없다는 말은, 그 키를 이 주소에서 누구나 받아갈 수 있다는 뜻이기도 하다.
IMDSv1 vs IMDSv2
| IMDSv1 | IMDSv2 | |
|---|---|---|
| 요청 방식 | 단순 GET 요청 | PUT으로 토큰 발급 후 토큰으로 GET 요청 (2단계) |
| SSRF 공격 | 취약 | 방어됨 (토큰 발급에 PUT 필요, 리다이렉트 차단) |
| 도입 시기 | EC2 출시부터 | 2019년 11월 |
# IMDSv1 (토큰 없이 바로 조회, 위험)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/[role-name]
# IMDSv2 (토큰 발급 후 조회, 안전)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/[role-name]SSRF 공격으로 인한 실제 피해 사례
Capital One 해킹 사건 (2019년)
- 피해 규모: 1억 600만 명 개인정보 유출, 벌금 8,000만 달러 이상
- 공격 방식: WAF의 SSRF 취약점으로 EC2 메타데이터(IMDSv1)에서 IAM Role 자격증명을 탈취하고, 이를 이용해 S3 버킷 700개에 접근
- 이 사건 이후 AWS가 IMDSv2를 출시
참고: Krebs on Security, Wiz.io
한 줄의 취약점이 계정 권한이 되는 과정

1. 웹 애플리케이션의 SSRF 취약점 발견
→ 서버가 사용자 입력 URL로 요청을 보내는 기능을 악용
2. SSRF로 EC2 메타데이터 접근
→ http://169.254.169.254/latest/meta-data/iam/security-credentials/[role-name]
→ IAM Role의 AccessKeyId, SecretAccessKey, Token 획득
3. 탈취한 자격증명으로 AWS 리소스 접근
→ S3 버킷 데이터 다운로드, EC2 인스턴스 제어
4. (심화) UserData를 이용한 원격 코드 실행
→ 인스턴스 중지 → UserData에 리버스 쉘 삽입 → 재시작 시 쉘 탈취
눈여겨볼 지점은 2번이다. 공격자는 서버에 로그인하지 못했고, 코드를 실행하지도 못했다. 서버에게 "이 주소로 요청 좀 보내줘"라고 시켰을 뿐인데 자격증명이 나왔다. 그리고 그 자격증명의 권한 범위는 그 서버가 아니라 Role에 붙은 정책 전체다. 그래서 한 대의 취약점이 계정 사고로 번진다.
이 점은 Role의 권한을 좁게 유지해야 하는 이유이기도 하다. 탈취를 막는 것과 탈취됐을 때의 피해를 줄이는 것은 다른 작업이고, 둘 다 필요하다.
IMDSv2가 SSRF를 방어하는 원리
IMDSv2는 토큰을 받아야만 메타데이터를 조회할 수 있고, 그 토큰을 받으려면 두 가지가 필요하다.
- PUT 요청: 일반적인 SSRF는 서버가 대신 GET을 보내게 만드는 형태라 메서드를 바꾸기 어렵다
- 커스텀 헤더:
X-aws-ec2-metadata-token-ttl-seconds를 붙여야 하는데, SSRF로는 헤더를 추가하기 어렵다
| SSRF 공격 단계 | IMDSv1 | IMDSv2 |
|---|---|---|
| 메타데이터 URL 접근 | GET으로 바로 가능 | 토큰 없이 접근 불가 |
| 토큰 발급 | 불필요 | PUT + 커스텀 헤더 필요 (SSRF로 어려움) |
| 자격증명 탈취 | 성공 | 차단됨 |
hop limit, 두 번째 방어선
토큰 응답에는 TTL과 함께 hop limit이 걸린다. 이 값은 토큰 응답 패킷이 몇 번의 네트워크 홉을 지날 수 있는지를 제한한다. 기본값은 1이고, 이는 인스턴스 자신만 토큰을 받을 수 있다는 뜻이다.
컨테이너를 띄우면 네트워크가 한 단계 더 생기므로, 컨테이너 안에서 호스트의 메타데이터를 조회하려면 hop limit이 2 이상이어야 한다. 이건 양날이다.
- hop limit 1: 컨테이너에서 호스트 자격증명을 가져갈 수 없다. 방어에 유리하다
- hop limit 2: 컨테이너에서도 조회된다. 필요한 경우가 있지만, 컨테이너가 침해되면 호스트 Role까지 노출된다
EC2 위에서 컨테이너를 돌리는 환경이라면 이 값을 확인해보는 게 좋다. 값을 올려야 한다면, 호스트 Role의 권한을 좁히는 작업을 같이 해두면 위험을 줄일 수 있다.
현재 상태 확인 방법
CLI로 확인
aws ec2 describe-instances \
--instance-ids i-[instance-id] \
--query "Reservations[*].Instances[*].MetadataOptions.HttpTokens" \
--output text
# "required" → 안전 / "optional" → 위험콘솔에서 확인
EC2 콘솔에서 인스턴스를 선택하고 "세부 정보" 탭의 "인스턴스 메타데이터 옵션"에서 확인한다.
- "필수(Required)" → IMDSv2만 허용 (안전)
- "선택 사항(Optional)" → IMDSv1도 허용 (위험)
적용 순서
1단계. 영향도를 먼저 센다
IMDSv2를 필수로 바꾸면 IMDSv1 요청이 즉시 차단된다. 그래서 무엇이 끊기는지 먼저 확인한다.
EC2 인스턴스의 MetadataNoToken 메트릭이 토큰 없이 들어온 요청 수를 알려준다.
- 값이 0이면 IMDSv1을 쓰는 프로세스가 없다는 뜻이다. 바로 적용해도 된다
- 값이 0보다 크면 그 요청을 보내는 주체를 먼저 찾아야 한다
찾을 때는 아래 목록부터 확인한다.
| 확인 항목 | 기준 |
|---|---|
| AWS SDK 버전 | 2019년 이후 버전이면 IMDSv2 자동 지원 |
| CloudWatch Agent | v1.247350.0 이상 |
| SSM Agent | v2.3.672.0 이상 |
| 직접 작성한 스크립트 | curl http://169.254.169.254/... 형태의 호출 확인 |
경험상 마지막 항목이 제일 오래 걸린다. 오래전에 누군가 넣어둔 부팅 스크립트나 헬스 체크 스크립트가 남아 있는 경우가 있다.
2단계. 적용한다
aws ec2 modify-instance-metadata-options \
--instance-id i-[instance-id] \
--http-tokens required \
--http-endpoint enabled재부팅이 필요 없고 즉시 적용된다. 되돌리는 것도 같은 명령으로 가능해서, 문제가 생기면 바로 optional로 되돌릴 수 있다. 이런 성질 덕분에 개발 환경부터 차례로 적용하기 좋다.
3단계. 새로 생기는 인스턴스도 막는다
기존 인스턴스를 전부 고쳐도, 다음 주에 새로 띄운 인스턴스가 optional이면 원점이다. 시작 템플릿(Launch Template)이나 Auto Scaling 구성에 메타데이터 옵션을 넣어두고, 리전 단위 기본값도 required로 지정할 수 있다.
4단계. 유지되는지 감시한다
AWS Config의 ec2-imdsv2-check 규칙으로 미적용 인스턴스를 지속적으로 잡는다. 이 감시 구성은 AWS Config 기반 인프라 규정 준수 모니터링에 정리했다.
적용 우선순위
| 우선순위 | 대상 | 이유 |
|---|---|---|
| 높음 | 웹 애플리케이션 실행 인스턴스 | SSRF 취약점 노출 가능성이 크다 |
| 높음 | IAM Role이 할당된 인스턴스 | 자격증명 탈취 시 피해가 크다 |
| 높음 | 퍼블릭 서브넷 인스턴스 | 외부 공격 표면이 넓다 |
| 중간 | 프라이빗 서브넷 인스턴스 | 내부에서만 접근 가능하지만 보험 차원 |
| 낮음 | IAM Role 없는 인스턴스 | 탈취할 자격증명이 없다 |
정리
- IMDSv1은 SSRF 취약점 하나로 IAM 자격증명이 통째로 넘어갈 수 있는 구조다
- IMDSv2는 PUT과 커스텀 헤더를 요구해서 그 경로를 끊는다. hop limit은 두 번째 방어선이다
- 적용 전에
MetadataNoToken으로 영향도를 세고, 개발 환경부터 차례로 적용한다 - 새로 생기는 인스턴스까지 막아야 적용이 끝난다
- 유지 여부는 Config 규칙
ec2-imdsv2-check가 본다
배운 점
확률은 낮고 피해는 큰 항목이 가장 밀리기 쉽다. IMDSv1은 취약한 코드가 있어야 위험해지기 때문에 평소에는 아무 일도 일어나지 않는다. 그래서 늘 뒷순위가 되는데, 사고가 나면 서버 한 대가 아니라 계정이 넘어간다. 위험을 "지금 문제가 되는가"가 아니라 "터졌을 때 어디까지 번지는가"로 재게 된 계기였다.
보안 설정을 켜는 일은 대부분 무언가를 끊는 일이다. 그래서 순서가 정해져 있다. 무엇이 끊기는지 먼저 세고, 되돌릴 수 있는 방법을 확인하고, 작은 환경부터 적용한다. 이 순서를 건너뛰면 보안 강화가 장애 원인이 된다. S3 퍼블릭 액세스 차단에서도 같은 순서를 밟았다.
막는 것과 피해를 줄이는 것은 다른 작업이다. IMDSv2로 탈취 경로를 끊어도, Role에 과한 권한이 붙어 있으면 다른 경로로 새는 순간 같은 규모의 사고가 난다. 자격증명을 Role로 옮긴 배경은 Access Key에서 IAM Role 기반 인증으로 전환에, 실제로 권한을 좁힌 기록은 S3 버킷 접근 제어 및 Presigned URL 운영 전략에 있다.
이어 읽기
- Access Key에서 IAM Role 기반 인증으로 전환: 왜, 어떻게: 임시 자격증명으로 옮긴 배경
- AWS Config 기반 인프라 규정 준수 모니터링: 적용 상태를 계속 감시하는 방법
- S3 퍼블릭 액세스 차단 가이드: 영향 범위를 먼저 확인하고 적용한 다른 사례