ffuf 사용법

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-typeapplication/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 이라는 주소에서 wwwtest.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