ffuf logo with running mascot

Les plus férus des notifications, que ce soit sur twitter ou github, n’auront pas manqué la parution de la version 2.0.0 de ffuf.

Dans mon précédent article Astuces avancées avec ffuf, je commençais par présenter ffuf : À quoi sert-il ? Qu’a-t-il de différent des autres ? Le reste de l’article se concentrant sur des usages avancés de l’outil. Pour résumer, ffuf est un scanneur de fichiers et dossiers pour application web mais aussi un fuzzer.

Cette fois-ci, l’article s’intéressera aux nouvelles fonctionnalités suivantes de la version 2.0 :

  • scrapper
  • traçage des requêtes (request backtracking)
  • support de XDG_CONFIG_HOME pour la configuration

Note 1 : Afin de vous exercer tout en lisant cet article, vous pouvez déployer la machine présente dans le salon ffuf TryHackMe, utiliser l’image docker ffufme ou bien utiliser la version en ligne de ffufme.

Note 2 : Cet article est aussi disponible en anglais 🇬🇧.

Configuration

Nous allons commencer par ce point car nous en aurons besoin pour les suivants.

Dans mon article précédent, j’avais détaillé l’usage Le fichier de configuration mais avais révélé qu’il ne se situait pas dans un emplacement standard.
C’est maintenant chose faite, puisque dorénavant ffuf ira chercher le fichier de configuration dans un emplacement respectant le standard XDG Base Directory : $XDG_CONFIG_HOME/ffuf/ffufrc.

Un premier lancement de ffuf 2.0 permet de créer le dossier de configuration.

$ tree ~/.config/ffuf/
/home/noraj/.config/ffuf/
├── history
└── scraper

Afin de migrer un fichier de configuration datant de ffuf 1.5, il suffit de le déplacer.

$ mv ~/.ffufrc ~/.config/ffuf/ffufrc

Scraper

Le scrapper permet d’extraire des données spécifiques des réponses HTTP soit via des expressions régulières (regexp) soit via des sélecteurs CSS.

Des règles peuvent être déployées dans $XDG_CONFIG_HOME/ffuf/scraper/ sous forme de fichiers JSON.

Attention, les règles de ffuf ne sont pas l’équivalent des modèles nuclei. Les règles ffuf sont beaucoup plus simples et ont pour seul but d’extraire des informations d’une des pages testées, là où les modèles nuclei permettent de faire du scan de vulnérabilités grâce à un DSL avancé, un support de plusieurs protocoles et des conditions poussées.

Imaginons un cas d’usage où un chasseur de bugs veuille récupérer l’adresse de contact du fichier security.txt de tous les sites ayant un programme de bug bounty ou de responsible disclosure référencés sur chaos de ProjectDiscovery.

Pour simuler, prenons simplement la liste d’hôtes suivants (hosts.txt) :

www.google.com
cloudflare.com
github.com
nginx.com
discord.com
ovh.com

Comme liste des fichiers à tester, nous prendrons la liste suivante (security.txt) :

/.well-known/security.txt
/security.txt

Et afin d’extraire l’adresse de contact, nous créons la règle ffuf suivante dans ~/.config/ffuf/scraper/security-txt.json :

{
    "active": false,
    "groupname": "security_txt",
    "rules": [
        {
            "name": "contact",
            "rule": "(?mi)Contact:(.*)",
            "target": "body",
            "type": "regexp",
            "onlymatched": true,
            "action": [
                "output"
            ]
        }
    ]
}

Ce qui donne la réponse suivante :

$ ffuf -u https://HOST/SEC --scrapers "security_txt" -w ./security.txt:SEC -w ./hosts.txt:HOST -mc 200 -r
[...]
________________________________________________

 :: Method           : GET
 :: URL              : https://HOST/SEC
 :: Wordlist         : SEC: /home/noraj/R_D/articles/ffuf-2.0.wiki/security.txt
 :: Wordlist         : HOST: /home/noraj/R_D/articles/ffuf-2.0.wiki/hosts.txt
 :: Follow redirects : true
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200
________________________________________________

[Status: 200, Size: 246, Words: 7, Lines: 7, Duration: 17ms]
    * HOST: www.google.com
    * SEC: /.well-known/security.txt
| SCR |
    * contact: Contact: https://g.co/vulnz
    * contact:  https://g.co/vulnz
    * contact: Contact: mailto:security@google.com
    * contact:  mailto:security@google.com

[Status: 200, Size: 169, Words: 6, Lines: 6, Duration: 45ms]
    * HOST: discord.com
    * SEC: /security.txt
| SCR |
    * contact: Contact: https://discord.com/security
    * contact:  https://discord.com/security
    * contact: Contact: mailto:security@discord.com
    * contact:  mailto:security@discord.com

