Developed by Newcastle University’s School of Mathematics, Statistics, and Physics, Numbas is an open-source e-assessment/e-learning system.

According to its website, Numbas is used in over 1.000 institutions worldwide and has been in use for over a decade. It is written using mainly JavaScript and Python with Django, with a MySQL database.

Initial Setup

For the analysis to take place, a local instance was set up in a Linux virtual machine, using Visual Studio Code for debugging.

Such setup can be of major help with analyzing certain features, as it allows, for example, to inspect values being held by variables and place breakpoints.

Feature Mapping

The next step was to do an initial mapping of the system, checking its main features and how they operate.

The platform provides the ability to register accounts by default. The analysis was conducted using a low-privileged user.

The “Themes” section was chosen as a good candidate for something of high impact, as it seemed to be using files on disk for importing and customization.

Due to the lack of validation of files being manipulated, it was identified to be possible to read files from disk, write files to disk, as well as to perform their deletion.

If exploited by an adversary, the impact could vary from data exposure, data loss, Denial of Service (DoS), and Remote Code Execution (RCE).

Technical Walkthrough

In Numbas, themes are handled as packaged files. The same applies to extensions.

A personal theme can be created within the application itself, through the profile page.

When writing files, its contents and name are both provided through POST parameters, named source and filename, respectively.

After saving, the file gets rendered within the application. For that, its path is fetched from a GET parameter named filename. By using a path traversal notation, it is possible to read arbitrary files from disk.

Below is shown the affected code. The value from filename is passed directly to load_source() on line 125, which then is passed to read() on line 107.

File deletion can be achieved the same way, this time through a different endpoint.

Below is shown the affected code, where the same scenario occurs – this time with the value from filename being consumed by os.remove().

Both scenarios are already critical, as any secrets, keys, and credentials could be read from disk, and Denial of Service could be achieved by deleting certain key files.

However, code execution could also be reached, by replacing one of the files available.

The exploitation process therefore consisted of creating a new theme as a low-privileged user, creating a new file, and, using a path traversal notation, placing it in a parent directory to replace one of the files shown above.

After sending the request to replace the file, Django will recognize that an existing file was updated and reload it – consequently executing the code written to it.

Below is shown an example of using a reverse shell.

Although the example above of placing a reverse shell works, it is not an elegant solution, as it could cause a Denial of Service (DoS) if the provided address and port are unreachable.

This happens due to Django’s default behavior, where if anything goes wrong during the initialization or reloading, it won’t continue to load the application normally – stopping everything instead.

Puzzling Pieces Together

Last but not least, it was time to automate all the steps, from unauthenticated to establishing a connection for executing commands against the target.

To make the exploit more reliable, a different approach using a Web shell and a pseudo shell was taken.

The proof-of-concept exploit is available in the Exploit Database.

Below is displayed an example of usage.

The ExploitDB version also has a more “passive” option for just checking if the target is vulnerable. This option will only attempt to read a file from disk to determine if the instance is vulnerable.

Closing Thoughts

After delivering a report to the developer responsible, the issues were quickly fixed in less than a week, and an advisory was published not long after.

If you are running or hosting Numbas, the vulnerabilities can be mitigated in your instance by just updating Numbas to version 7.3 or above.


  • February 17th, 2024: First contact with the developer responsible
  • February 21st, 2024: CVEs requested to MITRE
  • February 23rd, 2024: Technical report sent to the developer
  • February 27th, 2024: Commit made to the repository, vulnerabilities fixed
  • March 1st, 2024: Security advisory published
  • March 7th, 2024: Feedback received from MITRE with the CVE assigned



Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *