DigitalOcean Red Team Automation Part 1

Introduction

The ability to automate your offensive security infrastructure is an important DevOps skill for any red team operator, so in this post, I am going to go over the steps required to automate your own red team infrastructure using Cobalt Strike, DigitalOcean, and Terraform.

Red Team Automation Diagram

I will be building the red team infrastructure seen in the above image, which will require obtaining an Access Token from each API I will need to access.  I am going to assume the reader already knows how to obtain Access Tokens from the respective providers, but in case you don’t, refer to the links below for the following services:

The next step would be to ensure all the prerequisites are taken care of so I can start with the build configuration.

Prerequisites

Install Terraform

Before I start with building out the Terraform configuration, I am going to make sure Terraform is installed on my host machine.  I am using Arch Linux as my host operating system, so these are the steps I took to set up Terraform:

pacman -S unzip --noconfirm
wget https://releases.hashicorp.com/terraform/0.12.18/terraform_0.12.18_linux_amd64.zip
unzip terraform_0.12.18_linux_amd64.zip
sudo mv terraform /usr/local/bin/
terraform --version

SSH Key Generation

Since the above infrastructure has 4 droplets (or machines), I will be generating 4 different SSH Keys, 1 for each droplet.

ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-teamserver_rsa -q -N ""
ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-cf-redirector_rsa -q -N ""
ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-dns-redirector_rsa -q -N ""
ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-https-redirector_rsa -q -N ""

File Structure

Now that Terraform is installed, I want to setup the file structure for this project.  I used the following commands to set up the file structure I will be using:

cd ~/C2/Automation
mkdir -p ./bootstrap/{files,scripts}
mkdir ssh-keys
cp /opt/cobaltstrike.tar.gz ./bootstrap/files/
touch ./bootstrap/scripts/cf-server-setup.sh
touch ./bootstrap/scripts/dns-server-setup.sh
touch ./bootstrap/scripts/https-server-setup.sh
touch ./bootstrap/scripts/teamserver-setup.sh
cp ~/.ssh/c2-teamserver_rsa.pub ./ssh-keys/teamserver.pub
cp ~/.ssh/c2-cf-redirector_rsa.pub ./ssh-keys/cf-server.pub
cp ~/.ssh/c2-dns-redirector_rsa.pub ./ssh-keys/dns-server.pub
cp ~/.ssh/c2-https-redirector_rsa.pub ./ssh-keys/https-server.pub
touch {providers.tf,variables.tf,do-resources.tf,terraform.tfvars}
tree ./
./
├── bootstrap
│   ├── files
│   │   └── cobaltstrike.tar.gz
│   └── scripts
│       ├── cf-server-setup.sh
│       ├── dns-server-setup.sh
│       ├── https-server-setup.sh
│       └── teamserver-setup.sh
├── do-resources.tf
├── providers.tf
├── ssh-keys
│   ├── cf-server.pub
│   ├── dns-server.pub
│   ├── https-server.pub
│   └── teamserver.pub
├── terraform.tfvars
└── variables.tf

4 directories, 13 files

I created 2 root level folders bootstrap and ssh-keys.  Within the bootstrap folder, I created 2 more folders files and scripts.  The files folder will contain any files I want to move from my host machine over to the DigitalOcean droplet. The scripts folder will contain any scripts I want to run on the droplet after it has been created.  Back on the root level, the ssh-keys folder contains 1 ssh public key for each droplet being created, with a total of 4 files.

Droplet Configuration

The next step I am going to take in this process is setting up the droplets within DigitalOcean. Each droplet (or machine) will be created in a different region within the DigitalOcean network to create some network segregation. For this red team infrastructure, I will be creating 4 different droplets using Terraform and the DigitalOcean API.

Droplets

  • Team Server – Ubuntu 18.04 LTS – 4GB RAM – 2 vCPUs – 50GB SSD
  • DNS Redirector – Ubuntu 16.04 LTS – 1GB RAM – 1 vCPU – 20GB SSD
  • HTTP/S Redirector – Ubuntu 16.04 LTS – 1GB RAM – 1 vCPU – 20GB SSD
  • CloudFront Redirector – Ubuntu 16.04 LTS – 1GB RAM – 1 vCPU – 20GB SSD

