Local File Inclusion via file:// URI in Mail Compose
Mail composition handler processes image URLs found in outgoing HTML email bodies without validating their URI scheme
Mail composition handler processes image URLs found in outgoing HTML email bodies without validating their URI scheme
EGroupware's mail composition handler processes image URLs found in outgoing HTML email bodies without validating their URI scheme. The condition used to detect local files checks only that the URL does not start with http, which evaluates to true for file:// URIs. This causes file_get_contents() to read arbitrary files from the server filesystem and embed their contents as inline MIME attachments in outgoing mail. Any authenticated user with mail access can exploit this to exfiltrate sensitive server files.
| Field | Value |
|---|---|
| CVE ID | CVE-2026-45016 |
| GHSA | GHSA-c8m7-r2jv-rw63 |
| Severity | High |
| CWE | CWE-73 (External Control of File Name or Path) |
| Package | egroupware (PHP) |
| Affected | < 26.5.20260507 |
| Vulnerability | Arbitrary File Read via file:// URI in Inline Image Processing |
| Impact | Server-side file disclosure to authenticated users |
EGroupware's processURL2InlineImages function, located in api/src/Mail.php, iterates over image URLs embedded in an outgoing HTML email body and fetches their contents to embed them as inline MIME parts. The intent is to resolve relative or local HTTP URLs, but the URI scheme check is insufficient: it only rejects strings that start with http, allowing file://, ftp://, php://, and other schemes to pass through unchecked and reach file_get_contents().
The vulnerable logic sits inside the processURL2InlineImages function in api/src/Mail.php. For each image URL found in the HTML body, the code determines whether to fetch the file locally:
// api/src/Mail.php
foreach ($images[2] as $i => $url)
{
$basedir = $data = '';
$needTempFile = true;
$attachmentData = ['name' => '', 'type' => '', 'file' => '', 'tmp_name' => ''];
if (!str_starts_with($url, 'data:'))
{
$attachmentData['name'] = basename($url);
if (($directory = dirname($url)) == '.') $directory = '';
$ext = pathinfo($attachmentData['name'], PATHINFO_EXTENSION);
$attachmentData['type'] = MimeMagic::ext2mime($ext);
if (strlen($directory) > 1 && !str_ends_with($directory, '/')) {
$directory .= '/';
}
// ...
// Sink: file_get_contents is called when the URL does not start with "http"
if ($myUrl[0] != '/' && strlen($basedir) > 1 && !str_ends_with($basedir, '/')) {
$basedir .= '/';
}
if ($needTempFile && empty($attachment) && !str_starts_with($myUrl, "http"))
{
try {
$data = file_get_contents($basedir . urldecode($myUrl)); // sink
}
catch (\Throwable $e) {
_egw_log_exception($e);
}
}
}
}
The condition !str_starts_with($myUrl, "http") is intended to identify local paths, but it also matches any URI scheme that is not HTTP or HTTPS. The evaluation for a file:// URI is unambiguous:
str_starts_with('file:///etc/passwd', 'http') → false
!false → true
→ file_get_contents('file:///etc/passwd') executes
The contents of the file are then written to a temporary file and attached to the outgoing email as an inline MIME part, where the sender can retrieve it from the sent message or a draft.
The scheme check relies on a negative string prefix match against a single value ("http") rather than an explicit allowlist of permitted schemes. This is a classic incomplete blocklist: it correctly excludes HTTP and HTTPS but permits every other scheme, including file://, ftp://, php://filter/, data://, and any future scheme that does not begin with the string http. The function was designed for convenience, resolving relative image paths in outgoing mail, but the absence of scheme validation turns it into an arbitrary file read primitive for any authenticated user.
Replace the incomplete prefix check with a strict scheme allowlist using a regular expression that explicitly permits only http:// and https:// and rejects everything else:
// Before (vulnerable): rejects only strings starting with "http"
if ($needTempFile && empty($attachment) && !str_starts_with($myUrl, "http"))
{
$data = file_get_contents($basedir . urldecode($myUrl));
}
// After (safe): allowlist of permitted schemes, all others are skipped
if (!preg_match('#^https?://#i', $myUrl))
{
continue; // reject file://, ftp://, php://, data://, and anything else
}
if ($needTempFile && empty($attachment))
{
$data = file_get_contents($basedir . urldecode($myUrl));
}
This ensures that file://, ftp://, php://filter/, and any other non-HTTP scheme never reach file_get_contents(), regardless of how the URL is constructed or encoded.
"http" is not equivalent to checking that it is a relative path; it permits every non-HTTP scheme that exists or will ever exist.file_get_contents(), fopen(), curl_exec(), or similar functions that processes user-supplied URLs needs a strict scheme allowlist upstream, not a blocklist./etc/passwd. Configuration files containing database credentials, private keys, environment files, and application secrets are equally readable if the web server process has read access to them.Open the mail compose window and switch to HTML body mode. Insert the following image tag referencing a sensitive server file:
<img src="file:///etc/passwd">
Any file readable by the web server process can be targeted. High-value targets include:
file:///etc/passwd
file:///var/www/html/egroupware/api/config.php
file:///proc/self/environ
Send the message to any address, including the attacker's own account, or save it as a draft. The processURL2InlineImages function runs during MIME assembly, before the message leaves the server.
Open the sent message or draft. The file referenced by the file:// URI has been fetched by the server, written to a temporary file, and attached as an inline MIME part. The contents of /etc/passwd (or whichever file was targeted) are now embedded in the message body and fully readable by the attacker.
Content-Type: text/plain; name="passwd"
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename="passwd"
cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaAo...
Connect your repositories and let AI agents handle continuous scanning, research, and triage.
Choose which categories you are happy to allow. You can change your mind at any time using the Cookie settings link in the footer.