VYPR
Moderate severityNVD Advisory· Published Dec 30, 2022· Updated Apr 9, 2025

Argument Injection in froxlor/froxlor

CVE-2022-4864

Description

Froxlor prior to 2.0.0-beta1 allows argument injection via insecure Request::get() calls, enabling authenticated attackers to manipulate server-side parameters.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Froxlor prior to 2.0.0-beta1 allows argument injection via insecure Request::get() calls, enabling authenticated attackers to manipulate server-side parameters.

Vulnerability

Overview

CVE-2022-4864 is an argument injection vulnerability in Froxlor, a server administration software. The root cause is that multiple controller files used the Request::get() method, which directly retrieves HTTP GET parameters without sanitization. The commit f2485ecd9aab8da544b5e12891d82ae6fcff5fc7 replaced these calls with Request::any() to enforce more flexible and secure handling [2]. This change indicates that untrusted input could previously reach sensitive operations.

Exploitation

An attacker with at least low-level access to the Froxlor panel can exploit this by crafting HTTP GET requests with malicious parameter names or values. Because Request::get() does not restrict the parameter source, an attacker could inject additional arguments into internal function calls, bypassing intended access controls. The vulnerability affects all versions prior to the beta release of 2.0.0 [1].

Impact

Successful exploitation allows an authenticated attacker to perform actions that normally require higher privileges, such as modifying server configurations, accessing other admin's data, or executing unintended administrative operations. This could lead to full compromise of the hosting platform's management interface [4].

Mitigation

The vulnerability has been patched in Froxlor version 2.0.0-beta1. Users should upgrade immediately. No workaround is documented; the fix involves changing all Request::get() calls to Request::any() across multiple controllers [2]. The project is actively maintained, and the patch is available in the official repository [3].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
froxlor/froxlorPackagist
>= 2.0.0-beta0, < 2.0.0-beta12.0.0-beta1

Affected products

2

Patches

1
f2485ecd9aab

adjust Request-class methods to be more flexible

