Lấy Instance ID từ EC2 Metadata: Tại Sao IMDSv2 An Toàn Hơn IMDSv1

Bạn đang viết một script chạy trên EC2 và cần lấy Instance ID — nghe có vẻ đơn giản, nhưng cách bạn gọi metadata service lại quyết định liệu server có bị khai thác qua SSRF hay không. Đây là lý do tại sao IMDSv2 tồn tại và cách dùng nó đúng cách.

TL;DR — Lấy Instance ID với IMDSv2

BướcMô tảLệnh
1Lấy session tokencurl -X PUT ... /latest/api/token
2Dùng token để lấy Instance IDcurl -H 'X-aws-ec2-metadata-token: ...' .../instance-id
3Enforce IMDSv2Tắt IMDSv1 qua modify-instance-metadata-options

Instance Metadata Service Hoạt Động Như Thế Nào

IMDS là một HTTP endpoint nội bộ chạy tại địa chỉ 169.254.169.254 — một link-local address chỉ có thể truy cập từ bên trong instance. Nó không đi qua internet, không qua VPC routing table, và không cần IAM credential để đọc metadata cơ bản. Bất kỳ process nào chạy trên instance đều có thể gọi nó.

IMDSv1 là mô hình request/response thuần túy: gửi GET, nhận dữ liệu. Không có xác thực, không có session. IMDSv2 thêm một lớp session-oriented: bạn phải lấy token trước bằng PUT request, rồi đính kèm token đó vào mọi request tiếp theo. Token có TTL do bạn kiểm soát.

graph TD subgraph V1["IMDSv1 — Không xác thực"] A1["Script / Attacker"] -->|"GET /latest/meta-data/instance-id"| B1["IMDS 169.254.169.254"] B1 -->|"200 OK: i-1234567890abcdef0"| A1 end subgraph V2["IMDSv2 — Session Token bắt buộc"] A2["Script hợp lệ"] -->|"PUT /latest/api/token
TTL header bắt buộc"| B2["IMDS 169.254.169.254"] B2 -->|"Token: abc123..."| A2 A2 -->|"GET /latest/meta-data/instance-id
X-aws-ec2-metadata-token: abc123"| B2 B2 -->|"200 OK: i-1234567890abcdef0"| A2 A3["SSRF Attacker"] -->|"GET only — không có token"| B2 B2 -->|"401 Unauthorized"| A3 end
  1. IMDSv1 flow: Script gửi GET thẳng tới 169.254.169.254, nhận ngay Instance ID — không cần bất kỳ xác thực nào.
  2. IMDSv2 flow: Script phải PUT để lấy token trước, sau đó mới GET với token đó. Thiếu token → HTTP 401.
  3. SSRF attack path với IMDSv1: Attacker lợi dụng lỗ hổng SSRF trên ứng dụng để khiến server tự gọi metadata endpoint, lấy IAM credentials mà không cần access vào instance.
  4. Tại sao IMDSv2 chặn được: PUT request với X-Forwarded-For header bị IMDS từ chối. Hầu hết SSRF proxy không thể thực hiện PUT với custom header đúng cách.

Tại Sao IMDSv1 Nguy Hiểm — Câu Chuyện Thực Tế

Năm 2019, vụ breach Capital One xảy ra một phần do SSRF kết hợp với IMDSv1. Attacker khai thác một misconfigured WAF, khiến nó tự gọi http://169.254.169.254/latest/meta-data/iam/security-credentials/ và trả về IAM role credentials. Không cần SSH, không cần password — chỉ cần một HTTP request được forward sai chỗ.

Với IMDSv1, bất kỳ code nào có thể khiến server thực hiện HTTP GET — dù là qua SSRF, XXE, hay open redirect — đều có thể đọc toàn bộ metadata bao gồm temporary IAM credentials. Token session của IMDSv2 yêu cầu PUT method với TTL header, đây là rào cản mà hầu hết SSRF vector không vượt qua được vì chúng thường chỉ forward GET requests.

IMDSv2 không phải là bất khả xâm phạm — nếu attacker có RCE trên instance, họ vẫn lấy được token. Nhưng nó loại bỏ toàn bộ lớp tấn công SSRF mà không cần thay đổi application logic.

Lấy Instance ID với IMDSv2 — Từng Bước

Bước 1: Lấy Session Token

Token được tạo bằng PUT request. Header X-aws-ec2-metadata-token-ttl-seconds là bắt buộc — nó xác định thời gian sống của token tính bằng giây. Giá trị hợp lệ từ 1 đến 21600 (6 giờ).

