October 27, 2017

RTOps: Automating Redirector Deployment With Ansible


Reading Time: 20mins

This blog will cover what redirectors are, why they are important for red teams, and how to automate their deployment with Ansible. This is the first post in our RTOps blog series, and will serve as a jumping off point for further redirector strategies, and Red Team infrastructure automation

TLDR: If you just want to leverage the ansible role this blog post builds, you can clone it from here: https://github.com/tevora-threat/rt_redirectors

What are redirectors

Redirectors sit in front of our actual C2 servers, and securely proxy command and control traffic from the target back to our C2 listeners. These prevent the client from being able to see our actual C2, and should be easy to spin up and tear down.

Redirectors come in many forms, from hop.php applications popularized by Metasploit and Empire, to something as simple as socat

We are going to focus on deploying a C2 using hop.php AND/OR mod_rewrite, allowing the redirectors to support Empire and Cobalt agents with the following features:

  • Proxy traffic to our C2 servers
  • Redirect or proxy unwanted traffic away from our c2 servers
  • Have legimate publicly signed certificates with communication over https

To meet all these goals, Apache with mod_rewrite is an excellent choice.

Redirecting using mod_rewrite

Mod_rewrite is a great component of Apache that allows us to transparently proxy or redirect requests based on regular expression matches. This could, for example, allow our evil ‘amazonss.com’ site redirect or proxy traffic to ‘amazon.com’, making it appear legit, unless traffic matching our C2 hits the server, in which case mod_rewrite will transparently proxy the request to our C2 servers.

@bluescreenofjeff has an awesome writeup on how to do this. Rather than cover what Jeff has already, this blog will go over how to deploy redirectors using his method, and a few tweaks to work better with ansible and lets-encrypt.

Automating Redirector Deployment with Ansible

To deploy our redirectors, we will be using Ansible, a configuration management tool comparable to puppet, chef, and fabric. Ansible is nice because is it agentless and works completely over SSH. We are introducing ansible now as we will be baking many of our commands and configurations into Ansible configs, so will be covering both redirector setup and Ansible features as we go.

Ansible Primer

If you already know ansible, feel free to skip this section. For those that don’t, this section is mostly guidance on which portions of Ansible’s documentation you need to read or reference to understand the configurations we go over in this post.


Ansible uses config files in YAML format to specify actions it takes on remote servers. These configurations are called ‘playbooks’.

Playbooks can define variables, specify which ‘hosts’ to execute on, what tasks to run, and more. See Ansible’s excellent documentation on the subject: http://docs.ansible.com/ansible/latest/playbooks_intro.html#basic. The most important component of a playbook is it’s task list.

Running a Playbook

The command ansible-playbook can be used to execute a specified playbook, and the -i flag of that command specifies which ‘inventory’ file to run the playbook against: http://docs.ansible.com/ansible/latest/intro_inventory.html


For organized re-usability of playbooks, Ansible offers a ‘roles’ construct, which allows us to organize multiple playbooks into a folder structure that can be re-used by other top level playbooks. http://ansible-docs.readthedocs.io/zh/stable-2.0/rst/playbooks_roles.html

We leverage this in our automation by creating a redirector role, which consists of all the legwork to spin up and configure a redirector. Our playbooks to deploy specific redirectors largely consist of variables to be passed to this role. This allows us to separate the configuration of a unique redirector instance from the general process of setting up a redirector.

If you don’t fully follow the documentation for ansible roles, don’t worry. Most of this post will go through creating the redirector role, so you can learn by example.

Ansible Redirector Role


Alright, we are almost ready to dive into creating our Ansible Role to facilitate redirector provisioning. First, lets walk through some formal requirememts of our role so we know what tasks we need to implement:

  • Apache Installation: We need to make sure we have apache installed with the proper modules
  • Apache Configuration: We need to setup configurations for our redirectors. We can use vhosts for per domain configuration
  • Copy Hop.php File(s) to Server: We need to get our hop.php file to the server if using that method of redirection.
  • Redirector Rules: We need to install the redirector rules. We can do this in our apache config’s vhost section instead of .htaccess files
  • Lets Encrypt Installation: We need to install lets encrypt on the target servers and python-certbot-apache
  • Lets Encrypt Configuration: We will want let’s encrypt to setup legit public certs for our sites.

