🔓 Hack the Shed

Drink all the booze; hack all the things.

2019-06-19

#security #owasp

"Has anyone ever participated in a Capture-the-flag style hacking event? Does anyone fancy trying one?"

So, fairly innocuously, began what turned out to be a pretty interesting event.

We recently ran a Capture the Flag (CTF) event at my workplace. For anyone unfamiliar with the concept (which, in all fairness, was the position of many of the participants), there are a few variants of CTF events but the essential premise is this: teams compete to find specific, intended vulnerabilities in a software application. Once found, the application presents the team with a code (the eponymous "flag") which gains the team points. At the end of the competition, the team with the most points wins. Simple, no?

The application in this instance was the OWASP Juice Shop, a truly sublime piece of work, created by Bjoern Kimminich. The site presents itself as a "a small online shop which sells - surprise! - fruit & vegetable juice and associated products". In reality, it's an immensely well-crafted presentation of potential (and all-too-real) security vulnerabilities.

OWASP Juice Shop

Setup

Getting an instance of the Juice Shop up and running is straightforward enough—it's easily deployable as a Heroku Element, or you can get it running with Docker in a single command:

docker run --rm -p 3000:3000 bkimminich/juice-shop

That'll give you a fully-functioning version of the Juice Shop at which you can hack away to your heart's content. However, I wanted to keep the competition private and although Heroku does allow for restricting access, it comes at a cost. No, I was going to run this myself…

Deployment

Although the documentation is available for free on the web, it's also available for purchase on Leanpub. In the hope that some of the money makes it back to the contributors, I'd highly recommend acquiring a copy.

By default, the application effectively runs in a "standalone" mode, allowing users to find the various vulnerabilities but can also be run in CTF mode, presenting unique CTF flag codes upon discovery, again well documented.

For the purposes of the competition, there are two components: the Juice Shop itself (one per team) plus a scoreboard.

The scoreboard serves a multitude of purposes: handling registrations for each of the users (and their corresponding teams), offering a centralised point in which flags can be collated and tracked and, most importantly, presenting the scores for everyone to see.

There are two main scoreboards covered in the documentation: CTFd and Facebook's fbctf.

Spoilers: I opted for the former. The latter, although it has a nice aesthetic, presented far too many problems during setup while the former "just worked".

So my plan was this:

Easy enough, right?

The Virtual Machine

I opted for a Virtual Machine hosted by DigitalOcean (sorry, a droplet) running Debian Stretch (because Debian) and decided to run everything using Docker (because DevOps).

For the purposes of locking the machine down during setup, I used Uncomplicated Firewall to restrict access from anywhere except the office:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from ${OFFICE_IP} to ssh

The Juice Shop(s)

It's worth noting that the Juice Shop does allow for customisation—everything from logos to shop items and such. However, frankly I didn't want to detract from the beauty of the thing—it really is a marvel, the level of effort which has been applied to the project.

In order to generate unique keys for each of the flags (lest some enterprising participant use a code they happened upon elsewhere…) we first generated a random string:

CTF_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)

Finally, for each instance of the Juice Shop that we needed (two, in our case), we launch a Docker container for each:

docker run -d \
    --name juice-shop-ctf \
    --env "CTF_KEY=${CTF_KEY}" \
    --env "NODE_ENV=ctf" \
    --publish ${JUICE_SHOP_PORT}:3000 \
    bkimminich/juice-shop

Note that each ran on a different port on the host (specified via JUICE_SHOP_PORT above) and we passed in the CTF_KEY value generated earlier.

CTFd

Running an instance of the scoreboard was, thankfully, not much more difficult:

git clone https://github.com/CTFd/CTFd.git
cd CTFd/

Once we had a copy of the codebase there was only one further step before launching—including the random key we generated earlier. In the cloned folder, we need to edit the docker-compose.yml file to include a SECRET_KEY value:

version: '2'

services:
  ctfd:
    …
    environment:
      - SECRET_KEY=${CTF_KEY}
      …

The CTF_KEY above was replaced with the actual generated value. Once that single line was added, the instance was ready to launch.

As hinted in the above step, CTFd uses docker-compose to handle the various services which are needed, the installation instructions for which are nicely straightforward. Once installed…

docker-compose up -d

DNS

With two Juice Shop instances and a scoreboard running, it was time to make them accessible.

