Production-Ready GoPhish with NGINX, MySQL, and Docker

Andrew Long
The Startup
Published in
7 min readApr 3, 2020

Are you here because your boss told you they want to try this whole phishing simulation thing out? Are you trying to scale-up your current GoPhish setup and running into weird certificate issues as you add new domains? Did you feel like that last question was oddly specific? Well, good news — I don’t care, and I’m also going to show you how I like to setup GoPhish on Docker with an NGINX reverse proxy.

Why would I ever want to do this?

That’s a great question, I’m so glad you asked it without being prompted. Well, maybe it’s one of those things I said in the beginning. Or maybe you want to be able to easily reproduce/transport your phishing sim setup. In my case, a client really needed better security in front of GoPhish. For some reason, the GoPhish server kept getting flagged for certificate mismatches. It’s not actually a mystery, GoPhish wasn’t really meant to hotswap a bunch of valid certs around for your dozen phishing domains, and that’s ok. There’s a lot that it does really well, this isn’t one of those things.

Let’s see what we need do to make sure that a target never sees this dreaded roadblock:

I’ve been under a rock for the last decade, what is NGINX?

First of all, you said it wrong. Think “engine X”. If you said it correctly, just pass the aggression onto the next person that says ‘expresso’. NGINX is a webserver, much like Apache (but not at all), that is heavily specialized at being the best damn reverse proxy you could hope for.

But what is a reverse proxy? It’s essentially an intermediary filter that acts as the crossing guard for all of your web traffic. It will listen on the ports you specify, typically 80 and 443, and figure out where to route the traffic based on rules you specify (domain name, directory, etc…). On a high level, our setup will work something like this:

I tried to make the colors uglier, but this is the best I could do

Connections come in from the great unknown (the fart cloud on the left), and hit the ports opened up for traffic on the host. In our setup, the NGINX docker container has two forwarded ports (80 and 443) that are accepting connections. Using a local address that cannot be reached from outside of the host, NGINX is forwarding traffic to GoPhish which is listening on an internal port of our choice. GoPhish then responds, and that traffic is forwarded back through NGINX and out to the client.

This arrangement gives us a couple of nice things:

1. The ability to handle multiple valid certificates and serve them based on the domain being requested.

2. The security of NGINX, which can act as a WAF if configured properly.

Building the NGINX container

So, let’s start with this piece, and build out from there. I’m going to use examples for an Ubuntu host throughout this article, and I don’t care if you cry. If you haven’t installed docker yet, go here and come back after. Kick off your NGINX container with the following command:

docker run --name reverse_proxy -dit -p 80:80 -p 443:443 nginx

This will start the container, named reverse_proxy , with ports 80/443 exposed to the outside world. When you hit the host’s ip at one of those ports, you’ll be talking to NGINX. Next, let’s get into this container with the following command:

docker exec -it reverse_proxy bash

This will give you a shell into the container. We’ll need to grab Certbot now, because LetsEncrypt is amazing and will do a ton of legwork for us. Run the following in the shell:

sudo apt update
sudo apt install python3-acme python3-certbot python3-mock python3-openssl python3-pkg-resources python3-pyparsing python3-zope.interface
sudo apt install python3-certbot-nginx

For anyone experienced with docker — yes, we could’ve done this with a Dockerfile. If you’re going to push a few of these out, that’s a great idea. For this case, let’s go the manual route for the sake of brevity. So anyway, you now have a functioning NGINX server capable of issuing SSL certs. Let’s make one!

Generating SSL certificates

First, let’s create a new config named yourwebsitename_com.conf (or whatever your domain is called) inside /etc/nginx/conf.d/ and put the following code inside:

server{
listen 80;
server_name yourwebsitename.com www.yourwebsitename.com;
location / {
proxy_pass http://172.17.0.2:80;
}
}

And then restart NGINX: service nginx restart. Note: the proxy_pass directive should be set to the local IP of the GoPhish server. I’ll cover how to manually set the IP later, check this link out for more information.

When you run the NGINX restart command, the shell will close because this container restarts when the NGINX service restarts. Get back into the shell and run the following:

