#!/bin/bash

# Title: Docker Environment Checker
# Description: Check for Docker/container environment escape vectors
# Author: Jonas Resch

# Detect if running inside a container
detect_container() {
  print_subtitle "Container Detection"
  
  print_info "Checking if we're running inside a container..."
  
  IS_CONTAINER=""
  CONTAINER_TYPE=""
  
  # Check for Docker
  if [ -f /.dockerenv ] || grep -q "docker" /proc/1/cgroup 2>/dev/null; then
    IS_CONTAINER="1"
    CONTAINER_TYPE="Docker"
    print_warning "Running inside a ${YELLOW}Docker container${NC}"
  # Check for LXC
  elif grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
    IS_CONTAINER="1"
    CONTAINER_TYPE="LXC"
    print_warning "Running inside a ${YELLOW}LXC container${NC}"
  # Check for SystemD-nspawn
  elif grep -q "systemd-nspawn" /proc/1/cgroup 2>/dev/null; then
    IS_CONTAINER="1"
    CONTAINER_TYPE="systemd-nspawn"
    print_warning "Running inside a ${YELLOW}systemd-nspawn container${NC}"
  # Check for Kubernetes
  elif [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]; then
    IS_CONTAINER="1"
    CONTAINER_TYPE="Kubernetes"
    print_warning "Running inside a ${YELLOW}Kubernetes pod${NC}"
  # Other container indicators
  elif grep -qi "container=\|docker\|lxc\|pod" /proc/1/environ 2>/dev/null; then
    IS_CONTAINER="1"
    CONTAINER_TYPE="Unknown"
    print_warning "Running inside an ${YELLOW}unknown container type${NC}"
  else
    print_success "Not running inside a container"
  fi
  
  # Check if we're running in a container with increased privileges
  if [ "$IS_CONTAINER" ]; then
    # Check for privileged mode
    if ls -la /dev 2>/dev/null | grep -q "nvidia"; then
      print_critical "${RED}Container appears to be running in privileged mode (nvidia devices exposed)${NC}"
    elif ls -la /dev 2>/dev/null | grep -q "sda"; then
      print_critical "${RED}Container appears to be running in privileged mode (host devices exposed)${NC}"
    elif ip link show 2>/dev/null | grep -q "host0"; then
      print_critical "${RED}Container appears to be running with host networking${NC}"
    fi
    
    # Check namespace isolation
    if [ -r /proc/1/ns ]; then
      readlink /proc/1/ns/* 2>/dev/null | while read -r line; do
        if echo "$line" | grep -q "init"; then
          print_critical "${RED}Container shares namespace with host! This is insecure.${NC}"
          break
        fi
      done
    fi
  fi
}

# Check for Docker group membership
check_docker_group() {
  print_subtitle "Docker Group Membership"
  
  print_info "Checking for users in the docker group..."
  
  # Check if docker group exists
  if grep -q "^docker:" /etc/group 2>/dev/null; then
    # Get members of docker group
    docker_users=$(grep "^docker:" /etc/group 2>/dev/null | cut -d: -f4 | tr ',' '\n')
    
    if [ -n "$docker_users" ]; then
      print_critical "${RED}Found users in the 'docker' group:${NC}"
      echo "$docker_users" | while read -r user; do
        if [ -n "$user" ]; then
          print_critical " ${RED}→ $user${NC}"
        fi
      done
      
      print_critical "${RED}Users in the docker group can escalate to root:${NC}"
      print_critical " ${RED}→ docker run -v /:/mnt -it ubuntu chroot /mnt bash${NC}"
    else
      print_success "No users in the 'docker' group"
    fi
  else
    print_success "Docker group not found on the system"
  fi
  
  # Check if current user can run docker
  if command_exists docker && docker ps >/dev/null 2>&1; then
    print_critical "${RED}Current user can execute docker commands!${NC}"
    print_critical " ${RED}→ This can be used to escalate privileges:${NC}"
    print_critical " ${RED}→ docker run -v /:/mnt -it ubuntu chroot /mnt bash${NC}"
  fi
}

# Check for container escape vectors
check_container_escape_vectors() {
  print_subtitle "Container Escape Vectors"
  
  if [ ! "$IS_CONTAINER" ]; then
    print_success "Not running in a container, skipping container escape check"
    return
  fi
  
  print_info "Checking for container escape vectors..."
  
  # Check for mounted docker socket
  if [ -S /var/run/docker.sock ]; then
    print_critical "${RED}Docker socket is mounted inside the container!${NC}"
    print_critical " ${RED}→ This allows for easy container escape:${NC}"
    print_critical " ${RED}→ Run a container with host root filesystem mounted${NC}"
    ls -la /var/run/docker.sock
  fi
  
  # Check for volume mounts that may enable escape
  if [ -f /proc/mounts ]; then
    suspicious_mounts=$(grep -E "/ |/etc|/proc|/sys|/var/run|/dev" /proc/mounts | grep -v "^overlay")
    
    if [ -n "$suspicious_mounts" ]; then
      print_critical "${RED}Found suspicious mounts that might enable container escape:${NC}"
      echo "$suspicious_mounts" | while read -r mount; do
        print_critical " ${RED}→ $mount${NC}"
      done
    fi
  fi
  
  # Check for dangerous capabilities
  if command_exists capsh; then
    caps=$(capsh --print 2>/dev/null)
    
    if echo "$caps" | grep -q "cap_sys_admin"; then
      print_critical "${RED}Container has CAP_SYS_ADMIN capability!${NC}"
      print_critical " ${RED}→ This can be used for container escape:${NC}"
      print_critical " ${RED}→ Mount host filesystem and chroot into it${NC}"
    fi
    
    dangerous_caps=("cap_dac_override" "cap_dac_read_search" "cap_chown" "cap_setuid" "cap_setgid" "cap_net_admin" "cap_net_raw" "cap_sys_module" "cap_sys_ptrace")
    
    for cap in "${dangerous_caps[@]}"; do
      if echo "$caps" | grep -q "$cap"; then
        print_critical "${RED}Container has $cap capability!${NC}"
      fi
    done
  elif [ -r /proc/self/status ]; then
    # Alternative method if capsh isn't available
    capeff=$(grep "CapEff:" /proc/self/status 2>/dev/null | cut -d: -f2 | tr -d ' ')
    
    if [ -n "$capeff" ]; then
      # Convert hex to binary and check for specific bits
      capeff_dec=$(printf "%d" "0x$capeff" 2>/dev/null)
      
      # Check for CAP_SYS_ADMIN (bit 21)
      if [ "$((($capeff_dec >> 21) & 1))" -eq 1 ]; then
        print_critical "${RED}Container has CAP_SYS_ADMIN capability!${NC}"
        print_critical " ${RED}→ This can be used for container escape${NC}"
      fi
    fi
  fi
  
  # Check for CVE-2019-5736 (runc < 1.0-rc6)
  if [ -f /bin/sh ] && [ -f /proc/self/exe ]; then
    if [ "$CONTAINER_TYPE" = "Docker" ] || [ "$CONTAINER_TYPE" = "Kubernetes" ]; then
      # Indirect way to check for potential vulnerability (not 100% accurate)
      print_warning "${YELLOW}Container might be vulnerable to CVE-2019-5736 (runc container escape)${NC}"
      print_warning " ${YELLOW}→ Affects Docker versions before 18.09.2${NC}"
      print_warning " ${YELLOW}→ https://unit42.paloaltonetworks.com/breaking-docker-via-runc-explaining-cve-2019-5736${NC}"
    fi
  fi
  
  # Check for cgroup release_agent escape
  if [ -w /proc/sysrq-trigger ] && [ -d /sys/fs/cgroup/notify_on_release ]; then
    print_critical "${RED}Container vulnerable to cgroup release_agent escape!${NC}"
    print_critical " ${RED}→ https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes${NC}"
  fi
  
  # Check for unprotected kubectl command
  if command_exists kubectl && [ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]; then
    if kubectl auth can-i "*" "*" 2>/dev/null | grep -q "yes"; then
      print_critical "${RED}Kubernetes service account has admin privileges!${NC}"
      print_critical " ${RED}→ Can be used to escape to other containers or nodes${NC}"
    fi
  fi
}

# Check for exposed Docker API
check_exposed_docker_api() {
  print_subtitle "Exposed Docker API"
  
  print_info "Checking for exposed Docker API endpoints..."
  
  # List of potential Docker API endpoint locations
  api_endpoints=(
    "unix:///var/run/docker.sock"
    "http://localhost:2375"
    "http://127.0.0.1:2375"
    "http://localhost:2376"
    "http://127.0.0.1:2376"
  )
  
  for endpoint in "${api_endpoints[@]}"; do
    if [[ "$endpoint" == unix://* ]]; then
      # Check for Unix socket
      socket_path=$(echo "$endpoint" | sed 's|unix://||')
      
      if [ -S "$socket_path" ]; then
        if [ -r "$socket_path" ] && [ -w "$socket_path" ]; then
          print_critical "${RED}Exposed Docker API socket: $socket_path (read/write)${NC}"
          print_critical " ${RED}→ This allows full Docker control, leading to host compromise${NC}"
        elif [ -r "$socket_path" ]; then
          print_warning "${YELLOW}Exposed Docker API socket: $socket_path (readable)${NC}"
          print_warning " ${YELLOW}→ This allows Docker information gathering${NC}"
        fi
      fi
    else
      # Check for HTTP endpoints
      host=$(echo "$endpoint" | cut -d/ -f3 | cut -d: -f1)
      port=$(echo "$endpoint" | cut -d: -f3)
      
      if nc -zw1 "$host" "$port" 2>/dev/null; then
        print_critical "${RED}Exposed Docker API endpoint: $endpoint${NC}"
        print_critical " ${RED}→ This allows remote Docker control without authentication${NC}"
        print_critical " ${RED}→ Exploit: curl -s $endpoint/containers/json | jq .${NC}"
      fi
    fi
  done
}

# Check for misconfigured Docker
check_docker_configuration() {
  print_subtitle "Docker Configuration"
  
  print_info "Checking for Docker configuration issues..."
  
  # Check for Docker daemon with disabled security options
  if [ -r /etc/docker/daemon.json ]; then
    print_warning "Checking Docker daemon configuration..."
    
    # Look for potentially insecure settings
    if grep -q "\"userns-remap\": \"off\"" /etc/docker/daemon.json || \
       grep -q "\"no-new-privileges\": false" /etc/docker/daemon.json || \
       grep -q "\"selinux-enabled\": false" /etc/docker/daemon.json || \
       grep -q "\"apparmor-profile\": \"\"" /etc/docker/daemon.json || \
       grep -q "\"insecure-registries\"" /etc/docker/daemon.json; then
      print_critical "${RED}Potentially insecure Docker daemon configuration:${NC}"
      grep -E "userns-remap|no-new-privileges|selinux-enabled|apparmor-profile|insecure-registries" /etc/docker/daemon.json | while read -r line; do
        print_critical " ${RED}→ $line${NC}"
      done
    else
      print_success "Docker daemon configuration appears to be secure"
    fi
  fi
  
  # Check for images with known vulnerabilities
  if command_exists docker && docker ps >/dev/null 2>&1; then
    print_warning "Checking for potentially vulnerable Docker images..."
    
    # Check for outdated or dangerous base images
    outdated_images=$(docker images 2>/dev/null | grep -E "alpine:|debian:|ubuntu:" | grep -E "latest|old|[0-9]{1,2}\.[0-9]{1,2}$")
    
    if [ -n "$outdated_images" ]; then
      print_warning "${YELLOW}Potentially outdated Docker images:${NC}"
      echo "$outdated_images" | while read -r line; do
        print_warning " ${YELLOW}→ $line${NC}"
      done
    fi
    
    # Check for containers running as root (default)
    if docker ps --format "{{.Names}}:{{.Image}}" 2>/dev/null | head -n 5 | while read -r container; do
      user=$(docker inspect --format "{{.Config.User}}" "$(echo "$container" | cut -d: -f1)" 2>/dev/null)
      if [ -z "$user" ] || [ "$user" = "root" ] || [ "$user" = "0" ]; then
        print_warning " ${YELLOW}→ Container running as root: $container${NC}"
      fi
    done; then
      print_warning "${YELLOW}Detected containers running as root - this is less secure${NC}"
    fi
  fi
}

# Main function to run all Docker environment checks
docker_environment_checks() {
  print_title "Docker/Container Environment"
  
  # Run all Docker/container environment checks
  detect_container
  check_docker_group
  check_container_escape_vectors
  check_exposed_docker_api
  check_docker_configuration
  
  # Wait for user if wait mode is enabled
  wait_for_user
} 