اختبار الأمان
يحدد اختبار الأمان الثغرات الأمنية قبل أن يستغلها المهاجمون. في هذا الدرس، سنستكشف مسح الثغرات الأمنية وأساسيات اختبار الاختراق وإرشادات اختبار OWASP وتدقيق التبعيات وتقنيات اختبار الأمان العملية لحماية تطبيقاتك.
لماذا يهم اختبار الأمان
يساعدك اختبار الأمان على:
- تحديد الثغرات الأمنية قبل نشر الإنتاج
- حماية بيانات المستخدمين ومنع الانتهاكات
- الامتثال لمعايير الأمان (PCI-DSS، GDPR، HIPAA)
- منع الخسائر المالية من حوادث الأمان
- الحفاظ على ثقة العملاء وسمعة العلامة التجارية
- اكتشاف التبعيات القديمة ذات الثغرات الأمنية المعروفة
- التحقق من صحة ضوابط الأمان وآليات المصادقة
مهم: اختبار الأمان ليس نشاطًا لمرة واحدة. الاختبار المنتظم والمراقبة المستمرة والبقاء على اطلاع بالتهديدات الناشئة ضرورية للحفاظ على أمان التطبيق.
أهم 10 ثغرات أمنية في OWASP
تمثل أهم 10 في OWASP أخطر مخاطر أمان تطبيقات الويب:
- A01: التحكم في الوصول المكسور - الوصول غير المصرح به إلى الموارد
- A02: إخفاقات التشفير - كشف البيانات الحساسة
- A03: الحقن - هجمات حقن SQL والأوامر وLDAP
- A04: التصميم غير الآمن - عيوب أساسية في البنية
- A05: سوء التكوين الأمني - التكوينات الافتراضية، الأخطاء المطولة
- A06: المكونات الضعيفة - المكتبات القديمة ذات CVEs المعروفة
- A07: إخفاقات المصادقة - سياسات كلمة مرور ضعيفة، مشاكل الجلسة
- A08: سلامة البرامج والبيانات - خطوط أنابيب CI/CD غير آمنة
- A09: إخفاقات التسجيل والمراقبة - مسارات تدقيق غير كافية
- A10: تزوير الطلب من جانب الخادم (SSRF) - جلب الموارد البعيدة
اختبار حقن SQL
حقن SQL هو أحد أخطر الثغرات الأمنية. اختبر تطبيقك للتحقق من التعقيم المناسب للإدخال:
<?php
namespace Tests\Security;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class SqlInjectionTest extends TestCase
{
use RefreshDatabase;
public function test_login_prevents_sql_injection()
{
User::factory()->create([
'email' => 'admin@example.com',
'password' => bcrypt('password123'),
]);
// محاولة حقن SQL
$response = $this->post('/login', [
'email' => "admin@example.com' OR '1'='1",
'password' => "anything' OR '1'='1",
]);
// يجب ألا يتم التوثيق
$response->assertRedirect('/login');
$response->assertSessionHasErrors();
$this->assertGuest();
}
public function test_search_prevents_sql_injection()
{
// إنشاء بيانات الاختبار
Product::factory()->count(10)->create();
// محاولة حقن SQL في البحث
$maliciousQueries = [
"' OR 1=1--",
"' UNION SELECT * FROM users--",
"'; DROP TABLE products;--",
"1' AND 1=(SELECT COUNT(*) FROM users)--",
];
foreach ($maliciousQueries as $query) {
$response = $this->getJson("/api/search?q=" . urlencode($query));
// يجب إرجاع نتائج آمنة أو فارغة
$response->assertOk();
// يجب ألا يكشف عن بنية قاعدة البيانات
$this->assertStringNotContainsString('users', $response->content());
$this->assertStringNotContainsString('DROP TABLE', $response->content());
}
}
public function test_parameterized_queries_are_used()
{
// التحقق من عدم استخدام الاستعلامات الخام
$searchTerm = "test' OR '1'='1";
DB::enableQueryLog();
Product::where('name', 'like', "%{$searchTerm}%")->get();
$queries = DB::getQueryLog();
// التأكد من أن الاستعلام يستخدم ربط المعاملات
$this->assertNotEmpty($queries);
$this->assertStringContainsString('?', $queries[0]['query']);
$this->assertContains($searchTerm, $queries[0]['bindings']);
}
}
اختبار البرمجة النصية عبر المواقع (XSS)
تسمح ثغرات XSS للمهاجمين بحقن نصوص برمجية ضارة في صفحات الويب:
<?php
namespace Tests\Security;
use Tests\TestCase;
class XssProtectionTest extends TestCase
{
public function test_user_input_is_escaped_in_views()
{
$user = User::factory()->create([
'name' => '<script>alert("XSS")</script>',
]);
$response = $this->actingAs($user)->get('/profile');
$content = $response->content();
// يجب تجنب علامات البرمجة النصية
$this->assertStringNotContainsString('<script>', $content);
$this->assertStringContainsString('<script>', $content);
}
public function test_comment_submission_escapes_html()
{
$post = Post::factory()->create();
$maliciousComment = '<img src=x onerror="alert(1)">';
$response = $this->post("/posts/{$post->id}/comments", [
'body' => $maliciousComment,
]);
// استرداد التعليق
$comment = Comment::latest()->first();
// يجب تجنب HTML
$this->assertStringNotContainsString('<img', $comment->body);
$this->assertStringNotContainsString('onerror', $comment->body);
}
public function test_json_responses_prevent_xss()
{
$response = $this->getJson('/api/user?name=<script>alert(1)</script>');
$response->assertOk();
$response->assertHeader('Content-Type', 'application/json');
// يجب أن يتجنب JSON الأحرف الخاصة
$content = $response->content();
$this->assertStringNotContainsString('<script>', $content);
}
}
اختبار الحماية من CSRF
تخدع هجمات تزوير الطلب عبر المواقع (CSRF) المستخدمين لأداء إجراءات غير مرغوب فيها:
<?php
namespace Tests\Security;
use Tests\TestCase;
class CsrfProtectionTest extends TestCase
{
public function test_post_requests_require_csrf_token()
{
$user = User::factory()->create();
// محاولة POST بدون رمز CSRF
$response = $this->actingAs($user)->post('/profile/update', [
'name' => 'New Name',
]);
// يجب رفضه (حالة 419)
$response->assertStatus(419);
}
public function test_delete_requests_require_csrf_token()
{
$user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $user->id]);
// محاولة DELETE بدون رمز CSRF
$response = $this->actingAs($user)
->delete("/posts/{$post->id}");
$response->assertStatus(419);
$this->assertDatabaseHas('posts', ['id' => $post->id]);
}
public function test_api_routes_exempt_from_csrf()
{
// يجب أن تستخدم مسارات API مصادقة الرمز المميز بدلاً من ذلك
$response = $this->postJson('/api/data', [
'key' => 'value',
]);
// يجب ألا يُرجع خطأ CSRF
$this->assertNotEquals(419, $response->status());
}
}
اختبار المصادقة والتفويض
تحقق من أن ضوابط المصادقة والتفويض تعمل بشكل صحيح:
<?php
namespace Tests\Security;
use Tests\TestCase;
class AuthorizationTest extends TestCase
{
public function test_guest_cannot_access_protected_routes()
{
$protectedRoutes = [
['/dashboard', 'get'],
['/profile', 'get'],
['/posts/create', 'get'],
['/admin', 'get'],
];
foreach ($protectedRoutes as [$route, $method]) {
$response = $this->$method($route);
$response->assertRedirect('/login');
}
}
public function test_users_cannot_access_other_users_data()
{
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$privatePost = Post::factory()->create([
'user_id' => $user2->id,
'is_private' => true,
]);
// يجب ألا يصل المستخدم1 إلى منشور المستخدم2 الخاص
$response = $this->actingAs($user1)
->get("/posts/{$privatePost->id}");
$response->assertForbidden();
}
public function test_regular_user_cannot_access_admin_panel()
{
$user = User::factory()->create(['role' => 'user']);
$response = $this->actingAs($user)->get('/admin');
$response->assertForbidden();
}
public function test_password_reset_token_cannot_be_reused()
{
$user = User::factory()->create();
// طلب إعادة تعيين كلمة المرور
$token = Password::createToken($user);
// استخدام الرمز مرة واحدة
$this->post('/reset-password', [
'token' => $token,
'email' => $user->email,
'password' => 'newpassword123',
'password_confirmation' => 'newpassword123',
]);
// محاولة إعادة استخدام نفس الرمز
$response = $this->post('/reset-password', [
'token' => $token,
'email' => $user->email,
'password' => 'anotherpassword',
'password_confirmation' => 'anotherpassword',
]);
$response->assertSessionHasErrors();
}
}
مسح ثغرات التبعيات
قم بفحص التبعيات بانتظام للثغرات الأمنية المعروفة باستخدام الأدوات الآلية:
# تدقيق أمان Composer (PHP)
composer audit
# التحقق من الحزم القديمة
composer outdated
# تدقيق NPM (JavaScript)
npm audit
# إصلاح الثغرات الأمنية تلقائيًا
npm audit fix
# تدقيق Yarn (JavaScript)
yarn audit
# Snyk CLI (متعدد اللغات)
npm install -g snyk
snyk auth
snyk test
snyk monitor
# GitHub Dependabot
# التمكين في إعدادات المستودع ← الأمان ← تنبيهات Dependabot
اختبار كشف البيانات الحساسة
تأكد من عدم تسرب البيانات الحساسة في الاستجابات أو السجلات:
<?php
namespace Tests\Security;
use Tests\TestCase;
class SensitiveDataExposureTest extends TestCase
{
public function test_passwords_not_included_in_api_responses()
{
$user = User::factory()->create();
$response = $this->actingAs($user)->getJson('/api/user');
$response->assertOk();
$response->assertJsonMissing(['password' => $user->password]);
// التأكد من عدم وجود حقل كلمة المرور على الإطلاق
$data = $response->json();
$this->assertArrayNotHasKey('password', $data);
}
public function test_credit_card_numbers_are_masked()
{
$order = Order::factory()->create([
'card_last_four' => '1234',
]);
$response = $this->actingAs($order->user)
->getJson("/api/orders/{$order->id}");
$data = $response->json();
// يجب ألا يكون رقم البطاقة الكامل موجودًا
$this->assertArrayNotHasKey('card_number', $data);
// يجب عرض آخر 4 أرقام فقط
$this->assertEquals('****1234', $data['card_display']);
}
public function test_error_messages_do_not_expose_internals()
{
// تشغيل خطأ في قاعدة البيانات
$response = $this->getJson('/api/invalid-endpoint');
$content = $response->content();
// يجب ألا يكشف عن بنية قاعدة البيانات
$this->assertStringNotContainsString('SQL', $content);
$this->assertStringNotContainsString('table', strtolower($content));
$this->assertStringNotContainsString('/var/www/', $content);
// يجب عرض رسالة خطأ عامة
$this->assertStringContainsString('Not Found', $content);
}
public function test_api_tokens_are_not_logged()
{
$user = User::factory()->create();
$token = $user->createToken('test')->plainTextToken;
Log::spy();
$this->withHeader('Authorization', "Bearer {$token}")
->getJson('/api/user');
// التأكد من عدم تسجيل الرمز
Log::shouldNotHaveReceived('info')
->with(Mockery::on(function ($message) use ($token) {
return strpos($message, $token) !== false;
}));
}
}
أفضل ممارسة: لا تخزن أبدًا بيانات حساسة مثل كلمات المرور أو بطاقات الائتمان أو مفاتيح API بنص عادي. استخدم دائمًا التشفير والتجزئة (bcrypt، Argon2) والترميز حيثما كان ذلك مناسبًا.
اختبار تحديد المعدل
يمنع تحديد المعدل هجمات القوة الغاشمة وإساءة استخدام API:
<?php
namespace Tests\Security;
use Tests\TestCase;
class RateLimitingTest extends TestCase
{
public function test_login_rate_limiting_prevents_brute_force()
{
$user = User::factory()->create();
// محاولة تسجيلات دخول فاشلة متعددة
for ($i = 0; $i < 10; $i++) {
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'wrong-password',
]);
}
// يجب تحديد معدل المحاولة التالية
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'wrong-password',
]);
$response->assertStatus(429); // طلبات كثيرة جدًا
$response->assertSee('Too many login attempts');
}
public function test_api_rate_limiting()
{
$user = User::factory()->create();
// إجراء 60 طلبًا (بافتراض حد 60/دقيقة)
for ($i = 0; $i < 60; $i++) {
$response = $this->actingAs($user)->getJson('/api/data');
$response->assertOk();
}
// يجب تحديد معدل الطلب رقم 61
$response = $this->actingAs($user)->getJson('/api/data');
$response->assertStatus(429);
// يجب أن يتضمن رأس إعادة المحاولة بعد
$this->assertNotNull($response->headers->get('Retry-After'));
}
}
اختبار سوء التكوينات الأمنية
تحقق من أن تطبيقك مكون بشكل صحيح:
<?php
namespace Tests\Security;
use Tests\TestCase;
class SecurityConfigurationTest extends TestCase
{
public function test_debug_mode_disabled_in_production()
{
if (app()->environment('production')) {
$this->assertFalse(config('app.debug'),
'يجب تعطيل وضع التصحيح في الإنتاج'
);
}
}
public function test_https_enforced_in_production()
{
if (app()->environment('production')) {
$response = $this->get('/');
// التحقق مما إذا كان HTTPS مفروضًا
$this->assertTrue(
$response->isRedirect() || request()->secure(),
'يجب فرض HTTPS في الإنتاج'
);
}
}
public function test_security_headers_present()
{
$response = $this->get('/');
// التحقق من رؤوس الأمان
$response->assertHeader('X-Frame-Options', 'SAMEORIGIN');
$response->assertHeader('X-Content-Type-Options', 'nosniff');
$response->assertHeader('X-XSS-Protection', '1; mode=block');
// سياسة أمان المحتوى
$this->assertNotNull($response->headers->get('Content-Security-Policy'));
}
public function test_sensitive_files_not_accessible()
{
$sensitiveFiles = [
'/.env',
'/phpinfo.php',
'/.git/config',
'/composer.json',
'/package.json',
];
foreach ($sensitiveFiles as $file) {
$response = $this->get($file);
$response->assertNotFound();
}
}
public function test_default_credentials_changed()
{
// التأكد من عدم وجود حسابات مسؤول افتراضية
$this->assertDatabaseMissing('users', [
'email' => 'admin@admin.com',
]);
$this->assertDatabaseMissing('users', [
'username' => 'admin',
'password' => bcrypt('admin'),
]);
}
}
أساسيات اختبار الاختراق
يساعد اختبار الاختراق اليدوي في اكتشاف الثغرات الأمنية التي قد تفوتها الأدوات الآلية:
# تقنيات اختبار الاختراق الشائعة:
# 1. اجتياز الدليل
https://example.com/download?file=../../etc/passwd
https://example.com/uploads/../../../config/database.php
# 2. العبث بالمعاملات
https://example.com/user/profile?id=123
# جرب: ?id=124 (الوصول إلى بيانات مستخدم آخر)
# 3. الإسناد الجماعي
POST /api/users/123
{
"name": "John",
"is_admin": true // محاولة التصعيد إلى امتيازات أعلى
}
# 4. مراجع الكائنات المباشرة غير الآمنة (IDOR)
GET /api/orders/5001 // طلب المستخدم الخاص
GET /api/orders/5002 // محاولة الوصول إلى طلب مستخدم آخر
# 5. حقن الأوامر
https://example.com/ping?host=google.com;cat /etc/passwd
https://example.com/backup?file=data.zip && rm -rf /
# 6. حقن LDAP
username: admin)(&(password=*))
password: anything
# 7. كيان XML الخارجي (XXE)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>
تحذير: قم فقط بإجراء اختبار الاختراق على التطبيقات التي تمتلكها أو لديك إذن كتابي صريح لاختبارها. الاختبار غير المصرح به غير قانوني وغير أخلاقي.
أدوات الفحص الأمني الآلية
استخدم الأدوات الآلية لفحص الثغرات الأمنية بشكل مستمر:
# OWASP ZAP (وكيل الهجوم Zed)
# ماسح أمان تطبيقات الويب مفتوح المصدر
docker run -t owasp/zap2docker-stable zap-baseline.py -t https://example.com
# Nikto - ماسح خادم الويب
nikto -h https://example.com
# Nmap - ماسح الشبكة
nmap -sV -sC example.com
# SQLMap - اختبار حقن SQL
sqlmap -u "https://example.com/search?q=test" --batch --banner
# Burp Suite Community Edition
# التحميل من: https://portswigger.net/burp/communitydownload
# Wapiti - ماسح ثغرات الويب
wapiti -u https://example.com
# Arachni - ماسح أمان تطبيقات الويب
arachni https://example.com --checks=*
# SonarQube - جودة الكود والأمان
# يتكامل مع خطوط أنابيب CI/CD
docker run -d --name sonarqube -p 9000:9000 sonarqube:latest
تمرين 1: أنشئ مجموعة اختبارات أمان شاملة تتحقق من: (1) حقن SQL في جميع مدخلات النموذج، (2) XSS في المحتوى الذي ينشئه المستخدم، (3) حماية CSRF على العمليات التي تغير الحالة، (4) التفويض المناسب على نقاط نهاية API، و(5) كشف البيانات الحساسة في السجلات.
تمرين 2: قم بإعداد فحص التبعيات الآلي في خط أنابيب CI/CD الخاص بك باستخدام Snyk أو GitHub Dependabot. قم بتكوينه لإخفاق البناء إذا تم اكتشاف ثغرات أمنية عالية الخطورة.
تمرين 3: قم بإجراء تدقيق أمني لتطبيقك باستخدام OWASP ZAP. قم بتوثيق جميع النتائج مع مستويات الخطورة (حرجة، عالية، متوسطة، منخفضة) وأنشئ خطة معالجة مع الأولويات.
الملخص
في هذا الدرس، غطينا ممارسات اختبار الأمان الأساسية:
- فهم أهم 10 ثغرات أمنية في OWASP وكيفية اختبارها
- اختبار ثغرات حقن SQL وXSS وCSRF
- التحقق من ضوابط المصادقة والتفويض
- فحص التبعيات للثغرات الأمنية المعروفة
- اختبار كشف البيانات الحساسة في الاستجابات والسجلات
- تنفيذ واختبار تحديد المعدل لمنع الإساءة
- التحقق من التكوينات والرؤوس الأمنية
- استخدام أدوات الفحص الأمني الآلية
- تقنيات اختبار الاختراق الأساسية
الأمان ليس جهدًا لمرة واحدة بل عملية مستمرة. قم بدمج اختبار الأمان في سير عمل التطوير الخاص بك، وابق على اطلاع بالتهديدات الناشئة، وقم بتحديث تبعياتك وممارساتك الأمنية بانتظام.