For reference, we had two teams: Anomynoush (an acknowledgement of the noted hacktivist collective and a visiting dignitary) and H4ck t3h P14n3t! (which, if you don't get the reference: shame on you!). So we would need three subdomains:

Creating the various subdomains for each of the services was a matter of creating DNS A records—the exact steps for this will vary depending on with whom your domain is registered. In this instance, the domain was registered with Namecheap and their documentation is decent enough, albeit specific to their interface.

HTTPS

Naturally we're going to make everything accessible over HTTPS and that means certificates. And frankly, few services have done more for the proliferation of HTTPS than Let's Encrypt.

In this case, I used Certbot to facilitate the creation of each certificate. There are a few different methods and various plugins but the steps that worked for me were:

sudo apt install certbot python-certbot-nginx
sudo ufw allow 80
sudo certbot --nginx -d ${CTF_HOST}
sudo ufw delete allow 80

Yes, it involved temporarily opening up port 80 in the firewall, but it worked. Once that was repeated for each of the hostnames (represented by CTF_HOST above), I just needed something to route traffic to the containers.

NGINX

NGINX is awesome. In this case, it's providing a reverse-proxy, handling routing of incoming requests to the corresponding containers.

Again, DigitalOcean have excellent documentation on the setup but here's the setup for one of the Juice Shop instances:

server {
    server_name h4ckt3hp14n3t.example.com;
    listen 443 ssl http2;
    client_max_body_size 1M;

    add_header X-Clacks-Overhead "GNU Terry Pratchett";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; connect-src 'self'";

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Forwarded-Protocol $scheme;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_connect_timeout 300s;
        proxy_read_timeout 300s;
        proxy_pass http://localhost:3001/;
    }

    ssl on;
    ssl_certificate /etc/letsencrypt/live/h4ckt3hp14n3t.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/h4ckt3hp14n3t.example.com/privkey.pem;

    ssl_session_timeout 5m;
    ssl_session_cache  shared:SSL:10m;

    ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;
    ssl_protocols TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
}

The important things to note here are:

Once each of the sites are configured as per the above, the final step is to tell the firewall to allow incoming connections:

sudo ufw allow from ${OFFICE_IP} to any port https

Setting Up the Scoreboard

Importing the Flags

The first stage in setting up the CTFd instance is adding the various flags from the Juice Shop. Thankfully, there's a nice tool, juice-shop-ctf-cli created for just this purpose. There's some configuration but I created a ctfd.yaml file with the various options:

ctfFramework: CTFd 2.x
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: Kjrcvim78J5MLR6a7zoaSO73LZJoSWDB
countryMapping: https://raw.githubusercontent.com/bkimminich/juice-shop/master/config/fbctf.yml
insertHints: paid
insertHintUrls: paid

After which you can use the juice-shop-ctf-cli:

npm install -g juice-shop-ctf-cli
juice-shop-ctf --config ctfd.yaml

This creates a ZIP file which can be uploaded to CTFd's Admin. section.

Users & Teams

CTFd allows for running competitions in either a user- or team-based mode. I wanted people to be grouped into teams with each person having their own login.

This is where I ran into issues.

The first step was to set up an app. password on my personal Gmail account. Once configured, I manually created accounts for each of the participants—and CTFd kindly forwarded the login details via email to each individual—and added the two teams.

At which point I couldn't figure out for the life of me how to add users to those teams. Users can create teams and then pass the details to other users to join but no one could seemingly join the one's I'd created.

After calmly reading the documentation and in no way panicking because it was lunch time and the competition was only a few hours away, I ended up nuking the CTFd instance.

And then it got weird…

After recreating the CTFd instance all my users were still there, as were the flags. But I'd removed the containers entirely—what was happening?! It turns out that CTFd keeps everything in local directory—.data—and that was persisting following the deletion.

So no, I wasn't losing my mind; but I was running out of time…

By this point I'd pretty much given up. I'm sure the documentation covers it somewhere but I still couldn't put my newly-created users in newly-created teams. So I cheated. Using some of the credentials I'd already minted for two of the users (one from each team), I created the team using that user. Then manually logged in as each user and joined the right team.

Running the Event

Beforehand

On the day of the competition, I made a quick presentation earlier in the day, hoping to cover the running order and answer any queries (rather than doing everything 5 minutes before we started):

Start: 16:00
gather teams,
grab beers

Stop: 18:00
present the trophy…

Oh, and set the necessary ground-rules:

rules:
* don’t be a dick.
* don’t break it.
* have fun.
* learn things.

Competition Time

For the actual event, the two teams were set up in their own area. I'd asked that one person from each time stream to a nearby television; a separate TV was used to screen the scoreboard—I'd hoped that we'd get a few spectators and this would keep things interesting. Turns out we did—a couple had even joined in and bagged a flag by the end of the competition.

At 16:00 the competitors grabbed beers, got their heads down and started. The competition's duration was dictated by the official soundtrack, running for about 2 hours.

It was interesting to watch the various ways people worked through the hints. One team immediately created a Teams channel (our internal comms. tool of choice) to keep track of flags; some coordinated their team-mates, providing some excellent Google-fu to assist in tracking down some of the more obscure clues.

But, as the last bars of the soundtrack faded, there could be only one winning team—Anomynoush!—who could claim the trophy: The Golden Hacker™.

The Golden Hacker™

What's next?

The VM was budgeted for the month so the following day the Juice Shops were started back up to allow people to continue catching some of the more elusive flags.

One thing that had surprised me was the amount of progress made in that first hour. I'd intentionally kept the competition fairly short to avoid anyone feeling "stuck"; I should never have doubted them, should I? With that in mind, there's now the intention to run some smaller, shorter sessions for anyone who couldn't make it.

The CTF event was, I think, not only a great way to raise security awareness within teams but it was fun. The Juice Shop is an ideal format for this sort of event—it's wonderfully presented but it's also a genuine application with genuine vulnerabilities.

I just need to figure out how to top it for the next one…