Ok, we’ve got a pretty decent list to get started on. Lets start making our role.

Role Structure

Roles use folder structure to determine certain functionaility. Let’s lay out a folder structure for our redirectors role:

├── files
├── handlers
├── tasks
├── templates
└── vars

We wont need all these folders for our role, but it is good to layout the initial structure before we start work. Each folder has special meaning in the context of a role. Most importantly, the tasks folder will contain all the tasks we want to run. When you import or include a role in a playbook, it will execute the file main.yml in the tasks folder.

We could put all our tasks in that main.yml file, but it would be better top break them out into multiple files for readability and re-use. Looking at our requirements list, we can see our tasks break down into two general categories: setting up apache, and setting up lets config.

Lets create three yml files: apache.yml, letsencrypt.yml, and main.yml.

Role folder setup

├── files
├── handlers
├── meta
├── tasks
│   ├── apache.yml
│   ├── letsencrypt.yml
│   └── main.yml
├── templates
└── vars

We can ‘include’ the apache and letsencrypt files in main.yml, allowing them to be run when we use the redirectors role.


- include: "apache.yml"
- include: "letsencrypt.yml"

Right now both apache.yml, and letsencrypt.yml are empty, so using the role will not do anything. However, we now are ready to start implementing our tasks in those two files.

Setup Apache


Installing modules in ansible is a cinch. To keep it even simpler, we will just be supporting debian based systems with this role. We can leverage the ‘apt’ task to make sure apache and required dependencies are installed on our target server.


- name: Install Apache web server
  apt: name=apache2 update_cache=yes state=latest
- name: Install php
  apt: name=php update_cache=yes state=latest

- name: Install mod php
  apt: name=libapache2-mod-php update_cache=yes state=latest

Our apache.yml file now will install apache, php, and libapache2-mod-php.

Next, we will append the following to our apache.yml to enable the headers, rewrite, proxy, ssl, proxy_http, and php7.0 mods. We will also disable mod_deflate and mod_cache to ensure our redirector doesn’t encode or otherwise process our C2 traffic in undesireable ways.
apache.yml continued

- apache2_module: name=headers state=present
- apache2_module: name=rewrite state=present
- apache2_module: name=proxy state=present
- apache2_module: name=ssl state=present
- apache2_module: name=proxy_http state=present
- apache2_module: name=cache state=absent
- apache2_module: name=php7.0 state=present
- apache2_module: name=deflate state=absent force=yes

And like that, we have a task list that installs and sets all the necessary apache modules for us!


Now we can move on to configuration. We’ll start with the an easy one, copying over the hop.php file(s) to the target server. We simply use ansible’s ‘copy’ task

apache.yml continued

- name: copy hop folder
    src: "{{ hop_dir }}/"
    dest: /var/webroot
  when: hop_dir is defined

Now when we have the hop_dir variable defined in our redirector instance playbook, it will copy the contents of that directory on our local host into the root of our site.

Before we go any further, we need to cover how variables will be layed out in our instance playbooks, and how they interact with our role. Let’s do that by walking through a completed playbook that will setup our redirector instance using our redirectors role.

Instance Playbook Sample provision_redirector_example.yml

