Files
Siro/docs/08_security/التقرير_الأمني_Backend_Siro.md

638 lines
21 KiB
Markdown

---
lang: ar
dir: rtl
---
# 🔐 تقرير الفحص الأمني الشامل — Backend تطبيق Siro
**التاريخ:** 8 يوليو 2026
**الفاحص:** مدقق أمني مستقل
**النسخة:** 1.0
---
## 📊 ملخص تنفيذي
تم فحص backend Siro فحصاً أمنياً شاملاً، شمل:
- بنية المصادقة والتفويض (JWT، OTP، Rate Limiting)
- طبقة قاعدة البيانات والاستعلامات
- إدارة الأسرار والتشفير
- نقاط الدخول الحساسة (تسجيل الدخول، رفع الملفات، تحويلات المحفظة)
- أمان الويب العام (CORS، Headers، إدارة الأخطاء)
### 📈 التقييم العام
**المستوى:** جيد جداً ✅
**الدرجة:** 8.2/10
Backend مبني بمستوى أمني واعي وقوي. لا توجد ثغرات حرجة قابلة للاستغلال المباشر، لكن هناك **3 نقاط عالية الأولوية** يجب إغلاقها فوراً قبل الإنتاج.
---
## ✅ نقاط القوة (ما تم إنجازه بشكل صحيح)
### 1️⃣ البوابة الموحدة والـ Headers الأمنية
**الملف:** `backend/core/bootstrap.php`
✓ جميع headers الأمان مُفعّلة:
- `Strict-Transport-Security: max-age=31536000` (HSTS)
- `X-Frame-Options: DENY` (منع ClickJacking)
- `X-Content-Type-Options: nosniff` (منع MIME sniffing)
- `Content-Security-Policy: default-src 'self'` (صارم جداً)
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Permissions-Policy: geolocation=(), microphone=(), camera=()`
- حذف `X-Powered-By` (إخفاء تكنولوجيا الخادم)
✓ عرض الأخطاء مُطفأ في الإنتاج، مربوط بـ `APP_DEBUG`
✓ لا تسريب لـ stack traces أو معلومات النظام
---
### 2️⃣ نظام المصادقة JWT — قوي ومتعدد الطبقات
**الملف:** `backend/core/Auth/JwtService.php`
الميزات الأمنية:
| الميزة | التفاصيل |
|-------|---------|
| **JTI + Blacklist** | كل توكن له معرّف فريد (JTI) في Redis لمنع التكرار والإلغاء |
| **Device Fingerprint** | ربط الجهاز عبر `sha256(fingerprint + pepper)` — يمنع سرقة التوكن بين أجهزة |
| **Refresh Tokens** | منفصلة ومخزنة بآمان في Redis (30 يوم) |
| **HMAC مشتق** | كل مستخدم يحصل على `hash_hmac('sha256', userId, hmacSecret)` |
| **التحقق من Issuer** | تتحقق من أن مصدر التوكن (iss) صحيح |
| **تقييد Registration Tokens** | توكنات التسجيل تعمل فقط على 37 endpoint محددة مسبقاً، ممنوعة من باقي الـ API |
| **بصمة التطبيق** | للدور `service`: التحقق من توقيع التطبيق الرسمي |
---
### 3️⃣ لا توجد ثغرات SQL Injection
**الملفات:** جميع الملفات الرئيسية
✓ **جميع الاستعلامات** تستخدم PDO Prepared Statements
✓ `ATTR_EMULATE_PREPARES => false` — يُجبر عل فصل البيانات عن الأوامر
✓ حتى الـ `WHERE` الديناميكي في `login.php` يبني placeholders:
```php
$conditions[] = "passengers.phone = :phone";
$params[':phone'] = $phone;
$stmt = $con->prepare($sql);
$stmt->execute($params); // آمن 100%
```
---
### 4️⃣ عدم وجود RCE (Remote Code Execution)
✓ لا يوجد `eval()` أو `create_function()`
✓ لا يوجد `shell_exec()` أو `system()` مع مدخلات المستخدم
✓ لا يوجد `passthru()` أو `proc_open()`
✓ لا SSRF: لا يوجد `file_get_contents($userUrl)` أو `curl على URL من المستخدم`
---
### 5️⃣ كلمات المرور و OTP محمية بـ Bcrypt
**الملفات:** `core/Services/OtpService.php`، جميع نقاط الدخول
✓ **Bcrypt في كل مكان:** `password_hash(..., PASSWORD_BCRYPT)`
✓ **OTP محمي بـ Bcrypt في Redis:**
- تُخزّن مُشفّرة: `password_hash($otp, PASSWORD_BCRYPT)`
- صلاحية 5 دقائق
- حد أقصى 3 محاولات خاطئة
- وقفل الحساب 30 دقيقة بعد الفشل
✓ **حماية من Timing Attack:** في `loginAdmin.php`:
```php
$elapsed = microtime(true) - $startTime;
if ($elapsed < 0.1) usleep((int)((0.1 - $elapsed) * 1000000));
```
---
### 6️⃣ رفع الملفات آمن جداً
**الملفات:** `core/helpers.php`، `upload_audio.php`
✓ **فحص MIME حقيقي** عبر `finfo` (وليس الامتداد فقط):
```php
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($file['tmp_name']);
if (!in_array($mime_type, $allowed_mimes, true)) {
return ['success' => false, 'error' => 'Invalid file type'];
}
```
✓ **أسماء عشوائية:** `bin2hex(random_bytes(16))` — منع التخمين
✓ **حدود حجم:** 5MB أقصى
✓ **صلاحيات آمنة:** `mkdir($targetDir, 0750, true)` — قراءة للويب فقط
---
### 7️⃣ إدارة الأسرار والمفاتيح — نظيفة جداً
✓ **تاريخ Git نظيف:**
- ملف `.env` **لم يُرفع أبداً** ويوجد في `.gitignore`
- لا `.enckey` في Git
- لا `service-account.json` في Git
- لا أي مفاتيح (`*.pem`, `*.key`) في Git
✓ **المفاتيح تُقرأ من:**
- ملفات خارج الـ webroot (`/home/username/.enckey`)
- متغيرات البيئة (`getenv('ENCRYPTION_KEY_PATH')`)
- لا توجد مفاتيح مُدرجة في الكود (hardcoded)
---
### 8️⃣ التشفير الحديث (AES-256-GCM)
**الملف:** `core/Security/EncryptionHelper.php`
✓ **GCM للبيانات الثنائية:**
- IV عشوائي (12 بايت)
- Authentication tag (تحقق من عدم التعديل)
- معايير NIST
✓ **توافقية CBC القديم** للبيانات الموجودة (مرحلة انتقالية)
---
### 9️⃣ تحويلات المحفظة محمية من الانتحال
**الملف:** `ride/driverWallet/transfer.php`
✓ **معرّف المُرسِل يأتي من JWT فقط:**
```php
if (empty($user_id) || $role !== 'driver') {
exit; // ممنوع تمرير senderID من الطلب
}
$senderID = $user_id; // من JWT آمن
```
✓ **حدود قصوى لكل دولة:** Syria: 500، Egypt: 1000، Jordan: 15
✓ **منع التحويل للنفس**
✓ **التحقق من وجود المستقبِل** قبل المعالجة
---
### 🔟 CORS بقائمة سماح محددة
**الملف:** `core/bootstrap.php`
✓ قائمة `CORS_ALLOWED_ORIGINS` محدودة:
```php
$allowedOrigins = array_map('trim', explode(',', getenv('CORS_ALLOWED_ORIGINS') ?: 'https://siromove.com,...'));
```
✓ Credentials **لا تُرسل إلا للنطاقات المسموح بها**
✓ لا wildcard مع credentials
---
### 1️⃣1️⃣ حماية من Email Header Injection
✓ تنظيف CR/LF في رؤوس البريد:
```php
$from = str_replace(["\r", "\n", "\r\n"], '', $from);
```
---
## ⚠️ النقاط السلبية والتحديات الأمنية
---
### 🔴 مستوى عالي جداً (حرج) — يجب إصلاحها فوراً
#### **H-1: تسريب الكود المصدري (Source Code Disclosure)**
**الملف:** `backend/check_server_code.php`
```php
<?php
header('Content-Type: text/plain');
echo file_get_contents(__DIR__ . '/auth/loginFromGooglePassenger.php');
```
**الخطورة:** 🔴🔴🔴
- **أي شخص** يستطيع زيارة `check_server_code.php` والحصول على الكود المصدري الكامل
- **بدون مصادقة نهائياً** — لا JWT، لا معرّفات، بلا حماية
- يكشف منطق تسجيل الدخول، أسرار API، وآليات المصادقة
**المثال:**
```
GET /siro/check_server_code.php HTTP/1.1
```
✅ النتيجة: الكود المصدري كامل
**الحل:**
```bash
# احذف الملف من السيرفر تماماً
rm backend/check_server_code.php
```
---
#### **H-2: تعطيل حماية Rate Limiting على التسجيل**
**الملف:** `backend/core/Auth/RateLimiter.php` (سطور 48-51)
```php
// Disable limit for register during debugging
if ($type === 'register') {
return true; // ⚠️ يسمح بطلبات غير محدودة
}
```
**الخطورة:** 🔴🔴🔴
- كود تصحيح (debugging) **بقي في الإنتاج**
- endpoint التسجيل **بدون أي حد** على الطلبات
- هجوم **Brute Force على أرقام الهاتف:**
- المهاجم يرسل 10,000 OTP لأرقام مختلفة
- كل OTP تكلفة مالية (رسائل SMS)
- إساءة استخدام رصيد SMS والموارد
**مثال الهجوم:**
```bash
# إرسال 10,000 طلب تسجيل بدون تأخير
for i in {1..10000}; do
curl -X POST https://api.siromove.com/auth/signup \
-d "phone=962791${i}&country=jordan"
done
```
**الحل:**
أزل الشرط من السطرين 48-51:
```php
// قبل (خطأ):
if ($type === 'register') {
return true;
}
// بعد (صحيح):
// (احذف هذا الشرط كله)
```
الحد الأصلي (`3 محاولات/ساعة`) كافٍ جداً.
---
#### **H-3: عدم حماية الملفات الحساسة على مستوى الخادم**
**الملفات المعرّضة:**
- `.env` — قاعدة بيانات، مفاتيح API
- `.git/` — تاريخ كامل المشروع
- `logs/` — معلومات النظام والأخطاء
- `composer.json` / `composer.lock` — نسخ المكتبات
**الخطورة:** 🔴🔴🔴
إذا كان document root للخادم هو `backend/` مباشرة، قد يستطيع المهاجم:
- تنزيل `.env` ← الحصول على جميع الأسرار (DB passwords, API keys)
- نسخ repository من `.git` ← تحليل كل تغيير وكل سر في التاريخ
- قراءة `logs/` ← الحصول على أسماء جداول، أرقام مستخدمين، stack traces
**الحل (Nginx):**
```nginx
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ (composer\.json|composer\.lock) {
deny all;
}
location /logs/ {
deny all;
}
```
**الحل (Apache):**
أنشئ ملف `.htaccess` في مجلد `backend/`:
```apache
# منع الوصول للملفات المخفية
<FilesMatch "^\.">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# منع الوصول للملفات الحساسة
<FilesMatch "(composer\.json|composer\.lock)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</FilesMatch>
# منع الوصول لمجلد logs
<Directory "*/logs">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</Directory>
# منع تنزيل ملفات PHP
<FilesMatch "\.php$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</FilesMatch>
```
**الأفضل: فصل Document Root**
```
/var/www/siro/
├── public/ ← document root فقط
│ └── index.php
├── backend/ ← خارج الـ webroot
│ ├── core/
│ ├── auth/
│ └── .env
```
---
### 🟠 مستوى متوسط — يجب إصلاحها قريباً
#### **M-1: التشفير CBC بـ IV ثابت (Deterministic Encryption)**
**الملف:** `core/Security/EncryptionHelper.php` (سطر 24 و دالة `encryptData`)
```php
$this->cbcIv = $cbcIv ?: getenv('initializationVector') ?: str_repeat('0', 16);
// ثم في encryptData:
$encrypted = openssl_encrypt($padded, self::ALGO_CBC, $this->key,
OPENSSL_RAW_DATA, $this->cbcIv);
```
**المشكلة:**
- نفس النص ينتج نفس الـ ciphertext دائماً
- **تسريب معلومات**: إذا كنت تشفّر أرقام هواتف، هاتف "962791234567" دائماً ينتج نفس القيمة
- **لا توثيق (MAC)**: لا يوجد فحص عدم التعديل على البيانات
**مثال الهجوم:**
```
عميل 1 يشفّر "962791234567" → ينتج "abc123def456"
عميل 2 يشفّر "962791234567" → ينتج نفس "abc123def456" ❌
المهاجم يرى التطابق ويكتشف نفس الهاتف
```
**الحل:**
استخدم `encryptBinary` بدلاً منه (يستخدم GCM مع IV عشوائي):
```php
// قبل (خطأ):
$encrypted = $encryptionHelper->encryptData($plainText);
// بعد (صحيح):
// تحويل النص إلى binary وتشفيره بـ GCM
$encrypted = $encryptionHelper->encryptBinary(
mb_convert_encoding($plainText, 'UTF-8')
);
```
---
#### **M-2: تعطيل التحقق من شهادة TLS (SSL Certificate Verification)**
**الملف:** `bot/cron_silent_push_inactive.php` (سطر 88)
```php
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
```
**الخطورة:** 🟠🟠
- يرسل **مفتاح Firebase الخاص بك** إلى خادم FCM
- بدون التحقق من أن الخادم هو فعلاً `fcm.googleapis.com`
- قابل لهجمات **MITM (Man in the Middle)**:
- هاكر يعترض الاتصال
- يقرأ مفتاح Firebase
- يرسل إشعارات وهمية
```
┌─────────────┐ SSL DISABLED ┌─────────────┐
│ Siro API │────────────────────→ │ ATTACKER │
└─────────────┘ No Verify └─────────────┘
↓
يقرأ مفتاح Firebase
```
**الحل:**
أزل السطر، أو اجعله `true`:
```php
// قبل:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// بعد:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
// أو احذفه (الافتراضي true)
```
**ملاحظة إضافية:**
الملف يستخدم FCM Legacy (القديم). الـ [backend/ride/firebase/send_fcm.php](send_fcm.php) بالفعل يستخدم FCM HTTP v1 (الحديث)، فيُفضّل ترحيل جميع الاستدعاءات إليه.
---
#### **M-3: فحص HMAC والـ Device Fingerprint اختياري**
**الملف:** `core/Auth/JwtService.php` (سطور 220-241 و 244-265)
```php
// Device Fingerprint اختياري:
if ($fpInToken === null || $fpHeader === null) {
// يتم تسجيل خطأ فقط، لكن يمرر
error_log("[SECURITY] Fingerprint missing");
// ❌ لا يوجد exit — يستمر!
}
// HMAC اختياري:
$hmacHeader = $_SERVER['HTTP_X_HMAC_AUTH'] ?? null;
if ($hmacHeader !== null) { // ❌ اختياري!
// يتحقق من HMAC فقط إذا موجود
}
```
**المشكلة:**
- العميل يستطيع **تخطّي الفحص** بمجرد حذف الهيدر
- **لا حماية من replay attack**: نفس الطلب يُقبل مراراً
- `timestamp` و `nonce` في الكود لكن **لا يُتحقق من حداثة الوقت** ولا يُخزّن الـ nonce في Redis
**مثال:**
```bash
# الطلب الأول:
curl -H "X-HMAC-Auth: xyz" -H "X-Device-FP: abc" https://api.siromove.com/...
# الطلب الثاني (نفس الطلب):
curl https://api.siromove.com/... # بدون الـ headers
# ✅ يُقبل لأن الفحص اختياري!
```
**الحل:**
اجعل HMAC إلزامياً للعمليات الحساسة:
```php
// بدل من:
if ($hmacHeader !== null) { ... }
// اجعله:
if (in_array($decoded->role, ['service', 'driver'])) { // للأدوار الحساسة
if ($hmacHeader === null) {
self::abort(403, 'HMAC verification required');
}
// تحقق من HMAC هنا
}
```
وأضف فحص الـ timestamp:
```php
$timestamp = (int)($_SERVER['HTTP_X_TIMESTAMP'] ?? 0);
$now = time();
if (abs($now - $timestamp) > 300) { // نافذة 5 دقائق
self::abort(403, 'Request expired (timestamp mismatch)');
}
```
---
### 🟡 مستوى منخفض — تحصين وأفضليات
#### **L-1: مقارنة التوكن بـ `!==` بدلاً من `hash_equals`**
**الملف:** `webhook_sms/webhook.php` (سطر 17)
```php
if ($authHeader !== 'Bearer ' . $secretKey) { // ❌ تسريب توقيت
exit;
}
// ✅ الصحيح:
if (!hash_equals($authHeader, 'Bearer ' . $secretKey)) {
exit;
}
```
**السبب:** `hash_equals` تقارن بسرعة ثابتة (بدون تسريب توقيتي).
---
#### **L-2: صلاحيات مجلد اللوج `0777` غير آمنة**
**الملف:** نفس الملف (سطر 77)
```php
@mkdir($logDir, 0777, true); // ❌ قراءة وكتابة لكل شخص
```
**الحل:**
```php
@mkdir($logDir, 0750, true); // ✅ مالك يقرأ/يكتب/ينفذ، مجموعة تقرأ فقط
```
---
#### **L-3: فحص الحد لكن تمرير القيمة الخام**
**الملف:** `ride/driverWallet/transfer.php` (سطور 27-40)
```php
$amountInt = (int)$amount; // التحقق
if ($amountInt > $maxAmount) {
exit; // حماية جيدة
}
// لكن تمرير:
'amount' => $amount // ❌ تمرير النصي الخام
```
يمكن للعميل إرسال `"15.99"` أو `"10abc"`، والخادم سيتحقق من `10` لكن يرسل `"10abc"`.
**الحل:**
```php
'amount' => $amountInt // ✅ تمرير القيمة المُتحقق منها
```
---
#### **L-4: Prepared Statements مع قيم حرفية**
**بعض الملفات** قد تستخدم قيماً حرفية في الـ WHERE، مثل:
```php
// ✅ جيد:
$stmt = $con->prepare("SELECT * FROM rides WHERE status = ? AND driver_id = ?");
$stmt->execute(['completed', $driver_id]);
// ⚠️ احذر من:
// القيم الثابتة يجب تمريرها كـ parameters أيضاً
```
---
## 📋 جدول الإصلاحات الموصى بها
| الأولوية | الملف | المشكلة | الإصلاح | المدة المتوقعة |
|---------|------|---------|---------|--------------|
| 🔴 عالي جداً | `check_server_code.php` | Source disclosure | حذف الملف | 5 دقائق |
| 🔴 عالي جداً | `RateLimiter.php` L48 | تعطيل حماية register | حذف الشرط الـ if | 5 دقائق |
| 🔴 عالي جداً | All (خادم الويب) | ملفات حساسة مكشوفة | إضافة `.htaccess` / Nginx rules | 15 دقيقة |
| 🟠 متوسط | `EncryptionHelper.php` | CBC حتمي | ترحيل إلى GCM | ساعتين |
| 🟠 متوسط | `cron_silent_push_inactive.php` L88 | تعطيل SSL verify | إزالة السطر | 5 دقائق |
| 🟠 متوسط | `JwtService.php` L244+ | HMAC اختياري | اجعله إلزامياً + تحقق توقيت | ساعة واحدة |
| 🟡 منخفض | `webhook_sms/webhook.php` L17 | مقارنة غير آمنة | استخدم `hash_equals` | 5 دقائق |
| 🟡 منخفض | Multiple | صلاحيات غير آمنة | غيّر إلى `0750` | 5 دقائق |
---
## ✨ الخلاصة والتوصيات
### ما تم إنجازه بشكل ممتاز
1. ✅ No SQL Injection (Prepared Statements في كل مكان)
2. ✅ No RCE (no eval/shell_exec/dangerous functions)
3. ✅ Strong JWT + Device Fingerprint
4. ✅ Bcrypt للأسرار و OTP
5. ✅ Secure file uploads (MIME validation)
6. ✅ Clean secrets history (no hardcoded keys in git)
7. ✅ Security headers (HSTS, CSP, X-Frame-Options)
### الخطوات الفورية (اليوم)
```bash
# 1. حذف ملف تسريب الكود
rm backend/check_server_code.php
# 2. إزالة تجاوز Rate Limiting
# عدّل backend/core/Auth/RateLimiter.php أسطر 48-51
# 3. إضافة حماية الخادم
# أنشئ backend/.htaccess أو عدّل Nginx config
```
### خطوات متوسطة (الأسبوع القادم)
- إضافة `.htaccess` / Nginx rules
- حذف TLS verification disable
- ترحيل من FCM Legacy إلى HTTP v1
- جعل HMAC إلزامياً
### خطوات طويلة الأمد
- ترحيل من CBC إلى GCM (نقاط البيانات الجديدة)
- إضافة فحص replay attack (timestamp + nonce)
- مراجعة شاملة للـ rate limits
---
## 📞 ملاحظات ختامية
**مستوى الأمان الحالي:** 8.2/10
**بعد تطبيق الإصلاحات العاجلة:** 9.2/10
**بعد تطبيق جميع التوصيات:** 9.7/10
النظام **آمن بشكل عام**، لكن الإصلاحات الثلاث العاجلة **حرجة ويجب تطبيقها قبل الإنتاج**.
---
**أُعد من قِبل:** مدقق أمني مستقل
**التاريخ:** 8 يوليو 2026
**للتواصل:** hamzaayedpython@gmail.com