[...]

[Status: 200, Size: 282, Words: 7, Lines: 7, Duration: 66ms]
    * HOST: ovh.com
    * SEC: /.well-known/security.txt
| SCR |
    * contact: Contact: csirt@ovhcloud.com
    * contact:  csirt@ovhcloud.com

:: Progress: [12/12] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

Autre scénario, mettons-nous dans les bottes d’un auditeur qui scrute ardemment les nouvelles offres d’emplois d’ACCEIS, et souhaite récupérer l’intitulé de tous les postes disponibles ainsi que les URL des offres. Il créé alors le fichier de configuration suivant ~/.config/ffuf/scraper/acceis.json.

{
    "active": false,
    "groupname": "acceis",
    "rules": [
        {
            "name": "title",
            "rule": "p.post__title",
            "target": "body",
            "type": "query",
            "onlymatched": true,
            "action": [
                "output"
            ]
        },
        {
            "name": "url",
            "rule": "<a href=\"(.*)\" class=\"post__card \">",
            "target": "body",
            "type": "regexp",
            "onlymatched": true,
            "action": [
                "output"
            ]
        }
    ]
}

name correspond au label qui sera affiché quand une donnée sera extraite, nous récupérons donc le titre à l’aide d’un sélecteur CSS et utilisons une expression régulière pour extraire l’URL.

Voici le résultat ci-dessous. printf '/' | ... -w ... est utilisé uniquement pour faire plaisir à ffuf car il est impossible de ne pas spécifier un mot-clé de fuzzing, nous en créons donc un virtuel depuis l’entrée standard (STDIN).

$ printf '/' | ffuf -u https://www.acceis.fr/nos-offres-demploi/FUZZ -w - --scrapers "acceis"
[...]
________________________________________________

 :: Method           : GET
 :: URL              : https://www.acceis.fr/nos-offres-demploi/FUZZ
 :: Wordlist         : FUZZ: -
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

[Status: 200, Size: 82501, Words: 6239, Lines: 573, Duration: 50ms]
    * FUZZ: /
| SCR |
    * title:
                Ingénieur d’Affaires en cybersécurité
    * title:
                Auditeur en cybersécurité
    * title:
                Consultant(e) cybersécurité
    * title:
                Ingénieur(e) évaluation en cybersécurité et cryptographie
    * url: <a href="https://www.acceis.fr/nos-offres-demploi/ingenieur-daffaires-en-cybersecurite/" class="post__card ">
    * url: https://www.acceis.fr/nos-offres-demploi/ingenieur-daffaires-en-cybersecurite/
    * url: <a href="https://www.acceis.fr/nos-offres-demploi/auditeur-en-cybersecurite/" class="post__card ">
    * url: https://www.acceis.fr/nos-offres-demploi/auditeur-en-cybersecurite/
    * url: <a href="https://www.acceis.fr/nos-offres-demploi/consultante-cybersecurite/" class="post__card ">
    * url: https://www.acceis.fr/nos-offres-demploi/consultante-cybersecurite/
    * url: <a href="https://www.acceis.fr/nos-offres-demploi/ingenieur-evaluation-en-cybersecurite-et-cryptographie/" class="post__card ">
    * url: https://www.acceis.fr/nos-offres-demploi/ingenieur-evaluation-en-cybersecurite-et-cryptographie/

:: Progress: [1/1] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

Un joueur de CTF pourrait tout aussi bien créer une règle d’extraction avec une expression régulière correspondant au format de flag afin de détecter ce dernier tout en parcourant le site. (Nous verrons si cela se révèle utile au BreizhCTF 🙃🏳️)

Pour récupérer le classement d’AttackerKB (ou d’une plateforme de CTF ou plateforme de challenge en ligne) :

{
    "active": true,
    "groupname": "attackerkb",
    "rules": [
        {
            "name": "user",
            "rule": ".username>a",
            "target": "body",
            "type": "query",
            "onlymatched": true,
            "action": [
                "output"
            ]
        }
    ]
}

Résultats :

$ printf '/' | ffuf -u https://attackerkb.com/leaderboard -w - --scrapers "attackerkb" -H "X-Fake: FUZZ"
[...]
________________________________________________

 :: Method           : GET
 :: URL              : https://attackerkb.com/leaderboard
 :: Wordlist         : FUZZ: -
 :: Header           : X-Fake: FUZZ
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

[Status: 200, Size: 22528, Words: 5990, Lines: 430, Duration: 941ms]
    * FUZZ: /
| SCR |
    * user: gwillcox-r7
    * user: wvu-r7
    * user: busterb
    * user: zeroSteiner
    * user: ccondon-r7
    * user: wchen-r7
    * user: bwatters-r7
    * user: nu11secur1ty
    * user: kevthehermit
    * user: space-r7