TOKEN=$(curl -s -X PUT \
  'http://169.254.169.254/latest/api/token' \
  -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')

Bước 2: Dùng Token để Lấy Instance ID

Sau khi có token, đính kèm vào mọi request metadata bằng header X-aws-ec2-metadata-token.

INSTANCE_ID=$(curl -s \
  'http://169.254.169.254/latest/meta-data/instance-id' \
  -H "X-aws-ec2-metadata-token: $TOKEN")

echo "Instance ID: $INSTANCE_ID"

Bước 3: Script Hoàn Chỉnh — Production-Ready

Trong môi trường thực tế, bạn cần xử lý trường hợp token request thất bại và kiểm tra response trước khi dùng.

🔽 Xem script hoàn chỉnh
#!/bin/bash
set -euo pipefail

IMDS_ENDPOINT='http://169.254.169.254/latest'

# Lấy IMDSv2 token
TOKEN=$(curl -s -f -X PUT \
  "${IMDS_ENDPOINT}/api/token" \
  -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600') || {
  echo 'ERROR: Không thể lấy IMDSv2 token. Kiểm tra IMDSv2 đã được bật chưa.' >&2
  exit 1
}

# Lấy Instance ID
INSTANCE_ID=$(curl -s -f \
  "${IMDS_ENDPOINT}/meta-data/instance-id" \
  -H "X-aws-ec2-metadata-token: ${TOKEN}") || {
  echo 'ERROR: Không thể lấy instance-id từ IMDS.' >&2
  exit 1
}

# Lấy Region
REGION=$(curl -s -f \
  "${IMDS_ENDPOINT}/meta-data/placement/region" \
  -H "X-aws-ec2-metadata-token: ${TOKEN}") || {
  echo 'ERROR: Không thể lấy region từ IMDS.' >&2
  exit 1
}

echo "Instance ID : ${INSTANCE_ID}"
echo "Region      : ${REGION}"

Enforce IMDSv2 — Tắt IMDSv1 Hoàn Toàn

Chỉ dùng IMDSv2 trong script là chưa đủ nếu IMDSv1 vẫn còn hoạt động trên instance. Attacker vẫn có thể gọi IMDSv1 trực tiếp. Bạn phải enforce ở cấp instance.

Enforce trên instance đang chạy

aws ec2 modify-instance-metadata-options \
  --instance-id i-1234567890abcdef0 \
  --http-tokens required \
  --http-endpoint enabled \
  --region us-east-1

Sau lệnh này, mọi request không có IMDSv2 token sẽ nhận HTTP 401. IMDSv1 bị vô hiệu hóa hoàn toàn.

Enforce khi launch instance mới (Launch Template)

aws ec2 create-launch-template \
  --launch-template-name 'imdsv2-enforced' \
  --version-description 'IMDSv2 required' \
  --launch-template-data '{
    "MetadataOptions": {
      "HttpTokens": "required",
      "HttpEndpoint": "enabled"
    }
  }' \
  --region us-east-1

Kiểm tra trạng thái hiện tại

aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[0].Instances[0].MetadataOptions' \
  --region us-east-1

Output cần có "HttpTokens": "required". Nếu thấy "optional", IMDSv1 vẫn đang hoạt động.

Phát Hiện Instance Đang Dùng IMDSv1 — Ở Quy Mô Lớn

Đây là điểm mà nhiều team bỏ sót: bạn enforce IMDSv2 trên instance mới nhưng quên kiểm tra fleet hiện tại. AWS Security Hub có control EC2.8 kiểm tra chính xác điều này — nó flag các instance có HttpTokens không phải required.

Nếu không dùng Security Hub, bạn có thể scan toàn bộ region bằng CLI:

aws ec2 describe-instances \
  --query 'Reservations[].Instances[?MetadataOptions.HttpTokens!=`required`].[InstanceId,MetadataOptions.HttpTokens]' \
  --output table \
  --region us-east-1

Lệnh này trả về danh sách instance ID và trạng thái HttpTokens — bất kỳ giá trị nào khác required là cần xử lý ngay.

Sử Dụng IMDSv2 trong Python và AWS SDK

Nếu script của bạn dùng Python thay vì bash, boto3 không gọi IMDS trực tiếp để lấy instance metadata — bạn cần dùng requests hoặc urllib với cùng flow token. Tuy nhiên, boto3 tự động dùng IMDSv2 khi lấy credentials từ instance profile kể từ phiên bản hỗ trợ IMDSv2.

🔽 Xem ví dụ Python
import urllib.request

IMDS_ENDPOINT = 'http://169.254.169.254/latest'

def get_imdsv2_token(ttl_seconds=21600):
    req = urllib.request.Request(
        f'{IMDS_ENDPOINT}/api/token',
        method='PUT',
        headers={'X-aws-ec2-metadata-token-ttl-seconds': str(ttl_seconds)}
    )
    with urllib.request.urlopen(req) as resp:
        return resp.read().decode('utf-8')

