304 North Cardinal St.
Dorchester Center, MA 02124

Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM

OWASP Juice Shop + CTFd = Easy DIY CTFs!

I recently setup OWASP Juice Shop + CTFd for some internal training/CTFs, and I wanted to share the process.

OWASP Juice Shop – Introduction

If you have never heard of Juice Shop, then I recommend you check out the OWASP project page.

There is also a useful GitHub repository with some more documentation as well as Docker images.

For more information on CTFd, you can go to their GitHub repository as well.

AWS Configuration

First, I setup a new EC2 instance for my server.

OWASP Juice Shop - EC2 instance

I decided to go with an Ubuntu image in the t2.micro tier.

Ubuntu server


When I finished my configuration, the instance was created and running.

OWASP Juice Shop - Instance launched

OWASP Juice Shop – Setup

With the EC2 instance up, it was time to connect to the server.

root@kali:~/JuiceShop# chmod 600 juiceshop-keypair.pem 
root@kali:~/JuiceShop# ssh -i juiceshop-keypair.pem [email protected]
Please login as the user "ubuntu" rather than the user "root".

^CConnection to closed.
root@kali:~/JuiceShop# ssh -i juiceshop-keypair.pem [email protected]
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-1057-aws x86_64)

 * Documentation:
 * Management:
 * Support:

  System information as of Thu Feb 13 18:45:46 UTC 2020

  System load:  0.25              Processes:           89
  Usage of /:   13.8% of 7.69GB   Users logged in:     0
  Memory usage: 15%               IP address for eth0: 172.x.x.x
  Swap usage:   0%

0 packages can be updated.
0 updates are security updates.

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.

To run a command as administrator (user "root"), use "sudo ".
See "man sudo_root" for details.


First, I setup Docker on my new system. I’m not going to cover the installation process, but the following two links should be more than enough to get you started:

Next, I verified that Docker was up and running.

ubuntu@ip-172-x-x-x:~$ sudo systemctl status docker
�--� docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2020-02-13 18:48:54 UTC; 11s ago
 Main PID: 2989 (dockerd)
    Tasks: 8
   CGroup: /system.slice/docker.service
           �""�"�2989 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.312774297Z" level=warning msg="Your 
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.313013735Z" level=warning msg="Your 
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.313158796Z" level=warning msg="Your 
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.313436441Z" level=info msg="Loading 
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.483095557Z" level=info msg="Default 
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.601472072Z" level=info msg="Loading 
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.645443147Z" level=info msg="Docker d
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.645763489Z" level=info msg="Daemon h
Feb 13 18:48:54 ip-172-x-x-x systemd[1]: Started Docker Application Container Engine.
Feb 13 18:48:54 ip-172-x-x-x dockerd[2989]: time="2020-02-13T18:48:54.722256553Z" level=info msg="API list

With Docker running, I grabbed the Juice Shop image.

ubuntu@ip-172-x-x-x:~$ sudo docker pull bkimminich/juice-shop
Using default tag: latest
latest: Pulling from bkimminich/juice-shop
c9b1b535fdd9: Pull complete 
32eb17722c57: Pull complete 
c6a4a31e8940: Pull complete 
3f01d5292e29: Pull complete 
9c9e52c1b57b: Pull complete 
1ca529479291: Pull complete 
919fac11c0d7: Pull complete 
d989e0d66366: Pull complete 
Digest: sha256:d9c1a537f416ee0b163df906184cb836c7ae38861dc48c5f0c4289efe955ae26
Status: Downloaded newer image for bkimminich/juice-shop:latest

Next, I ran the image with the basic command, to ensure that it was working.

ubuntu@ip-172-x-x-x:~$ sudo docker run --rm -p 3000:3000 bkimminich/juice-shop

> [email protected] start /juice-shop
> node app

