Serendipity 2.0.3: From File Upload to Code Execution


Serendipity is an easy to maintain blog engine. There are a lot of plugins that can be used to extend the functionality, this article will focus on its core though. With close to 125,000 lines it is a medium-sized web application. In this post, we will show how attackers can bypass existing security mechanisms which can lead to remote code execution attacks.

RIPS Analysis

The analysis of Serendipity with RIPS took 67 seconds to complete. The total amount of issues is reasonable for a web application of this size. Most of the 36 low severe issues detected are information leakage issues, for example, when an error message leaks the DBMS system of a corrupted database query. In the following, we will investigate a more severe issue.

The truncated analysis results are available in our RIPS demo application. Please note that we limited the results to the issues described in this post in order to ensure a fix is available.

Case Study

RIPS identified two critical types of security vulnerabilities in Serendipity. First, several SQL injections were detected that can be used to elevate privileges from a regular user to administrative privileges. We will not explain these issues in detail though because we already described plenty of SQL injections in our previous advent calendar gifts. Today, we would like to present something a bit more unique.

File Upload Extension Bypass

An interesting vulnerability was revealed in Serendipity <= 2.0.3 inside the file upload manager. The issue can be exploited by an attacker after he extended the user privileges. Here, the goal of an attacker could be to upload an PHP file in order to execute arbitrary PHP code on the target. However, there is a security system in place that tries to prevent this.