Terraform Files

For the droplets creation, I will be creating the following files to be used with Terraform:

  • variables.tf
  • providers.tf
  • do-firewalls.tf
  • do-resources.tf
  • terraform.tfvars

I will be adding more content (and more files) as I continue through the build, but for droplet creation we can focus on the files listed above.

variables.tf

The variables.tf file tells Terraform which variables are expected to be set before starting the build.

# variables.tf

variable "do-token" {
  type = string
  description = "DigitalOcean Access Token"
}

variable "do-teamserver-pubkey" {
  type = string
  description = "Teamserver SSH Public Key"
}

variable "do-cf-redirector-pubkey" {
  type = string
  description = "CloudFront Redirector SSH Public Key"
}

variable "do-dns-redirector-pubkey" {
  type = string
  description = "DNS Redirector SSH Public Key"
}

variable "do-http-redirector-pubkey" {
  type = string
  description = "HTTP/S Redirector SSH Public Key"
}

In the file above, I have added a variable for the DigitalOcean Access Token, and a variable to store the ssh public key for each droplet I am going to create.

providers.tf

The providers.tf file tells Terraform which provider is responsible for understanding API interactions and exposing resources.

# providers.tf 

provider "digitalocean" {
    version = "~> 1.11" 
    token = var.do-token 
}

terraform.tfvars

The terraform.tfvars file tells Terraform which variables to set at runtime.

# terraform.tfvars 

do-token = "<your Access Token here>" 

do-teamserver-pubkey = "/ssh-keys/teamserver.pub" 
do-cf-redirector-pubkey = "/ssh-keys/cf-server.pub" 
do-dns-redirector-pubkey = "/ssh-keys/dns-server.pub" 
do-http-redirector-pubkey = "/ssh-keys/https-server.pub"

do-firewalls.tf

The do-firewalls.tf file contains the configuration for the 3 firewalls I will be setting up within DigitalOcean.

# do-firewalls.tf

resource "digitalocean_firewall" "webfw" {
  name = "webfw"
  droplet_ids = [
    digitalocean_droplet.cf-redirector.id,
    digitalocean_droplet.https-redirector.id
  ]
  inbound_rule {
    protocol         = "tcp"
    port_range       = "65522"
    source_addresses = [var.operator_ip]
  }
  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0"]
  }
  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol         = "tcp"
    port_range       = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol         = "udp"
    port_range       = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol         = "icmp"
    destination_addresses = ["0.0.0.0/0"]
  }
}
resource "digitalocean_firewall" "dnsfw" {
  name = "dnsfw"
  droplet_ids = [
    digitalocean_droplet.dns-redirector.id
  ]
  inbound_rule {
    protocol = "tcp"
    port_range = "65522"
    source_addresses = [var.operator_ip]
  }
  inbound_rule {
    protocol = "udp"
    port_range = "53"
    source_addresses = ["0.0.0.0/0"]
  }
  inbound_rule {
    protocol = "tcp"
    port_range = "53"
    source_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol = "tcp"
    port_range = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol = "udp"
    port_range = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol = "icmp"
    destination_addresses = ["0.0.0.0/0"]
  }
}

resource "digitalocean_firewall" "teamfw" {
  name = "teamfw"
  droplet_ids = [
    digitalocean_droplet.teamserver.id
  ]
  inbound_rule {
    protocol = "tcp"
    port_range = "65522"
    source_addresses = [var.operator_ip]
  }
  inbound_rule {
    protocol = "tcp"
    port_range = "50050"
    source_addresses = [var.operator_ip]
  }
  inbound_rule {
    protocol = "udp"
    port_range = "1-65535"
    source_addresses = [digitalocean_droplet.dns-redirector.ipv4_address]
  }
  inbound_rule {
    protocol = "tcp"
    port_range = "53"
    source_addresses = [digitalocean_droplet.dns-redirector.ipv4_address]
  }
  inbound_rule {
    protocol = "tcp"
    port_range = "80"
    source_addresses = [
      digitalocean_droplet.cf-redirector.ipv4_address,
      digitalocean_droplet.https-redirector.ipv4_address
    ]
  }
  outbound_rule {
    protocol = "tcp"
    port_range = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol = "udp"
    port_range = "1-65535"
    destination_addresses = ["0.0.0.0/0"]
  }
  outbound_rule {
    protocol = "icmp"
    destination_addresses = ["0.0.0.0/0"]
  }
}