def get_instance_id(token):
    req = urllib.request.Request(
        f'{IMDS_ENDPOINT}/meta-data/instance-id',
        headers={'X-aws-ec2-metadata-token': token}
    )
    with urllib.request.urlopen(req) as resp:
        return resp.read().decode('utf-8')

token = get_imdsv2_token()
instance_id = get_instance_id(token)
print(f'Instance ID: {instance_id}')

Symptom → Misdiagnosis → Nguyên Nhân Thực — Một Tình Huống Thường Gặp

Symptom: Script trả về HTTP 401 khi gọi http://169.254.169.254/latest/meta-data/instance-id sau khi team security enforce IMDSv2.

Misdiagnosis: Team nghĩ IMDS endpoint bị disable hoặc instance không có IAM role. Họ kiểm tra IAM instance profile — mọi thứ đều đúng. Họ thử curl -v và thấy connection thành công nhưng vẫn 401.

Nguyên nhân thực: Script cũ dùng IMDSv1 — GET request không có token. Sau khi HttpTokens được set thành required, mọi request thiếu token đều bị từ chối với 401. Đây là behavior đúng, không phải lỗi.

Fix: Cập nhật script theo flow IMDSv2 ở trên. Kiểm tra toàn bộ codebase, user-data scripts, và third-party agents đang chạy trên instance — tất cả đều phải dùng IMDSv2 trước khi enforce.

stateDiagram-v2 [*] --> Optional: Trạng thái mặc định Optional --> Optional: IMDSv1 GET — thành công Optional --> Optional: IMDSv2 PUT+GET — thành công Optional --> Required: aws ec2 modify-instance-metadata-options
--http-tokens required Required --> Required: IMDSv2 PUT+GET — thành công Required --> Rejected: IMDSv1 GET — không có token Rejected --> [*]: HTTP 401 trả về
  1. HttpTokens = optional: Cả IMDSv1 và IMDSv2 đều hoạt động. Request không có token vẫn được phục vụ.
  2. HttpTokens = required: Chỉ IMDSv2 được chấp nhận. Request không có token nhận 401 ngay lập tức.
  3. Transition risk: Enforce trước khi cập nhật script → outage. Cập nhật script trước, enforce sau → an toàn.

IAM Policy — Kiểm Soát Quyền Thay Đổi Metadata Options

Không phải ai cũng nên có quyền thay đổi MetadataOptions của instance. Đây là IAM policy tối thiểu cho phép enforce IMDSv2 mà không cấp quyền rộng hơn:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowModifyInstanceMetadataOptions",
      "Effect": "Allow",
      "Action": "ec2:ModifyInstanceMetadataOptions",
      "Resource": "arn:aws:ec2:us-east-1:123456789012:instance/*"
    }
  ]
}

Lưu ý: ec2:DescribeInstances thường yêu cầu "Resource": "*" vì đây là List/Describe action — kiểm tra AWS Service Authorization Reference để xác nhận trước khi restrict.

Wrap-Up: Lấy Instance ID An Toàn với IMDSv2

Lấy Instance ID qua IMDS là tác vụ phổ biến trong mọi script vận hành EC2, nhưng cách thực hiện quyết định mức độ rủi ro bảo mật. IMDSv2 không chỉ là best practice — nó là rào cản cụ thể chống lại một lớp tấn công đã được ghi nhận trong các breach thực tế.

Ba việc cần làm ngay:

  1. Cập nhật tất cả script hiện tại sang IMDSv2 flow.
  2. Chạy lệnh describe-instances scan để tìm instance còn dùng HttpTokens: optional.
  3. Enforce HttpTokens: required trên toàn bộ fleet sau khi xác nhận script đã được cập nhật.

Tham khảo thêm: AWS EC2 User Guide — Instance Metadata ServiceInstance Metadata Categories.

Glossary

Thuật ngữGiải thích
IMDSInstance Metadata Service — HTTP endpoint tại 169.254.169.254 cung cấp thông tin về instance đang chạy
IMDSv2Phiên bản 2 của IMDS, yêu cầu session token trước khi đọc metadata
SSRFServer-Side Request Forgery — lỗ hổng khiến server thực hiện HTTP request thay cho attacker
HttpTokensTham số cấu hình IMDS: optional cho phép IMDSv1, required chỉ cho phép IMDSv2
Link-local addressĐịa chỉ IP 169.254.x.x chỉ có thể truy cập trong cùng network segment, không routable qua internet

Nhận xét

Bài đăng phổ biến từ blog này

EC2 Không Có Internet Trong Custom VPC: Cách Gắn Internet Gateway và Cập Nhật Route Table

RDS Multi-AZ: Lợi Ích Thực Sự Là Gì và Khi Nào Nên Bật?

Lỗi CORS trên API Gateway: Cách bật CORS và Lambda phải trả về header gì