Files
Siro/backend/serviceapp/register.php
T
Hamza-AyedandClaude Opus 5 2135edcf43 Close the remaining ciphertext joins and a SQL injection in email verification
- Customer-service notes joined to the account by comparing encrypted phone
  columns. Both notes tables now carry phone_key, written when a note is
  saved, and the three joins match on it.
- The email_verifications join was comparing a plaintext column against an
  encrypted one, so it never matched and `verified` was always NULL in both
  passenger and driver sign-in. It is now resolved in PHP against the
  decrypted address, which fixes a pre-existing bug rather than only
  preparing for GCM.
- auth/sendVerifyEmail.php built all three of its statements by interpolating
  the request values into SQL. Any caller could inject through the email or
  token field. Now parameterised.
- serviceapp/register.php duplicate detection consults the users indexes and
  writes them with the row.

Sweep confirms no join or lookup compares two encrypted columns any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 16:48:10 +03:00

105 lines
4.5 KiB
PHP

<?php
/**
* serviceapp/register.php
* التسجيل الذاتي لموظفي خدمة العملاء - الحساب يكون بحالة pending بانتظار موافقة الإدارة
*/
require_once __DIR__ . '/../core/bootstrap.php';
$firstName = filterRequest('first_name');
$lastName = filterRequest('last_name');
$email = filterRequest('email');
$phone = filterRequest('phone');
$password = filterRequest('password');
$fingerprint = filterRequest('fingerprint');
if (empty($firstName) || empty($lastName) || empty($email) || empty($phone) || empty($password) || empty($fingerprint)) {
jsonError("All fields are required.");
exit;
}
try {
// 1. التحقق من البيئة (Environment Whitelist)
$allowedPhonesStr = getenv('AUTHORIZED_SERVICE_PHONES');
if (!$allowedPhonesStr) {
jsonError("غير مصرح لك بالتسجيل كموظف خدمة (القائمة البيضاء غير معدة).");
exit;
}
$allowedPhones = array_map('trim', explode(',', $allowedPhonesStr));
if (!in_array($phone, $allowedPhones)) {
jsonError("أنت غير مصرح لك بالتسجيل كموظف خدمة. يرجى مراجعة الإدارة.");
exit;
}
$con = Database::get('main');
// 1. التحقق من عدم وجود الحساب مسبقاً (عن طريق البريد الإلكتروني، الهاتف أو البصمة)
$fpHash = hash('sha256', $fingerprint);
global $blindIndex;
$emailBidx = $blindIndex ? $blindIndex->index('users.email', $email) : null;
$phoneBidx = $blindIndex ? $blindIndex->index('users.phone', $phone) : null;
// كشف التكرار عبر الفهرس أيضاً: بدونه يُقبل نفس البريد مرتين تحت التشفير
// العشوائي لأن النصين المشفّرين لن يتطابقا.
$check = $con->prepare(
"SELECT id FROM users
WHERE email = ? OR phone = ? OR fingerprint_hash = ?
OR (? IS NOT NULL AND email_bidx = ?)
OR (? IS NOT NULL AND phone_bidx = ?)
LIMIT 1"
);
// تشفير الحقول للبحث عنها إذا كانت مشفرة في قاعدة البيانات (حسب تصميم النظام)
$encEmail = $encryptionHelper->encryptData($email);
// ملاحظة: البحث بالهاتف والبريد المشفر يتطلب مطابقة دقيقة أو البحث بالـ Hash إذا كان متوفراً
// هنا سنفترض البحث بالبيانات الممرة مباشرة أو المشفرة حسب ما تقتضيه سياسة connect.php
$check->execute([$email, $phone, $fpHash, $emailBidx, $emailBidx, $phoneBidx, $phoneBidx]);
if ($check->rowCount() > 0) {
jsonError("هذا الحساب أو الجهاز مسجل مسبقاً.");
exit;
}
// 2. تجهيز البيانات
$id = bin2hex(random_bytes(16));
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
// تشفير البيانات الحساسة قبل التخزين
$encFirstName = $encryptionHelper->encryptData($firstName);
$encLastName = $encryptionHelper->encryptData($lastName);
$encEmail = $encryptionHelper->encryptData($email);
$encPhone = $encryptionHelper->encryptData($phone);
$encFp = $encryptionHelper->encryptData($fingerprint);
// 3. الإدخال في قاعدة البيانات (الحالة الافتراضية هي 0 أو pending)
$sql = "INSERT INTO users (id, first_name, last_name, email, phone, password, fingerprint, fingerprint_hash, user_type, created_at, email_bidx, phone_bidx)
VALUES (:id, :fname, :lname, :email, :phone, :pass, :fp, :fp_hash, 'service', NOW(), :email_bidx, :phone_bidx)";
$stmt = $con->prepare($sql);
$stmt->execute([
':id' => $id,
':fname' => $encFirstName,
':lname' => $encLastName,
':email' => $encEmail,
':phone' => $encPhone,
':pass' => $hashedPassword,
':fp' => $encFp,
':fp_hash' => $fpHash,
':email_bidx' => $emailBidx,
':phone_bidx' => $phoneBidx
]);
printSuccess([
"status" => "pending",
"message" => "تم تقديم طلب التسجيل بنجاح. يرجى انتظار موافقة الإدارة."
]);
} catch (Exception $e) {
error_log("[Service Register Error] " . $e->getMessage());
jsonError("An internal error occurred. Please try again later.");
}
exit();