This is the first post in this series about the penetration test of the vulnerable web application, OWASP Juice Shop, and how we can utilize AWS WAF to protect against some hacking techniques.
Problem statement
Nowadays, most software products have web applications, and developing a secure application is challenging. There are many ways to hack a web application, and these techniques and ways to protect against them evolve daily.
The most direct way to protect is to write the application code according to best practices and continuously perform penetration tests. However, this is not always possible because the world is not ideal.
Depending on where you deploy and run your web application, you can get extra protection tools. Automat-IT builds AWS infrastructures for many companies and deploys a Web Application Firewall (WAF) by default in front of web applications.
But how does it actually work? Let’s play with a vulnerable web application and AWS WAF.
OWASP Juice Shop is the most modern and sophisticated insecure web application! It can be used in security training, awareness demos, CTFs, and as a guinea pig for security tools. Juice Shop encompasses vulnerabilities from the entire OWASP Top Ten and many other security flaws found in real-world applications.
For a detailed introduction, complete list of features, and architecture overview, please visit the official project page.
In this blog series, we will perform a penetration test of the OWASP Juice Shop web application and try to protect it with AWS WAF.
Setup
To install Docker on Amazon Linux 2023 and start the OWASP Juice Shop web application:
sudo yum update -y sudo yum install -y docker sudo service docker start sudo usermod -a -G docker ec2-user sudo docker run --restart always -d -p 3000:3000 bkimminich/juice-shop
I’ve prepared two identical environments, but one of them is with AWS WAF, and the second one is not:
Initially, I’ve enabled one Managed rule group.
OWASP Top 10 — The Complete Ruleset
Based on the FortiWeb web application firewall signatures and updated on a regular basis to include the latest threat information from FortiGuard Labs, the ruleset provides a comprehensive package to help address threats as described in OWASP Top 10
Which has the following list of rules:
Cross-Site-Scripting-01 Cross-Site-Scripting-02 Cross-Site-Scripting-03 SQL-Injection-01 SQL-Injection-02 SQL-Injection-03 Malicious-Robot Web-Scanner-01 Web-Scanner-02 Web-Scanner-03 OS-Command-Injection-01 OS-Command-Injection-02 Web-Application-Injection-01 Web-Application-Injection-02 Source-Code-Disclosure Database-Vulnerability-Exploit-01 Database-Vulnerability-Exploit-02 Database-Vulnerability-Exploit-03 Web-Server-Vulnerability-Exploit-01 Web-Server-Vulnerability-Exploit-03 Web-Server-Vulnerability-Exploit-04 Web-Application-Vulnerability-Exploit-01 Web-Application-Vulnerability-Exploit-02 Web-Application-Vulnerability-Exploit-03 Web-Application-Vulnerability-Exploit-04 Web-Application-Vulnerability-Exploit-05 Web-Application-Vulnerability-Exploit-06 Web-Application-Vulnerability-Exploit-07
We don’t know what logic is hidden inside the rules, but let’s just start the penetration test.
Start a Penetration Test
Usually, a penetration test begins with Reconnaissance (Information Gathering):
- Passive Recon: WHOIS lookup, subdomain enumeration (
Sublist3r
,Amass
), DNS enumeration, Google dorking, publicly available info (e.g., GitHub, Pastebin) — this is out of scope of the current article - Active Recon: Discover exposed endpoints, identify servers and technologies (
Wappalyzer
,BuiltWith
,WhatWeb
), port scan (nmap
), directory brute-forcing (dirb
,gobuster,
ffuf
, etc.)
Let’s try a couple of tools to scan the web server for directories using a dictionary file (Kali Linux),(/usr/share/wordlists/dirb/common.txt). This list contains 4600+ options for enumeration:
There are different lists – some smaller and some bigger. But let’s begin with it and try to discover directories exposed by the web application (without AWS WAF):
$ gobuster dir -u http://open-webapp-1170625636.us-east-1.elb.amazonaws.com -w /usr/share/wordlists/dirb/common.txt --exclude-length 71432 -t 10 -o gobuster_results.txt =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://open-webapp-1170625636.us-east-1.elb.amazonaws.com [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Negative Status codes: 404 [+] Exclude Length: 71432 [+] User Agent: gobuster/3.6 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /api (Status: 500) [Size: 3017] /apis (Status: 500) [Size: 3019] /assets (Status: 301) [Size: 156] [--> /assets/] /ftp (Status: 200) [Size: 11062] /profile (Status: 500) [Size: 1154] /promotion (Status: 200) [Size: 6586] /redirect (Status: 500) [Size: 3119] /restaurants (Status: 500) [Size: 3033] /rest (Status: 500) [Size: 3019] /restore (Status: 500) [Size: 3025] /restored (Status: 500) [Size: 3027] /restricted (Status: 500) [Size: 3031] /robots.txt (Status: 200) [Size: 28] /snippets (Status: 200) [Size: 792] /video (Status: 200) [Size: 10075518] Progress: 4614 / 4615 (99.98%) =============================================================== Finished ===============================================================
$ ffuf -u http://open-webapp-1170625636.us-east-1.elb.amazonaws.com/FUZZ -w /usr/share/wordlists/dirb/common.txt -fs 71432 -t 10 -timeout 30 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev ________________________________________________ :: Method : GET :: URL : http://open-webapp-1170625636.us-east-1.elb.amazonaws.com/FUZZ :: Wordlist : FUZZ: /usr/share/wordlists/dirb/common.txt :: Follow redirects : false :: Calibration : false :: Timeout : 30 :: Threads : 10 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 :: Filter : Response size: 71432 ________________________________________________ api [Status: 500, Size: 3017, Words: 235, Lines: 50, Duration: 155ms] apis [Status: 500, Size: 3019, Words: 235, Lines: 50, Duration: 155ms] assets [Status: 301, Size: 156, Words: 6, Lines: 11, Duration: 120ms] ftp [Status: 200, Size: 11062, Words: 1568, Lines: 357, Duration: 117ms] profile [Status: 500, Size: 1154, Words: 159, Lines: 50, Duration: 159ms] promotion [Status: 200, Size: 6586, Words: 560, Lines: 177, Duration: 217ms] redirect [Status: 500, Size: 3119, Words: 244, Lines: 50, Duration: 126ms] rest [Status: 500, Size: 3019, Words: 235, Lines: 50, Duration: 134ms] restricted [Status: 500, Size: 3031, Words: 235, Lines: 50, Duration: 146ms] restored [Status: 500, Size: 3027, Words: 235, Lines: 50, Duration: 148ms] restaurants [Status: 500, Size: 3033, Words: 235, Lines: 50, Duration: 149ms] restore [Status: 500, Size: 3025, Words: 235, Lines: 50, Duration: 151ms] robots.txt [Status: 200, Size: 28, Words: 3, Lines: 2, Duration: 114ms] snippets [Status: 200, Size: 792, Words: 1, Lines: 1, Duration: 151ms] video [Status: 200, Size: 10075518, Words: 0, Lines: 0, Duration: 0ms] Video [Status: 200, Size: 10075518, Words: 0, Lines: 0, Duration: 0ms] :: Progress: [4614/4614] :: Job [1/1] :: 60 req/sec :: Duration: [0:01:16] :: Errors: 0 ::
The results are similar.
Let’s do the same on the WAF-protected application:
$ gobuster dir -u http://waf-webapp-617047269.us-east-1.elb.amazonaws.com -w /usr/share/wordlists/dirb/common.txt --exclude-length 71432 -t 10 -o gobuster_results.txt =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://waf-webapp-617047269.us-east-1.elb.amazonaws.com [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Negative Status codes: 404 [+] Exclude Length: 71432 [+] User Agent: gobuster/3.6 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /api (Status: 500) [Size: 3017] /apis (Status: 500) [Size: 3019] /assets (Status: 301) [Size: 156] [--> /assets/] /ftp (Status: 200) [Size: 11062] /profile (Status: 500) [Size: 1154] /promotion (Status: 200) [Size: 6586] /redirect (Status: 500) [Size: 3119] /rest (Status: 500) [Size: 3019] /restaurants (Status: 500) [Size: 3033] /restore (Status: 500) [Size: 3025] /restricted (Status: 500) [Size: 3031] /restored (Status: 500) [Size: 3027] /robots.txt (Status: 200) [Size: 28] /server-status (Status: 403) [Size: 118] /server-info (Status: 403) [Size: 118] /snippets (Status: 200) [Size: 792] Progress: 4614 / 4615 (99.98%)[ERROR] context deadline exceeded (Client.Timeout or context cancellation while reading body) Progress: 4614 / 4615 (99.98%) =============================================================== Finished ===============================================================
Gobuster still works.
FFUF resulted in all 403 codes, so WAF noticed something suspicious:
$ ffuf -u http://waf-webapp-617047269.us-east-1.elb.amazonaws.com/FUZZ -w /usr/share/wordlists/dirb/common.txt -fs 71432 -t 10 -timeout 30 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0-dev ________________________________________________ :: Method : GET :: URL : http://waf-webapp-617047269.us-east-1.elb.amazonaws.com/FUZZ :: Wordlist : FUZZ: /usr/share/wordlists/dirb/common.txt :: Follow redirects : false :: Calibration : false :: Timeout : 30 :: Threads : 10 :: Matcher : Response status: 200-299,301,302,307,401,403,405,500 :: Filter : Response size: 71432 ________________________________________________ .bash_history [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .config [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 116ms] .cache [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 116ms] .git/HEAD [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 116ms] .forward [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 120ms] .cvsignore [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 122ms] .history [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 125ms] [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 125ms] .cvs [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 126ms] .bashrc [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 126ms] .htpasswd [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .hta [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 116ms] .htaccess [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .listing [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 114ms] .listings [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 114ms] .mysql_history [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .passwd [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .profile [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .rhosts [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 117ms] .perf [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 119ms] .subversion [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 113ms] .ssh [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 114ms] .sh_history [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 114ms] .svn [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .svn/entries [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .swf [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] @ [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] .web [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] _ [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] _adm [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] _archive [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 113ms] _admin [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 114ms] _ajax [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 114ms] _assets [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] _backup [Status: 403, Size: 118, Words: 3, Lines: 7, Duration: 115ms] ......................................................
You can see metrics in the AWS WAF console:
And traffic characteristics:
Details about the blocked requests:
But anyway, we got the successful scan from Gobuster, so we need rules other than the Fortinet-all_rules Managed rule group.
There are other managed WAF rules, for example, “Core rule set”:
Contains rules that are generally applicable to web applications. This provides protection against exploitation of a wide range of vulnerabilities, including those described in OWASP publications.
Let’s add it:
And rerun the Gobuster:
$ gobuster dir -u http://waf-webapp-617047269.us-east-1.elb.amazonaws.com -w /usr/share/wordlists/dirb/common.txt --exclude-length 71432 -t 10 -o gobuster_results.txt =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://waf-webapp-617047269.us-east-1.elb.amazonaws.com [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Negative Status codes: 404 [+] Exclude Length: 71432 [+] User Agent: gobuster/3.6 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== Error: the server returns a status code that matches the provided options for non existing urls. http://waf-webapp-617047269.us-east-1.elb.amazonaws.com/8454b721-1d27-47f7-8f1f-d356c8556219 => 403 (Length: 118). To continue please exclude the status code or the length ### IGNORE 403 ERRORS $ gobuster dir -u http://waf-webapp-617047269.us-east-1.elb.amazonaws.com -w /usr/share/wordlists/dirb/common.txt --exclude-length 71432 -t 10 -o gobuster_results.txt -b 403 =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://waf-webapp-617047269.us-east-1.elb.amazonaws.com [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Negative Status codes: 403 [+] Exclude Length: 71432 [+] User Agent: gobuster/3.6 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== Progress: 4614 / 4615 (99.98%) =============================================================== Finished ===============================================================
Now AWS WAF protects our application from scanning, but let’s dive deeper.
Maybe the new ruleset relies only on the “User Agent” header. Here is a sample of the blocked request:
Obviously, the User-Agent is not good:
Let’s change it for Gobuster:
$ gobuster dir -u http://waf-webapp-617047269.us-east-1.elb.amazonaws.com -w /usr/share/wordlists/dirb/common.txt --exclude-length 71432 -t 10 -o gobuster_results.txt -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0" =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://waf-webapp-617047269.us-east-1.elb.amazonaws.com [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Negative Status codes: 404 [+] Exclude Length: 71432 [+] User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /.config (Status: 403) [Size: 520] /akeeba.backend.log (Status: 403) [Size: 520] /apis (Status: 500) [Size: 3019] /api (Status: 500) [Size: 3017] /assets (Status: 301) [Size: 156] [--> /assets/] /awstats.conf (Status: 403) [Size: 520] /development.log (Status: 403) [Size: 520] /ftp (Status: 200) [Size: 11062] /php.ini (Status: 403) [Size: 520] /production.log (Status: 403) [Size: 520] /profile (Status: 500) [Size: 1154] /promotion (Status: 200) [Size: 6586] /redirect (Status: 500) [Size: 3119] /restore (Status: 500) [Size: 3025] /rest (Status: 500) [Size: 3019] /restaurants (Status: 500) [Size: 3033] /restored (Status: 500) [Size: 3027] /restricted (Status: 500) [Size: 3031] /robots.txt (Status: 200) [Size: 28] /server-info (Status: 403) [Size: 520] /server-status (Status: 403) [Size: 520] /snippets (Status: 200) [Size: 792] /spamlog.log (Status: 403) [Size: 520] /web.config (Status: 403) [Size: 520] /WS_FTP.LOG (Status: 403) [Size: 520] /Video (Status: 200) [Size: 10075518] Progress: 4614 / 4615 (99.98%) =============================================================== Finished ===============================================================
And we see the partially successful scan again (code 200).
Let’s now add a Rate-based rule, for example, 300 per minute:
Scan again:
$ gobuster dir -u http://waf-webapp-617047269.us-east-1.elb.amazonaws.com -w /usr/share/wordlists/dirb/common.txt --exclude-length 71432 -t 10 -o gobuster_results.txt -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0" =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://waf-webapp-617047269.us-east-1.elb.amazonaws.com [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Negative Status codes: 404 [+] Exclude Length: 71432 [+] User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0 [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /.config (Status: 403) [Size: 520] /akeeba.backend.log (Status: 403) [Size: 520] /api (Status: 500) [Size: 3017] /apis (Status: 500) [Size: 3019] /assets (Status: 301) [Size: 156] [--> /assets/] /awstats.conf (Status: 403) [Size: 520] /demo (Status: 403) [Size: 520] /delicious (Status: 403) [Size: 520] /demo2 (Status: 403) [Size: 520] /deployment (Status: 403) [Size: 520] /demos (Status: 403) [Size: 520] /departments (Status: 403) [Size: 520] /deploy (Status: 403) [Size: 520] /deny (Status: 403) [Size: 520] /denied (Status: 403) [Size: 520] /descargas (Status: 403) [Size: 520] /design (Status: 403) [Size: 520] /designs (Status: 403) [Size: 520] /desktop (Status: 403) [Size: 520] /desktopmodules (Status: 403) [Size: 520] /destinations (Status: 403) [Size: 520] /desktops (Status: 403) [Size: 520] /deutsch (Status: 403) [Size: 520] /details (Status: 403) [Size: 520] /detail (Status: 403) [Size: 520] /dev (Status: 403) [Size: 520] /dev2 (Status: 403) [Size: 520] /dev60cgi (Status: 403) [Size: 520] /developer (Status: 403) [Size: 520] /development (Status: 403) [Size: 520] /development.log (Status: 403) [Size: 520] /develop (Status: 403) [Size: 520] ..................................... /zoeken (Status: 403) [Size: 520] /zipfiles (Status: 403) [Size: 520] /zips (Status: 403) [Size: 520] /zimbra (Status: 403) [Size: 520] /zoom (Status: 403) [Size: 520] /zope (Status: 403) [Size: 520] /zt (Status: 403) [Size: 520] Progress: 4614 / 4615 (99.98%) /zorum (Status: 403) [Size: 520] =============================================================== Finished ===============================================================
As we saw, there are many ways to perform discovery for further hacking and many ways to protect against them.
Protecting an Exposed Directory
Earlier, we found several directories that can be targets for further discovery or attacks, for example, /ftp
=============================================================== Starting gobuster in directory enumeration mode =============================================================== .......... /ftp (Status: 200) [Size: 11062] ..........
The first thing we can do is go and see:
Sometimes we can find sensitive information:
Ideally, you would handle such things on the application side and not expose what should not be explicitly exposed. But let’s assume someone made a mistake, and now we need to fix it as soon as possible, while developers fix the code:
If you want to allow or block web requests based on strings that match a regular expression (regex) pattern that appears in the requests, create one or more regex match conditions. A regex match condition is a type of string match condition that identifies the pattern that you want to search for and the part of web requests, such as a specified header or the query string, that you want AWS WAF to inspect for the pattern.
I use URI path
matches regex /ftp*
Try to reach the /ftp
directory again — 403 Forbidden. This part is protected:
Conclusion
This is the first post in this series about the penetration test of the vulnerable web application, OWASP Juice Shop, and how we can utilize AWS WAF to protect against some hacking techniques.
We looked at initial application scanning, enumeration, AWS-managed, and custom WAF rules.
Stay tuned for our next post, where we will look at SQL injections and other interesting cases.