:: Progress: [1/1] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:01] :: Errors: 0 ::

Ou encore une personne assoiffée de connaissances voulant être au courant du sujet des derniers épisodes de Hack’n’Speak sur Apple Podcast.

{
    "active": true,
    "groupname": "applepodcast",
    "rules": [
        {
            "name": "title",
            "rule": "ol.tracks.tracks--linear-show li.tracks__track.tracks__track--podcast a.link.tracks__track__link--block",
            "target": "body",
            "type": "query",
            "onlymatched": true,
            "action": [
                "output"
            ]
        }
    ]
}
$ printf '/' | ffuf -u https://podcasts.apple.com/fr/podcast/hackn-speak/id1548697084 -w - --scrapers "applepodcast" -H "X-Fake: FUZZ"
[...]
________________________________________________

 :: Method           : GET
 :: URL              : https://podcasts.apple.com/fr/podcast/hackn-speak/id1548697084
 :: Wordlist         : FUZZ: -
 :: Header           : X-Fake: FUZZ
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

[Status: 200, Size: 349047, Words: 22795, Lines: 1651, Duration: 25ms]
    * FUZZ: /
| SCR |
    * title:
          0x1B - @M4yFly | Retour sur la création du lab GOAD et une RCE 9.8 sur GLPI CVE-2022-35914

    * title:
          0x1A - @g0h4n | Retour sur la création de RustHound, l'outil crossplateforme plus rapide que Sharphound !

    * title:
          0x19 - @rkvl | Retour sur la création de Sliver & le redteam aux US (gilet pare balles non obligatoire)

    * title:
          0x18 - @Swissky | Retour sur la création de PayloadsAllTheThings & SSRFmap !

    * title:
          0x17 - @_ZakSec | Retour sur la création de Masky et on parle purple team !

    * title:
          0x16 - @snyff | Retour sur la création et la philosophie de PentesterLab

:: Progress: [1/1] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

Vous l’aurez compris, les cas d’usages sont multiples, nous allons donc arrêter ici pour les exemples.

Traçage des requêtes (Ffufhash)

Dans mon article précédent, la partie sur les mutateurs expliquait comment le numéro de variation était stocké dans la variable d’environnement FFUF_NUM afin d’être affiché puis permettait de retrouver la graine du mutateur ex. : radamsa --seed 2.

Ce concept vient d’être généralisé à l’ensemble de ffuf grâce au mot clé FFUFHASH qui génère un hash pour chaque charge utile afin d’en obtenir un identifiant unique.

Ceci est particulièrement utile pour les requêtes en aveugle (blind) ou hors limite (out of bound, OOB) car la charge utile ne sera pas affichée sur ffuf.

Imaginons que vous tentiez d’exploiter une SSRF ou une RCE OOB qui va tenter de faire une connexion HTTP vers un domaine que vous contrôlez. Quand vous recevez la connexion, vous savez alors que l’application est vulnérable. Le hic ? Impossible de savoir quelle charge utile parmi les 200 000 testées a permis de contourner le WAF car celle-ci a été filtrée ou contient des caractères qui ne sont pas valides dans le champ où vous pourriez récupérer l’information. Ainsi, il faudra utiliser FFUFHASH comme canari.

Ici, j’utilise un collecteur de requêtes HTTP auquel je concatène le canari dans le paramètre url et j’utilise le code de validation auquel je concatène la charge utile SQL pour l’injection dans le paramètre validation_code.

ffuf -u https://example.org/endpoint?url=https://noraj.requestcatcher.com/FFUFHASH&validation_code=556cc23863fef20fab5c456db166bc6eFUZZ -w /usr/share/seclists/Fuzzing/SQLi/quick-SQLi.txt

Vous recevrez par exemple une requête à l’adresse https://noraj.requestcatcher.com/24e511. Puis pour chercher dans ffuf quelle est la charge utile à l’origine de cette requête, il suffira d’utiliser l’option -search.

$ ffuf -search 24e511
Request candidate(s) for hash 24e511
-------------------------------------------
ffuf job started at: 2023-02-13T17:14:25+01:00

GET /24e511?payload=556cc23863fef20fab5c456db166bc6e%27%20OR%20%271%27=%271 HTTP/1.1
Host: noraj.requestcatcher.com
User-Agent: Fuzz Faster U Fool v2.0.0-dev
Accept-Encoding: gzip

Ce qui me permet de retrouver que la charge utile ayant fonctionné était ' OR '1'='1.

À propos de l’auteur

Article écrit par Alexandre ZANNI alias noraj, Ingénieur en Test d’Intrusion chez ACCEIS.