Attacking Web Apps with Ffuf
… refers to a testing technique that sends various types of user input to a certain interface to study how it would react. If you were fuzzing for SQLi vulnerabilities, you would be sending random special characters and seeing how the server would react. If you were fuzzing for a buffer overflow, you would be sending long strips and incrementing their length to see if and when the binary would break.
You usually utilize pre-defined wordlists of commonly used terms for each type of test for web fuzzing to see if the webserver would accept them. This is done because web servers do not usually provide a directory of all available links and domains, and so you would have to check for various links and see which ones return pages.
The tool ‘ffuf’ will be used for the coming examples.
Wordlists
To determine which pages exist, you shoud have a wordlist containing commonly used words for web directories and pages. SecLists might help.
Directory Fuzzing
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://SERVER_IP:PORT/FUZZ
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://SERVER_IP:PORT/FUZZ
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
<SNIP>
blog [Status: 301, Size: 326, Words: 20, Lines: 10]
:: Progress: [87651/87651] :: Job [1/1] :: 9739 req/sec :: Duration: [0:00:09] :: Errors: 0 ::
Extension Fuzzing
One common way to identify, what types of pages the website uses, is by finding the server type through the HTTP response headers and guessing the extension. For example, if the server is apache, then it may be .php, or if it was IIS, then it could be .asp or .apsx.
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/Web-Content/web-extensions.txt:FUZZ -u http://SERVER_IP:PORT/blog/indexFUZZ
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://SERVER_IP:PORT/blog/indexFUZZ
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/Web-Content/web-extensions.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 5
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
.php [Status: 200, Size: 0, Words: 1, Lines: 1]
.phps [Status: 403, Size: 283, Words: 20, Lines: 10]
:: Progress: [39/39] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
Page Fuzzing
You place the keyword where the filename should be, and use the same wordlist you used for fuzzing directories.
Exmaple:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://SERVER_IP:PORT/blog/FUZZ.php
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://SERVER_IP:PORT/blog/FUZZ.php
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
index [Status: 200, Size: 0, Words: 1, Lines: 1]
REDACTED [Status: 200, Size: 465, Words: 42, Lines: 15]
:: Progress: [87651/87651] :: Job [1/1] :: 5843 req/sec :: Duration: [0:00:15] :: Errors: 0 ::
Recursive Fuzzing
If you had dozens of directories, each with their own subdirectories and files, fuzzing for directories,then going under these directories, and then fuzzing for files would take a very long time to complete. To be able to automate this, you can utilize recursive fuzzing.
In ffuf you enable recursive fuzzing with the -recursion flag, and you can specify the depth with the -recursion-depth flag. Set at 1 it will only fuzz the main directories and their sub-directories. When using recursion, you can specify the extension with -e .php.
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://SERVER_IP:PORT/FUZZ -recursion -recursion-depth 1 -e .php -v
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://SERVER_IP:PORT/FUZZ
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
:: Extensions : .php
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
[Status: 200, Size: 986, Words: 423, Lines: 56] | URL | http://SERVER_IP:PORT/
* FUZZ:
[INFO] Adding a new job to the queue: http://SERVER_IP:PORT/forum/FUZZ
[Status: 200, Size: 986, Words: 423, Lines: 56] | URL | http://SERVER_IP:PORT/index.php
* FUZZ: index.php
[Status: 301, Size: 326, Words: 20, Lines: 10] | URL | http://SERVER_IP:PORT/blog | --> | http://SERVER_IP:PORT/blog/
* FUZZ: blog
<...SNIP...>
[Status: 200, Size: 0, Words: 1, Lines: 1] | URL | http://SERVER_IP:PORT/blog/index.php
* FUZZ: index.php
[Status: 200, Size: 0, Words: 1, Lines: 1] | URL | http://SERVER_IP:PORT/blog/
* FUZZ:
<...SNIP...>
Sub-domain Fuzzing
A subdomain is any webstie underlying another domain. For example, https://photos.google.com is the ‘photos’ subdomain of ‘google.com’.
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/DNS/subdomains-top1million-5000.txt:FUZZ -u https://FUZZ.inlanefreight.com/
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : https://FUZZ.inlanefreight.com/
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
[Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 381ms]
* FUZZ: support
[Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 385ms]
* FUZZ: ns3
[Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 402ms]
* FUZZ: blog
[Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 180ms]
* FUZZ: my
[Status: 200, Size: 22266, Words: 2903, Lines: 316, Duration: 589ms]
* FUZZ: www
<...SNIP...>
important
If a domain does not have a public DNS, there are no public subdomains for the domain. Even though you added the main domain to your local hosts file, only the primary domain was included. Therefore, when a tools tries to find subdomains, it won’t find them in the local file and will query the DNS, which doesn’t have them.
Vhost Fuzzing
When it comes to fuzzing subdomains that do not have a public DNS record or sub-domains under websites that are not public, you could use Vhost Fuzzing.
Vhosts vs. Sub-domains
The key difference between Vhosts and sub-domains is that a Vhost is basically a ‘sub-domain’ served on the same server and has the same IP, such that a single IP could be serving two or more different websites.
Vhosts may or may not have public DNS records.
To scan for Vhosts without manually adding the entire wordlist to /etc/hosts, you will be fuzzing HTTP headers, specifically the Host header. To do that, you can use the -H flag.
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/DNS/subdomains-top1million-5000.txt:FUZZ -u http://academy.htb:PORT/ -H 'Host: FUZZ.academy.htb'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://academy.htb:PORT/
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
mail2 [Status: 200, Size: 900, Words: 423, Lines: 56]
dns2 [Status: 200, Size: 900, Words: 423, Lines: 56]
ns3 [Status: 200, Size: 900, Words: 423, Lines: 56]
dns1 [Status: 200, Size: 900, Words: 423, Lines: 56]
lists [Status: 200, Size: 900, Words: 423, Lines: 56]
webmail [Status: 200, Size: 900, Words: 423, Lines: 56]
static [Status: 200, Size: 900, Words: 423, Lines: 56]
web [Status: 200, Size: 900, Words: 423, Lines: 56]
www1 [Status: 200, Size: 900, Words: 423, Lines: 56]
<...SNIP...>
Filtering Results
Ffuf provides the option to match or filter out a specific HTTP code, response size, or amount of words.
d41y@htb[/htb]$ ffuf -h
...SNIP...
MATCHER OPTIONS:
-mc Match HTTP status codes, or "all" for everything. (default: 200,204,301,302,307,401,403)
-ml Match amount of lines in response
-mr Match regexp
-ms Match HTTP response size
-mw Match amount of words in response
FILTER OPTIONS:
-fc Filter HTTP status codes from response. Comma separated list of codes and ranges
-fl Filter by amount of lines in response. Comma separated list of line counts and ranges
-fr Filter regexp
-fs Filter HTTP response size. Comma separated list of sizes and ranges
-fw Filter by amount of words in response. Comma separated list of word counts and ranges
<...SNIP...>
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/DNS/subdomains-top1million-5000.txt:FUZZ -u http://academy.htb:PORT/ -H 'Host: FUZZ.academy.htb' -fs 900
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://academy.htb:PORT/
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/DNS/subdomains-top1million-5000.txt
:: Header : Host: FUZZ.academy.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response size: 900
________________________________________________
<...SNIP...>
admin [Status: 200, Size: 0, Words: 1, Lines: 1]
:: Progress: [4997/4997] :: Job [1/1] :: 1249 req/sec :: Duration: [0:00:04] :: Errors: 0 ::
Parameter Fuzzing - GET
If you try to access a page, and see this:

