CVE-2023-38491 - How a File Upload Vulnerability Impacted Kirby CMS – Details, Exploit, and Fixes

Kirby is a flexible, flat-file content management system (CMS) widely used for its simplicity and security. However, a recent high-severity vulnerability, CVE-2023-38491, put many Kirby-powered sites at risk. This article breaks down how this bug works, who can exploit it, its impact, and how to stay safe. Technical explanations, code snippets, exploit details, and all references are included for easy use and understanding.

What is CVE-2023-38491?

CVE-2023-38491 is a security vulnerability affecting Kirby CMS versions before 3.5.8.3, 3.6.6.3, 3.7.5.2, 3.8.4.1, and 3.9.6. The vulnerability allows attackers with upload permissions to upload files with unrecognized extensions (e.g., .xyz) containing malicious HTML and JavaScript. If an authenticated user or innocent visitor clicks the link to the uploaded file (and *that file hasn’t been viewed yet*), the browser may render it as text/html and execute embedded JavaScript with the victim’s session privileges.

##### TL;DR: An attacker can upload a booby-trapped file, trick another user into opening it, and steal their session or perform actions as them.

Sites without a safe list for upload file extensions

- Sites using custom code/plugins that rely on the vulnerable method

If your panels are locked down (no untrusted uploads), or you strictly whitelist file types (e.g., only images: JPG, PNG, PDF), *you’re not affected*.

Technical Breakdown: How the Exploit Works

The key bug is in the file response mechanism. Kirby uses a method called Kirby\Http\Response::file() to serve uploaded files to users.

Suppose a malicious Panel user uploads a file named attack.xyz. Its contents might look like

<!-- attack.xyz -->
<html>
  <body>
    <script>
      // Malicious code – for example, stealing user info or their session
      fetch('/api/users', { credentials: 'include' })
         .then(r => r.json())
         .then(data => alert("Your API Data: " + JSON.stringify(data)));
    </script>
    <h1>Gotcha!</h1>
  </body>
</html>

2. Triggering the Attack

- The attacker copies the direct link to attack.xyz and sends it to a victim who’s authenticated on the same Kirby site.
- When the victim opens the link for the first time, Kirby’s backend can’t guess the file’s MIME type (because .xyz isn’t standard). It falls back to serving the file as text/html instead of forcing a download or blocking it.

The victim’s browser loads and parses the file as HTML, so the <script> tag runs.

Result: The JavaScript code runs in the context of the logged-in user’s session. This could steal cookies, perform operations with their permissions, or leak confidential info.

In affected versions, the code handling file downloads looked roughly like this

// Old vulnerable code: simplified
mime_content_type($file) // Tries to guess MIME from file extension
// For unknown extensions, this may return 'text/html' (dangerous fallback)

If the file was never viewed or processed before, the server might respond

HTTP/1.1 200 OK
Content-Type: text/html

> This means browsers render it as a web page. If it contains scripts, they run.

Attacker (with upload access) uploads evil.xyz containing an XSS payload or AJAX to Kirby API.

2. Attacker gets the link to the file: https://victimsite.com/media/pages/somepage/filename/evil.xyz

`

Content-Type: text/html

and renders the file as a page.

6. The malicious JavaScript runs as the victim, able to access site data, make changes, or trick the API.

Here’s an example XSS payload to demonstrate how this could look for an exploit in practice

<!-- Save as payload.xyz and upload via Panel -->
<html>
<head><title>XSS Test</title></head>
<body>
<script>
fetch('/api/site', { credentials: 'include' })
 .then(response => response.json())
 .then(data => alert("Stolen data: " + JSON.stringify(data)));
</script>
</body>
</html>

A real attacker could substitute alert() for something more dangerous (like exfiltrating data via a remote server).

Official Fix

As of the following versions:

Kirby’s maintainers have patched the issue. The revised method now

- Serves unknown file types as text/plain (not HTML)

Now, opening the same attack.xyz file will not execute scripts

Content-Type: text/plain
X-Content-Type-Options: nosniff

The browser displays the file as raw text. JavaScript doesn't run. Attack stopped.

Example PHP check

$allowed = ['jpg', 'jpeg', 'png', 'pdf'];
if (!in_array($uploadedFile->extension(), $allowed)) {
    throw new Exception('Disallowed file type!');
}

Conclusion

CVE-2023-38491 is a serious vulnerability in Kirby CMS that could allow session hijacking, privilege escalation, or sensitive data exfiltration if left unpatched in situations where untrusted users can upload arbitrary files. The fix is simple: update to the patched version of Kirby, double-check your file upload configurations, and stay vigilant with user permissions. Remember, XSS and content-type errors remain a top attack vector in modern web apps. Don't let your site be an easy target!

References

- Original Security Advisory (Kirby CMS GitHub)
- CVE-2023-38491 at NVD
- Kirby Changelog
- Official Kirby Documentation
- Kirby Patch Pull Request (GitHub)

Timeline

Published on: 07/27/2023 16:15:00 UTC
Last modified on: 08/03/2023 13:35:00 UTC