├── .github └── workflows │ └── docker-image.yml ├── .gitignore ├── ARCHITECTURE.md ├── Dockerfile ├── IMPROVEMENTS.md ├── README.MD ├── docs ├── ARCHITECTURE.md ├── CONFIGURATION.md └── TROUBLESHOOTING.md ├── image.png ├── scripts ├── env_validator.sh ├── install-teamserver.sh ├── start-listeners.sh ├── start-teamserver.sh └── state_monitor.sh ├── services ├── dns-listener.cna.template ├── http-listener.cna.template ├── https-listener.cna.template └── smb-listener.cna.template └── supervisord.conf /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] # Trigger on pushes to main branch 6 | pull_request: 7 | branches: [ "main" ] # Optional: Trigger on PRs to main 8 | schedule: 9 | - cron: '0 0 * * 0' # Weekly rebuilds 10 | 11 | env: 12 | REGISTRY: docker.io 13 | IMAGE_NAME: ${{ vars.DOCKERHUB_USERNAME }}/cobaltstrike 14 | 15 | jobs: 16 | build-and-push: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | packages: write 21 | security-events: write # Required for SARIF upload 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Log in to Docker Hub 28 | uses: docker/login-action@v3 29 | with: 30 | username: ${{ vars.DOCKERHUB_USERNAME }} 31 | password: ${{ secrets.DOCKERHUB_TOKEN }} 32 | 33 | - name: Build and push Docker image 34 | uses: docker/build-push-action@v6 35 | with: 36 | context: . 37 | push: true 38 | tags: | 39 | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | *.out 4 | *.err 5 | 6 | # Ignore Docker Compose override files 7 | docker-compose.override.yml 8 | 9 | # Ignore compiled CNA files (if any are generated) 10 | *.cna 11 | 12 | # Ignore environment variables and sensitive files 13 | .env 14 | *.env 15 | 16 | # Ignore temporary and backup files 17 | *.tmp 18 | *.bak 19 | *~ 20 | 21 | # Ignore yml 22 | *.yml 23 | restart.sh 24 | 25 | # ignore profiles 26 | profiles/ 27 | 28 | # Ignore compiled Java class files (if Java is involved) 29 | *.class 30 | 31 | # Ignore OS-generated files 32 | .DS_Store 33 | Thumbs.db 34 | 35 | # Ignore IDE-specific files 36 | .vscode/ 37 | .idea/ 38 | *.swp 39 | 40 | # Ignore runtime-generated files 41 | !profiles/*.profile 42 | 43 | # Ignore secrets or license keys 44 | LICENSE 45 | *.license 46 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Docker Cobalt Strike Architecture Improvements 2 | 3 | ## Current Architecture 4 | 5 | The current implementation uses Docker to containerize Cobalt Strike with the following components: 6 | 7 | - Ubuntu 22.04 base image 8 | - Supervisord for process management 9 | - Shell scripts for installation and service management 10 | - CNA templates for listener configuration 11 | 12 | ## Identified Issues 13 | 14 | ### 1. Security Concerns 15 | - Container runs as root 16 | - Default password in environment variables 17 | - No health checks 18 | - No package version pinning 19 | - No SSL/TLS configuration for internal communication 20 | - No secrets management 21 | 22 | ### 2. Container Configuration 23 | - Basic Dockerfile without multi-stage builds 24 | - No .dockerignore file 25 | - No container resource limits 26 | - No proper signal handling 27 | - No container health monitoring 28 | 29 | ### 3. Process Management 30 | - Basic supervisord configuration 31 | - File-based service coordination 32 | - Limited retry and restart policies 33 | - No proper dependency management 34 | - No graceful shutdown handling 35 | 36 | ### 4. Error Handling & Reliability 37 | - Basic error handling with set -e 38 | - No cleanup on failure 39 | - No retry mechanisms for downloads 40 | - No checksum verification 41 | - No backup mechanisms 42 | 43 | ### 5. Configuration Management 44 | - Hardcoded paths and configurations 45 | - Basic environment variable validation 46 | - No configuration validation 47 | - Limited profile selection logic 48 | 49 | ## Improvement Plan 50 | 51 | ### 1. Security Enhancements 52 | 53 | #### Container Security 54 | ```dockerfile 55 | # Add to Dockerfile 56 | ARG USER_ID=1000 57 | ARG GROUP_ID=1000 58 | 59 | RUN groupadd -g $GROUP_ID cobaltstrike && \ 60 | useradd -u $USER_ID -g $GROUP_ID -m cobaltstrike 61 | 62 | USER cobaltstrike 63 | 64 | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ 65 | CMD nc -z localhost 50050 || exit 1 66 | ``` 67 | 68 | #### Package Version Pinning 69 | ```dockerfile 70 | # Update package installation 71 | RUN apt-get update && \ 72 | apt-get install -y --no-install-recommends \ 73 | curl=7.81.0-1ubuntu1.15 \ 74 | netcat=1.10-46 \ 75 | expect=5.45.4-2build1 \ 76 | supervisor=4.2.1-2ubuntu4 \ 77 | gettext-base=0.21-4ubuntu4 \ 78 | openjdk-11-jdk=11.0.20.1+1-0ubuntu1~22.04 79 | ``` 80 | 81 | ### 2. Process Management Improvements 82 | 83 | #### Enhanced Supervisord Configuration 84 | ```ini 85 | [supervisord] 86 | nodaemon=true 87 | logfile=/var/log/supervisor/supervisord.log 88 | pidfile=/var/run/supervisord.pid 89 | childlogdir=/var/log/supervisor 90 | user=cobaltstrike 91 | 92 | [program:cs_installer] 93 | command=/opt/cobaltstrike/scripts/install-teamserver.sh 94 | autostart=true 95 | autorestart=false 96 | startretries=3 97 | stopwaitsecs=10 98 | stdout_logfile=/var/log/supervisor/cs_installer.out.log 99 | stderr_logfile=/var/log/supervisor/cs_installer.err.log 100 | 101 | [program:teamserver] 102 | command=/opt/cobaltstrike/scripts/start-teamserver.sh 103 | autostart=false 104 | autorestart=true 105 | startretries=3 106 | startsecs=10 107 | stopwaitsecs=10 108 | stdout_logfile=/var/log/supervisor/teamserver.out.log 109 | stderr_logfile=/var/log/supervisor/teamserver.err.log 110 | 111 | [program:listener] 112 | command=/opt/cobaltstrike/scripts/start-listeners.sh 113 | autostart=false 114 | autorestart=true 115 | startretries=3 116 | startsecs=10 117 | stopwaitsecs=10 118 | stdout_logfile=/var/log/supervisor/listener.out.log 119 | stderr_logfile=/var/log/supervisor/listener.err.log 120 | 121 | [eventlistener:processes] 122 | command=python3 /opt/cobaltstrike/scripts/process_monitor.py 123 | events=PROCESS_STATE 124 | ``` 125 | 126 | ### 3. Enhanced Error Handling 127 | 128 | #### Installation Script Improvements 129 | ```bash 130 | #!/bin/bash 131 | set -euo pipefail 132 | trap cleanup EXIT 133 | 134 | cleanup() { 135 | rm -f /tmp/cobaltstrike-dist-linux.tgz 136 | # Additional cleanup tasks 137 | } 138 | 139 | download_with_retry() { 140 | local url=$1 141 | local output=$2 142 | local max_attempts=3 143 | local attempt=1 144 | 145 | while [ $attempt -le $max_attempts ]; do 146 | if curl -sSL --retry 3 -o "$output" "$url"; then 147 | return 0 148 | fi 149 | echo "Download attempt $attempt failed. Retrying..." 150 | ((attempt++)) 151 | sleep 5 152 | done 153 | return 1 154 | } 155 | ``` 156 | 157 | ### 4. Configuration Management 158 | 159 | #### Environment Configuration 160 | ```bash 161 | # Add to start-teamserver.sh 162 | validate_config() { 163 | local required_vars=( 164 | "TEAMSERVER_PASSWORD" 165 | "C2_PROFILE_NAME" 166 | "LICENSE_KEY" 167 | ) 168 | 169 | for var in "${required_vars[@]}"; do 170 | if [ -z "${!var}" ]; then 171 | echo "Error: Required environment variable $var is not set" 172 | exit 1 173 | fi 174 | done 175 | 176 | if [ ${#TEAMSERVER_PASSWORD} -lt 12 ]; then 177 | echo "Error: TEAMSERVER_PASSWORD must be at least 12 characters" 178 | exit 1 179 | fi 180 | } 181 | ``` 182 | 183 | ### 5. Monitoring & Logging 184 | 185 | #### Process Monitor Script 186 | ```python 187 | #!/usr/bin/env python3 188 | import sys 189 | import os 190 | from supervisor.childutils import listener 191 | 192 | def write_status(status): 193 | with open('/var/log/supervisor/status.log', 'a') as f: 194 | f.write(f"{status}\n") 195 | 196 | def main(): 197 | while True: 198 | headers, payload = listener.wait() 199 | if headers['eventname'].startswith('PROCESS_STATE'): 200 | write_status(f"{headers['eventname']}: {payload}") 201 | listener.ok() 202 | 203 | if __name__ == "__main__": 204 | main() 205 | ``` 206 | 207 | ## Implementation Strategy 208 | 209 | 1. Create a staging branch for testing improvements 210 | 2. Implement security improvements first 211 | 3. Update process management 212 | 4. Enhance error handling 213 | 5. Improve configuration management 214 | 6. Add monitoring and logging 215 | 7. Test thoroughly in isolated environment 216 | 8. Document all changes and new features 217 | 218 | ## Additional Recommendations 219 | 220 | 1. **Backup Strategy** 221 | - Implement regular state backups 222 | - Add backup verification 223 | - Document recovery procedures 224 | 225 | 2. **Documentation** 226 | - Add detailed setup instructions 227 | - Document all environment variables 228 | - Include troubleshooting guide 229 | - Add architecture diagrams 230 | 231 | 3. **Testing** 232 | - Add integration tests 233 | - Create validation scripts 234 | - Document testing procedures 235 | 236 | 4. **Monitoring** 237 | - Add Prometheus metrics 238 | - Create Grafana dashboards 239 | - Set up alerting 240 | 241 | 5. **CI/CD** 242 | - Add GitHub Actions workflow 243 | - Implement automated testing 244 | - Add security scanning -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image 2 | FROM ubuntu:22.04 3 | 4 | # Avoid interactive prompts during apt installs 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | 7 | # Required environment variables that must be provided at runtime: 8 | # - TEAMSERVER_PASSWORD: Password for the team server (min 8 characters) 9 | # - LICENSE_KEY: Your Cobalt Strike license key 10 | # Note: A C2 profile file (*.profile) must be mounted in /opt/cobaltstrike/profiles 11 | 12 | # Optional environment variables for listeners: 13 | # - DNS_LISTENER_DOMAIN_NAME: Domain name for DNS listener 14 | # - DNS_LISTENER_STAGER_DOMAIN_NAME: Domain name for DNS stager 15 | # - HTTPS_LISTENER_DOMAIN_NAME: Domain name for HTTPS listener 16 | # - HTTP_LISTENER_DOMAIN_NAME: Domain name for HTTP listener 17 | # - SMB_LISTENER_NAMED_PIPE_NAME: Named pipe for SMB listener 18 | 19 | # Expose necessary ports 20 | EXPOSE 50050 21 | 22 | # Install required packages 23 | RUN apt-get update && \ 24 | apt-get install -y --no-install-recommends \ 25 | curl \ 26 | netcat \ 27 | expect \ 28 | supervisor \ 29 | gettext-base \ 30 | openjdk-11-jdk && \ 31 | rm -rf /var/lib/apt/lists/* 32 | 33 | # Set up directories 34 | WORKDIR /opt/cobaltstrike 35 | RUN mkdir -p /opt/cobaltstrike/server /opt/cobaltstrike/client /opt/cobaltstrike/services /opt/cobaltstrike/scripts /var/log/supervisor 36 | RUN chmod 755 /opt/cobaltstrike 37 | 38 | # Copy scripts to the container 39 | COPY scripts/* /opt/cobaltstrike/scripts/ 40 | RUN chmod +x /opt/cobaltstrike/scripts/* 41 | 42 | # Copy Cobalt Strike CNA templates to the container 43 | COPY services/* /opt/cobaltstrike/services/ 44 | RUN chmod +x /opt/cobaltstrike/services/* 45 | 46 | # Copy supervisord configuration 47 | COPY supervisord.conf /etc/supervisor/supervisord.conf 48 | 49 | # Use supervisord as the entrypoint 50 | ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] 51 | -------------------------------------------------------------------------------- /IMPROVEMENTS.md: -------------------------------------------------------------------------------- 1 | # Process Management and Error Handling Improvements 2 | 3 | ## Current Implementation Analysis 4 | 5 | ### Process Management Issues 6 | - Basic supervisord configuration using file flags for coordination 7 | - Sequential listener setup without parallelization 8 | - No proper dependency tracking between services 9 | - Limited retry and restart policies 10 | - Basic process monitoring 11 | 12 | ### Error Handling Issues 13 | - Basic error handling with set -e 14 | - No comprehensive validation of environment variables 15 | - No retry mechanisms for downloads and operations 16 | - No proper cleanup on failures 17 | - Limited logging of errors and operations 18 | 19 | ## Improvement Plan 20 | 21 | ### 1. Process Management Enhancements 22 | 23 | #### Updated Supervisord Configuration 24 | ```ini 25 | [supervisord] 26 | nodaemon=true 27 | logfile=/var/log/supervisor/supervisord.log 28 | pidfile=/var/run/supervisord.pid 29 | childlogdir=/var/log/supervisor 30 | 31 | [program:cs_installer] 32 | command=/opt/cobaltstrike/scripts/install-teamserver.sh 33 | priority=1 34 | autostart=true 35 | autorestart=unexpected 36 | startretries=3 37 | exitcodes=0 38 | stdout_logfile=/var/log/supervisor/cs_installer.out.log 39 | stderr_logfile=/var/log/supervisor/cs_installer.err.log 40 | 41 | [program:teamserver] 42 | command=/opt/cobaltstrike/scripts/start-teamserver.sh 43 | priority=2 44 | autostart=false 45 | autorestart=true 46 | startretries=3 47 | startsecs=10 48 | stopwaitsecs=10 49 | stdout_logfile=/var/log/supervisor/teamserver.out.log 50 | stderr_logfile=/var/log/supervisor/teamserver.err.log 51 | 52 | [program:listener] 53 | command=/opt/cobaltstrike/scripts/start-listeners.sh 54 | priority=3 55 | autostart=false 56 | autorestart=true 57 | startretries=3 58 | startsecs=10 59 | stopwaitsecs=10 60 | stdout_logfile=/var/log/supervisor/listener.out.log 61 | stderr_logfile=/var/log/supervisor/listener.err.log 62 | 63 | [eventlistener:state_monitor] 64 | command=/opt/cobaltstrike/scripts/state_monitor.sh 65 | events=PROCESS_STATE 66 | buffer_size=100 67 | ``` 68 | 69 | #### Process State Monitor (state_monitor.sh) 70 | ```bash 71 | #!/bin/bash 72 | 73 | # Read STDIN in a loop for supervisor events 74 | while read line; do 75 | # Write event to state log 76 | echo "$(date '+%Y-%m-%d %H:%M:%S') - $line" >> /var/log/supervisor/state.log 77 | 78 | # Parse the event 79 | if echo "$line" | grep -q "PROCESS_STATE_EXITED"; then 80 | process_name=$(echo "$line" | awk '{print $3}') 81 | exit_code=$(echo "$line" | awk '{print $5}') 82 | 83 | # Log process exit 84 | echo "Process $process_name exited with code $exit_code" >> /var/log/supervisor/state.log 85 | 86 | # Handle specific process failures 87 | case "$process_name" in 88 | cs_installer) 89 | if [ "$exit_code" != "0" ]; then 90 | echo "FATAL: Installer failed with code $exit_code" >> /var/log/supervisor/state.log 91 | kill -15 $(cat /var/run/supervisord.pid) 92 | fi 93 | ;; 94 | teamserver) 95 | if [ "$exit_code" != "0" ]; then 96 | echo "ERROR: Teamserver failed with code $exit_code" >> /var/log/supervisor/state.log 97 | fi 98 | ;; 99 | listener) 100 | if [ "$exit_code" != "0" ]; then 101 | echo "ERROR: Listener failed with code $exit_code" >> /var/log/supervisor/state.log 102 | fi 103 | ;; 104 | esac 105 | fi 106 | 107 | # Echo back OK to supervisor 108 | echo "READY" 109 | done 110 | ``` 111 | 112 | ### 2. Environment Variable Validation 113 | 114 | #### Environment Configuration (env_validator.sh) 115 | ```bash 116 | #!/bin/bash 117 | 118 | # Required environment variables and their validation rules 119 | declare -A ENV_VARS=( 120 | ["TEAMSERVER_HOST"]="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$|^[a-zA-Z0-9.-]+$" 121 | ["TEAMSERVER_PASSWORD"]="^.{8,}$" 122 | ["C2_PROFILE_NAME"]="^[a-zA-Z0-9._-]+\.profile$" 123 | ["LICENSE_KEY"]="^[A-Za-z0-9-]+$" 124 | ) 125 | 126 | # Optional environment variables for listeners 127 | declare -A OPTIONAL_ENV_VARS=( 128 | ["DNS_LISTENER_DOMAIN_NAME"]="^[a-zA-Z0-9.-]+$" 129 | ["HTTPS_LISTENER_DOMAIN_NAME"]="^[a-zA-Z0-9.-]+$" 130 | ["SMB_C2_NAMED_PIPE_NAME"]="^[a-zA-Z0-9._-]+$" 131 | ) 132 | 133 | validate_env_vars() { 134 | local has_error=0 135 | 136 | # Validate required variables 137 | for var in "${!ENV_VARS[@]}"; do 138 | if [ -z "${!var}" ]; then 139 | echo "ERROR: Required environment variable $var is not set" 140 | has_error=1 141 | continue 142 | } 143 | 144 | if ! echo "${!var}" | grep -qE "${ENV_VARS[$var]}"; then 145 | echo "ERROR: Environment variable $var has invalid format" 146 | has_error=1 147 | fi 148 | done 149 | 150 | # Validate optional variables if set 151 | for var in "${!OPTIONAL_ENV_VARS[@]}"; do 152 | if [ -n "${!var}" ]; then 153 | if ! echo "${!var}" | grep -qE "${OPTIONAL_ENV_VARS[$var]}"; then 154 | echo "ERROR: Environment variable $var has invalid format" 155 | has_error=1 156 | fi 157 | fi 158 | done 159 | 160 | return $has_error 161 | } 162 | ``` 163 | 164 | ### 3. Enhanced Error Handling 165 | 166 | #### Updated Installation Script 167 | ```bash 168 | #!/bin/bash 169 | set -euo pipefail 170 | 171 | # Source environment validator 172 | source /opt/cobaltstrike/scripts/env_validator.sh 173 | 174 | # Validate environment variables 175 | if ! validate_env_vars; then 176 | echo "Environment validation failed. Exiting." 177 | exit 1 178 | fi 179 | 180 | # Function for retrying operations 181 | retry_operation() { 182 | local max_attempts=$1 183 | local delay=$**2** 184 | local command="${@:3}" 185 | local attempt=1 186 | 187 | while [ $attempt -le $max_attempts ]; do 188 | if eval "$command"; then 189 | return 0 190 | fi 191 | 192 | echo "Attempt $attempt failed. Retrying in $delay seconds..." 193 | sleep $delay 194 | ((attempt++)) 195 | done 196 | 197 | echo "Operation failed after $max_attempts attempts" 198 | return 1 199 | } 200 | 201 | # Download with retry and validation 202 | download_file() { 203 | local url=$1 204 | local output=$2 205 | local max_attempts=3 206 | local delay=5 207 | 208 | retry_operation $max_attempts $delay "curl -sSL -o $output $url" 209 | } 210 | 211 | # Main installation process 212 | main() { 213 | # Validate environment first 214 | if ! validate_env_vars; then 215 | echo "Environment validation failed" 216 | exit 1 217 | } 218 | 219 | # Download and install 220 | if ! download_file "$DOWNLOAD_URL" "/tmp/cobaltstrike-dist-linux.tgz"; then 221 | echo "Failed to download Cobalt Strike" 222 | exit 1 223 | fi 224 | 225 | # Extract and verify 226 | if ! tar xzf /tmp/cobaltstrike-dist-linux.tgz -C $CS_DIR --strip-components=1; then 227 | echo "Failed to extract Cobalt Strike" 228 | exit 1 229 | fi 230 | 231 | # Run update script 232 | if ! retry_operation 3 5 "cd $CS_DIR && echo '$LICENSE_KEY' | java -XX:ParallelGCThreads=4 -XX:+AggressiveHeap -XX:+UseParallelGC -jar update.jar -Type:linux"; then 233 | echo "Failed to run update script" 234 | exit 1 235 | fi 236 | 237 | # Verify installation 238 | if [ ! -f "$TEAMSERVER_IMAGE" ]; then 239 | echo "TeamServerImage not found after installation" 240 | exit 1 241 | fi 242 | 243 | echo "Installation completed successfully" 244 | touch /opt/cobaltstrike/installer_done.flag 245 | } 246 | 247 | # Cleanup function 248 | cleanup() { 249 | rm -f /tmp/cobaltstrike-dist-linux.tgz 250 | if [ $? -ne 0 ]; then 251 | echo "Installation failed, cleaning up..." 252 | fi 253 | } 254 | 255 | # Set up trap for cleanup 256 | trap cleanup EXIT 257 | 258 | # Run main installation 259 | main 260 | ``` 261 | 262 | ### 4. Parallel Listener Setup 263 | 264 | #### Updated Listener Script 265 | ```bash 266 | #!/bin/bash 267 | set -euo pipefail 268 | 269 | source /opt/cobaltstrike/scripts/env_validator.sh 270 | 271 | # Validate environment variables 272 | if ! validate_env_vars; then 273 | echo "Environment validation failed. Exiting." 274 | exit 1 275 | fi 276 | 277 | # Function to set up a listener 278 | setup_listener() { 279 | local script_name=$1 280 | local env_var=$2 281 | local output_file="/var/log/supervisor/listener_${script_name}.log" 282 | 283 | if [ -n "${!env_var}" ]; then 284 | echo "Setting up $script_name listener..." 285 | { 286 | envsubst < "$CNA_DIR/$script_name.template" > "$CNA_DIR/$script_name" 287 | $CS_DIR/client/agscript 127.0.0.1 50050 svc "$TEAMSERVER_PASSWORD" "$CNA_DIR/$script_name" 288 | } > "$output_file" 2>&1 & 289 | else 290 | echo "Skipping $script_name listener (${env_var} not set)" 291 | fi 292 | } 293 | 294 | # Start listeners in parallel 295 | setup_listener "dns-listener.cna" "DNS_LISTENER_DOMAIN_NAME" 296 | setup_listener "https-listener.cna" "HTTPS_LISTENER_DOMAIN_NAME" 297 | setup_listener "http-listener.cna" "HTTP_LISTENER_DOMAIN_NAME" 298 | setup_listener "smb-listener.cna" "SMB_C2_NAMED_PIPE_NAME" 299 | 300 | # Wait for all background processes to complete 301 | wait 302 | 303 | echo "Listener setup completed" 304 | ``` 305 | 306 | ## Implementation Steps 307 | 308 | 1. **Process Management Updates** 309 | - Replace supervisord.conf with new version 310 | - Add state_monitor.sh script 311 | - Update process dependencies and priorities 312 | 313 | 2. **Environment Validation** 314 | - Add env_validator.sh script 315 | - Integrate validation into all scripts 316 | - Test with various environment configurations 317 | 318 | 3. **Error Handling** 319 | - Update installation script with retry logic 320 | - Add cleanup procedures 321 | - Implement proper logging 322 | 323 | 4. **Listener Management** 324 | - Update listener script for parallel execution 325 | - Add proper logging for each listener 326 | - Implement retry logic for listener setup 327 | 328 | ## Testing Plan 329 | 330 | 1. Test environment validation with: 331 | - Missing required variables 332 | - Invalid variable formats 333 | - Optional variables 334 | 335 | 2. Test process management with: 336 | - Service startup order 337 | - Service failure scenarios 338 | - Process monitoring 339 | 340 | 3. Test error handling with: 341 | - Network failures 342 | - Invalid configurations 343 | - Process crashes 344 | 345 | 4. Test listener setup with: 346 | - Multiple listeners 347 | - Failed listener scenarios 348 | - Parallel execution -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Cobalt Strike Docker Container 2 | 3 | This repository provides a Docker container for running a Cobalt Strike Teamserver. 4 | 5 | ![alt text](image.png) 6 | 7 | ## Overview 8 | Cobalt Strike is a Red Team post-exploitation framework. This project aims to make deployment of Cobalt Strike straightforward using Docker Compose. 9 | 10 | ### Features 11 | - **Easy deployment**: Quickly spin up a Cobalt Strike Teamserver with Docker Compose 12 | - **Custom C2 profiles**: Load custom C2 profiles from the `./profiles` directory 13 | - **Auto-Start Listeners**: Preconfigure listeners via environment variables (HTTP/S, DNS, SMB) 14 | - **Robust Process Management**: Enhanced error handling and service management 15 | - **Comprehensive Logging**: Detailed logs for troubleshooting 16 | 17 | ## Quick Start 18 | 19 | ### Prerequisites 20 | - Valid Cobalt Strike License 21 | - Docker and Docker Compose 22 | - C2 Profile file 23 | 24 | ### Basic Usage 25 | 1. Create your docker-compose.yml: 26 | ```yaml 27 | version: "3.8" 28 | services: 29 | cobaltstrike: 30 | image: chryzsh/cobaltstrike 31 | container_name: cobaltstrike 32 | ports: 33 | - "50050:50050" 34 | volumes: 35 | - ./profiles:/opt/cobaltstrike/profiles 36 | - ./logs:/var/log/supervisor 37 | environment: 38 | - LICENSE_KEY=your_license_key_here 39 | - TEAMSERVER_PASSWORD=your_secure_password 40 | - C2_PROFILE_NAME=your.profile 41 | restart: unless-stopped 42 | ``` 43 | 44 | 2. Start the container: 45 | ```bash 46 | docker-compose up -d 47 | ``` 48 | 49 | ## Documentation 50 | 51 | For detailed information, please see the following guides: 52 | 53 | - [Configuration Guide](docs/CONFIGURATION.md) 54 | - Environment variables 55 | - Docker Compose configuration 56 | - Volume mounts and port mappings 57 | - Security considerations 58 | 59 | - [Troubleshooting Guide](docs/TROUBLESHOOTING.md) 60 | - Common issues and solutions 61 | - Log analysis 62 | - Recovery procedures 63 | 64 | - [Architecture Documentation](docs/ARCHITECTURE.md) 65 | - Technical details 66 | - Implementation specifics 67 | - Design decisions 68 | 69 | ## Security Notes 70 | - Use strong credentials 71 | - Restrict access to trusted networks 72 | - Ensure proper licensing and authorization 73 | 74 | ## Contributing 75 | Contribute by submitting issues or pull requests. See [Architecture Documentation](docs/ARCHITECTURE.md) for technical details. 76 | 77 | ## Disclaimer 78 | - This project is intended for **legal and authorized use only** 79 | - Cobalt Strike is a commercial tool—ensure you have a valid license from [HelpSystems](https://www.helpsystems.com/) 80 | - Maintainers are not responsible for misuse of this setup 81 | 82 | ## License 83 | MIT License - See [LICENSE](LICENSE) for details 84 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture Documentation 2 | 3 | ## Overview 4 | This document details the technical architecture of the Docker Cobalt Strike container implementation. 5 | 6 | ## Core Components 7 | 8 | ### Container Structure 9 | - Based on Ubuntu 22.04 10 | - Uses Supervisor for process management 11 | - Three main processes: installation, teamserver, and listeners 12 | 13 | ### Process Management 14 | - Supervisor manages process dependencies and lifecycle 15 | - Enhanced error handling and recovery 16 | - Process state monitoring and logging 17 | - Parallel listener setup 18 | 19 | ### Environment Validation 20 | - Centralized validation for all variables 21 | - Required variables: 22 | - TEAMSERVER_PASSWORD 23 | - LICENSE_KEY 24 | - C2_PROFILE_NAME 25 | - Optional listener configurations 26 | - Regex-based format validation 27 | 28 | ## Implementation Details 29 | 30 | ### Installation Process 31 | - License verification 32 | - Download token retrieval 33 | - Automated installation 34 | - Installation state management 35 | 36 | ### Teamserver Management 37 | - Internal communication via localhost 38 | - C2 profile validation and loading 39 | - Process monitoring and recovery 40 | - Error handling and logging 41 | 42 | ### Listener Management 43 | - Parallel listener setup 44 | - Individual error handling per listener 45 | - Templated CNA scripts 46 | - Automatic retry mechanisms 47 | 48 | ## Design Decisions 49 | 50 | ### Container Communication 51 | - Internal services use localhost (127.0.0.1) 52 | - External access through Docker port mapping 53 | - Clear separation of internal/external communication 54 | 55 | ### Error Handling Strategy 56 | - Comprehensive validation before operations 57 | - Retry mechanisms for critical operations 58 | - Detailed logging for troubleshooting 59 | - Proper cleanup on failures 60 | 61 | ### Configuration Management 62 | - Required vs optional configuration 63 | - No default sensitive values 64 | - Clear validation rules 65 | - Explicit requirements -------------------------------------------------------------------------------- /docs/CONFIGURATION.md: -------------------------------------------------------------------------------- 1 | # Configuration Guide 2 | 3 | ## Environment Variables 4 | 5 | ### Required Variables 6 | 7 | #### TEAMSERVER_PASSWORD 8 | - **Description**: Password for the Cobalt Strike team server 9 | - **Format**: Minimum 8 characters 10 | - **Example**: `TEAMSERVER_PASSWORD=your_secure_password` 11 | - **Security Note**: Choose a strong password, avoid defaults 12 | 13 | #### LICENSE_KEY 14 | - **Description**: Your Cobalt Strike license key 15 | - **Format**: Alphanumeric with hyphens 16 | - **Example**: `LICENSE_KEY=XXXX-YYYY-ZZZZ` 17 | - **Note**: Must be a valid, active license 18 | 19 | #### C2_PROFILE_NAME 20 | - **Description**: Name of your C2 profile file 21 | - **Format**: Must end in .profile 22 | - **Example**: `C2_PROFILE_NAME=custom.profile` 23 | - **Location**: Must exist in mounted profiles directory 24 | 25 | ### Optional Listener Variables 26 | 27 | #### HTTPS_LISTENER_DOMAIN_NAME 28 | - **Description**: Domain for HTTPS C2 communication 29 | - **Format**: Valid domain name 30 | - **Example**: `HTTPS_LISTENER_DOMAIN_NAME=example.com` 31 | - **Port**: Uses 443 by default 32 | 33 | #### HTTP_LISTENER_DOMAIN_NAME 34 | - **Description**: Domain for HTTP C2 communication 35 | - **Format**: Valid domain name 36 | - **Example**: `HTTP_LISTENER_DOMAIN_NAME=example.com` 37 | - **Port**: Uses 80 by default 38 | 39 | #### DNS_LISTENER_DOMAIN_NAME 40 | - **Description**: Domain for DNS C2 communication 41 | - **Format**: Valid domain name 42 | - **Example**: `DNS_LISTENER_DOMAIN_NAME=c2.example.com` 43 | - **Port**: Uses 53/udp by default 44 | 45 | #### SMB_C2_NAMED_PIPE_NAME 46 | - **Description**: Named pipe for SMB listener 47 | - **Format**: Alphanumeric with dots, underscores, hyphens 48 | - **Example**: `SMB_C2_NAMED_PIPE_NAME=custom_pipe` 49 | 50 | ## Docker Compose Configuration 51 | 52 | ### Basic Configuration 53 | ```yaml 54 | version: "3.8" 55 | services: 56 | cobaltstrike: 57 | image: chryzsh/cobaltstrike 58 | container_name: cobaltstrike 59 | ports: 60 | - "50050:50050" # Required: Teamserver 61 | volumes: 62 | - ./profiles:/opt/cobaltstrike/profiles # Required: C2 profiles 63 | environment: 64 | - TEAMSERVER_PASSWORD=your_secure_password 65 | - LICENSE_KEY=your-license-key 66 | - C2_PROFILE_NAME=your.profile 67 | restart: unless-stopped 68 | ``` 69 | 70 | ### Full Configuration with Listeners 71 | ```yaml 72 | version: "3.8" 73 | services: 74 | cobaltstrike: 75 | image: chryzsh/cobaltstrike 76 | container_name: cobaltstrike 77 | ports: 78 | - "50050:50050" # Teamserver 79 | - "443:443" # HTTPS listener 80 | - "80:80" # HTTP listener 81 | - "53:53/udp" # DNS listener 82 | volumes: 83 | - ./profiles:/opt/cobaltstrike/profiles 84 | - ./logs:/var/log/supervisor 85 | environment: 86 | # Required 87 | - TEAMSERVER_PASSWORD=your_secure_password 88 | - LICENSE_KEY=your-license-key 89 | - C2_PROFILE_NAME=your.profile 90 | # Optional Listeners 91 | - HTTPS_LISTENER_DOMAIN_NAME=example.com 92 | - HTTP_LISTENER_DOMAIN_NAME=example.com 93 | - DNS_LISTENER_DOMAIN_NAME=c2.example.com 94 | - SMB_C2_NAMED_PIPE_NAME=custom_pipe 95 | restart: unless-stopped 96 | ``` 97 | 98 | ## Volume Mounts 99 | 100 | ### Profiles Directory 101 | - **Path**: `./profiles:/opt/cobaltstrike/profiles` 102 | - **Required**: Yes 103 | - **Contents**: Your C2 profile files 104 | - **Note**: Must contain the profile specified in C2_PROFILE_NAME 105 | 106 | ### Logs Directory 107 | - **Path**: `./logs:/var/log/supervisor` 108 | - **Required**: No, but recommended 109 | - **Contents**: Supervisor and service logs 110 | - **Purpose**: Troubleshooting and monitoring 111 | 112 | ## Port Mappings 113 | 114 | ### Required Ports 115 | - **50050**: Teamserver communication 116 | - Required for client connections 117 | - Must be exposed 118 | 119 | ### Optional Ports (Listener Dependent) 120 | - **443**: HTTPS listener 121 | - **80**: HTTP listener 122 | - **53/udp**: DNS listener 123 | 124 | ## Security Considerations 125 | 126 | ### Password Security 127 | - Use strong passwords for TEAMSERVER_PASSWORD 128 | - Avoid default or easily guessable passwords 129 | - Consider using environment files instead of docker-compose.yml 130 | 131 | ### Network Security 132 | - Limit access to port 50050 to trusted networks 133 | - Consider using VPN or SSH tunnels for remote access 134 | - Use proper certificates in production for HTTPS 135 | 136 | ## Advanced Configuration 137 | 138 | ### Custom Certificates 139 | For production HTTPS listeners: 140 | 1. Mount certificate files 141 | 2. Update C2 profile accordingly 142 | 3. Consider terminating TLS at redirector 143 | 144 | ### Multiple Profiles 145 | To manage multiple profiles: 146 | 1. Place all profiles in mounted directory 147 | 2. Switch profiles by updating C2_PROFILE_NAME 148 | 3. Restart container to apply changes -------------------------------------------------------------------------------- /docs/TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting Guide 2 | 3 | ## Common Issues and Solutions 4 | 5 | ### Container Startup Issues 6 | 7 | #### Container Exits Immediately 8 | **Symptoms:** 9 | - Container stops right after starting 10 | - Repeated restart attempts 11 | 12 | **Solutions:** 13 | 1. Check environment variables: 14 | ```bash 15 | docker logs cobaltstrike | grep "ERROR:" 16 | ``` 17 | 2. Verify required files: 18 | - C2 profile exists in mounted profiles directory 19 | - License key is valid 20 | - All required environment variables are set 21 | 22 | #### Container Runs But Nothing Happens 23 | **Symptoms:** 24 | - Container appears running but no services start 25 | - No error messages in main logs 26 | 27 | **Solutions:** 28 | 1. Check Supervisor logs: 29 | ```bash 30 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/*' 31 | ``` 32 | 2. Check individual service logs: 33 | ```bash 34 | cat logs/cs_installer.out.log 35 | cat logs/teamserver.out.log 36 | cat logs/listener.out.log 37 | ``` 38 | 39 | ### Teamserver Connection Issues 40 | 41 | #### Can't Connect to Teamserver 42 | **Symptoms:** 43 | - Unable to connect via client 44 | - Connection refused errors 45 | 46 | **Solutions:** 47 | 1. Test port accessibility: 48 | ```bash 49 | nc localhost 50050 -vz 50 | ``` 51 | 2. Check Teamserver status: 52 | ```bash 53 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/teamserver*' 54 | ``` 55 | 3. Verify no port conflicts: 56 | ```bash 57 | docker ps | grep 50050 58 | ``` 59 | 60 | ### Listener Issues 61 | 62 | #### Listeners Not Starting 63 | **Symptoms:** 64 | - Teamserver running but listeners not appearing 65 | - Listener setup errors in logs 66 | 67 | **Solutions:** 68 | 1. Check listener-specific logs: 69 | ```bash 70 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/listener*' 71 | ``` 72 | 2. Verify listener environment variables: 73 | - Check format of domain names 74 | - Ensure no conflicts in port mappings 75 | 76 | #### SSL/TLS Certificate Warnings 77 | **Symptoms:** 78 | - SSL warnings in HTTPS listener 79 | - Certificate validation failures 80 | 81 | **Solutions:** 82 | 1. This is expected with default certificates 83 | 2. Use proper certificates in production 84 | 3. Consider terminating TLS at your redirector 85 | 86 | ### Profile Issues 87 | 88 | #### Profile Not Loading 89 | **Symptoms:** 90 | - Teamserver fails to start 91 | - Profile-related errors in logs 92 | 93 | **Solutions:** 94 | 1. Verify profile location: 95 | ```bash 96 | docker exec -it cobaltstrike ls -l /opt/cobaltstrike/profiles 97 | ``` 98 | 2. Check profile syntax: 99 | - Ensure valid .profile extension 100 | - Verify profile format 101 | - Check file permissions 102 | 103 | ### Log Analysis 104 | 105 | #### Checking All Logs 106 | ```bash 107 | # View all supervisor logs 108 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/*' 109 | 110 | # View specific service logs 111 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/cs_installer.out.log' 112 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/teamserver.out.log' 113 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/listener.out.log' 114 | 115 | # View process state changes 116 | docker exec -it cobaltstrike /bin/bash -c 'cat /var/log/supervisor/state.log' 117 | ``` 118 | 119 | #### Common Error Messages 120 | 121 | 1. "Environment validation failed": 122 | - Check required variables are set 123 | - Verify variable formats 124 | - Look for specific validation errors 125 | 126 | 2. "TeamServerImage not found": 127 | - Installation failed or incomplete 128 | - Check installation logs 129 | - Verify license key validity 130 | 131 | 3. "Trapped java.io.EOFException": 132 | - Normal during listener setup 133 | - Can be ignored if listeners are working 134 | 135 | 4. "Remote host terminated the handshake": 136 | - Normal for non-CS client connections 137 | - Can be ignored in logs 138 | 139 | ### Recovery Procedures 140 | 141 | #### Complete Reset 142 | If you need to start fresh: 143 | ```bash 144 | # Stop and remove container 145 | docker stop cobaltstrike 146 | docker rm cobaltstrike 147 | 148 | # Remove any persisted data 149 | rm -rf logs/* 150 | 151 | # Start fresh 152 | docker-compose up -d 153 | ``` 154 | 155 | #### Recovering from Failed Installation 156 | ```bash 157 | # Access container 158 | docker exec -it cobaltstrike /bin/bash 159 | 160 | # Check installation status 161 | ls -l /opt/cobaltstrike/server/TeamServerImage 162 | 163 | # Remove installation flag if needed 164 | rm /opt/cobaltstrike/installer_done.flag 165 | 166 | # Restart container 167 | docker restart cobaltstrike -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chryzsh/docker-cobaltstrike/7fbf3bb8d7f959bb2deefdcc6e4d93da40146b5a/image.png -------------------------------------------------------------------------------- /scripts/env_validator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Required environment variables and their validation rules 4 | declare -A ENV_VARS=( 5 | ["TEAMSERVER_PASSWORD"]="^.{8,}$" 6 | ["LICENSE_KEY"]="^[A-Za-z0-9-]+$" 7 | ) 8 | 9 | validate_env_vars() { 10 | local has_error=0 11 | 12 | # Validate required variables only 13 | for var in "${!ENV_VARS[@]}"; do 14 | if [ -z "${!var+x}" ]; then 15 | echo "ERROR: Required environment variable $var is not set" 16 | has_error=1 17 | continue 18 | fi 19 | 20 | if ! printf '%s' "${!var}" | grep -qE "${ENV_VARS[$var]}"; then 21 | echo "ERROR: Environment variable $var has invalid format" 22 | case "$var" in 23 | "TEAMSERVER_PASSWORD") 24 | echo " Password must be at least 8 characters long" 25 | ;; 26 | "LICENSE_KEY") 27 | echo " Must contain only letters, numbers, and hyphens" 28 | ;; 29 | esac 30 | has_error=1 31 | fi 32 | done 33 | 34 | return $has_error 35 | } 36 | -------------------------------------------------------------------------------- /scripts/install-teamserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Source environment validator 5 | source /opt/cobaltstrike/scripts/env_validator.sh 6 | 7 | # Default directories and files 8 | CS_DIR="/opt/cobaltstrike" 9 | LICENSE_FILE="${CS_DIR}/.cobaltstrike.license" 10 | UPDATE_SCRIPT="${CS_DIR}/update" 11 | TEAMSERVER_IMAGE="${CS_DIR}/server/TeamServerImage" 12 | 13 | # Function for retrying operations 14 | retry_operation() { 15 | local max_attempts=$1 16 | local delay=$2 17 | local command="${@:3}" 18 | local attempt=1 19 | 20 | while [ $attempt -le $max_attempts ]; do 21 | if eval "$command"; then 22 | return 0 23 | fi 24 | 25 | echo "Attempt $attempt failed. Retrying in $delay seconds..." 26 | sleep $delay 27 | ((attempt++)) 28 | done 29 | 30 | echo "Operation failed after $max_attempts attempts" 31 | return 1 32 | } 33 | 34 | # Download with retry and validation 35 | download_file() { 36 | local url=$1 37 | local output=$2 38 | local max_attempts=3 39 | local delay=5 40 | 41 | retry_operation $max_attempts $delay "curl -sSL -o $output $url" 42 | } 43 | 44 | # Cleanup function 45 | cleanup() { 46 | rm -f /tmp/cobaltstrike-dist-linux.tgz 47 | if [ $? -ne 0 ]; then 48 | echo "Installation failed, cleaning up..." 49 | fi 50 | } 51 | 52 | # Set up trap for cleanup 53 | trap cleanup EXIT 54 | 55 | # Main installation process 56 | main() { 57 | # Validate environment first 58 | if ! validate_env_vars; then 59 | echo "Environment validation failed" 60 | exit 1 61 | fi 62 | 63 | # Ensure a license file exists or use the LICENSE_KEY environment variable 64 | if [ ! -f "$LICENSE_FILE" ]; then 65 | if [ -n "$LICENSE_KEY" ]; then 66 | echo "No mounted license file found; writing LICENSE_KEY env to $LICENSE_FILE" 67 | echo "$LICENSE_KEY" >"$LICENSE_FILE" 68 | else 69 | echo "ERROR: No license file at $LICENSE_FILE and no LICENSE_KEY env var provided." 70 | exit 1 71 | fi 72 | fi 73 | 74 | # Install or update Cobalt Strike if required 75 | if [ ! -f "$TEAMSERVER_IMAGE" ]; then 76 | echo "Cobalt Strike not found; attempting to download and install..." 77 | 78 | # Retrieve the download token with retry 79 | echo "Retrieving Cobalt Strike download token..." 80 | if ! TOKEN_RESPONSE=$(retry_operation 3 5 "curl -s -X POST -d \"dlkey=\$(cat \"$LICENSE_FILE\")\" https://download.cobaltstrike.com/download"); then 81 | echo "Failed to retrieve download token" 82 | exit 1 83 | fi 84 | 85 | # Extract token and version 86 | TOKEN=$(echo "$TOKEN_RESPONSE" | grep -oP 'href="/downloads/\K[^/]+') 87 | VERSION=$(echo "$TOKEN_RESPONSE" | grep -oP 'href="/downloads/[^/]+/\K[^/]+(?=/cobaltstrike-dist-windows\.zip)') 88 | 89 | if [ -z "$TOKEN" ] || [ -z "$VERSION" ]; then 90 | echo "ERROR: Could not retrieve a valid token or version." 91 | exit 1 92 | fi 93 | 94 | # Download and extract with retry 95 | echo "Downloading cobaltstrike-dist-linux.tgz..." 96 | DOWNLOAD_URL="https://download.cobaltstrike.com/downloads/${TOKEN}/${VERSION}/cobaltstrike-dist-linux.tgz" 97 | if ! download_file "$DOWNLOAD_URL" "/tmp/cobaltstrike-dist-linux.tgz"; then 98 | echo "Failed to download Cobalt Strike" 99 | exit 1 100 | fi 101 | 102 | # Extract and verify 103 | if ! tar xzf /tmp/cobaltstrike-dist-linux.tgz -C $CS_DIR --strip-components=1; then 104 | echo "Failed to extract Cobalt Strike" 105 | exit 1 106 | fi 107 | 108 | # Run update script with retry 109 | echo "Running the update script..." 110 | if ! retry_operation 3 5 "cd $CS_DIR && echo '$(cat "$LICENSE_FILE")' | java -XX:ParallelGCThreads=4 -XX:+AggressiveHeap -XX:+UseParallelGC -jar update.jar -Type:linux"; then 111 | echo "Failed to run update script" 112 | exit 1 113 | fi 114 | fi 115 | 116 | # The final check to see if the installation was successful 117 | if [ -f "$TEAMSERVER_IMAGE" ]; then 118 | echo "Installation completed successfully." 119 | touch /opt/cobaltstrike/installer_done.flag 120 | else 121 | echo "TeamServerImage not found. Installation did not complete properly." 122 | exit 1 123 | fi 124 | } 125 | 126 | # Run main installation 127 | main 128 | -------------------------------------------------------------------------------- /scripts/start-listeners.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Source environment validator 5 | source /opt/cobaltstrike/scripts/env_validator.sh 6 | 7 | # Default directories 8 | CS_DIR="/opt/cobaltstrike" 9 | CNA_DIR="$CS_DIR/services" 10 | 11 | # Get container's primary IP 12 | TEAMSERVER_HOST=$(hostname -I | awk '{print $1}') 13 | if [ -z "$TEAMSERVER_HOST" ]; then 14 | echo "Error: Failed to determine container IP address" 15 | exit 1 16 | fi 17 | 18 | # Validate environment variables 19 | if ! validate_env_vars; then 20 | echo "Environment validation failed. Exiting." 21 | exit 1 22 | fi 23 | 24 | # Function to get environment variable value safely 25 | get_env_var() { 26 | local var_name=$1 27 | eval echo "\${$var_name:-}" 28 | } 29 | 30 | # Function to set up a listener with retry logic 31 | setup_listener() { 32 | local script_name=$1 33 | local env_var=$2 34 | local max_attempts=3 35 | local attempt=1 36 | local output_file="/var/log/supervisor/listener_${script_name}.log" 37 | local var_value=$(get_env_var "$env_var") 38 | 39 | if [ -n "$var_value" ]; then 40 | echo "Setting up $script_name listener..." 41 | 42 | while [ $attempt -le $max_attempts ]; do 43 | { 44 | # Create the CNA script from template 45 | if ! envsubst < "$CNA_DIR/$script_name.template" > "$CNA_DIR/$script_name"; then 46 | echo "Failed to create CNA script from template" 47 | return 1 48 | fi 49 | 50 | cd "$CS_DIR/client" || exit 1 51 | # Run agscript with localhost 52 | if $CS_DIR/client/agscript "$TEAMSERVER_HOST" 50050 svc "$TEAMSERVER_PASSWORD" "$CNA_DIR/$script_name"; then 53 | echo "Successfully set up $script_name listener" 54 | return 0 55 | fi 56 | 57 | echo "Attempt $attempt failed. Retrying in 5 seconds..." 58 | sleep 5 59 | ((attempt++)) 60 | } >> "$output_file" 2>&1 61 | done 62 | 63 | echo "Failed to set up $script_name listener after $max_attempts attempts" 64 | return 1 65 | else 66 | echo "Skipping $script_name listener (${env_var} not set)" 67 | return 0 68 | fi 69 | } 70 | 71 | # Array to store background process PIDs 72 | declare -a pids 73 | 74 | # Start listeners in parallel 75 | echo "Starting listeners in parallel..." 76 | 77 | # DNS Listener 78 | setup_listener "dns-listener.cna" "DNS_LISTENER_DOMAIN_NAME" & 79 | pids+=($!) 80 | 81 | # HTTPS Listener 82 | setup_listener "https-listener.cna" "HTTPS_LISTENER_DOMAIN_NAME" & 83 | pids+=($!) 84 | 85 | # HTTP Listener 86 | setup_listener "http-listener.cna" "HTTP_LISTENER_DOMAIN_NAME" & 87 | pids+=($!) 88 | 89 | # SMB Listener 90 | setup_listener "smb-listener.cna" "SMB_LISTENER_NAMED_PIPE_NAME" & 91 | pids+=($!) 92 | 93 | # Wait for all background processes and check their exit status 94 | failed_listeners=0 95 | for pid in "${pids[@]}"; do 96 | if ! wait $pid; then 97 | ((failed_listeners++)) 98 | fi 99 | done 100 | 101 | # Report final status 102 | if [ $failed_listeners -eq 0 ]; then 103 | echo "All listeners were set up successfully" 104 | exit 0 105 | else 106 | echo "ERROR: $failed_listeners listener(s) failed to set up properly" 107 | echo "Check individual listener logs in /var/log/supervisor/ for details" 108 | exit 1 109 | fi 110 | -------------------------------------------------------------------------------- /scripts/start-teamserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Source environment validator 5 | source /opt/cobaltstrike/scripts/env_validator.sh 6 | 7 | # Default directories and files 8 | CS_DIR="/opt/cobaltstrike" 9 | LICENSE_FILE="${CS_DIR}/.cobaltstrike.license" 10 | UPDATE_SCRIPT="${CS_DIR}/update" 11 | TEAMSERVER_SCRIPT="${CS_DIR}/server/teamserver" 12 | TEAMSERVER_IMAGE="${CS_DIR}/server/TeamServerImage" 13 | PROFILE_DIR="$CS_DIR/profiles" 14 | 15 | # Get container's primary IP 16 | TEAMSERVER_HOST=$(hostname -I | awk '{print $1}') 17 | if [ -z "$TEAMSERVER_HOST" ]; then 18 | echo "Error: Failed to determine container IP address" 19 | exit 1 20 | fi 21 | 22 | # Validate environment variables 23 | if ! validate_env_vars; then 24 | echo "Environment validation failed. Exiting." 25 | exit 1 26 | fi 27 | 28 | # Function to get first C2 profile 29 | verify_c2_profile() { 30 | if [ ! -d "$PROFILE_DIR" ]; then 31 | echo "Error: Profile directory $PROFILE_DIR does not exist. Mount your profiles directory to the container." 32 | return 1 33 | fi 34 | 35 | # Find first .profile file 36 | local profile_path=$(find "$PROFILE_DIR" -maxdepth 1 -type f -name "*.profile" | head -n 1) 37 | if [ -z "$profile_path" ]; then 38 | echo "Error: No .profile files found in $PROFILE_DIR" 39 | return 1 40 | fi 41 | 42 | echo "$profile_path" 43 | return 0 44 | } 45 | 46 | # Function to verify teamserver prerequisites 47 | verify_prerequisites() { 48 | # Check for TeamServerImage 49 | if [ ! -f "$TEAMSERVER_IMAGE" ]; then 50 | echo "Error: TeamServerImage not found at $TEAMSERVER_IMAGE" 51 | return 1 52 | fi 53 | 54 | # Check for teamserver script 55 | if [ ! -f "$TEAMSERVER_SCRIPT" ]; then 56 | echo "Error: Teamserver script not found at $TEAMSERVER_SCRIPT" 57 | return 1 58 | fi 59 | 60 | # Check for license file 61 | if [ ! -f "$LICENSE_FILE" ]; then 62 | echo "Error: License file not found at $LICENSE_FILE" 63 | return 1 64 | fi 65 | 66 | return 0 67 | } 68 | 69 | # Main function 70 | main() { 71 | echo "Verifying teamserver prerequisites..." 72 | if ! verify_prerequisites; then 73 | echo "Failed to verify prerequisites" 74 | exit 1 75 | fi 76 | 77 | echo "Verifying C2 profile..." 78 | PROFILE_PATH=$(verify_c2_profile) 79 | if [ $? -ne 0 ]; then 80 | echo "Failed to verify C2 profile" 81 | exit 1 82 | fi 83 | echo "Using C2 profile: $PROFILE_PATH" 84 | 85 | # Launch the teamserver 86 | echo "Starting Cobalt Strike Teamserver..." 87 | echo "Host: $TEAMSERVER_HOST" 88 | echo "Profile: $PROFILE_PATH" 89 | 90 | cd "$CS_DIR/server" || exit 1 91 | 92 | # Execute teamserver with proper error handling 93 | if ! exec "$TEAMSERVER_SCRIPT" \ 94 | "$TEAMSERVER_HOST" \ 95 | "$TEAMSERVER_PASSWORD" \ 96 | "$PROFILE_PATH"; then 97 | echo "Failed to start teamserver" 98 | exit 1 99 | fi 100 | } 101 | 102 | # Run main function 103 | main 104 | -------------------------------------------------------------------------------- /scripts/state_monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Read STDIN in a loop for supervisor events 4 | while read line; do 5 | # Write event to state log 6 | echo "$(date '+%Y-%m-%d %H:%M:%S') - $line" >>/var/log/supervisor/state.log 7 | 8 | # Parse the event 9 | if echo "$line" | grep -q "PROCESS_STATE_EXITED"; then 10 | process_name=$(echo "$line" | awk '{print $3}') 11 | exit_code=$(echo "$line" | awk '{print $5}') 12 | 13 | # Log process exit 14 | echo "Process $process_name exited with code $exit_code" >>/var/log/supervisor/state.log 15 | 16 | # Handle specific process failures 17 | case "$process_name" in 18 | cs_installer) 19 | if [ "$exit_code" != "0" ]; then 20 | echo "FATAL: Installer failed with code $exit_code" >>/var/log/supervisor/state.log 21 | kill -15 $(cat /var/run/supervisord.pid) 22 | fi 23 | ;; 24 | teamserver) 25 | if [ "$exit_code" != "0" ]; then 26 | echo "ERROR: Teamserver failed with code $exit_code" >>/var/log/supervisor/state.log 27 | fi 28 | ;; 29 | listener) 30 | if [ "$exit_code" != "0" ]; then 31 | echo "ERROR: Listener failed with code $exit_code" >>/var/log/supervisor/state.log 32 | fi 33 | ;; 34 | esac 35 | fi 36 | 37 | # Echo back OK to supervisor 38 | echo "READY" 39 | done 40 | -------------------------------------------------------------------------------- /services/dns-listener.cna.template: -------------------------------------------------------------------------------- 1 | println("dns-listener.cna: Creating DNS Listener..."); 2 | on ready { 3 | listener_create_ext( 4 | "DNS", 5 | "windows/beacon_dns/reverse_dns_txt", 6 | %( 7 | beacons => "${DNS_LISTENER_DOMAIN_NAME}", 8 | host => "${DNS_LISTENER_STAGER_DOMAIN_NAME}", 9 | port => 53, 10 | bindto => 53, 11 | strategy => "round-robin", 12 | maxretry => "exit-10-5-5m" 13 | ) 14 | ); 15 | println("DNS Listener created."); 16 | sleep(1000); 17 | 18 | println("Closing client now."); 19 | closeClient(); 20 | } -------------------------------------------------------------------------------- /services/http-listener.cna.template: -------------------------------------------------------------------------------- 1 | println("cs-http-listener.cna: Creating HTTP Listener..."); 2 | on ready { 3 | listener_create_ext( 4 | "HTTP", 5 | "windows/beacon_http/reverse_http", 6 | %( 7 | host => "${HTTP_LISTENER_DOMAIN_NAME}", 8 | port => 80, 9 | beacons => "${HTTP_LISTENER_DOMAIN_NAME}", 10 | althost => "${HTTP_LISTENER_DOMAIN_NAME}", 11 | bindto => 80, 12 | strategy => "round-robin", 13 | maxretry => "exit-10-5-5m" 14 | ) 15 | ); 16 | println("HTTP Listener created."); 17 | println("HTTPS Listener created."); 18 | sleep(1000); 19 | 20 | println("Closing client now."); 21 | closeClient(); 22 | } -------------------------------------------------------------------------------- /services/https-listener.cna.template: -------------------------------------------------------------------------------- 1 | println("https-listener.cna: Creating HTTPS Listener..."); 2 | on ready { 3 | listener_create_ext( 4 | "HTTPS", 5 | "windows/beacon_https/reverse_https", 6 | %( 7 | host => "${HTTPS_LISTENER_DOMAIN_NAME}", 8 | port => 443, 9 | beacons => "${HTTPS_LISTENER_DOMAIN_NAME}", 10 | althost => "${HTTPS_LISTENER_DOMAIN_NAME}", 11 | bindto => 443, 12 | strategy => "round-robin", 13 | maxretry => "exit-10-5-5m" 14 | ) 15 | ); 16 | println("HTTPS Listener created."); 17 | sleep(1000); 18 | 19 | println("Closing client now."); 20 | closeClient(); 21 | } -------------------------------------------------------------------------------- /services/smb-listener.cna.template: -------------------------------------------------------------------------------- 1 | println("cs-smb-listener.cna: Creating SMB Listener..."); 2 | on ready { 3 | listener_create_ext( 4 | "SMB", 5 | "windows/beacon_bind_pipe", 6 | %( 7 | port => "${SMB_LISTENER_NAMED_PIPE_NAME}" 8 | ) 9 | ); 10 | println("SMB Listener created."); 11 | println("Script execution completed. Waiting 3s before closing..."); 12 | sleep(3000); 13 | 14 | println("Closing client now."); 15 | closeClient(); 16 | } -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/var/log/supervisor/supervisord.log 4 | pidfile=/var/run/supervisord.pid 5 | childlogdir=/var/log/supervisor 6 | 7 | [program:cs_installer] 8 | command=/opt/cobaltstrike/scripts/install-teamserver.sh 9 | priority=1 10 | autostart=true 11 | autorestart=unexpected 12 | startretries=3 13 | exitcodes=0 14 | stdout_logfile=/var/log/supervisor/cs_installer.out.log 15 | stderr_logfile=/var/log/supervisor/cs_installer.err.log 16 | 17 | [program:teamserver] 18 | command=/bin/bash -c 'while [ ! -f /opt/cobaltstrike/installer_done.flag ]; do sleep 5; done; /opt/cobaltstrike/scripts/start-teamserver.sh' 19 | priority=2 20 | autostart=true 21 | autorestart=true 22 | startretries=3 23 | startsecs=10 24 | stopwaitsecs=10 25 | stdout_logfile=/var/log/supervisor/teamserver.out.log 26 | stderr_logfile=/var/log/supervisor/teamserver.err.log 27 | 28 | [program:listener] 29 | command=/bin/bash -c 'while ! nc -z localhost 50050; do sleep 5; done; /opt/cobaltstrike/scripts/start-listeners.sh' 30 | priority=3 31 | autostart=true 32 | autorestart=unexpected 33 | startretries=3 34 | startsecs=10 35 | stopwaitsecs=10 36 | stdout_logfile=/var/log/supervisor/listener.out.log 37 | stderr_logfile=/var/log/supervisor/listener.err.log 38 | 39 | [eventlistener:state_monitor] 40 | command=/opt/cobaltstrike/scripts/state_monitor.sh 41 | events=PROCESS_STATE 42 | buffer_size=100 --------------------------------------------------------------------------------