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ước | Mô tả | Lệnh |
|---|---|---|
| 1 | Lấy session token | curl -X PUT ... /latest/api/token |
| 2 | Dùng token để lấy Instance ID | curl -H 'X-aws-ec2-metadata-token: ...' .../instance-id |
| 3 | Enforce IMDSv2 | Tắ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.
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
- 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. - IMDSv2 flow: Script phải PUT để lấy token trước, sau đó mới GET với token đó. Thiếu token → HTTP 401.
- 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.
- Tại sao IMDSv2 chặn được: PUT request với
X-Forwarded-Forheader 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.
--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ề
- HttpTokens = optional: Cả IMDSv1 và IMDSv2 đều hoạt động. Request không có token vẫn được phục vụ.
- HttpTokens = required: Chỉ IMDSv2 được chấp nhận. Request không có token nhận 401 ngay lập tức.
- 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:
- Cập nhật tất cả script hiện tại sang IMDSv2 flow.
- Chạy lệnh
describe-instancesscan để tìm instance còn dùngHttpTokens: optional. - Enforce
HttpTokens: requiredtrê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 Service và Instance Metadata Categories.
Glossary
| Thuật ngữ | Giải thích |
|---|---|
| IMDS | Instance Metadata Service — HTTP endpoint tại 169.254.169.254 cung cấp thông tin về instance đang chạy |
| IMDSv2 | Phiên bản 2 của IMDS, yêu cầu session token trước khi đọc metadata |
| SSRF | Server-Side Request Forgery — lỗ hổng khiến server thực hiện HTTP request thay cho attacker |
| HttpTokens | Tham 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
Đăng nhận xét