do-resources.tf

This file will contain all the configuration needed to interact with the DigitalOcean API to create 4 droplets (or machines), group all droplets into 1 project within the DigitalOcean dashboard, setup the SSH keys for each droplet, and then connect to each droplet and run the bootstrap shell script.

# do-resources.tf

# DigitalOcean SSH Key Configuration
#
resource "digitalocean_ssh_key" "teamserver" {
  name       = "C2 Team Server SSH Key"
  public_key = file("${path.module}${var.do-teamserver-pubkey}")
}
resource "digitalocean_ssh_key" "cf-redirector" {
  name       = "CloudFront Redirector SSH Key"
  public_key = file("${path.module}${var.do-cf-redirector-pubkey}")
}
resource "digitalocean_ssh_key" "https-redirector" {
  name       = "HTTP/S Redirector SSH Key"
  public_key = file("${path.module}${var.do-http-redirector-pubkey}")
}
resource "digitalocean_ssh_key" "dns-redirector" {
  name       = "DNS Redirector SSH Key"
  public_key = file("${path.module}${var.do-dns-redirector-pubkey}")
}


# DigitalOcean Droplet Creation
#
resource "digitalocean_droplet" "teamserver" {
  image  = "ubuntu-18-04-x64"
  name   = "teamserver"
  region = "nyc3"
  size   = "s-2vcpu-4gb"
  ssh_keys = [digitalocean_ssh_key.teamserver.fingerprint]
  connection {
    host = digitalocean_droplet.teamserver.ipv4_address
    type = "ssh"
    user = "root"
    private_key = file("~/.ssh/c2-teamserver_rsa")
  }
  provisioner "file" {
    source      = "bootstrap/scripts/teamserver-setup.sh"
    destination = "/tmp/setup.sh"
  }
  provisioner "file" {
    source      = "bootstrap/files/cobaltstrike.tar.gz"
    destination = "/opt/cobaltstrike.tar.gz"
  }
  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/setup.sh && bash /tmp/setup.sh"
    ]
  }
}

resource "digitalocean_droplet" "dns-redirector" {
  image  = "ubuntu-16-04-x64"
  name   = "dns-redirector"
  region = "fra1"
  size   = "s-1vcpu-1gb"
  ssh_keys = [digitalocean_ssh_key.dns-redirector.fingerprint]
  depends_on = [digitalocean_droplet.teamserver]
  connection {
    host = digitalocean_droplet.dns-redirector.ipv4_address
    type = "ssh"
    user = "root"
    private_key = file("~/.ssh/c2-dns-redirector_rsa")
  }
  provisioner "file" {
    source      = "bootstrap/scripts/dns-server-setup.sh"
    destination = "/tmp/setup.sh"
  }
  provisioner "remote-exec" {
    inline = [
      "echo ${digitalocean_droplet.teamserver.ipv4_address} > /root/teamserver_ip.txt",
      "chmod +x /tmp/setup.sh && bash /tmp/setup.sh"
    ]
  }
}

