602 lines
17 KiB
Markdown
602 lines
17 KiB
Markdown
# دليل الإصلاحات الشامل - مشروع سيرو
|
||
|
||
**التاريخ:** 16 يونيو 2026
|
||
**المرحلة:** التطبيق العملي للإصلاحات
|
||
**الحالة:** قيد التطوير
|
||
|
||
---
|
||
|
||
## النقطة 1️⃣: مشكلة البصمة الضعيفة (Weak Fingerprint Authentication)
|
||
|
||
### الملفات المتأثرة:
|
||
|
||
- `backend/login.php` (الراكب)
|
||
- `backend/loginJwtDriver.php` (السائق)
|
||
|
||
### المشكلة الحقيقية:
|
||
|
||
```php
|
||
// ❌ الحالة الحالية في login.php
|
||
$fpVerified = hash_equals($storedFp, $fingerprint);
|
||
// ✅ يحمي من timing attacks فقط
|
||
// ⚠️ لكن البصمة نفسها ضعيفة وقابلة للاستخراج
|
||
```
|
||
|
||
**لماذا ضعيفة؟**
|
||
|
||
- البصمة مستخرجة من جهاز المستخدم (ANDROID_ID + IMEI + MAC + Build.MODEL)
|
||
- يمكن استخراجها عبر:
|
||
- Frida/Objection أثناء التطبيق
|
||
- ADB إذا كان USB debugging مفعل
|
||
- مفاتيح الجهاز المُخزنة
|
||
- بمجرد استخراج البصمة، يمكن تزويرها بنسخ نفس القيمة
|
||
|
||
### الحل: تطبيق MFA (Multi-Factor Authentication)
|
||
|
||
**المتطلبات:**
|
||
|
||
```
|
||
عامل 1: بصمة الجهاز (الحالي) ✅
|
||
عامل 2: OTP عبر SMS ← أضف هذا
|
||
عامل 3: رمز الخادم (Server Token) ← أضف هذا
|
||
```
|
||
|
||
---
|
||
|
||
## النقطة 2️⃣: مشكلة التشفير - IV الثابت
|
||
|
||
### الملف المتأثر:
|
||
|
||
- `backend/encrypt_decrypt.php` (الأساسي)
|
||
|
||
### المشكلة الفعلية:
|
||
|
||
```php
|
||
// ❌ المشكلة الحرجة جداً
|
||
$iv = getenv('initializationVector'); // 16 بايت ثابت!
|
||
|
||
public function encryptData($plainText) {
|
||
$plainText = mb_convert_encoding($plainText, 'UTF-8');
|
||
$paddedText = $this->addPadding($plainText);
|
||
$encrypted = openssl_encrypt($paddedText, 'AES-256-CBC',
|
||
$this->key, OPENSSL_RAW_DATA, $this->iv);
|
||
// ↑ نفس IV في كل مرة = نفس ciphertext لنفس plaintext!
|
||
return base64_encode($encrypted);
|
||
}
|
||
```
|
||
|
||
**مثال عملي:**
|
||
|
||
```
|
||
التشفير الأول: "+20123456789" → "abc123xyz=="
|
||
التشفير الثاني: "+20123456789" → "abc123xyz==" (متطابق!)
|
||
⚠️ هجوم معروف - يمكن كسر التشفير تماماً
|
||
```
|
||
|
||
### الحل: توليد IV عشوائي
|
||
|
||
```php
|
||
✅ الحل الصحيح:
|
||
|
||
public function encryptData($plainText) {
|
||
$plainText = mb_convert_encoding($plainText, 'UTF-8');
|
||
$paddedText = $this->addPadding($plainText);
|
||
|
||
// توليد IV عشوائي لكل تشفير
|
||
$randomIV = openssl_random_pseudo_bytes(16);
|
||
|
||
$encrypted = openssl_encrypt($paddedText, 'AES-256-CBC',
|
||
$this->key, OPENSSL_RAW_DATA, $randomIV);
|
||
|
||
// ضمّ IV مع النص المشفر
|
||
$result = $randomIV . $encrypted;
|
||
|
||
return base64_encode($result);
|
||
}
|
||
|
||
public function decryptData($encryptedData) {
|
||
$encrypted = base64_decode($encryptedData);
|
||
|
||
// استخرج IV من أول 16 بايت
|
||
$iv = substr($encrypted, 0, 16);
|
||
$ciphertext = substr($encrypted, 16);
|
||
|
||
$decrypted = openssl_decrypt($ciphertext, 'AES-256-CBC',
|
||
$this->key, OPENSSL_RAW_DATA, $iv);
|
||
|
||
return $this->removePadding($decrypted);
|
||
}
|
||
```
|
||
|
||
**الخطوات:**
|
||
|
||
1. توليد 16 بايت عشوائية → `openssl_random_pseudo_bytes(16)`
|
||
2. تشفير البيانات بـ IV العشوائي
|
||
3. ضمّ IV + Ciphertext
|
||
4. تحويل إلى base64
|
||
5. عند فك التشفير: استخرج IV + Ciphertext وفك التشفير
|
||
|
||
---
|
||
|
||
## النقطة 3️⃣: SQL Injection - الحالة الحالية وضح!
|
||
|
||
### الملف المتأثر:
|
||
|
||
- `backend/functions.php` (في `findBestDrivers()`)
|
||
|
||
### الحالة الحالية - ممتازة ✅
|
||
|
||
```php
|
||
// ✅ هذا الكود **آمن تماماً** من SQL Injection
|
||
$carType = trim($carType);
|
||
$allowedCarTypes = [
|
||
'Comfort', 'Mishwar Vip', 'Scooter', 'Pink Bike',
|
||
'Electric', 'Lady', 'Van', 'Awfar Car', 'Fixed Price',
|
||
'Speed', 'Rayeh Gai'
|
||
];
|
||
|
||
// ✅ استخدام Allowlist (أفضل طريقة)
|
||
if (!in_array($carType, $allowedCarTypes, true)) {
|
||
$carType = 'Speed';
|
||
}
|
||
|
||
// ✅ معاملات SQL آمنة
|
||
$stmt = $con->prepare($sql);
|
||
$stmt->execute($allParams);
|
||
```
|
||
|
||
### لماذا هذا آمن؟
|
||
|
||
1. **Allowlist:** قائمة بيضاء للقيم المسموحة فقط
|
||
2. **Prepared Statements:** معاملات آمنة
|
||
3. **Type Strict (`true`):** التحقق الدقيق (`in_array(..., true)`)
|
||
|
||
### المشاكل المتبقية:
|
||
|
||
**نقاط أخرى قد تحتاج فحص:**
|
||
|
||
- `/backend/auth/` - هل تستخدم prepared statements؟
|
||
- `/backend/ride/` - هل جميع الاستعلامات آمنة؟
|
||
- `/walletintaleq.intaleq.xyz/v2/main/` - **حرج! تحتاج فحص كامل**
|
||
|
||
---
|
||
|
||
## النقطة 4️⃣: نظام المحفظة - أهم نقطة 🔴
|
||
|
||
### الملفات المتأثرة:
|
||
|
||
```
|
||
/walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/
|
||
├── add.php ..................... 🔴 حرج - بلا مصادقة
|
||
├── transfer.php ............... ⚠️ عالي
|
||
├── update.php ................. ⚠️ عالي
|
||
├── addFromAdmin.php ........... 🔴 حرج - مفتاح API ثابت
|
||
├── get.php .................... ⚠️ عالي
|
||
├── getWalletByDriver.php ...... ⚠️ عالي
|
||
└── getDriverDetails.php ....... ⚠️ عالي
|
||
```
|
||
|
||
### الفهم الحالي:
|
||
|
||
```
|
||
تطبيق السائق
|
||
↓
|
||
POST /add ← يضيف الأموال
|
||
↓
|
||
/walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/add.php
|
||
↓
|
||
← لا يوجد مصادقة! ❌
|
||
```
|
||
|
||
### الحل المقترح: (S2S - Server to Server)
|
||
|
||
```
|
||
تطبيق السائق
|
||
↓
|
||
POST /wallet/add (مع JWT توكن)
|
||
↓
|
||
BACKEND SERVER (backend/wallet/) ← نقطة جديدة
|
||
↓
|
||
• التحقق من JWT ✅
|
||
• فحص الملكية ✅
|
||
• التحقق من المبلغ ✅
|
||
↓
|
||
POST /v2/main/ride/driverWallet/add
|
||
(مع توقيع HMAC-SHA256)
|
||
↓
|
||
WalletIntaleq Server
|
||
↓
|
||
• التحقق من التوقيع ✅
|
||
• تنفيذ العملية ✅
|
||
• تسجيل دقيق ✅
|
||
```
|
||
|
||
### الخطوات العملية:
|
||
|
||
#### 1️⃣ إنشاء endpoint في Backend
|
||
|
||
```php
|
||
// backend/wallet/add.php (جديد)
|
||
|
||
require_once __DIR__ . '/../core/bootstrap.php';
|
||
|
||
function requireAuth() {
|
||
$headers = getallheaders();
|
||
$authHeader = $headers['Authorization'] ?? '';
|
||
|
||
if (!preg_match('/Bearer\s+(\S+)/', $authHeader, $matches)) {
|
||
http_response_code(401);
|
||
jsonError('Authentication required');
|
||
}
|
||
|
||
$token = $matches[1];
|
||
$jwtService = new JwtService($redis);
|
||
|
||
try {
|
||
$decoded = $jwtService->verifyAccessToken($token);
|
||
return $decoded;
|
||
} catch (Exception $e) {
|
||
http_response_code(401);
|
||
jsonError('Invalid token');
|
||
}
|
||
}
|
||
|
||
try {
|
||
$user = requireAuth(); // JWT verification
|
||
|
||
// التحقق من الدور (التفويض)
|
||
if ($user->role !== 'driver') {
|
||
http_response_code(403);
|
||
jsonError('Permission denied');
|
||
}
|
||
|
||
$driverID = $user->id;
|
||
$amount = floatval(filterRequest('amount'));
|
||
$source = filterRequest('source');
|
||
|
||
// التحقق من المبلغ
|
||
if ($amount <= 0 || $amount > 10000) {
|
||
jsonError('Invalid amount (max 10,000)', 400);
|
||
}
|
||
|
||
// تحديد السرعة
|
||
$limiter = new RateLimiter($redis);
|
||
$limiter->enforce("wallet_add_{$driverID}", 'add', 1, 60); // 1 request per 60 seconds
|
||
|
||
// تسجيل التدقيق
|
||
$auditLog = "Driver {$driverID} requested to add {$amount} from {$source}";
|
||
securityLog($auditLog);
|
||
|
||
// الاتصال بـ WalletIntaleq عبر S2S
|
||
$walletResponse = callWalletAPI('add', [
|
||
'driver_id' => $driverID,
|
||
'amount' => $amount,
|
||
'source' => $source,
|
||
'backend_request' => true,
|
||
]);
|
||
|
||
if ($walletResponse['status'] === 'success') {
|
||
jsonSuccess(['message' => 'تمت إضافة الأموال بنجاح']);
|
||
} else {
|
||
jsonError('Wallet operation failed', 500);
|
||
}
|
||
|
||
} catch (Exception $e) {
|
||
securityLog("Wallet add error: " . $e->getMessage());
|
||
jsonError('Internal error', 500);
|
||
}
|
||
```
|
||
|
||
#### 2️⃣ دالة S2S API call آمنة
|
||
|
||
```php
|
||
// backend/core/WalletConnector.php (جديد)
|
||
|
||
class WalletConnector {
|
||
private $walletUrl = 'https://walletintaleq.intaleq.xyz/v2/main/';
|
||
private $hmacSecret = null;
|
||
|
||
public function __construct() {
|
||
$this->hmacSecret = getenv('WALLET_HMAC_SECRET');
|
||
if (!$this->hmacSecret) {
|
||
throw new Exception("WALLET_HMAC_SECRET not configured");
|
||
}
|
||
}
|
||
|
||
public function call($endpoint, $data) {
|
||
// إضافة timestamp لمنع Replay Attacks
|
||
$data['timestamp'] = time();
|
||
$data['nonce'] = bin2hex(random_bytes(16));
|
||
|
||
// فرز البيانات وإنشاء signature
|
||
ksort($data);
|
||
$payload = json_encode($data);
|
||
$signature = hash_hmac('sha256', $payload, $this->hmacSecret);
|
||
|
||
// إرسال الطلب
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $this->walletUrl . $endpoint,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => $payload,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_HTTPHEADER => [
|
||
'Content-Type: application/json',
|
||
'X-Signature: ' . $signature,
|
||
'X-Timestamp: ' . $data['timestamp'],
|
||
'X-Backend-ID: ' . getenv('BACKEND_ID'),
|
||
],
|
||
CURLOPT_SSL_VERIFYPEER => true,
|
||
CURLOPT_SSL_VERIFYHOST => 2,
|
||
CURLOPT_TIMEOUT => 10,
|
||
]);
|
||
|
||
$response = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
// التحقق من الاستجابة
|
||
$decoded = json_decode($response, true);
|
||
if ($httpCode !== 200 || $decoded['status'] !== 'success') {
|
||
throw new Exception("Wallet API error: " . $response);
|
||
}
|
||
|
||
return $decoded;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3️⃣ تعديل WalletIntaleq
|
||
|
||
```php
|
||
// walletintaleq.intaleq.xyz/v2/main/ride/driverWallet/add.php
|
||
|
||
<?php
|
||
// ✅ الآن يتلقى طلبات من Backend فقط
|
||
|
||
require_once __DIR__ . '/../../../core/bootstrap.php';
|
||
|
||
header('Content-Type: application/json');
|
||
|
||
try {
|
||
// الحصول على البيانات المرسلة
|
||
$payload = file_get_contents('php://input');
|
||
$data = json_decode($payload, true);
|
||
|
||
// التحقق من التوقيع (HMAC)
|
||
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
|
||
$expectedSignature = hash_hmac('sha256', $payload, getenv('WALLET_HMAC_SECRET'));
|
||
|
||
if (!hash_equals($signature, $expectedSignature)) {
|
||
http_response_code(401);
|
||
jsonError('Invalid signature');
|
||
}
|
||
|
||
// التحقق من الـ Timestamp (Replay Attack Prevention)
|
||
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? 0;
|
||
if (abs(time() - $timestamp) > 300) { // 5 دقائق
|
||
http_response_code(401);
|
||
jsonError('Request expired');
|
||
}
|
||
|
||
// الآن آمن - نفذ العملية
|
||
$driverID = $data['driver_id'];
|
||
$amount = $data['amount'];
|
||
$source = $data['source'];
|
||
|
||
// أضف إلى قاعدة البيانات
|
||
$stmt = $con->prepare(
|
||
"INSERT INTO driverWallet (driver_id, amount, source, created_at, updated_at)
|
||
VALUES (?, ?, ?, NOW(), NOW())"
|
||
);
|
||
$stmt->execute([$driverID, $amount, $source]);
|
||
|
||
// تسجيل التدقيق
|
||
securityLog("Wallet added: Driver $driverID, Amount $amount from $source");
|
||
|
||
jsonSuccess(['message' => 'Added successfully']);
|
||
|
||
} catch (Exception $e) {
|
||
securityLog("Wallet error: " . $e->getMessage());
|
||
jsonError('Internal error', 500);
|
||
}
|
||
?>
|
||
```
|
||
|
||
---
|
||
|
||
## النقطة 5️⃣: أمان تطبيقات الهاتف - الأذونات
|
||
|
||
### الملف المتأثر:
|
||
|
||
- `siro_driver/android/app/src/main/AndroidManifest.xml`
|
||
|
||
### الأذونات الحالية:
|
||
|
||
```xml
|
||
<!-- ✅ ضرورية لتطبيق السائق -->
|
||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||
|
||
<!-- ⚠️ تحتاج فحص -->
|
||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||
|
||
<!-- ✅ ضرورية للاتصالات -->
|
||
<uses-permission android:name="android.permission.CAMERA" />
|
||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||
```
|
||
|
||
### الفحص المطلوب:
|
||
|
||
```bash
|
||
1. بحث في codebase تطبيق السائق:
|
||
grep -r "getExternalFilesDir\|getExternalCacheDir\|Environment.getExternalStorageDirectory" .
|
||
|
||
إذا لم تظهر نتائج → احذف WRITE/READ_EXTERNAL_STORAGE
|
||
|
||
2. بحث عن SYSTEM_ALERT_WINDOW:
|
||
grep -r "TYPE_APPLICATION_OVERLAY\|canDrawOverlays" .
|
||
|
||
إذا لم تظهر → احذفها
|
||
|
||
3. بحث عن MODIFY_AUDIO_SETTINGS:
|
||
grep -r "setVolume\|adjustStreamVolume" .
|
||
|
||
إذا لم تظهر → احذفها
|
||
```
|
||
|
||
---
|
||
|
||
## النقطة 6️⃣: load_env.php - تحميل آمن للمتغيرات
|
||
|
||
### الملف:
|
||
|
||
- `backend/load_env.php`
|
||
|
||
### الحالة الحالية:
|
||
|
||
```php
|
||
<?php
|
||
function loadEnvironment($env_file) {
|
||
if (!file_exists($env_file)) {
|
||
error_log("❌ .env not found: $env_file");
|
||
return false;
|
||
}
|
||
|
||
$lines = file($env_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||
foreach ($lines as $line) {
|
||
$line = trim($line);
|
||
if (empty($line) || strpos($line, '#') === 0) continue;
|
||
|
||
$parts = explode('=', $line, 2);
|
||
if (count($parts) === 2) {
|
||
[$keyName, $value] = $parts;
|
||
$value = trim($value, "\"'");
|
||
putenv("$keyName=$value");
|
||
$_ENV[$keyName] = $value;
|
||
$_SERVER[$keyName] = $value;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
```
|
||
|
||
### القوة الأمنية:
|
||
|
||
✅ **جيد جداً:**
|
||
|
||
1. يتحقق من وجود الملف
|
||
2. يتجاهل التعليقات (#)
|
||
3. يتجاهل الأسطر الفارغة
|
||
4. يزيل علامات الاقتباس
|
||
5. يحمل في `$_ENV` و `$_SERVER`
|
||
|
||
### الطريقة الموصى بها (Composer autoload):
|
||
|
||
```json
|
||
// composer.json - الأفضل
|
||
|
||
"autoload": {
|
||
"files": ["src/bootstrap.php"]
|
||
}
|
||
```
|
||
|
||
```php
|
||
// src/bootstrap.php
|
||
|
||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||
$dotenv->load();
|
||
```
|
||
|
||
---
|
||
|
||
## النقطة 7️⃣: backend/functions.php - الروابط الآمنة
|
||
|
||
### الحالة الحالية:
|
||
|
||
```php
|
||
❌ غير آمنة:
|
||
$url = "http://188.68.36.205:2021";
|
||
$url = "http://188.68.36.205:3031";
|
||
$url = "https://location.intaleq.xyz";
|
||
```
|
||
|
||
### المشاكل:
|
||
|
||
1. **IP عام مكشوف** - 188.68.36.205
|
||
2. **HTTP بدل HTTPS** - عرضة لـ MITM attacks
|
||
3. **لا يوجد certificate pinning**
|
||
|
||
### الحل:
|
||
|
||
```php
|
||
// ✅ الحل الآمن
|
||
|
||
function getAllowedSocketUrls(): array {
|
||
return [
|
||
'https://location.siromove.com', // ✅ HTTPS + Domain
|
||
'https://socket.siromove.com', // ✅ HTTPS + Domain
|
||
// لا HTTP
|
||
];
|
||
}
|
||
|
||
function sendToLocationServer($action, $data) {
|
||
$url = "https://location.siromove.com/api";
|
||
|
||
// تثبيت الشهادة (Certificate Pinning)
|
||
$ch = curl_init();
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_URL => $url,
|
||
CURLOPT_POST => 1,
|
||
CURLOPT_POSTFIELDS => http_build_query($data),
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
|
||
// ✅ تأمين SSL/TLS
|
||
CURLOPT_SSL_VERIFYPEER => true,
|
||
CURLOPT_SSL_VERIFYHOST => 2,
|
||
CURLOPT_CAINFO => '/etc/ssl/certs/ca-bundle.crt',
|
||
|
||
// ✅ Certificate Pinning (اختياري لكن موصى)
|
||
// تحتاج حساب SHA-256 للشهادة
|
||
CURLOPT_PINNEDPUBLICKEY => 'sha256/AAAA....',
|
||
]);
|
||
|
||
$response = curl_exec($ch);
|
||
curl_close($ch);
|
||
|
||
return json_decode($response, true);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ملخص الإصلاحات
|
||
|
||
| النقطة | المشكلة | الحل | المدة |
|
||
| ------ | ------------------ | ------------- | -------- |
|
||
| 1 | بصمة ضعيفة | MFA + OTP | 8 ساعات |
|
||
| 2 | IV ثابت | توليد عشوائي | 4 ساعات |
|
||
| 3 | SQL Injection | آمن بالفعل ✅ | 0 ساعات |
|
||
| 4 | المحفظة بلا مصادقة | S2S + JWT | 16 ساعات |
|
||
| 5 | أذونات مفرطة | تقليص + فحص | 4 ساعات |
|
||
| 6 | load_env.php | آمن بالفعل ✅ | 0 ساعات |
|
||
| 7 | روابط HTTP | تبديل HTTPS | 2 ساعات |
|
||
|
||
**المجموع:** ~34 ساعة
|
||
|
||
---
|
||
|
||
## التالي:
|
||
|
||
1. تطبيق الإصلاحات واحدة تلو الأخرى
|
||
2. اختبار كل إصلاح
|
||
3. ترحيل قاعدة البيانات
|
||
4. نشر للإنتاج
|