- hosts: EnigmaticEmu
  gather_facts: False
  user: root
  - name: Install python for Ansible
    raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
    changed_when: False
  - setup: # aka gather_facts 
  - include_role:
      name: redirectors
      le_email: 'threat@tevora.com'
      hop_dir: hops/empire_hop
      vhosts: [
          servername: 'fakeamazon.com',
          http_port: 80,
          https_port: 443,
          c2filters: [
              rewritefilter: '^/orders/track/?$',
              host: ''
          configs: [ 
               'RewriteRule !\.php$ https://www.amazon.com/%{REQUEST_URI} [L,R=302]'
          servername: 'fakegoogle.com',
          http_port: 80,
          https_port: 443,
          config_files: [

Ok that is a lot of YAML, let’s break it down

  • - hosts: EnigmaticEmu
  • This specifies the hosts the playbook will execute on.
  • For this playbook to execute correctly, an inventory file must be used with the hostname or ip of enigmatic emu defined
  • example


  • gather_facts: False
  • The ansible gather facts task breaks on newer versions of Ubuntu with no python 2 installed
  • we disable gather facts, so we can make sure to install python 2 first. This is just some compatibility bootstrap stuff
  • user: root
  • this specifies what user to logon to the remote server with, ansible performs all its configurations over ssh
  • Change this to whatever user you want to use
  • if you need to sudo, may need to add become: true and call ansible-playbook with --ask-become-pass
  • pre_tasks:
    • these lines install python 2 if it is not there and run gather facts
  • tasks: Now we’ve reached the good part, our playbook tasks!
  • - include_role:
  • as you may imagine, this specfies a role to include, and the next line name: redirectors specifies to include our redirectors role
  • vars:
  • here is where we do the meat of our config
  • Our server contains three vars,le_email, hop_dir, and vhosts
  • Le email is used to specify the email address we use for lets_encrypt, be sure to use one you control
  • remember the hop_dir we used in the copy task of our role? Here is where it gets defined! The hop dir should located in ./files/ relative to the location of the playbook itself
  • hosts: [...]
  • our vhosts variable is a list of vhost config dictionaires. This allows us to setup multiple vhosts with different domains and C2 profiles per server.
  • Remember that this variable is a list, as it will be important in how we template our config files and other ways we use these variables in our redirectors role
  • servername: 'fakeamazon.com'
  • this is the hostname of our server, and you MUST have a dns record for this hostname pointing at the IP of the server.
  • http_port and https_port are pretty self explanatory. Choose what ports http and https will listen on
  • notice before we go on we have multiple ways to define mod rewrite rules in the config. This is largely due to experimentation, and plans to integrate this playbook with a python API
  • c2filters: [...]
  • a list of c2filter dicts
  • Any request whose URI matches rewritefilter will proxy the connection to host
  • configs[...]
  • a list of config strings
  • Each string will be added to the file.
  • config_files[...] filename or path
  • list of filenames or paths. The content of each file will be added to aache
  • usefull for leveraging mod_rewrite automation tool output such as @Inspired-Secs awesome tool: https://blog.inspired-sec.com/archive/2017/04/17/Mod-Rewrite-Automatic-Setup.html

OK, that was a lot to cover, but hopefully this config file and layout is making sense. We formatted this config mostly in JSON (YAML is a superset of JSON) but you can format it however you like as long as it matches up.

Notice in the config how there are multiple vhosts, and each one can use one or more methods of specifying the config

Back to the Role: VHOST Configs

With a schema decided for our redirector Role variables, we can move back to developing the role.

Now, we will set up our vhost configs. This is a bit more complicated than our previous tasks as we will need to not only add a task but include templates to define the config files.

These template files will allow us to build most of the config file, and dynamically replace portions with it based on our ansible variables.

First, lets create the tasks to instantiate the templates and copy them to the server.

apache.yml snippet

- name: create https virtual host files, one per servername
  template: src=apache_sslvhost.conf.j2 dest=/etc/apache2/sites-available/rtssl_{{ item.servername }}.conf
  with_items: "{{ vhosts }}"

- name: create http virtual host files, one per server name
  template: src=apache_vhost.conf.j2 dest=/etc/apache2/sites-available/rt_{{ item.servername }}.conf
  with_items: "{{ vhosts }}"

These tasks:

  1. take in the specified template files
  2. for EACH vhost in vhosts, do the following:
    a. pass them the ‘vhost’ variable which will be named ‘item’ in the template
    b. Run the Jinga 2 templating engine
    c. Copy the output to the apache directory on the server with the filname prefixed rt_ for http sites, rtssl_ for https sites and appended with the servername for the vhost.

The reason we create one configuration file per vhost is that Letsencrypt, specifically the certbot-apache component, does not support more than one vhost per config file. Because of this we will be provisioning multiple configuration files to the server.

Now lets create the SSL and HTTP templates. Make the template files and place them in your templates folder so that your directory tree should now look like:

├── files
├── handlers
│   └── main.yaml
├── meta
├── tasks
│   ├── apache.yml
│   ├── letsencrypt.yml
│   └── main.yml
├── templates
│   ├── apache_sslvhost.conf.j2
│   └── apache_vhost.conf.j2
└── vars

Now we can create Jinga2 templates to create our apache config files. See: https://github.com/tevora-threat/rt_redirectors/tree/master/templates

Notice how the templates use the variables we defined!

apache.yml continued

- name: disable shite sites
  command: a2dissite *

- name: enable the http sites
  command: a2ensite rt_{{ item.servername }}
  with_items: "{{ vhosts }}"
  - restart apache2

- name: enable the https sites
  command: a2ensite rtssl_{{ item.servername }}
  with_items: "{{ vhosts }}"
  - restart apache2

These tasks will disable all existing sites, to prevent having more than 1 vhost per config file and breaking lets encrypt. Then, all the red team sites will be enabled. Boom, we now have our apache sever stood up with all the proper configs!

Your final apache.yml should look like:https://github.com/tevora-threat/rt_redirectors/blob/master/tasks/apache.yml


Now… Let’s encrypt!

The lets encrypt yml file is pretty simple:

- name: Install letsencrypt
  apt: name=letsencrypt update_cache=yes state=latest

- name: add repository for ubuntu
    repo: 'ppa:certbot/certbot' 
    state: present
  when: ansible_distribution == 'Ubuntu'

- name: Install python-certbot-apache
  apt: name=python-certbot-apache update_cache=yes state=latest

- name: run letsencrypt
  command: letsencrypt --apache --agree-tos --email {{le_email}} --expand -n -q -d {{ vhosts|map(attribute='servername')| join(',') |quote}}
  ignore_errors: yes

The first few commands just install the needed requirements, and lastly, we run lets-encrypt! If you havent used let’s encrypt, 1st off you’ve been missing out, and 2nd off, go RTFM 🙂 : http://manpages.ubuntu.com/manpages/xenial/man1/letsencrypt.1.html.

We are using some hacky inline jinja templating here: {{vhosts|map(attribute='servername')| join(',') |quote}} to take the servername of every vhost, and transform it into a comma seperated list for the letsencrypt command to take. see: http://ansible-docs.readthedocs.io/zh/stable-2.0/rst/playbooks_filters.html

Ok, thats it! We’ve done it! We’ve developed a role that allows us to quickly deploy a redirector to an existing server!

Next steps are to run the role. Create your playbook in the form of the example we covered and run ansible-playbook -i <your_hosts_file> <your_playbook>. Ensure that this roles is in the roles directory in the same path of your playbook, and your hop and/or config files are placed correctly. Your directory layout should look like:

├── my_playbook.yml
├── files
│   └── empire_hop
│       └── news
│           └── login.php
└── roles
    ├── redirectors
    │   ├── files
    │   ├── handlers
    │   │   └── main.yaml
    │   ├── meta
    │   ├── tasks
    │   │   ├── apache.yml
    │   │   ├── letsencrypt.yml
    │   │   └── main.yml
    │   ├── templates
    │   │   ├── apache_sslvhost.conf.j2
    │   │   └── apache_vhost.conf.j2
    │   └── vars

Closing Thoughts

This redirector configuration automation is just a basis for our red team infrastructure automation. By building on this substrate, we can build powerful tools to bring up C2 servers quickly and effectively. Next post we’ll discuss how we can automate the server creation itself and provision these redirectors based on dynamic Terraform state files. See you then.

Discover in-depth compliance resources and featured events