resource "digitalocean_droplet" "https-redirector" {
  image  = "ubuntu-16-04-x64"
  name   = "https-redirector"
  region = "lon1"
  size   = "s-1vcpu-1gb"
  ssh_keys = [digitalocean_ssh_key.https-redirector.fingerprint]
  depends_on = [digitalocean_droplet.teamserver]
  connection {
    host = digitalocean_droplet.https-redirector.ipv4_address
    type = "ssh"
    user = "root"
    private_key = file("~/.ssh/c2-https-redirector_rsa")
  }
  provisioner "file" {
    source      = "bootstrap/scripts/https-server-setup.sh"
    destination = "/tmp/setup.sh"
  }
  provisioner "file" {
    source = "bootstrap/cron/certificate.sh"
    destination = "/tmp/cert.sh"
  }
  provisioner "remote-exec" {
    inline = [
      "echo ${digitalocean_droplet.https-redirector.ipv4_address} > /root/ip_address.txt",
      "echo ${digitalocean_droplet.teamserver.ipv4_address} > /root/teamserver_ip.txt",
      "echo ${var.cf-subdomain-api}.${var.cf-domain} > /root/domain.txt",
      "chmod +x /tmp/setup.sh && bash /tmp/setup.sh",
      "sleep 3"
    ]
  }
}

resource "digitalocean_droplet" "cf-redirector" {
  image = "ubuntu-16-04-x64"
  name = "cf-redirector"
  region = "tor1"
  size = "s-1vcpu-1gb"
  ssh_keys = [digitalocean_ssh_key.cf-redirector.fingerprint]
  depends_on = [digitalocean_droplet.teamserver]
  connection {
    host = digitalocean_droplet.cf-redirector.ipv4_address
    type = "ssh"
    user = "root"
    private_key = file("~/.ssh/c2-cf-redirector_rsa")
  }
  provisioner "file" {
    source = "bootstrap/scripts/cf-server-setup.sh"
    destination = "/tmp/setup.sh"
  }
  provisioner "file" {
    source = "bootstrap/cron/certificate.sh"
    destination = "/tmp/cert.sh"
  }
  provisioner "remote-exec" {
    inline = [
      "echo ${digitalocean_droplet.cf-redirector.ipv4_address} > /root/ip_address.txt",
      "echo ${digitalocean_droplet.teamserver.ipv4_address} > /root/teamserver_ip.txt",
      "echo ${var.cf-subdomain-cf}.${var.cf-domain} > /root/domain.txt",
      "chmod +x /tmp/setup.sh && bash /tmp/setup.sh",
      "sleep 3"
    ]
  }
}

# DigitalOcean Project
#
resource "digitalocean_project" "C2-Automation" {
  name        = "C2 Automation"
  description = "C2 Infrastructure Automation"
  purpose     = "C2 Infrastructure"
  environment = "Development"
  depends_on = [
    digitalocean_droplet.teamserver,
    digitalocean_droplet.cf-redirector,
    digitalocean_droplet.dns-redirector,
    digitalocean_droplet.https-redirector
  ]
  resources   = [
    digitalocean_droplet.teamserver.urn,
    digitalocean_droplet.cf-redirector.urn,
    digitalocean_droplet.dns-redirector.urn,
    digitalocean_droplet.https-redirector.urn
  ]
}


# Output variables
#
output "teamserver-ip" {
  value = digitalocean_droplet.teamserver.ipv4_address
}
output "cf-redirector-ip" {
  value = digitalocean_droplet.cf-redirector.ipv4_address
}
output "dns-redirector-ip" {
  value = digitalocean_droplet.dns-redirector.ipv4_address
}
output "https-redirector-ip" {
  value = digitalocean_droplet.https-redirector.ipv4_address
}

Shell Scripts

During the file structure setup done as part of the prerequisites above, I created 4 shell scripts within the bootstrap/scripts folder to run as each droplet (or machine) had completed its setup.

  • teamserver-setup.sh
  • cf-server-setup.sh
  • dns-server-setup.sh
  • https-server-setup.sh

The Terraform resource file above allows for a droplet (or machine) to have its own shell script to run after creation.  I am going to create a bootstrap shell script to configure each machine after its creation. This will allow me to fully automate the package installation and configuration of each machine deployed.

teamserver-setup.sh

This file will run once the teamserver droplet has been created.  This script will need to automate the installation of the Java JDK, and the CobaltStrike package that will be copied from the host machine as stated in the file provisioner code within do-resources.tf.