certbot --nginx

Follow the on-screen prompts that will ask which domain you want to setup SSL certs for, along with some other important options. When asked if you’d like to redirect HTTP, HTTPS Only, etc…, choose to redirect HTTP to HTTPS. Certbot will generate the SSL cert and also modify the NGINX config to redirect HTTP -> HTTPS while preserving your internal routing. Restart NGINX again, and you should be rolling. You can test the config from within the container with nginx -t .

Setting up GoPhish

We’re going to cheat a little and pull the latest pre-built GoPhish docker image to quickstart this process a bit. We’ll want to have a predictable IP address, since NGINX has to know where to send traffic, and this time we do not want to expose any ports on the host machine. Run the following command to get GoPhish rolling:

docker run -dit --ip 172.17.0.2 -p 3333:3333 --name gophish_prod gophish/gophish

Now GoPhish is running, with the phisher server only exposed in the virtual docker network (172.17.0.1/24), and reachable only by other programs/containers locally. I’ve exposed port 3333 so you can still reach the admin UI by hitting the ip:port directly. If you’ve used GoPhish in the past, you might be wondering why I haven’t even looked at config.json yet. Well, here’s what comes with the GoPhish docker image:

{
"admin_server": {
"listen_url": "0.0.0.0:3333",
"use_tls": true,
"cert_path": "gophish_admin.crt",
"key_path": "gophish_admin.key"
},
"phish_server": {
"listen_url": "0.0.0.0:80",
"use_tls": false,
"cert_path": "example.crt",
"key_path": "example.key"
},
"db_name": "sqlite3",
"db_path": "gophish.db",
"migrations_prefix": "db/db_",
"contact_address": "",
"logging": {
"filename": "",
"level": ""
}
}

Looks like we have the admin interface running on 3333, and the phish server on port 80. I cheated and read ahead, so there’s nothing you’ll have to change to get to the admin interface or allow targets to hit the phish server. To use MySQL, instead of the sqlite default, run the following commands from the host:

docker exec gophish_prod sed -i 's|sqlite3|mysql|g' config.jsondocker exec gophish_prod sed -i 's|gophish.db|gophish:password@(172.17.0.3:3306)/gophish?charset=utf8&parseTime=True&loc=UTC|g' config.json

The first command switches the database type to mysql, the second provides the credentials and host address for the database. The format is:

username:password@(host:port)/database?options

Setting up MySQL

Now let’s setup the database. GoPhish doesn’t play nice with MySQL out of the box, the way it handles dates isn’t the way MySQL wants by default. Luckily, it’s just a matter of setting a few options. To get the database container running, do the following:

docker run -dit --ip 172.17.0.3 -e MYSQL_ROOT_PASSWORD=password /
--name gophish_db mysql

Now, get into the database, using the root password you just set:

docker exec gophish_db mysql -u root -p

and run the following:

mysql> CREATE USER gophish@'172.17.0.2' IDENTIFIED BY 'password'mysql> CREATE DATABASE gophish CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;mysql> GRANT ALL PRIVILEGES ON gophish.* TO gophish@'172.17.0.2' WITH GRANT OPTION;mysql> SET GLOBAL CONFIG

Now, exit mysql and let’s alter the mysql config to allow invalid dates, because MySQL doesn’t like how we’re going to be saving campaign dates:

docker exec gophish_db echo / 
'[mysqld]\n
sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' >> /etc/mysql/my.cnf /
&& service mysql restart

You now have a MySQL database ready to accept connections from your GoPhish instance. Now let’s get GoPhish restarted and running with MySQL:

docker restart gophish_prod

You did it!

time to ruin some coworker’s day

Now go brag to your boss that you figured it all out by yourself. In case you need some more justification for why you just did these additional steps, here you go:

About the author

Andrew is a CyberSecurity Engineer and Software Developer specializing in Phishing Simulation. When he’s not writing articles, you can catch him on Twitter or Linkedin.

--

--

Andrew Long
The Startup

Principal Product Security Engineer @ Flock Saftey. Avid security researcher, dedicated father, and nerdy analog electronics collector.