switch ($serendipity['GET']['adminAction']) {
    case 'add':

        foreach($_FILES['serendipity']['name']['userfile'] AS $idx => $uploadfiles) {
            foreach($uploadfiles AS $uploadfile) {
                $target_filename = $serendipity['POST']['target_filename'][$idx];
                if (!empty($target_filename)) {
                    $tfile = $target_filename;
                } elseif (!empty($uploadfile)) {
                    $tfile = $uploadfile;
                } else {
                $tfile = serendipity_uploadSecure(basename($tfile));
                if (serendipity_isActiveFile($tfile)) {


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

The actual file upload is protected by the function serendipity_isActiveFile(). It checks the name of a file and stops the upload if the file ends with a blacklisted extension, such as php. It is always recommended to specify the allowed file extensions in a whitelist instead. Although this check hinders attackers depending on the web server’s configuration, there is another way to bypass the blacklist that works configuration independently.


switch ($serendipity['GET']['adminAction']) {
    case 'add':

        if ($serendipity['POST']['adminSubAction'] == 'properties') {
            $properties = serendipity_parsePropertyForm();

function serendipity_parsePropertyForm() {
    global $serendipity;

    foreach($serendipity['POST']['mediaProperties'] AS $id => $media) {

        if ($serendipity['POST']['oldDir'][$id] != $serendipity['POST']['newDir'][$id]) {
function serendipity_moveMediaDirectory($oldDir, $newDir, $type = 'dir', $item_id = null, $file = null) {
    global $serendipity;
    $real_oldDir = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $oldDir;
    $real_newDir = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $newDir;

    if ($type == 'filedir') {

        $oldfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $oldDir . $pick['name'] . (empty($pick['extension']) ? '' : '.' . $pick['extension']);
        $newfile = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $newDir . $pick['name'] . (empty($pick['extension']) ? '' : '.' . $pick['extension']);

        $renameValues = array(array(
            'from'    => $oldfile,
            'to'      => $newfile,

        rename($renameValues[0]['from'], $renameValues[0]['to']);

With the help of the file manager, it is possible to move files to another directory. The method serendipity_moveMediaDirectory() moves a file by using PHP’s built-in rename() function. What is stopping an attacker from abusing this functionality and from simply renaming an uploaded file from something.jpg to something.php? Line 3378 in the code shown above does. It appends the old file extension to the new name if there is an extension available, so the file would be renamed to something.php.jpg. There is one thing the developer did not consider though: a malicious file without extension. An attacker can simply create a file with the name php and move it to the “directory” something.. This elegant solution renames the file to something.php and allows the attacker to execute arbitrary PHP code when accessing the file in the web root.

Time Line

2016/09/14First contact with vendor
2016/09/15Vendor responds with fix
2016/09/26Vendor releases fixed version


Serendipity is a solid blog software. There are some rough edges - no doubt - but its creators are keen on improving the code and making sure that its users are secure. They responded very fast and a fixed version was released after only a few days. We would like to thank the Serendipity team for the very professional collaboration.

The vulnerability shown here is a prime example for the fact that file uploads in PHP are dangerous. It is easy to make a mistake and the consequences are disastrous. In order to prevent such vulnerabilities it is advised to not allow direct access to uploaded files. Instead, these files can be stored outside of the web directory and meta data can be retrieved from the database when necessary.

Follow us on Twitter to be notified when the next gift of our advent calendar is opened!

APAV Time Table

19 Feb 2019Simon ScannellWordPress 5.0.0 Remote Code Execution
29 Jan 2019Simon ScannellCTF Writeup: Complex Drupal POP Chain
15 Jan 2019Simon ScannellLearnings from WordPress Security Month
24 Dec 2016Johannes DahseWhat we learned from our Advent Calendar
23 Dec 2016Hendrik Buchwalde107 2.1.2: SQL Injection through Object Injection
22 Dec 2016Daniel PeerenSecurity Compliance with Static Code Analysis
21 Dec 2016Martin BednorzAbanteCart 1.2.8 - Multiple SQL Injections
20 Dec 2016Martin BednorzKliqqi From Cross-Site Request Forgery to Code Execution
19 Dec 2016Robin PeraglieosClass 3.6.1: Remote Code Execution via Image File
18 Dec 2016Daniel PeerenContinuous Integration - Jenkins at your service
17 Dec 2016Johannes DahseOpenConf 5.30 - Multi-Step Remote Command Execution
16 Dec 2016Robin PeraglieRedaxo 5.2.0: Remote Code Execution via CSRF
15 Dec 2016Dennis DeteringGuest Post: Vtiger 6.5.0 - SQL Injection
14 Dec 2016Hendrik BuchwaldThe State of Wordpress Security
13 Dec 2016Johannes DahsephpBB 2.0.23 - From Variable Tampering to SQL Injection
12 Dec 2016Martin BednorzTeampass Unauthenticated SQL Injection
11 Dec 2016Daniel PeerenRescanning Applications with RIPS
10 Dec 2016Hendrik BuchwaldNon-Exploitable Security Issues
9 Dec 2016Hendrik BuchwaldPrecurio 2.1: Remote Command Execution via Xinha Plugin
8 Dec 2016Martin BednorzPHPKit 1.6.6: Code Execution for Privileged Users
7 Dec 2016Hendrik BuchwaldSerendipity 2.0.3: From File Upload to Code Execution
6 Dec 2016Robin PeraglieRoundcube 1.2.2: Command Execution via Email
5 Dec 2016Hendrik BuchwaldExpression Engine 3.4.2: Code Reuse Attack
4 Dec 2016Johannes DahseIntroducing the RIPS analysis engine
3 Dec 2016Martin BednorzeFront 3.6.15: Steal your professors password
2 Dec 2016Martin BednorzCoppermine 1.5.42: Second-Order Command Execution
1 Dec 2016Hendrik BuchwaldFreePBX 13: From Cross-Site Scripting to Remote Command Execution
25 Nov 2016Martin BednorzAnnouncing the Advent of PHP Application Vulnerabilities

Disclaimer: The information provided here is for educational purposes only. It is your responsibility to obey all applicable local, state and federal laws. RIPS Technologies GmbH assumes no liability and is not responsible for any misuse or damages caused by direct or indirect use of the information provided.

Tags: hendrik buchwald, php, security, serendipity, file upload, security bypass,

Author: Hendrik Buchwald

CISO, Co-Founder

Hendrik graduated in computer science at the Ruhr-University Bochum and is a professional software engineer. In addition to designing and building complex systems, he particularly enjoys breaking and securing web applications. He is an experienced security consultant and the lead developer of the open source web application firewall Shadow Daemon.

Is your application secure?  Scan Your Code

Related Posts


comments powered by Disqus