That indicates that there must be something that identifies users to verify whether they have access to read the flag. There was no login, nor any cookie that can be verified at the backend. So, perhaps there is a key that you can pass to the page to read the flag. Such keys would usually be passed as a parameter, using either a GET or a POST HTTP request.
GET Requests are usually passed right after the URL, with a ? symbol, like:
http://admin.academy.htb:PORT/admin/admin.php?param1=key
All you have to do is replace param1 with FUZZ and rerun the scan. Pick an appropriate wordlist, like ‘/opt/useful/seclists/Discovery/Web-Content/burp-parameter-names.txt’.
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://admin.academy.htb:PORT/admin/admin.php?FUZZ=key -fs xxx
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://admin.academy.htb:PORT/admin/admin.php?FUZZ=key
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/Web-Content/burp-parameter-names.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response size: xxx
________________________________________________
<...SNIP...> [Status: xxx, Size: xxx, Words: xxx, Lines: xxx]
Once you get a hit and try the result, it might look like this:

Parameter Fuzzing - POST
POST requests are not passed with the URL and cannot simply be appended after a ? symbol. POST requests are passed in the data field within the HTTP request.
To fuzz the data field, you can use the -d flag. You also have to add -X POST to send POST requests.
Example:
d41y@htb[/htb]$ ffuf -w /opt/useful/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://admin.academy.htb:PORT/admin/admin.php -X POST -d 'FUZZ=key' -H 'Content-Type: application/x-www-form-urlencoded' -fs xxx
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : POST
:: URL : http://admin.academy.htb:PORT/admin/admin.php
:: Wordlist : FUZZ: /opt/useful/seclists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : FUZZ=key
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response size: xxx
________________________________________________
id [Status: xxx, Size: xxx, Words: xxx, Lines: xxx]
<...SNIP...>
A possible next step:
d41y@htb[/htb]$ curl http://admin.academy.htb:PORT/admin/admin.php -X POST -d 'id=key' -H 'Content-Type: application/x-www-form-urlencoded'
<div class='center'><p>Invalid id!</p></div>
<...SNIP...>
Value Fuzzing
After fuzzing a working parameter, you now have to fuzz the correct value.
When it comes to fuzzing parameter values, you may always find a pre-made wordlist that would work for you, as each parameter would expect a certain type of value.
The command should be fairly similar to the POST command, but the FUZZ keyword should be put where the parameter value would be.
Example:
d41y@htb[/htb]$ ffuf -w ids.txt:FUZZ -u http://admin.academy.htb:PORT/admin/admin.php -X POST -d 'id=FUZZ' -H 'Content-Type: application/x-www-form-urlencoded' -fs xxx
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.0.2
________________________________________________
:: Method : POST
:: URL : http://admin.academy.htb:30794/admin/admin.php
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : id=FUZZ
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
:: Filter : Response size: xxx
________________________________________________
<...SNIP...> [Status: xxx, Size: xxx, Words: xxx, Lines: xxx]