ffuf 사용법

ffuf(Fuzz Faster U Fool) 는 Go 언어로 작성된 웹 퍼저(fuzzer) 프로그램이다.
웹 서버에 대한 모의해킹을 진행할 때, 해당 서버에 어떤 디렉토리, 파일, 리소스가 존재하는지 알아야 할 필요가 있다. 웹 퍼저는 대상 웹 서버에 지정한 방식으로 대량의 입력 값을 전송, 그에 대한 response 를 해석하여 대상 리소스의 존재 여부를 알 수 있는 프로그램이다.
이 글에서는 ffuf 를 이용하여 웹 콘텐츠를 찾는 기본적인 방법에 대해 다룬다. 예제에서 사용하는 wordlist 는 SecLists 를 사용했다.
Install
Linux OS 에서는 다음과 같은 apt 설치 명령어로 ffuf 를 설치할 수 있다.
sudo apt install ffuf
Basic usage
ffuf 를 사용하기 위해서는 기본적으로 wordlist(-w
), 퍼징 대상 웹 주소(-u
)에 대한 옵션을 필요로 한다.
wordlist 는 퍼징에 사용할 단어들을 저장한 파일이다. 예를 들어 아래 내용은 웹 서버에서 자주 사용되는 디렉토리 경로를 모아놓은 Seclists 의 directory-list-2.3-medium.txt
파일의 일부를 보여준다.
index
images
download
2006
news
crack
serial
warez
full
12
contact
about
search
spacer
... 후략 ...
wordlist 와 대상 웹 서버 주소를 정했으면 다음과 같은 명령어로 퍼징을 실행할 수 있다.
ffuf -w {wordlist 경로} -u {웹 서버 도메인 or IP:Port}/FUZZ
ex) ffuf -w ./SecLists-master/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://test.com:1234/FUZZ
주목할만한 부분은 FUZZ
라는 단어이다. FUZZ
는 지정한 wordlist 의 단어 하나하나로 치환되어 패킷을 송신하게 된다. 예를 들어 위 wordlist 를 사용하면 순차적으로 test.com:1234/index
, test.com:1234/images
, test.com:1234/download
에 대한 접근을 시도하게 된다.
예제 명령어를 실행하면 다음과 같은 결과가 출력된다. ffuf 는 기본적으로 Matcher 에 해당하는 response code(200,204,301,302,307,401,403,405,500) 를 받으면 해당 word 를 출력한다. 아래 예제의 결과에 따르면 test.com:1234
에서 /about
, /upload
라는 디렉토리를 가진 것을 알 수 있다.
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.0.0-dev
________________________________________________
:: Method : GET
:: URL : http://test.com:1234/FUZZ
:: Wordlist : FUZZ: /home/kali/Desktop/SecLists-master/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
[Status: 200, Size: 2939, Words: 492, Lines: 72, Duration: 240ms]
* FUZZ: about
[Status: 200, Size: 7140, Words: 1952, Lines: 210, Duration: 200ms]
* FUZZ: upload
Get parameter fuzzing
HTTP 의 Get method 는 URL 에 query 할 정보를 포함하는 통신 방식이다. ffuf 를 이용하면 지정한 Get parameter 에 대해 퍼징을 시도할 수 있다.
명령어는 기본적인 디렉토리 탐색 명령어와 거의 유사하다. 다른 점이라고는 FUZZ
가 디렉토리가 아닌 Get Parameter 의 값이 들어갈 부분을 대체하고 있을 뿐이다.
ffuf -w {wordlist 경로} -u {웹 서버 도메인 or IP:Port}/{paramter}=FUZZ
ex) ffuf -w ./SecLists-master/Fuzzing/LFI/LFI-Jhaddix.txt -u http://test.com:1234/index.php?view=FUZZ
예제 명령어은 index.php
에서 view
라는 parameter 를 처리하며, 이를 통해 LFI(Local File Inclusion) 공격이 가능하다고 예상하여 실행되었다고 가정한다. wordlist 도 LFI 공격에 사용될 만한 단어들을 모은 LFI-Jhaddix.txt
파일을 사용했다.
퍼징할 결과, /etc/passwd
와 관련된 일부 단어에서 200 Status 를 수신받을 수 있었으며, /etc/passwd
에 대한 LFI 공격이 가능하다는 것을 확인할 수 있었다.
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.0.0-dev
________________________________________________
:: Method : GET
:: URL : http://test.com:1234/index.php?view=FUZZ
:: Wordlist : FUZZ: /home/kali/Desktop/SecLists-master/Fuzzing/LFI/LFI-Jhaddix.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 1935
________________________________________________
[Status: 200, Size: 3309, Words: 526, Lines: 82, Duration: 263ms]
* FUZZ: ../../../../../../../../../../../../../../../../../../../../../../etc/passwd
[Status: 200, Size: 3309, Words: 526, Lines: 82, Duration: 263ms]
* FUZZ: ../../../../../../../../../../../../../../../../../../../../../etc/passwd
[Status: 200, Size: 3309, Words: 526, Lines: 82, Duration: 263ms]
* FUZZ: ../../../../../../../../../../../../../../../../../../../etc/passwd
[Status: 200, Size: 3309, Words: 526, Lines: 82, Duration: 264ms]
* FUZZ: ../../../../../../../../../../../../../../../../../../etc/passwd
[Status: 200, Size: 3309, Words: 526, Lines: 82, Duration: 265ms]
* FUZZ: ../../../../../../../../../../../../../../../../../etc/passwd
[Status: 200, Size: 3309, Words: 526, Lines: 82, Duration: 269ms]
* FUZZ: ../../../../../../../../../../../../../../../../../../../../etc/passwd
:: Progress: [922/922] :: Job [1/1] :: 147 req/sec :: Duration: [0:00:07] :: Errors: 0 ::
Post data fuzzing
POST 방식과 GET 방식의 가장 큰 차이는 URL 을 통해 파라미터를 전달하느냐 마느냐이다. POST 방식은 파라미터를 HTTP 패킷의 data 필드에 넣어 전달한다.
ffuf 에서 data 필드에 파라미터를 전달하기 위해 -d
플래그를 추가하고, -X POST
를 추가하여 POST 방식을 사용한다는 것을 명시한다.
PHP 에서는 POST 패킷의content-type
이application/x-www-form-urlencoded
인 경우만 허용한다. 따라서 ffuf 에 "-H 'Content-Type: application/x-www-form-urlencoded'" 을 추가해야 한다.
ffuf -w {wordlist 경로} -u {웹 서버 도메인 or IP:Port} -X POST -d 'FUZZ={value}'
ex) ffuf -w /opt/useful/SecLists/Discovery/Web-Content/burp-parameter-names.txt -u http://test.com:1234/index.php -X POST -d 'FUZZ=value' -H 'Content-Type: application/x-www-form-urlencoded'
다음은 burp-parameter-names.txt
파일을 wordlist 로 사용하여 index.php
에 POST 방식으로 퍼징한 결과다.
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : POST
:: URL : http://test.com:1234/index.php
:: Wordlist : FUZZ: /opt/useful/SecLists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : FUZZ=value
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
... 생략 ...
Subdomain, Virtual host discovery
웹 서버에서 서브 도메인, 혹은 가상 호스트를 운영 중이라면 ffuf 를 이용해 퍼징하여 경로를 찾아낼 수도 있다.
Subdomain discovery
서브 도메인이란 다른 도메인 밑에 존재하는 도메인을 뜻한다. 예를 들어 http://www.test.com
이라는 주소에서 www
는 test.com
의 서브 도메인이다.
앞선 명령어와 마찬가지로, 서브 도메인이 들어갈 자리에 FUZZ
를 넣어 실행하면 된다.
ffuf -w {wordlist 경로} -u {웹 서버 도메인 or IP:Port}
ex) ffuf -w /opt/useful/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u http://FUZZ.test.com/
다음은 subdomains-top1million-5000.txt
파일을 wordlist 로 사용하여 test.com
의 서브 도메인을 퍼징하는 명령어이다.
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : GET
:: URL : http://FUZZ.test.com/
:: Wordlist : FUZZ: /opt/useful/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
________________________________________________
www [Status: 200, Size: 1544, Words: 26, Lines: 1]
... 후략 ...
Virtual host discovery
가상 호스트는 서브 도메인과 다른 몇 가지 특징을 가진다.
- 도메인과 동일한 IP 에서 운영됨.
- 서브 도메인과는 달리 Public DNS 레코드를 가지지 않을 수도 있다.
가상 호스트가 Public DNS 레코드를 가지지 않는다면, IP 를 알 수 없어 통신이 불가능할 수도 있다. 하지만 가상 호스트는 도메인과 동일한 IP 를 가지니, 이를 이용하여 통신하면 된다. 또한 가상 호스트와 통신할 때는 HTTP 헤더의 Host
값을 가상 호스트의 서브 도메인 값으로 변경해야만 정상적인 통신이 가능하다.
ffuf -w {wordlist 경로} -u {웹 서버 도메인 or IP:Port} -H 'Host: FUZZ.{웹 서버 도메인}'
ex) ffuf -w /opt/useful/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u http://test.com/ -H 'Host: FUZZ.test.com'
서브 도메인을 퍼징할 때와는 달리, -u
옵션에 FUZZ
가 사라지고, -H
옵션의 Host
값에 FUZZ
가 추가되었다.
Filter
ffuf 는 기본적으로 퍼징한 결과 중, Response Status 값이 404가 아닌 응답에 대해 결과를 출력한다. 하지만 웹 서버의 설정에 따라서는 존재하지 않는 리소스에 대해서도 404 가 아닌 200 Status 값과 함께 에러 메시지를 보내기도 한다.
예를 들어 아래와 같은 명령어를 실행했더니 모든 word 에 대해 동일한 응답을 받았다고 가정하자. 이런 경우 위에서 언급한 것처럼 서버에서 리소스의 존재 여부와 상관 없이 무조건 동일한 응답을 보냈다고 추측할 수 있다.
ffuf -w ./SecLists-master/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://test.com:1234/FUZZ
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : POST
:: URL : http://test.com:1234/index.php
:: Wordlist : FUZZ: /opt/useful/SecLists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : FUZZ=value
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
index [Status: 200, Size: 900, Words: 423, Lines: 56]
images [Status: 200, Size: 900, Words: 423, Lines: 56]
download [Status: 200, Size: 900, Words: 423, Lines: 56]
2006 [Status: 200, Size: 900, Words: 423, Lines: 56]
news [Status: 200, Size: 900, Words: 423, Lines: 56]
serial [Status: 200, Size: 900, Words: 423, Lines: 56]
warez [Status: 200, Size: 900, Words: 423, Lines: 56]
full [Status: 200, Size: 900, Words: 423, Lines: 56]
12 [Status: 200, Size: 900, Words: 423, Lines: 56]
... 후략 ...
위와 같은 경우 동일하게 응답 크기가 900 바이트이므로, -fs
옵션을 사용해 지정한 크기의 응답은 결과 값에서 필터링할 수 있다. 또는 응답되는 단어 수는 일정하나, 응답 바이트 크기는 일정하지 않을 경우 -fw
옵션을 사용하여 지정한 수의 word 가 응답될 경우만 필터링할 수도 있다.
ffuf -w ./SecLists-master/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://test.com:1234/FUZZ -fs 900
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.1.0-git
________________________________________________
:: Method : POST
:: URL : http://test.com:1234/index.php
:: Wordlist : FUZZ: /opt/useful/SecLists/Discovery/Web-Content/burp-parameter-names.txt
:: Header : Content-Type: application/x-www-form-urlencoded
:: Data : FUZZ=value
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403
________________________________________________
about [Status: 200, Size: 1444, Words: 645, Lines: 99]
Etc Options
앞서 소개한 기능 외에도, ffuf 는 다양한 옵션과 기능을 제공하고 있으므로, 공식 매뉴얼을 참고하면 유연한 퍼징이 가능하다.
다음은 2.0.0 버전의 ffuf 가 지원하는 옵션이다
$ ffuf -h
Fuzz Faster U Fool - v2.0.0-dev
HTTP OPTIONS:
-H Header `"Name: Value"`, separated by colon. Multiple -H flags are accepted.
-X HTTP method to use
-b Cookie data `"NAME1=VALUE1; NAME2=VALUE2"` for copy as curl functionality.
-d POST data
-http2 Use HTTP2 protocol (default: false)
-ignore-body Do not fetch the response content. (default: false)
-r Follow redirects (default: false)
-recursion Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it. (default: false)
-recursion-depth Maximum recursion depth. (default: 0)
-recursion-strategy Recursion strategy: "default" for a redirect based, and "greedy" to recurse on all matches (default: default)
-replay-proxy Replay matched requests using this proxy.
-sni Target TLS SNI, does not support FUZZ keyword
-timeout HTTP request timeout in seconds. (default: 10)
-u Target URL
-x Proxy URL (SOCKS5 or HTTP). For example: http://127.0.0.1:8080 or socks5://127.0.0.1:8080
GENERAL OPTIONS:
-V Show version information. (default: false)
-ac Automatically calibrate filtering options (default: false)
-acc Custom auto-calibration string. Can be used multiple times. Implies -ac
-ach Per host autocalibration (default: false)
-ack Autocalibration keyword (default: FUZZ)
-acs Autocalibration strategy: "basic" or "advanced" (default: basic)
-c Colorize output. (default: false)
-config Load configuration from a file
-json JSON output, printing newline-delimited JSON records (default: false)
-maxtime Maximum running time in seconds for entire process. (default: 0)
-maxtime-job Maximum running time in seconds per job. (default: 0)
-noninteractive Disable the interactive console functionality (default: false)
-p Seconds of `delay` between requests, or a range of random delay. For example "0.1" or "0.1-2.0"
-rate Rate of requests per second (default: 0)
-s Do not print additional information (silent mode) (default: false)
-sa Stop on all error cases. Implies -sf and -se. (default: false)
-scraperfile Custom scraper file path
-scrapers Active scraper groups (default: all)
-se Stop on spurious errors (default: false)
-search Search for a FFUFHASH payload from ffuf history
-sf Stop when > 95% of responses return 403 Forbidden (default: false)
-t Number of concurrent threads. (default: 40)
-v Verbose output, printing full URL and redirect location (if any) with the results. (default: false)
MATCHER OPTIONS:
-mc Match HTTP status codes, or "all" for everything. (default: 200,204,301,302,307,401,403,405,500)
-ml Match amount of lines in response
-mmode Matcher set operator. Either of: and, or (default: or)
-mr Match regexp
-ms Match HTTP response size
-mt Match how many milliseconds to the first response byte, either greater or less than. EG: >100 or <100
-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
-fmode Filter set operator. Either of: and, or (default: or)
-fr Filter regexp
-fs Filter HTTP response size. Comma separated list of sizes and ranges
-ft Filter by number of milliseconds to the first response byte, either greater or less than. EG: >100 or <100
-fw Filter by amount of words in response. Comma separated list of word counts and ranges
INPUT OPTIONS:
-D DirSearch wordlist compatibility mode. Used in conjunction with -e flag. (default: false)
-e Comma separated list of extensions. Extends FUZZ keyword.
-ic Ignore wordlist comments (default: false)
-input-cmd Command producing the input. --input-num is required when using this input method. Overrides -w.
-input-num Number of inputs to test. Used in conjunction with --input-cmd. (default: 100)
-input-shell Shell to be used for running command
-mode Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork, sniper (default: clusterbomb)
-request File containing the raw http request
-request-proto Protocol to use along with raw request (default: https)
-w Wordlist file path and (optional) keyword separated by colon. eg. '/path/to/wordlist:KEYWORD'
OUTPUT OPTIONS:
-debug-log Write all of the internal logging to the specified file.
-o Write output to file
-od Directory path to store matched results to.
-of Output file format. Available formats: json, ejson, html, md, csv, ecsv (or, 'all' for all formats) (default: json)
-or Don't create the output file if we don't have results (default: false)
EXAMPLE USAGE:
Fuzz file paths from wordlist.txt, match all responses but filter out those with content-size 42.
Colored, verbose output.
ffuf -w wordlist.txt -u https://example.org/FUZZ -mc all -fs 42 -c -v
Fuzz Host-header, match HTTP 200 responses.
ffuf -w hosts.txt -u https://example.org/ -H "Host: FUZZ" -mc 200
Fuzz POST JSON data. Match all responses not containing text "error".
ffuf -w entries.txt -u https://example.org/ -X POST -H "Content-Type: application/json" \
-d '{"name": "FUZZ", "anotherkey": "anothervalue"}' -fr "error"
Fuzz multiple locations. Match only responses reflecting the value of "VAL" keyword. Colored.
ffuf -w params.txt:PARAM -w values.txt:VAL -u https://example.org/?PARAM=VAL -mr "VAL" -c
More information and examples: https://github.com/ffuf/ffuf