├── tasks ├── main.yml ├── letsencrypt.yml └── apache.yml ├── handlers └── main.yaml ├── templates ├── apache_vhost.conf.j2 └── apache_sslvhost.conf.j2 └── README.md /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: "apache.yml" 3 | - include: "letsencrypt.yml" -------------------------------------------------------------------------------- /handlers/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart apache2 3 | service: name=apache2 state=restarted 4 | -------------------------------------------------------------------------------- /tasks/letsencrypt.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install letsencrypt 3 | apt: name=letsencrypt update_cache=yes state=latest 4 | 5 | - name: add repository for ubuntu 6 | apt_repository: 7 | repo: 'ppa:certbot/certbot' 8 | state: present 9 | when: ansible_distribution == 'Ubuntu' 10 | 11 | - name: Install python-certbot-apache 12 | apt: name=python-certbot-apache update_cache=yes state=latest 13 | 14 | - name: run letsencrypt 15 | command: certbot --installer apache --authenticator standalone --pre-hook "service apache2 stop" --post-hook "service apache2 start" --agree-tos --email {{le_email}} --expand -n -q -d {{ vhosts|map(attribute='servername')| join(',') |quote}} 16 | ignore_errors: yes 17 | 18 | -------------------------------------------------------------------------------- /tasks/apache.yml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | 4 | - name: Install Apache web server 5 | apt: name=apache2 update_cache=yes state=latest 6 | 7 | - name: create https virtual host files, one per servername 8 | template: src=apache_sslvhost.conf.j2 dest=/etc/apache2/sites-available/rtssl_{{ item.servername }}.conf 9 | with_items: "{{ vhosts }}" 10 | 11 | - name: create http virtual host files, one per servername 12 | template: src=apache_vhost.conf.j2 dest=/etc/apache2/sites-available/rt_{{ item.servername }}.conf 13 | with_items: "{{ vhosts }}" 14 | 15 | 16 | 17 | 18 | - name: Install php 19 | apt: name=php update_cache=yes state=latest 20 | ignore_errors: true 21 | 22 | 23 | - name: Install mod php 24 | apt: name=libapache2-mod-php update_cache=yes state=latest 25 | ignore_errors: true 26 | 27 | 28 | 29 | 30 | - apache2_module: name=headers state=present 31 | - apache2_module: name=rewrite state=present 32 | - apache2_module: name=proxy state=present 33 | - apache2_module: name=ssl state=present 34 | - apache2_module: name=proxy_http state=present 35 | - apache2_module: name=cache state=absent 36 | - apache2_module: name=php7.0 state=present 37 | - apache2_module: name=deflate state=absent force=yes 38 | 39 | 40 | - name: copy hop folder 41 | copy: 42 | src: "{{ hop_dir }}/" 43 | dest: /var/www/html/ 44 | when: hop_dir is defined 45 | 46 | - name: disable shite sites 47 | command: a2dissite * 48 | 49 | 50 | - name: enable the http sites 51 | command: a2ensite rt_{{ item.servername }} 52 | with_items: "{{ vhosts }}" 53 | notify: 54 | - restart apache2 55 | 56 | - name: enable the https sites 57 | command: a2ensite rtssl_{{ item.servername }} 58 | with_items: "{{ vhosts }}" 59 | notify: 60 | - restart apache2 61 | 62 | 63 | - service: name=apache2 state=restarted 64 | 65 | 66 | -------------------------------------------------------------------------------- /templates/apache_vhost.conf.j2: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | 4 | 5 | Options Indexes FollowSymLinks 6 | AllowOverride All 7 | Require all granted 8 | 9 | 10 | ProxyPreserveHost On 11 | 12 | ServerName {{ item.servername }} 13 | DocumentRoot /var/www/html 14 | # The ServerName directive sets the request scheme, hostname and port that 15 | # the server uses to identify itself. This is used when creating 16 | # redirection URLs. In the context of virtual hosts, the ServerName 17 | # specifies what hostname must appear in the request's Host: header to 18 | # match this virtual host. For the default virtual host (this file) this 19 | # value is not decisive as it is used as a last resort host regardless. 20 | # However, you must set it for any further virtual host explicitly. 21 | #ServerName www.example.com 22 | 23 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 24 | # error, crit, alert, emerg. 25 | # It is also possible to configure the loglevel for particular 26 | # modules, e.g. 27 | #LogLevel info ssl:warn 28 | ErrorLog ${APACHE_LOG_DIR}/error.log 29 | CustomLog ${APACHE_LOG_DIR}/access.log combined 30 | # For most configuration files from conf-available/, which are 31 | # enabled or disabled at a global level, it is possible to 32 | # include a line for only one particular virtual host. For example the 33 | # following line enables the CGI configuration for this host only 34 | # after it has been globally disabled with "a2disconf". 35 | #Include conf-available/serve-cgi-bin.conf 36 | 37 | 38 | {% if item.config_files is defined %} 39 | {% for config_file in item.config_files %} 40 | {{ lookup('file', config_file) }} 41 | {% endfor %} 42 | {% endif %} 43 | 44 | {% if item.pre_configs is defined %} 45 | {% for config in item.pre_configs %} 46 | {{ config}} 47 | {% endfor %} 48 | {% endif %} 49 | 50 | 51 | {% if item.c2filters is defined %} 52 | {% for c in item.c2filters %} 53 | RewriteCond %{REQUEST_URI} {{ c.rewritefilter }} 54 | RewriteRule ^.*$ https://{{ c.host }}%{REQUEST_URI} [P,NE] 55 | {% endfor %} 56 | {% endif %} 57 | 58 | {% if item.configs is defined %} 59 | {% for config in item.configs %} 60 | {{ config}} 61 | {% endfor %} 62 | {% endif %} 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /templates/apache_sslvhost.conf.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Options Indexes FollowSymLinks 7 | AllowOverride All 8 | Require all granted 9 | 10 | 11 | ProxyPreserveHost On 12 | RewriteEngine On 13 | 14 | SSLEngine On 15 | SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem 16 | SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key 17 | 18 | 19 | SSLProxyEngine On 20 | SSLProxyVerify none 21 | SSLProxyCheckPeerCN off 22 | SSLProxyCheckPeerName off 23 | SSLProxyCheckPeerExpire off 24 | ServerName {{ item.servername }} 25 | DocumentRoot /var/www/html 26 | # The ServerName directive sets the request scheme, hostname and port that 27 | # the server uses to identify itself. This is used when creating 28 | # redirection URLs. In the context of virtual hosts, the ServerName 29 | # specifies what hostname must appear in the request's Host: header to 30 | # match this virtual host. For the default virtual host (this file) this 31 | # value is not decisive as it is used as a last resort host regardless. 32 | # However, you must set it for any further virtual host explicitly. 33 | #ServerName www.example.com 34 | 35 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 36 | # error, crit, alert, emerg. 37 | # It is also possible to configure the loglevel for particular 38 | # modules, e.g. 39 | #LogLevel info ssl:warn 40 | ErrorLog ${APACHE_LOG_DIR}/error.log 41 | CustomLog ${APACHE_LOG_DIR}/access.log combined 42 | # For most configuration files from conf-available/, which are 43 | # enabled or disabled at a global level, it is possible to 44 | # include a line for only one particular virtual host. For example the 45 | # following line enables the CGI configuration for this host only 46 | # after it has been globally disabled with "a2disconf". 47 | #Include conf-available/serve-cgi-bin.conf 48 | 49 | 50 | {% if item.config_files is defined %} 51 | {% for config_file in item.config_files %} 52 | {{ lookup('file', config_file) }} 53 | {% endfor %} 54 | {% endif %} 55 | 56 | 57 | {% if item.pre_configs is defined %} 58 | {% for config in item.pre_configs %} 59 | {{ config}} 60 | {% endfor %} 61 | {% endif %} 62 | 63 | {% if item.c2filters is defined %} 64 | {% for c in item.c2filters %} 65 | RewriteCond %{REQUEST_URI} {{ c.rewritefilter }} 66 | RewriteRule ^.*$ https://{{ c.host }}%{REQUEST_URI} [P,NE] 67 | {% endfor %} 68 | {% endif %} 69 | 70 | {% if item.configs is defined %} 71 | {% for config in item.configs %} 72 | {{ config}} 73 | {% endfor %} 74 | {% endif %} 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ansible role that allows for quickly deploying a redirector to an existing server with mod_rewrite proxy rules, 2 | 3 | Supports Debian and Ubuntu, tested in Digital ocean and Azure 4 | 5 | See threat.tevora.com/automating-redirector-deployment-with-ansible for a blog walking through redirectors, ansible, and a deep dive on this role 6 | 7 | To get started, clone this repo, install ansible, and place this repo in your roles folder. See sample playbook below for an example of how to build your redirector instance config 8 | 9 | **Instance Playbook Sample provision_redirector_example.yml** 10 | ```language-yaml 11 | - hosts: EnigmaticEmu 12 | gather_facts: False 13 | user: root 14 | pre_tasks: 15 | - name: Install python for Ansible 16 | raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal) 17 | changed_when: False 18 | - setup: # aka gather_facts 19 | tasks: 20 | - include_role: 21 | name: redirectors 22 | vars: 23 | le_email: 'threat@tevora.com' 24 | hop_dir: hops/empire_hop 25 | vhosts: [ 26 | { 27 | servername: 'fakeamazon.com', 28 | http_port: 80, 29 | https_port: 443, 30 | c2filters: [ 31 | { 32 | rewritefilter: '^/orders/track/?$', 33 | host: '123.124.125.126' 34 | } 35 | ], 36 | configs: [ 37 | 'RewriteRule !\.php$ https://www.amazon.com/%{REQUEST_URI} [L,R=302]' 38 | ] 39 | }, 40 | { 41 | servername: 'fakegoogle.com', 42 | http_port: 80, 43 | https_port: 443, 44 | config_files: [ 45 | "redirectors.txt", 46 | "apache_tweaks.conf" 47 | ], 48 | } 49 | ] 50 | ``` 51 | 52 | Breakdown of the example: 53 | 54 | * `- hosts: EnigmaticEmu` 55 | This specifies the hosts the playbook will execute on. 56 | For this playbook to execute correclty, an inventory file must be used with the hostname or ip of engigmatic emu defined 57 | 58 | * `gather_facts: False` 59 | The ansible gather facts task breaks on newer versions of ubuntu with no python 2 installed 60 | we disable gather facts, so we can make sure to install python 2 first. This is just some compatability bootstrap stuff 61 | 62 | * ` user: root` 63 | this specifies what user to logon to the remote server with, ansible performs all its configurations over ssh 64 | Change this to whatever user you want to use 65 | *if you need to sudo, may need to add `become: true` and call ansible-playbook with `--ask-become-pass` 66 | 67 | * `pre_tasks:` 68 | * these lines install python 2 if it is not there and run gather facts 69 | ` tasks: ` Now we've reached the good part, our playbook tasks! 70 | 71 | * `- include_role:` 72 | as you may imagine, this specfies a role to include, and the next line `name: redirectors` specifies to include our redirectors role 73 | 74 | * `vars:` 75 | here is where we do the meat of our config 76 | Our server contains three vars,le_email, hop_dir, and vhosts 77 | Le email is used to specify the email address we use for lets_encrypt, be sure to use one you control 78 | 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 79 | 80 | * `hosts: [...]` 81 | 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. 82 | 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 83 | 84 | * `servername: 'fakeamazon.com'` 85 | this is the hostname of our server, and you MUST have a dns record for this hostname pointing at the IP of the server. 86 | 87 | * `http_port` and `https_port` are pretty self explanatory. Choose what ports http and https will listen on 88 | 89 | * 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 90 | * `c2filters: [...]` 91 | a list of c2filter dicts 92 | Any request whose URI matches `rewritefilter` will proxy the connection to `host` 93 | 94 | * `configs[...]` 95 | a list of config strings 96 | Each string will be added to the file. 97 | 98 | * `config_files[...]` filename or path 99 | list of filenames or paths. The content of each file will be added to aache 100 | 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 101 | 102 | 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. 103 | 104 | Notice in the config how there are multiple vhosts, and each one can use one or more methods of specifying how it is injecting into the configuration templates 105 | 106 | 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. 107 | 108 | **Running the Playbook** 109 | 110 | To run the role. Create your playbook in the form of the example we covered and run `ansible-playbook -i `. 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: 111 | ``` 112 | ├── my_playbook.yml 113 | ├── files 114 | │   └── empire_hop 115 | │   └── news 116 | │   └── login.php 117 | └── roles 118 | ├── redirectors 119 | │   ├── files 120 | │   ├── handlers 121 | │   │   └── main.yaml 122 | │   ├── meta 123 | │   ├── tasks 124 | │   │   ├── apache.yml 125 | │   │   ├── letsencrypt.yml 126 | │   │   └── main.yml 127 | │   ├── templates 128 | │   │   ├── apache_sslvhost.conf.j2 129 | │   │   └── apache_vhost.conf.j2 130 | │   └── vars 131 | ``` 132 | --------------------------------------------------------------------------------