info: All dependencies in ./package.json are satisfied (OK)
info: Detected Node.js version v12.14.1 (OK)
info: Detected OS linux (OK)
info: Detected CPU x64 (OK)
info: Required file index.html is present (OK)
info: Required file styles.css is present (OK)
info: Required file main-es2015.js is present (OK)
info: Required file tutorial-es2015.js is present (OK)
info: Required file polyfills-es2015.js is present (OK)
info: Required file runtime-es2015.js is present (OK)
info: Required file vendor-es2015.js is present (OK)
info: Required file main-es5.js is present (OK)
info: Required file tutorial-es5.js is present (OK)
info: Required file polyfills-es5.js is present (OK)
info: Required file runtime-es5.js is present (OK)
info: Required file vendor-es5.js is present (OK)
info: Configuration default validated (OK)
info: Port 3000 is available (OK)
info: Server listening on port 3000

OWASP Juice Shop – Connection Issues

Unfortunately, I was unable to connect to the server on port 3000, as there were connection issues.

First, I verified that the service was listening on the right port.

ubuntu@ip-172-x-x-x:~$ sudo netstat -tulpn | grep LISTEN
tcp        0      0 *               LISTEN      606/systemd-resolve 
tcp        0      0    *               LISTEN      953/sshd            
tcp6       0      0 :::22                   :::*                    LISTEN      953/sshd            
tcp6       0      0 :::3000                 :::*                    LISTEN      4211/docker-proxy   

I thought this might be an issue related to IPv6, so I disabled it everywhere.

ubuntu@ip-172-x-x-x:~$ sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
net.ipv6.conf.all.disable_ipv6 = 1
ubuntu@ip-172-x-x-x:~$ sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
net.ipv6.conf.default.disable_ipv6 = 1
ubuntu@ip-172-x-x-x:~$ sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
net.ipv6.conf.lo.disable_ipv6 = 1

After also adding these flags to the proper configs, I also restarted my server.

I was still running into some issues, so I also enabled IPv4 IP forwarding.

net.ipv4.ip_forward = 1

None of these changes allowed me to reach port 3000, so I reached out to my local AWS expert. As it turns out, EC2 blocks all ports other than 22 by default, so I had to add the proper Security Group and permissions.

Security Groups

Obviously, this worked, and I was able to connect to the Juice Shop web interface!

Juice Shop running

Before moving on, I also set my container to run as a daemon, so that I could have it in the background.

ubuntu@ip-172-x-x-x:~$ sudo docker run --rm -d -p 3000:3000 bkimminich/juice-shop

Juice Shop CLI

The juice-shop-ctf-cli package helps to prepare the environment for a CTF, so that was next on my list.

First, I installed npm on my server.

ubuntu@ip-172-x-x-x:~$ sudo apt-get install npm
Reading package lists... Done
Building dependency tree       
Reading state information... Done

Next, I installed the cli package.

ubuntu@ip-172-x-x-x:~$ sudo npm install -g juice-shop-ctf-cli
npm WARN deprecated [email protected]: request has been deprecated, see
npm WARN deprecated [email protected]: use String.prototype.padStart()
/usr/local/bin/juice-shop-ctf -> /usr/local/lib/node_modules/juice-shop-ctf-cli/bin/juice-shop-ctf.js
�""�"��"� [email protected] 

Before running the juice-shop-ctf tool, I also generated a secret key to use for the flags.

ubuntu@ip-172-x-x-x:~$ date +%s | sha256sum | base64 | head -c 32 ; echo

With everything configured, it was time to generate my CTFd backup archive.

ubuntu@ip-172-x-x-x:~$ juice-shop-ctf

Generate OWASP Juice Shop challenge archive for setting up CTFd, FBCTF or RootTheBox score server
? CTF framework to generate data for? CTFd
? Juice Shop URL to retrieve challenges?
? Secret key  URL to ctf.key file? Mxxx2
? Insert a text hint along with each challenge? Free text hints
? Insert a hint URL along with each challenge? Free hint URLs

Backup archive written to /home/ubuntu/

After the import you will have to set up the CTF name and administrator credentials again!

For a step-by-step guide to import the ZIP-archive into CTFd, please refer to

Before moving on, I also setup my environment variables and reran my container.

ubuntu@ip-172-x-x-x:~$ sudo docker run --rm -d -e "CTF_KEY=Mxxx2" -e "NODE_ENV=ctf" -p 3000:3000 bkimminich/juice-shop

CTFd Installation and Configuration