https://github.com/froxlor/froxlorMichael KaufmannDec 30, 2022via ghsa
31 files changed · +87 57
  • admin_admins.php+1 1 modified
    @@ -39,7 +39,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if (($page == 'admins' || $page == 'overview') && $userinfo['change_serversettings'] == '1') {
     	if ($action == '') {
    
  • admin_configfiles.php+2 2 modified
    @@ -41,8 +41,8 @@
     	}
     
     	// get distro from URL param
    -	$distribution = Request::get('distribution');
    -	$reselect = Request::get('reselect', 0);
    +	$distribution = Request::any('distribution');
    +	$reselect = Request::any('reselect', 0);
     
     	// check for possible setting
     	if (empty($distribution)) {
    
  • admin_cronjobs.php+1 1 modified
    @@ -34,7 +34,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'cronjobs' || $page == 'overview') {
     	if ($action == '') {
    
  • admin_customers.php+1 1 modified
    @@ -42,7 +42,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '0') {
     	if ($action == '') {
    
  • admin_domains.php+1 1 modified
    @@ -47,7 +47,7 @@
     use Froxlor\Validate\Validate;
     use Froxlor\CurrentUser;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'domains' || $page == 'overview') {
     	if ($action == '') {
    
  • admin_index.php+1 1 modified
    @@ -40,7 +40,7 @@
     use Froxlor\Validate\Validate;
     use Froxlor\Language;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($action == 'logout') {
     	$log->logAction(FroxlorLogger::ADM_ACTION, LOG_NOTICE, "logged out");
    
  • admin_ipsandports.php+1 1 modified
    @@ -36,7 +36,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'ipsandports' || $page == 'overview') {
     	if ($action == '') {
    
  • admin_message.php+1 1 modified
    @@ -33,7 +33,7 @@
     use Froxlor\UI\Response;
     use Froxlor\User;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     $note_type = null;
     $note_msg = null;
    
  • admin_mysqlserver.php+1 1 modified
    @@ -36,7 +36,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'mysqlserver' || $page == 'overview') {
     	if ($action == '') {
    
  • admin_phpsettings.php+1 1 modified
    @@ -37,7 +37,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'overview') {
     	if ($action == '') {
    
  • admin_plans.php+2 2 modified
    @@ -39,7 +39,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == '' || $page == 'overview') {
     	if ($action == '') {
    @@ -263,7 +263,7 @@
     			}
     		}
     	} elseif ($action == 'jqGetPlanValues') {
    -		$planid = (int)Request::get('planid', 0);
    +		$planid = (int)Request::any('planid', 0);
     		try {
     			$json_result = HostingPlans::getLocal($userinfo, [
     				'id' => $planid
    
  • admin_settings.php+1 1 modified
    @@ -112,7 +112,7 @@
     		if ($_part == '' || $_part == 'all') {
     			UI::view('settings/index.html.twig', ['fields' => $fields]);
     		} else {
    -			$em = Request::get('em', '');
    +			$em = Request::any('em', '');
     			UI::view('settings/detailpart.html.twig', ['fields' => $fields, 'em' => $em]);
     		}
     	}
    
  • admin_templates.php+3 3 modified
    @@ -39,9 +39,9 @@
     use Froxlor\Validate\Validate;
     use Froxlor\CurrentUser;
     
    -$id = (int)Request::get('id');
    -$subjectid = intval(Request::get('subjectid'));
    -$mailbodyid = intval(Request::get('mailbodyid'));
    +$id = (int)Request::any('id');
    +$subjectid = intval(Request::any('subjectid'));
    +$mailbodyid = intval(Request::any('mailbodyid'));
     
     $available_templates = [
     	'createcustomer',
    
  • admin_traffic.php+1 1 modified
    @@ -31,7 +31,7 @@
     use Froxlor\UI\Request;
     use Froxlor\UI\Response;
     
    -$range = Request::get('range', 'currentmonth');
    +$range = Request::any('range', 'currentmonth');
     
     if ($page == 'overview' || $page == 'customers') {
     	try {
    
  • api_keys.php+1 1 modified
    @@ -49,7 +49,7 @@
     // and therefore does not need to require lib/init.php
     
     $del_stmt = Database::prepare("DELETE FROM `" . TABLE_API_KEYS . "` WHERE id = :id");
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     // do the delete and then just show a success-message and the apikeys list again
     if ($action == 'delete' && $id > 0) {
    
  • customer_domains.php+2 2 modified
    @@ -47,13 +47,13 @@
     	Response::redirectTo('customer_index.php');
     }
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'overview' || $page == 'domains') {
     	if ($action == '') {
     		$log->logAction(FroxlorLogger::USR_ACTION, LOG_NOTICE, "viewed customer_domains::domains");
     
    -		$parentdomain_id = (int)Request::get('pid', '0');
    +		$parentdomain_id = (int)Request::any('pid', '0');
     
     		try {
     			$domain_list_data = include_once dirname(__FILE__) . '/lib/tablelisting/customer/tablelisting.domains.php';
    
  • customer_email.php+1 1 modified
    @@ -47,7 +47,7 @@
     	Response::redirectTo('customer_index.php');
     }
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'overview' || $page == 'emails') {
     	if ($action == '') {
    
  • customer_extras.php+1 1 modified
    @@ -46,7 +46,7 @@
     	Response::redirectTo('customer_index.php');
     }
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'overview' || $page == 'htpasswds') {
     	// redirect if this customer sub-page is hidden via settings
    
  • customer_ftp.php+1 1 modified
    @@ -44,7 +44,7 @@
     	Response::redirectTo('customer_index.php');
     }
     
    -$id = (int)Request::get('id', 0);
    +$id = (int)Request::any('id', 0);
     
     if ($page == 'overview' || $page == 'accounts') {
     	if ($action == '') {
    
  • customer_mysql.php+1 1 modified
    @@ -50,7 +50,7 @@
     $sql_root = Database::getSqlData();
     Database::needRoot(false);
     
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     if ($page == 'overview' || $page == 'mysqls') {
     	if ($action == '') {
    
  • customer_traffic.php+1 1 modified
    @@ -37,7 +37,7 @@
     	Response::redirectTo('customer_index.php');
     }
     
    -$range = Request::get('range', 'currentyear');
    +$range = Request::any('range', 'currentyear');
     
     if ($page == 'current') {
     	$range = 'currentmonth';
    
  • dns_editor.php+1 1 modified
    @@ -40,7 +40,7 @@
     // This file is being included in admin_domains and customer_domains
     // and therefore does not need to require lib/init.php
     
    -$domain_id = (int)Request::get('domain_id');
    +$domain_id = (int)Request::any('domain_id');
     
     $record = isset($_POST['dns_record']) ? trim($_POST['dns_record']) : null;
     $type = isset($_POST['dns_type']) ? $_POST['dns_type'] : 'A';
    
  • error_report.php+1 1 modified
    @@ -37,7 +37,7 @@
     // This file is being included in admin_domains and customer_domains
     // and therefore does not need to require lib/init.php
     
    -$errid = Request::get('errorid');
    +$errid = Request::any('errorid');
     
     if (!empty($errid)) {
     	// read error file
    
  • lib/ajax.php+1 0 modified
    @@ -42,5 +42,6 @@
     try {
     	echo (new Ajax)->handle();
     } catch (Exception $e) {
    +	header("Content-Type: application/json");
     	echo \Froxlor\Api\Response::jsonErrorResponse($e->getMessage(), 500);
     }
    
  • lib/Froxlor/Ajax/Ajax.php+17 16 modified
    @@ -53,8 +53,8 @@ class Ajax
     	 */
     	public function __construct()
     	{
    -		$this->action = $_GET['action'] ?? $_POST['action'] ?? null;
    -		$this->theme = $_GET['theme'] ?? 'Froxlor';
    +		$this->action = Request::any('action');
    +		$this->theme = Request::any('theme', 'Froxlor');
     
     		UI::sendHeaders();
     		UI::sendSslHeaders();
    @@ -112,7 +112,8 @@ private function getNewsfeed()
     		$feed = "https://inside.froxlor.org/news/";
     
     		// Set custom feed if provided
    -		if (isset($_GET['role']) && $_GET['role'] == "customer") {
    +		$role = Request::get('role');
    +		if ($role == "customer") {
     			$custom_feed = Settings::Get("customer.news_feed_url");
     			if (!empty(trim($custom_feed))) {
     				$feed = $custom_feed;
    @@ -140,7 +141,7 @@ private function getNewsfeed()
     
     		if ($news === false) {
     			$err = [];
    -			foreach(libxml_get_errors() as $error) {
    +			foreach (libxml_get_errors() as $error) {
     				$err[] = $error->message;
     			}
     			return $this->errorResponse(
    @@ -205,7 +206,7 @@ private function getUpdateCheck()
     		} catch (Exception $e) {
     			// don't display anything if just not allowed due to permissions
     			if ($e->getCode() != 403) {
    -				Response::dynamicError($e->getMessage());
    +				return $this->errorResponse($e->getMessage(), $e->getCode());
     			}
     		}
     	}
    @@ -215,7 +216,7 @@ private function getUpdateCheck()
     	 */
     	private function searchGlobal()
     	{
    -		$searchtext = Request::get('searchtext');
    +		$searchtext = Request::any('searchtext');
     
     		$result = [];
     
    @@ -236,27 +237,27 @@ private function searchGlobal()
     	private function updateTablelisting()
     	{
     		$columns = [];
    -		foreach ((Request::get('columns') ?? []) as $value) {
    +		foreach ((Request::any('columns') ?? []) as $value) {
     			$columns[] = $value;
     		}
     		if (!empty($columns)) {
    -			Listing::storeColumnListingForUser([Request::get('listing') => $columns]);
    +			Listing::storeColumnListingForUser([Request::any('listing') => $columns]);
     			return $this->jsonResponse($columns);
     		}
     		return $this->errorResponse('At least one column must be selected', 406);
     	}
     
     	private function resetTablelisting()
     	{
    -		Listing::deleteColumnListingForUser([Request::get('listing') => []]);
    +		Listing::deleteColumnListingForUser([Request::any('listing') => []]);
     		return $this->jsonResponse([]);
     	}
     
     	private function editApiKey()
     	{
    -		$keyid = isset($_POST['id']) ? (int)$_POST['id'] : 0;
    -		$allowed_from = isset($_POST['allowed_from']) ? $_POST['allowed_from'] : "";
    -		$valid_until = isset($_POST['valid_until']) ? $_POST['valid_until'] : "";
    +		$keyid = Request::post('id', 0);
    +		$allowed_from = Request::post('allowed_from', "");
    +		$valid_until = Request::post('valid_until', "");
     
     		if (empty($keyid)) {
     			return $this->errorResponse('Invalid call', 406);
    @@ -318,9 +319,9 @@ private function editApiKey()
     	private function getConfigDetails()
     	{
     		if (isset($this->userinfo['adminsession']) && $this->userinfo['adminsession'] == 1 && $this->userinfo['change_serversettings'] == 1) {
    -			$distribution = isset($_POST['distro']) ? $_POST['distro'] : "";
    -			$section = isset($_POST['section']) ? $_POST['section'] : "";
    -			$daemon = isset($_POST['daemon']) ? $_POST['daemon'] : "";
    +			$distribution = Request::post('distro', "");
    +			$section = Request::post('section', "");
    +			$daemon = Request::post('daemon', "");
     
     			// validate distribution config-xml exists
     			$config_dir = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/lib/configfiles/');
    @@ -375,7 +376,7 @@ private function getConfigJsonExport()
     	 */
     	private function loadLanguageString()
     	{
    -		$langid = isset($_POST['langid']) ? $_POST['langid'] : "";
    +		$langid = Request::post('langid', "");
     		if (preg_match('/^([a-zA-Z\.]+)$/', $langid)) {
     			return $this->jsonResponse(lng($langid));
     		}
    
  • lib/Froxlor/Install/Install.php+4 4 modified
    @@ -80,8 +80,8 @@ public function __construct(array $cliData = [])
     		$this->formfield = require dirname(__DIR__, 3) . '/lib/formfields/install/formfield.install.php';
     
     		// set actual step
    -		$this->currentStep = $cliData['step'] ?? Request::get('step', 0);
    -		$this->extendedView = $cliData['extended'] ?? Request::get('extended', 0);
    +		$this->currentStep = $cliData['step'] ?? Request::any('step', 0);
    +		$this->extendedView = $cliData['extended'] ?? Request::any('extended', 0);
     		$this->maxSteps = count($this->formfield['install']['sections']);
     
     		// set actual php version and extensions
    @@ -114,7 +114,7 @@ public function __construct(array $cliData = [])
     	public function handle(): void
     	{
     		// handle form data
    -		if (!is_null(Request::get('submit')) && $this->currentStep) {
    +		if (!is_null(Request::any('submit')) && $this->currentStep) {
     			try {
     				$this->handleFormData($this->formfield['install']);
     			} catch (Exception $e) {
    @@ -266,7 +266,7 @@ private function validateRequest(array $fields): array
     	{
     		$attributes = [];
     		foreach ($fields as $name => $field) {
    -			$attributes[$name] = $this->validateAttribute(Request::get($name), $field);
    +			$attributes[$name] = $this->validateAttribute(Request::any($name), $field);
     			if (isset($field['next_to'])) {
     				$attributes = array_merge($attributes, $this->validateRequest($field['next_to']));
     			}
    
  • lib/Froxlor/UI/Request.php+30 2 modified
    @@ -31,19 +31,47 @@
     class Request
     {
     	/**
    -	 * Get key from current request.
    +	 * Get key from current $_GET or $_POST request.
     	 *
     	 * @param $key
     	 * @param string|null $default
     	 * @return mixed|string|null
     	 */
    -	public static function get($key, string $default = null)
    +	public static function any($key, string $default = null)
     	{
     		self::cleanAll();
     
     		return $_GET[$key] ?? $_POST[$key] ?? $default;
     	}
     
    +	/**
    +	 * Get key from current $_GET request.
    +	 *
    +	 * @param $key
    +	 * @param string|null $default
    +	 * @return mixed|string|null
    +	 */
    +	public static function get($key, string $default = null)
    +	{
    +		self::cleanAll();
    +
    +		return $_GET[$key] ?? $default;
    +	}
    +
    +	/**
    +	 * Get key from current $_POST request.
    +	 *
    +	 * @param $key
    +	 * @param string|null $default
    +	 * @return mixed|string|null
    +	 */
    +	public static function post($key, string $default = null)
    +	{
    +		self::cleanAll();
    +
    +		return $_POST[$key] ?? $default;
    +	}
    +
     	/**
     	 * Check for xss attempts and clean important globals and
     	 * unsetting every variable registered in $_REQUEST and as variable itself
    
  • lib/functions.php+1 1 modified
    @@ -43,5 +43,5 @@ function old(string $identifier, string $default = null, string $session = null)
     	if ($session && isset($_SESSION[$session])) {
     		return $_SESSION[$session][$identifier] ?? $default;
     	}
    -	return Request::get($identifier, $default);
    +	return Request::any($identifier, $default);
     }
    
  • lib/init.php+3 3 modified
    @@ -295,9 +295,9 @@
     unset($js);
     unset($css);
     
    -$action = Request::get('action');
    -$page = Request::get('page', 'overview');
    -$gSearchText = Request::get('searchtext');
    +$action = Request::any('action');
    +$page = Request::any('page', 'overview');
    +$gSearchText = Request::any('searchtext');
     
     // clear request data
     if (!$action && isset($_SESSION)) {
    
  • logfiles_viewer.php+2 2 modified
    @@ -39,8 +39,8 @@
     // This file is being included in admin_domains and customer_domains
     // and therefore does not need to require lib/init.php
     
    -$domain_id = (int)Request::get('domain_id');
    -$last_n = (int)Request::get('number_of_lines', 100);
    +$domain_id = (int)Request::any('domain_id');
    +$last_n = (int)Request::any('number_of_lines', 100);
     
     // user's with logviewenabled = false
     if (AREA != 'admin' && $userinfo['logviewenabled'] != '1') {
    
  • ssl_certificates.php+1 1 modified
    @@ -43,7 +43,7 @@
     // and therefore does not need to require lib/init.php
     
     $success_message = "";
    -$id = (int)Request::get('id');
    +$id = (int)Request::any('id');
     
     // do the delete and then just show a success-message and the certificates list again
     if ($action == 'delete') {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.