#!/usr/bin/env bash

export DEBIAN_FRONTEND=noninteractive
apt update
apt upgrade -yq
apt install screen -yq
systemctl disable systemd-resolved
systemctl stop systemd-resolved
rm /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
sed -i 's/#Port/Port/g' /etc/ssh/sshd_config
sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config
cd /opt && \
wget https://files-cdn.liferay.com/mirrors/download.oracle.com/otn-pub/java/jdk/8u121-b13/jdk-8u121-linux-x64.tar.gz
tar -zxvf jdk-8u121-linux-x64.tar.gz
echo "export PATH=$PATH:/opt/jdk1.8.0_121/bin" >> /root/.bashrc
source /root/.bashrc
export PATH=$PATH:/opt/jdk1.8.0_121/bin
tar -zxvf /opt/cobaltstrike.tar.gz -C /opt
cd /opt/cobaltstrike3.14 && nohup ./teamserver "$(cat /root/ip_address.txt)" "$(cat /root/password.txt)" &
systemctl restart ssh
sleep 2

cf-server-setup.sh

This file will run once the CloudFront redirector droplet has been created.  This script will need to automate the installation of Apache2 and Let’s Encrypt.

#!/usr/bin/env bash

source /root/.bashrc
export DEBIAN_FRONTEND=noninteractive
export PUBLIC_IP=""
PUBLIC_IP=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
apt update && apt full-upgrade -yq
rm /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
sed -i 's/#Port/Port/g' /etc/ssh/sshd_config
sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config
yes | add-apt-repository ppa:certbot/certbot
apt update
apt install apache2 -yq
apt install certbot -yq
a2enmod ssl rewrite proxy proxy_http
a2ensite default-ssl.conf
sed -i ':a;$!{N;ba};s/AllowOverride None/'"AllowOverride All"'/3' /etc/apache2/apache2.conf
systemctl stop apache2
systemctl reload ssh

https-server-setup.sh

This file will run once the HTTP/S redirector droplet has been created.  This script will need to automate the installation of Apache2 and Let’s Encrypt.

#!/usr/bin/env bash

source /root/.bashrc
export DEBIAN_FRONTEND=noninteractive
export PUBLIC_IP=""
PUBLIC_IP=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
apt update && apt full-upgrade -yq
rm /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
sed -i 's/#Port/Port/g' /etc/ssh/sshd_config
sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config
yes | add-apt-repository ppa:certbot/certbot
apt update
apt install apache2 -yq
apt install certbot -yq
a2enmod ssl rewrite proxy proxy_http
a2ensite default-ssl.conf
sed -i ':a;$!{N;ba};s/AllowOverride None/'"AllowOverride All"'/3' /etc/apache2/apache2.conf
systemctl stop apache2
systemctl reload ssh

dns-server-setup.sh

This file will run once the DNS redirector droplet has been created.  This script will need to redirect DNS UDP / TCP traffic to the teamserver.

#!/usr/bin/env bash

export DEBIAN_FRONTEND=noninteractive
TEAMSERVER=$(cat /root/teamserver_ip.txt)
apt update
apt upgrade -yq
rm /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
sed -i 's/#Port/Port/g' /etc/ssh/sshd_config
sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config
systemctl restart ssh
iptables -I INPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination "${TEAMSERVER}":53
iptables -t nat -A POSTROUTING -j MASQUERADE
iptables -I FORWARD -j ACCEPT
iptables -P FORWARD ACCEPT
sysctl net.ipv4.ip_forward=1

At this point I have the Terraform configuration to spin up 4 droplets (or machines) each in a different region, and to then run the bootstrap shell scripts associated with the specific droplet.  However, this is only the start of the infrastructure build, but its a good stopping point until part 2.

Coming Up

In Part 2, I will automate the C2 Cloudflare DNS configuration and add some DNS propagation logic to the bootstrap shell scripts so that I can automate the generation of SSL certificates with Let’s Encrypt.