Before installing CTFd, I needed to setup Docker Compose.

ubuntu@ip-172-x-x-x:~$ sudo curl -L "$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   617  100   617    0     0   3505      0 --:--:-- --:--:-- --:--:--  3505
100 16.4M  100 16.4M    0     0  13.2M      0  0:00:01  0:00:01 --:--:-- 17.4M
ubuntu@ip-172-x-x-x:~$ sudo chmod +x /usr/local/bin/docker-compose
ubuntu@ip-172-x-x-x:~$ docker-compose --version
docker-compose version 1.25.3, build d4d1b42b

For more information on the CTFd setup, you can read the following wiki page.

First, I setup a random CTFd_secret_key.

ubuntu@ip-172-x-x-x:~/CTFd$ python -c "import os; f=open('.ctfd_secret_key', 'a+'); f.write(os.urandom(64)); f.close()"

Next, I ran the docker-compose up command to stand up the environment.

ubuntu@ip-172-x-x-x:~/CTFd$ docker-compose up
ERROR: Couldn't connect to Docker daemon at http+docker://localhost - is it running?

If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.
ubuntu@ip-172-x-x-x:~/CTFd$ sudo docker-compose up
Creating network "ctfd_internal" with the default driver
Creating network "ctfd_default" with the default driver
Pulling db (mariadb:10.4)...
10.4: Pulling from library/mariadb
5c939e3a4d10: Pull complete

... <snip> ...

This took quite a while, but I also ran it in daemon mode once it finished.

ubuntu@ip-172-x-x-x:~/CTFd$ sudo docker-compose up -d
Building ctfd
Step 1/15 : FROM python:3.7-alpine
 ---> a5d195bb2a63
Step 2/15 : RUN apk update &&     apk add python python-dev linux-headers libffi-dev gcc make musl-dev py-pip mysql-client git openssl-dev
 ---> Using cache
 ---> 6c8cc986fd5c
Step 3/15 : RUN adduser -D -u 1001 -s /bin/bash ctfd
 ---> Using cache

... <snip> ...

WARNING: Image for service ctfd was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Pulling cache (redis:4)...
4: Pulling from library/redis
bc51dd8edc1b: Pull complete
37d80eb324ee: Pull complete
392b7748dfaf: Pull complete
74c85bb8b632: Pull complete
65aba7f17311: Pull complete
d2987ccbb89f: Pull complete
Digest: sha256:b47838c3ab42d810741bfc59d63d02b825c0d52e1dbe9a5d76385e3cbf8395b8
Status: Downloaded newer image for redis:4
Creating ctfd_cache_1 ... done
Creating ctfd_db_1    ... done
Creating ctfd_ctfd_1  ... done

With the installation complete, I verified that CTFd was listening on port 8000 as expected.

ubuntu@ip-172-x-x-x:~/CTFd$ netstat -tulpn | grep LISTEN
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 *               LISTEN      -                   
tcp        0      0    *               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::3000                 :::*                    LISTEN      -                   
tcp6       0      0 :::8000                 :::*                    LISTEN      -                   

When I went to port 8000 in my browser, I saw the CTFd setup pages.

OWASP Juice Shop - CTFd setup

After the initial configuration, I went to the Backup -> Import page, so that I could add my Juice Shop flags.

Import CTF

Before I could select my file, I had to upload the zip file from earlier to my EC2 instance.

root@kali:~/JuiceShop# scp -i juiceshop-keypair.pem [email protected]:/home/ubuntu/ .                                          100%   15KB  52.3KB/s   00:00    

When the archive was on my EC2 host, I could select it as my import file.

File selected

After the import, all the challenges (and flags) were pre-populated!

OWASP Juice Shop - Challenges populated

To test everything, I captured the 100-point DOM XSS flag.

Flag captured

As you can see, I am the only team on the scoreboard, and everything is working as expected.


OWASP Juice Shop – Conclusion

This was surprisingly simple to get running, and I’m looking forward to using it alongside some training.

The only real downside is that there are write-ups for everything online. That said, this still functions great for an introduction to web application testing as well as CTFs.

I may investigate posting some write-ups for Juice Shop here as well, so stay tuned!

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.