Module 16: Security Basics
Lesson 1: SQL Injection and Input Validation
- Duration: 90 minutes
- Learning objectives:
- Understand what SQL injection is and how it works
- Write vulnerable vs secure SQL queries
- Implement input validation in PHP
- Use prepared statements with PDO
Instructor notes
SQL injection is the most common web vulnerability. Show real examples of attacks. Emphasize that user input is NEVER safe by default. Use the "bobby tables" analogy to make it memorable.
Presentation outline
- What is SQL injection?
- How attackers exploit SQL injection
- Vulnerable code examples
- Prepared statements and parameterized queries
- Input validation techniques
Demonstration
- Show a vulnerable login form that accepts
' OR '1'='1
- Fix it with PDO prepared statements
- Add validation: required fields, email format, length checks
- Test with malicious input and verify it fails safely
AI prompts
- "Show me a vulnerable PHP login script with SQL injection. Then show the secure version using PDO prepared statements."
- "Write PHP input validation functions for: email, phone number, and password (min 8 chars, 1 number, 1 special char)."
- "What is the difference between client-side and server-side validation? Why do we need both?"
Starter code
<?php
// login.php - VULNERABLE CODE (DO NOT USE IN PRODUCTION)
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$pdo = new PDO('mysql:host=localhost;dbname=store', 'root', '');
$query = "SELECT * FROM users WHERE email = '$email' AND password = '$password'";
$stmt = $pdo->query($query);
$user = $stmt->fetch();
if ($user) {
echo "Welcome " . $user['name'];
} else {
echo "Invalid login";
}
?>
Final code
<?php
// login.php - SECURE CODE
session_start();
require_once 'config.php';
function validateInput($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
function validatePassword($password) {
if (strlen($password) < 8) {
return "Password must be at least 8 characters";
}
if (!preg_match('/[A-Z]/', $password)) {
return "Password must contain an uppercase letter";
}
if (!preg_match('/[a-z]/', $password)) {
return "Password must contain a lowercase letter";
}
if (!preg_match('/[0-9]/', $password)) {
return "Password must contain a number";
}
return true;
}
$email = validateInput($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$errors = [];
// Server-side validation
if (empty($email)) {
$errors[] = "Email is required";
} elseif (!validateEmail($email)) {
$errors[] = "Invalid email format";
}
if (empty($password)) {
$errors[] = "Password is required";
}
if (empty($errors)) {
try {
$stmt = $pdo->prepare("SELECT id, name, password_hash FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
header('Location: dashboard.php');
exit();
} else {
$errors[] = "Invalid email or password";
}
} catch(PDOException $e) {
error_log("Login error: " . $e->getMessage());
$errors[] = "An error occurred. Please try again.";
}
}
foreach ($errors as $error) {
echo "<p style='color: red;'>$error</p>";
}
?>
Exercises
- Write a login form and test it with SQL injection payloads like
' OR '1'='1.
- Create a registration form with validation for name, email, and password.
- Write a function that sanitizes user input for database queries.
Homework
- Take an existing CRUD application and fix all SQL injection vulnerabilities.
- Write a security report identifying 5 vulnerable code patterns you found online and how to fix them.
Quiz
- What is SQL injection?
- A) A database optimization technique
- B) Inserting malicious SQL through user input
- C) A backup method
- D) A query builder
- Answer: B
- Which is the safest way to query a database in PHP?
- A) String concatenation
- B) Prepared statements
- C) eval()
- D) mysql_query()
- Answer: B
- What does
htmlspecialchars() do?
- A) Encrypts data
- B) Converts special characters to HTML entities
- C) Validates email
- D) Hashes passwords
- Answer: B
- Where should input validation happen?
- A) Only in JavaScript
- B) Only in PHP
- C) Both client-side and server-side
- D) Only in the database
- Answer: C
- What is a prepared statement?
- A) A SQL query with placeholders
- B) A PHP array
- C) A database backup
- D) A connection method
- Answer: A
Lesson 2: XSS and Output Escaping
- Duration: 60 minutes
- Learning objectives:
- Understand Cross-Site Scripting (XSS)
- Differentiate between stored, reflected, and DOM-based XSS
- Apply output escaping in PHP
- Use Content Security Policy headers
Instructor notes
XSS lets attackers inject JavaScript into your pages. Show how a simple comment form can become an attack vector. Emphasize: escape on output, not input.
Presentation outline
- What is XSS?
- Types of XSS: stored, reflected, DOM-based
- How XSS attacks work
- Escaping output with
htmlspecialchars()
- Content Security Policy
Demonstration
- Create a vulnerable comment form
- Post a comment with
<script>alert('XSS')</script>
- Fix with
htmlspecialchars() on output
- Add CSP header as defense in depth
AI prompts
- "Explain the three types of XSS with simple examples. Include stored, reflected, and DOM-based."
- "Write a PHP comment system that is vulnerable to XSS, then fix it with proper output escaping."
- "What is Content Security Policy? Show me a basic CSP header for a PHP website."
Starter code
<?php
// comments.php - VULNERABLE TO XSS
$comments = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comments[] = $_POST['comment'];
}
foreach ($comments as $comment) {
echo "<div class='comment'>$comment</div>";
}
?>
<form method="POST">
<textarea name="comment"></textarea>
<button type="submit">Post</button>
</form>
Final code
<?php
// comments.php - SECURE AGAINST XSS
session_start();
require_once 'config.php';
// Set security headers
header('Content-Type: text/html; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
// Simple in-memory storage (use database in production)
$comments = $_SESSION['comments'] ?? [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comment = trim($_POST['comment'] ?? '');
if (!empty($comment) && strlen($comment) < 500) {
$comments[] = [
'text' => $comment,
'date' => date('Y-m-d H:i')
];
$_SESSION['comments'] = $comments;
}
}
function escapeOutput($string) {
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Comments</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';">
</head>
<body>
<h1>Comments</h1>
<?php foreach ($comments as $comment): ?>
<div class="comment">
<p><?php echo escapeOutput($comment['text']); ?></p>
<small><?php echo escapeOutput($comment['date']); ?></small>
</div>
<?php endforeach; ?>
<form method="POST">
<textarea name="comment" maxlength="500" required></textarea><br>
<button type="submit">Post Comment</button>
</form>
</body>
</html>
Exercises
- Post
<script>alert('XSS')</script> as a comment in the vulnerable version. See it execute.
- Fix the vulnerable code by escaping output with
htmlspecialchars().
- Add a CSP header to block inline scripts.
Homework
- Audit three public websites for XSS vulnerabilities using browser developer tools.
- Write a secure comment system that stores comments in a MySQL database.
Quiz
- What does XSS stand for?
- A) Cross-Site Scripting
- B) Extra Style Sheet Syntax
- C) Extended Server Script
- D) XML Site Security
- Answer: A
- Which PHP function prevents XSS?
- A) htmlspecialchars()
- B) strip_tags()
- C) Both can help
- D) mysqli_real_escape_string()
- Answer: C
- What is stored XSS?
- A) Malicious script saved in the database
- B) Script in the URL
- C) Script in JavaScript variables
- D) Script in CSS
- Answer: A
- When should you escape output?
- A) When storing data
- B) When displaying data
- C) When receiving data
- D) Never
- Answer: B
- What does CSP stand for?
- A) Content Security Policy
- B) Cross-Site Protection
- C) Cascading Style Protection
- D) Content Script Protocol
- Answer: A
Lesson 3: CSRF Protection and Session Security
- Duration: 60 minutes
- Learning objectives:
- Understand Cross-Site Request Forgery (CSRF)
- Implement CSRF tokens in forms
- Secure PHP sessions
- Prevent session fixation and hijacking
Instructor notes
CSRF tricks users into performing actions they did not intend. Use the "bank transfer" example. Emphasize that GET requests should never change state.
Presentation outline
- What is CSRF?
- How CSRF attacks work
- CSRF tokens
- Session security best practices
- Secure session configuration
Demonstration
- Create a money transfer form without CSRF protection
- Show how an attacker could use a hidden image tag to trigger it
- Add CSRF token generation and validation
- Configure secure session settings in php.ini or .htaccess
AI prompts
- "Explain CSRF with a simple bank transfer example. How does the attack happen?"
- "Write a PHP function that generates and validates CSRF tokens for forms."
- "What are the best practices for securing PHP sessions? Show code examples."
Starter code
<?php
// transfer.php - VULNERABLE TO CSRF
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$to = $_POST['to'];
$amount = $_POST['amount'];
// Transfer money without verification
echo "Transferred $amount to $to";
}
?>
<form method="POST">
<input type="text" name="to" placeholder="Recipient">
<input type="number" name="amount" placeholder="Amount">
<button type="submit">Transfer</button>
</form>
Final code
<?php
// transfer.php - SECURE WITH CSRF PROTECTION
session_start();
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function validateCSRFToken($token) {
if (empty($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
function regenerateSession() {
session_regenerate_id(true);
}
// Secure session configuration
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if (!validateCSRFToken($token)) {
die("Invalid CSRF token");
}
$to = filter_input(INPUT_POST, 'to', FILTER_SANITIZE_STRING);
$amount = filter_input(INPUT_POST, 'amount', FILTER_VALIDATE_FLOAT);
if ($amount > 0 && $amount <= 1000) {
echo "Transferred $" . number_format($amount, 2) . " to " . htmlspecialchars($to);
regenerateSession();
} else {
echo "Invalid amount";
}
}
$csrf_token = generateCSRFToken();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Transfer</title>
</head>
<body>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="text" name="to" placeholder="Recipient" required>
<input type="number" name="amount" placeholder="Amount" required min="1" max="1000">
<button type="submit">Transfer</button>
</form>
</body>
</html>
<?php
// secure_session.php - Session security configuration
session_start();
// Regenerate session ID periodically
if (!isset($_SESSION['created'])) {
$_SESSION['created'] = time();
} else if (time() - $_SESSION['created'] > 1800) {
session_regenerate_id(true);
$_SESSION['created'] = time();
}
// Check user agent consistency
if (isset($_SESSION['user_agent'])) {
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
session_destroy();
header('Location: login.php?error=session_invalid');
exit();
}
} else {
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
}
// Check IP address (optional, can break mobile users)
if (isset($_SESSION['ip_address'])) {
if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
session_destroy();
header('Location: login.php?error=session_invalid');
exit();
}
} else {
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
}
?>
Exercises
- Add CSRF tokens to a contact form.
- Test session fixation by manually setting PHPSESSID before login.
- Configure PHP to use secure cookie settings.
Homework
- Add CSRF protection to all forms in your CRUD application.
- Write a session security guide for your team explaining why each setting matters.
Quiz
- What does CSRF stand for?
- A) Cross-Site Request Forgery
- B) Client-Side Request Form
- C) Cascading Style Request Format
- D) Cross-Server Response Function
- Answer: A
- What prevents CSRF?
- A) CSRF tokens
- B) HTML escaping
- C) HTTPS
- D) Password hashing
- Answer: A
- What is session fixation?
- A) Setting a known session ID before login
- B) Fixing session errors
- C) Remembering user preferences
- D) Encrypting sessions
- Answer: A
- What does session_regenerate_id() do?
- A) Creates a new session ID
- B) Deletes all sessions
- C) Validates the session
- D) Encrypts session data
- Answer: A
- What does HttpOnly cookie flag do?
- A) Prevents JavaScript access
- B) Forces HTTPS
- C) Encrypts the cookie
- D) Extends cookie lifetime
- Answer: A
Lesson 4: Password Hashing and Authentication Security
- Duration: 60 minutes
- Learning objectives:
- Understand why plain text passwords are dangerous
- Hash passwords with
password_hash()
- Verify passwords with
password_verify()
- Implement secure authentication flows
Instructor notes
Password breaches are common. Show real-world examples of leaked databases. Emphasize that bcrypt (default in PHP) is slow by design to resist brute force.
Presentation outline
- Password security fundamentals
- Hashing vs encryption
- PHP password hashing functions
- Secure login implementation
- Rate limiting and account lockout
Demonstration
- Hash a password with
password_hash()
- Verify it with
password_verify()
- Show how the hash changes each time
- Implement a simple login with rate limiting
AI prompts
- "Show me how to securely hash and verify passwords in PHP using password_hash() and password_verify()."
- "Write a PHP login system with rate limiting that locks accounts after 5 failed attempts for 15 minutes."
- "What are the recommended password policies? How do they balance security and usability?"
Starter code
<?php
// auth.php - STORING PASSWORDS IN PLAIN TEXT (NEVER DO THIS)
$password = $_POST['password'];
// Save to database as-is
$pdo->query("INSERT INTO users (email, password) VALUES ('$email', '$password')");
?>
Final code
<?php
// auth.php - SECURE AUTHENTICATION SYSTEM
session_start();
require_once 'config.php';
function hashPassword($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
function rateLimitCheck($identifier) {
$maxAttempts = 5;
$lockoutTime = 900; // 15 minutes
$stmt = $pdo->prepare("SELECT attempts, last_attempt FROM login_attempts WHERE identifier = :identifier");
$stmt->execute(['identifier' => $identifier]);
$attempt = $stmt->fetch();
if ($attempt) {
$timeDiff = time() - strtotime($attempt['last_attempt']);
if ($attempt['attempts'] >= $maxAttempts && $timeDiff < $lockoutTime) {
return [
'allowed' => false,
'remaining' => $lockoutTime - $timeDiff
];
}
if ($timeDiff >= $lockoutTime) {
$stmt = $pdo->prepare("DELETE FROM login_attempts WHERE identifier = :identifier");
$stmt->execute(['identifier' => $identifier]);
}
}
return ['allowed' => true];
}
function recordLoginAttempt($identifier, $success) {
if ($success) {
$stmt = $pdo->prepare("DELETE FROM login_attempts WHERE identifier = :identifier");
$stmt->execute(['identifier' => $identifier]);
} else {
$stmt = $pdo->prepare("
INSERT INTO login_attempts (identifier, attempts, last_attempt)
VALUES (:identifier, 1, NOW())
ON DUPLICATE KEY UPDATE attempts = attempts + 1, last_attempt = NOW()
");
$stmt->execute(['identifier' => $identifier]);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
if (!$email || empty($password)) {
$error = "Please fill all fields";
} else {
$ip = $_SERVER['REMOTE_ADDR'];
$rateLimit = rateLimitCheck($ip);
if (!$rateLimit['allowed']) {
$minutes = ceil($rateLimit['remaining'] / 60);
$error = "Too many attempts. Try again in $minutes minutes.";
} else {
$stmt = $pdo->prepare("SELECT id, name, password_hash FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
if ($user && verifyPassword($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
recordLoginAttempt($ip, true);
header('Location: dashboard.php');
exit();
} else {
recordLoginAttempt($ip, false);
$error = "Invalid credentials";
}
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<?php if (isset($error)): ?>
<p style="color: red;"><?php echo htmlspecialchars($error); ?></p>
<?php endif; ?>
<form method="POST">
<input type="email" name="email" placeholder="Email" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>
<?php
// register.php - Secure registration with password hashing
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
$errors = [];
if (strlen($name) < 2) {
$errors[] = "Name must be at least 2 characters";
}
if (!$email) {
$errors[] = "Valid email required";
}
if (strlen($password) < 8) {
$errors[] = "Password must be at least 8 characters";
}
if (empty($errors)) {
$passwordHash = hashPassword($password);
try {
$stmt = $pdo->prepare("INSERT INTO users (name, email, password_hash) VALUES (:name, :email, :hash)");
$stmt->execute([
'name' => $name,
'email' => $email,
'hash' => $passwordHash
]);
echo "Registration successful!";
} catch(PDOException $e) {
if ($e->getCode() == 23000) {
echo "Email already exists";
} else {
echo "Error: " . $e->getMessage();
}
}
} else {
foreach ($errors as $error) {
echo "<p style='color: red;'>$error</p>";
}
}
}
?>
Exercises
- Hash a password and verify it matches the original.
- Attempt to login 6 times with wrong credentials and observe the rate limit.
- Create a password strength meter in JavaScript.
Homework
- Implement password reset functionality with secure tokens.
- Write a security audit of your authentication system, identifying 3 potential improvements.
Quiz
- What is password hashing?
- A) Encrypting passwords
- B) Converting passwords to irreversible strings
- C) Compressing passwords
- D) Encoding passwords
- Answer: B
- Which PHP function hashes passwords?
- A) md5()
- B) sha1()
- C) password_hash()
- D) crypt()
- Answer: C
- Why should passwords never be stored in plain text?
- A) It uses too much space
- B) If the database is hacked, passwords are exposed
- C) It is slow
- D) It is illegal
- Answer: B
- What does bcrypt do?
- A) Encrypts data
- B) Hashes passwords with a salt
- C) Compresses files
- D) Validates emails
- Answer: B
- What is rate limiting?
- A) Limiting database queries
- B) Restricting login attempts
- C) Limiting file uploads
- D) Restricting API calls
- Answer: B
Lesson 5: HTTPS and Security Headers
- Duration: 60 minutes
- Learning objectives:
- Understand how HTTPS works
- Enable HTTPS with Let's Encrypt
- Configure security headers
- Test security with browser developer tools
Instructor notes
HTTPS is no longer optional. Google ranks HTTPS sites higher and browsers mark HTTP sites as "Not Secure." Show how to check the lock icon in the browser.
Presentation outline
- What is HTTPS?
- SSL/TLS certificates
- Free certificates with Let's Encrypt
- Security headers explained
- Testing security
Demonstration
- Show the difference between HTTP and HTTPS in browser
- Enable SSL in cPanel
- Add security headers in .htaccess
- Test with securityheaders.com
AI prompts
- "What security headers should every PHP website have? Show me the .htaccess configuration."
- "How do I get a free SSL certificate for my website? Explain Let's Encrypt step by step."
- "How can I test my website security? List 5 free online tools."
Starter code
<?php
// .htaccess - No security headers
// Empty or missing .htaccess file
?>
Final code
# .htaccess - Security headers configuration
# Force HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Security Headers
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Content Security Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https:; font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none';"
# Hide PHP version
Header always unset X-Powered-By
# Prevent access to sensitive files
<FilesMatch "^\.">
Require all denied
</FilesMatch>
<FilesMatch "\.(env|log|sql|txt)$">
Require all denied
</FilesMatch>
# Disable directory listing
Options -Indexes
# PHP settings
php_value upload_max_filesize 10M
php_value post_max_size 10M
php_value max_execution_time 60
<?php
// security-check.php - Security testing script
$checks = [];
// Check HTTPS
$checks['HTTPS'] = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443;
// Check security headers
$headers = getallheaders();
$checks['X-Content-Type-Options'] = isset($headers['X-Content-Type-Options']);
$checks['X-Frame-Options'] = isset($headers['X-Frame-Options']);
$checks['Content-Security-Policy'] = isset($headers['Content-Security-Policy']);
// Check PHP version
$checks['PHP Version'] = version_compare(PHP_VERSION, '8.0.0', '>=');
// Check error display
$checks['Display Errors Off'] = ini_get('display_errors') == 0;
echo "<h1>Security Check Results</h1>";
foreach ($checks as $name => $status) {
echo $status ? "<p style='color: green;'>[PASS] $name</p>" : "<p style='color: red;'>[FAIL] $name</p>";
}
?>
Exercises
- Install an SSL certificate on your hosting account.
- Add security headers to your .htaccess file.
- Run your site through securityheaders.com and fix any issues.
Homework
- Force HTTPS on your website and set up automatic HTTP to HTTPS redirects.
- Test your website with SSLLabs.com. Write a report on the results.
Quiz
- What does HTTPS stand for?
- A) HyperText Transfer Protocol Secure
- B) High Tech Transfer Protocol
- C) HyperText Transfer Protocol Standard
- D) None
- Answer: A
- What is Let's Encrypt?
- A) A hosting provider
- B) A free SSL certificate authority
- C) A domain registrar
- D) A CDN
- Answer: B
- What does X-Frame-Options: DENY do?
- A) Blocks all framing
- B) Allows framing
- C) Blocks scripts
- D) Enables HTTPS
- Answer: A
- What is a Content Security Policy?
- A) A list of allowed resources
- B) A password policy
- C) A backup schedule
- D) A firewall rule
- Answer: A
- What does the lock icon in the browser mean?
- A) Connection is secure (HTTPS)
- B) Site is bookmarked
- C) JavaScript is enabled
- D) Cookies are enabled
- Answer: A
Back to Module README
الوحدة 16: أساسيات الأمن
الدرس 1: حقن SQL والتحقق من المدخلات
- المدة: 90 دقيقة
- أهداف التعلم:
- فهم ما هو حقن SQL وكيف يعمل
- كتابة استعلامات SQL ضعيفة مقابل آمنة
- تطبيق التحقق من المدخلات في PHP
- استخدام العبارات المجهزة (prepared statements) مع PDO
ملاحظات للمدرب
حقن SQL هو أكثر الثغرات شيوعاً في تطبيقات الويب. اعرض أمثلة حقيقية للهجمات. أكّد أن مدخلات المستخدم ليست آمنة أبداً افتراضياً. استخدم قصة "بوبي تايزل" (Bobby Tables) لجعل الفكرة سهلة التذكر.
مخطط العرض
- ما هو حقن SQL؟
- كيف يستغل المهاجمون حقن SQL
- أمثلة على كود ضعيف
- العبارات المجهزة والاستعلامات المعلمة
- تقنيات التحقق من المدخلات
العرض التوضيحي
- اعرض نموذج تسجيل دخول ضعيفاً يقبل
' OR '1'='1
- أصلحه باستخدام العبارات المجهزة مع PDO
- أضف التحقق: الحقول المطلوبة، صيغة البريد، فحص الطول
- اختبر بمدخلات خبيثة وتأكد من فشلها بأمان
أوامر الذكاء الاصطناعي
- "اعرض لي سكربت تسجيل دخول PHP ضعيف مع حقن SQL. ثم اعرض النسخة الآمنة باستخدام العبارات المجهزة مع PDO."
- "اكتب دوال تحقق من المدخلات في PHP لـ: البريد الإلكتروني، رقم الهاتف، وكلمة المرور (8 أحرف على الأقل، رقم واحد، رمز خاص واحد)."
- "ما الفرق بين التحقق من المدخلات في جهة العميل (client-side) وجهة الخادم (server-side)؟ ولماذا نحتاج كلاهما؟"
الكود الابتدائي
<?php
// login.php - كود ضعيف (لا تستخدمه في الإنتاج)
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$pdo = new PDO('mysql:host=localhost;dbname=store', 'root', '');
$query = "SELECT * FROM users WHERE email = '$email' AND password = '$password'";
$stmt = $pdo->query($query);
$user = $stmt->fetch();
if ($user) {
echo "Welcome " . $user['name'];
} else {
echo "Invalid login";
}
?>
الكود النهائي
<?php
// login.php - كود آمن
session_start();
require_once 'config.php';
function validateInput($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
function validatePassword($password) {
if (strlen($password) < 8) {
return "Password must be at least 8 characters";
}
if (!preg_match('/[A-Z]/', $password)) {
return "Password must contain an uppercase letter";
}
if (!preg_match('/[a-z]/', $password)) {
return "Password must contain a lowercase letter";
}
if (!preg_match('/[0-9]/', $password)) {
return "Password must contain a number";
}
return true;
}
$email = validateInput($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$errors = [];
// التحقق في جهة الخادم
if (empty($email)) {
$errors[] = "Email is required";
} elseif (!validateEmail($email)) {
$errors[] = "Invalid email format";
}
if (empty($password)) {
$errors[] = "Password is required";
}
if (empty($errors)) {
try {
$stmt = $pdo->prepare("SELECT id, name, password_hash FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
header('Location: dashboard.php');
exit();
} else {
$errors[] = "Invalid email or password";
}
} catch(PDOException $e) {
error_log("Login error: " . $e->getMessage());
$errors[] = "An error occurred. Please try again.";
}
}
foreach ($errors as $error) {
echo "<p style='color: red;'>$error</p>";
}
?>
تمارين
- اكتب نموذج تسجيل دخول واختبره بحمولات حقن SQL مثل
' OR '1'='1.
- أنشئ نموذج تسجيل مع تحقق من الاسم والبريد وكلمة المرور.
- اكتب دالة تنظّف مدخلات المستخدم لاستعلامات قاعدة البيانات.
الواجب المنزلي
- خذ تطبيق CRUD موجوداً وأصلح كل ثغرات حقن SQL فيه.
- اكتب تقريراً أمنياً تحدد فيه 5 أنماط كود ضعيفة وجدتها على الإنترنت وكيفية إصلاحها.
اختبار قصير
- ما هو حقن SQL؟
- A) تقنية لتحسين قاعدة البيانات
- B) إدخال SQL خبيث عبر مدخلات المستخدم
- C) طريقة للنسخ الاحتياطي
- D) أداة لبناء الاستعلامات
- الإجابة: B
- ما هو أكثر طريق آمن للاستعلام عن قاعدة البيانات في PHP؟
- A) دمج النصوص (string concatenation)
- B) العبارات المجهزة (prepared statements)
- C) eval()
- D) mysql_query()
- الإجابة: B
- ماذا تفعل دالة
htmlspecialchars()؟
- A) تشفّر البيانات
- B) تحوّل الرموز الخاصة إلى كيانات HTML
- C) تتحقق من البريد الإلكتروني
- D) تُجزئ كلمات المرور
- الإجابة: B
- أين يجب أن يتم التحقق من المدخلات؟
- A) في JavaScript فقط
- B) في PHP فقط
- C) في جهة العميل والخادم معاً
- D) في قاعدة البيانات فقط
- الإجابة: C
- ما هي العبارة المجهزة (prepared statement)؟
- A) استعلام SQL يحتوي على عناصر نائبة (placeholders)
- B) مصفوفة في PHP
- C) نسخة احتياطية لقاعدة البيانات
- D) طريقة للاتصال
- الإجابة: A
الدرس 2: XSS وتهريب المخرجات
- المدة: 60 دقيقة
- أهداف التعلم:
- فهم البرمجة عبر المواقع (XSS)
- التمييز بين XSS المخزّن، والعاكس، والقائم على DOM
- تطبيق تهريب المخرجات في PHP
- استخدام ترويسات سياسة أمان المحتوى (CSP)
ملاحظات للمدرب
يسمح XSS للمهاجمين بحقن JavaScript في صفحاتك. اعرض كيف يمكن لنموذج تعليق بسيط أن يتحول إلى وسيلة هجوم. أكّد: الهروب يتم عند الإخراج، لا عند الإدخال.
مخطط العرض
- ما هو XSS؟
- أنواع XSS: المخزّن، العاكس، القائم على DOM
- كيف تعمل هجمات XSS
- تهريب المخرجات باستخدام
htmlspecialchars()
- سياسة أمان المحتوى (CSP)
العرض التوضيحي
- أنشئ نموذج تعليق ضعيفاً
- انشر تعليقاً يحتوي على
<script>alert('XSS')</script>
- أصلحه باستخدام
htmlspecialchars() عند الإخراج
- أضف ترويسة CSP كطبقة دفاعية إضافية
أوامر الذكاء الاصطناعي
- "اشرح أنواع XSS الثلاثة بأمثلة بسيطة. تضمين المخزّن، والعاكس، والقائم على DOM."
- "اكتب نظام تعليقات PHP ضعيفاً أمام XSS، ثم أصلحه باستخدام تهريب المخرجات الصحيح."
- "ما هي سياسة أمان المحتوى (CSP)؟ اعرض ترويسة CSP أساسية لموقع PHP."
الكود الابتدائي
<?php
// comments.php - ضعيف أمام XSS
$comments = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comments[] = $_POST['comment'];
}
foreach ($comments as $comment) {
echo "<div class='comment'>$comment</div>";
}
?>
<form method="POST">
<textarea name="comment"></textarea>
<button type="submit">Post</button>
</form>
الكود النهائي
<?php
// comments.php - آمن أمام XSS
session_start();
require_once 'config.php';
// تعيين ترويسات الأمان
header('Content-Type: text/html; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
// تخزين بسيط في الذاكرة (استخدم قاعدة بيانات في الإنتاج)
$comments = $_SESSION['comments'] ?? [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comment = trim($_POST['comment'] ?? '');
if (!empty($comment) && strlen($comment) < 500) {
$comments[] = [
'text' => $comment,
'date' => date('Y-m-d H:i')
];
$_SESSION['comments'] = $comments;
}
}
function escapeOutput($string) {
return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Comments</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';">
</head>
<body>
<h1>Comments</h1>
<?php foreach ($comments as $comment): ?>
<div class="comment">
<p><?php echo escapeOutput($comment['text']); ?></p>
<small><?php echo escapeOutput($comment['date']); ?></small>
</div>
<?php endforeach; ?>
<form method="POST">
<textarea name="comment" maxlength="500" required></textarea><br>
<button type="submit">Post Comment</button>
</form>
</body>
</html>
تمارين
- انشر
<script>alert('XSS')</script> كتعليق في النسخة الضعيفة. وشاهده يُنفَّذ.
- أصلح الكود الضعيف بتهريب المخرجات باستخدام
htmlspecialchars().
- أضف ترويسة CSP لمنع السكربتات المضمّنة.
الواجب المنزلي
- افحص ثلاثة مواقع عامة للبحث عن ثغرات XSS باستخدام أدوات مطور المتصفح.
- اكتب نظام تعليقات آمن يخزّن التعليقات في قاعدة بيانات MySQL.
اختبار قصير
- ماذا يعني اختصار XSS؟
- A) البرمجة عبر المواقع (Cross-Site Scripting)
- B) بنية أوراق أنماط إضافية
- C) سكربت خادم ممتد
- D) أمان مواقع XML
- الإجابة: A
- أي دالة PHP تمنع XSS؟
- A) htmlspecialchars()
- B) strip_tags()
- C) كلاهما قد يساعد
- D) mysqli_real_escape_string()
- الإجابة: C
- ما هو XSS المخزّن؟
- A) سكربت خبيث محفوظ في قاعدة البيانات
- B) سكربت في الرابط URL
- C) سكربت في متغيرات JavaScript
- D) سكربت في CSS
- الإجابة: A
- متى يجب تهريب المخرجات؟
- A) عند تخزين البيانات
- B) عند عرض البيانات
- C) عند استقبال البيانات
- D) أبداً
- الإجابة: B
- ماذا يعني اختصار CSP؟
- A) سياسة أمان المحتوى (Content Security Policy)
- B) حماية عبر المواقع
- C) حماية الأنماط المتتالية
- D) بروتوكول سكربت المحتوى
- الإجابة: A
الدرس 3: حماية CSRF وأمن الجلسات
- المدة: 60 دقيقة
- أهداف التعلم:
- فهم التزوير عبر المواقع (CSRF)
- تطبيق رموز CSRF في النماذج
- تأمين جلسات PHP
- منع تثبيت الجلسة وسرقتها
ملاحظات للمدرب
يخدع CSRF المستخدمين لاتخاذ إجراءات لم يقصدوها. استخدم مثال "تحويل البنك". أكّد أن طلبات GET يجب ألا تغيّر الحالة أبداً.
مخطط العرض
- ما هو CSRF؟
- كيف تعمل هجمات CSRF
- رموز CSRF
- أفضل ممارسات أمن الجلسات
- إعداد جلسة آمنة
العرض التوضيحي
- أنشئ نموذج تحويل أموال بدون حماية CSRF
- اعرض كيف يمكن للمهاجم استخدام وسمة صورة مخفية لتنفيذه
- أضف توليد رمز CSRF والتحقق منه
- اضبط إعدادات جلسة آمنة في php.ini أو .htaccess
أوامر الذكاء الاصطناعي
- "اشرح CSRF بمثال بسيط لتحويل بنكي. كيف يحدث الهجوم؟"
- "اكتب دالة PHP تولّد رموز CSRF وتتحقق منها للنماذج."
- "ما هي أفضل الممارسات لتأمين جلسات PHP؟ اعرض أمثلة كودية."
الكود الابتدائي
<?php
// transfer.php - ضعيف أمام CSRF
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$to = $_POST['to'];
$amount = $_POST['amount'];
// تحويل الأموال دون تحقق
echo "Transferred $amount to $to";
}
?>
<form method="POST">
<input type="text" name="to" placeholder="Recipient">
<input type="number" name="amount" placeholder="Amount">
<button type="submit">Transfer</button>
</form>
الكود النهائي
<?php
// transfer.php - آمن مع حماية CSRF
session_start();
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function validateCSRFToken($token) {
if (empty($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
function regenerateSession() {
session_regenerate_id(true);
}
// إعداد جلسة آمنة
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if (!validateCSRFToken($token)) {
die("Invalid CSRF token");
}
$to = filter_input(INPUT_POST, 'to', FILTER_SANITIZE_STRING);
$amount = filter_input(INPUT_POST, 'amount', FILTER_VALIDATE_FLOAT);
if ($amount > 0 && $amount <= 1000) {
echo "Transferred $" . number_format($amount, 2) . " to " . htmlspecialchars($to);
regenerateSession();
} else {
echo "Invalid amount";
}
}
$csrf_token = generateCSRFToken();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Transfer</title>
</head>
<body>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token); ?>">
<input type="text" name="to" placeholder="Recipient" required>
<input type="number" name="amount" placeholder="Amount" required min="1" max="1000">
<button type="submit">Transfer</button>
</form>
</body>
</html>
<?php
// secure_session.php - إعداد أمان الجلسة
session_start();
// تجديد معرّف الجلسة دورياً
if (!isset($_SESSION['created'])) {
$_SESSION['created'] = time();
} else if (time() - $_SESSION['created'] > 1800) {
session_regenerate_id(true);
$_SESSION['created'] = time();
}
// التحقق من ثبات متصفح المستخدم
if (isset($_SESSION['user_agent'])) {
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
session_destroy();
header('Location: login.php?error=session_invalid');
exit();
}
} else {
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
}
// التحقق من عنوان IP (اختياري، قد يؤثر على مستخدمي الجوال)
if (isset($_SESSION['ip_address'])) {
if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
session_destroy();
header('Location: login.php?error=session_invalid');
exit();
}
} else {
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
}
?>
تمارين
- أضف رموز CSRF إلى نموذج تواصل.
- اختبر تثبيت الجلسة بتعيين PHPSESSID يدوياً قبل تسجيل الدخول.
- اضبط PHP لاستخدام إعدادات كوكيز آمنة.
الواجب المنزلي
- أضف حماية CSRF إلى كل النماذج في تطبيق CRUD الخاص بك.
- اكتب دليلاً لأمن الجلسات لفريقك تشرح فيه سبب أهمية كل إعداد.
اختبار قصير
- ماذا يعني اختصار CSRF؟
- A) التزوير عبر المواقع (Cross-Site Request Forgery)
- B) نموذج طلب جهة العميل
- C) صيغة طلب أنماط متتالية
- D) دالة استجابة عبر الخوادم
- الإجابة: A
- ما الذي يمنع CSRF؟
- A) رموز CSRF
- B) تهريب HTML
- C) HTTPS
- D) تجزئة كلمات المرور
- الإجابة: A
- ما هو تثبيت الجلسة (session fixation)؟
- A) تعيين معرّف جلسة معروف قبل تسجيل الدخول
- B) إصلاح أخطاء الجلسة
- C) تذكّر تفضيلات المستخدم
- D) تشفير الجلسات
- الإجابة: A
- ماذا تفعل دالة session_regenerate_id()؟
- A) تنشئ معرّف جلسة جديد
- B) تحذف كل الجلسات
- C) تتحقق من الجلسة
- D) تشفّر بيانات الجلسة
- الإجابة: A
- ماذا يفعل علم الكوكيز HttpOnly؟
- A) يمنع وصول JavaScript
- B) يفرض HTTPS
- C) يشفّر الكوكيز
- D) يمدّد عمر الكوكيز
- الإجابة: A
الدرس 4: تجزئة كلمات المرور وأمن المصادقة
- المدة: 60 دقيقة
- أهداف التعلم:
- فهم سبب خطورة تخزين كلمات المرور بنص واضح
- تجزئة كلمات المرور باستخدام
password_hash()
- التحقق من كلمات المرور باستخدام
password_verify()
- تنفيذ تدفقات مصادقة آمنة
ملاحظات للمدرب
تسريبات كلمات المرور أمر شائع. اعرض أمثلة حقيقية من قواعد بيانات مسروقة. أكّد أن bcrypt (الافتراضي في PHP) بطيء بالتصميم لمقاومة القوة الغاشمة.
مخطط العرض
- أساسيات أمان كلمات المرور
- التجزئة مقابل التشفير
- دوال تجزئة كلمات المرور في PHP
- تنفيذ تسجيل دخول آمن
- الحد من المحاولات وقفل الحساب
العرض التوضيحي
- جزّئ كلمة مرور باستخدام
password_hash()
- تحقق منها باستخدام
password_verify()
- اعرض كيف يتغير التجزئة في كل مرة
- نفّذ تسجيل دخول بسيطاً مع حد للمحاولات
أوامر الذكاء الاصطناعي
- "اعرض لي كيف أجزّئ وأتحقق من كلمات المرور بأمان في PHP باستخدام password_hash() و password_verify()."
- "اكتب نظام تسجيل دخول PHP مع حد للمحاولات يقفل الحسابات بعد 5 محاولات فاشلة لمدة 15 دقيقة."
- "ما هي سياسات كلمات المرور الموصى بها؟ وكيف توازن بين الأمان وسهولة الاستخدام؟"
الكود الابتدائي
<?php
// auth.php - تخزين كلمات المرور بنص واضح (لا تفعل ذلك أبداً)
$password = $_POST['password'];
// حفظها في قاعدة البيانات كما هي
$pdo->query("INSERT INTO users (email, password) VALUES ('$email', '$password')");
?>
الكود النهائي
<?php
// auth.php - نظام مصادقة آمن
session_start();
require_once 'config.php';
function hashPassword($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
function rateLimitCheck($identifier) {
$maxAttempts = 5;
$lockoutTime = 900; // 15 دقيقة
$stmt = $pdo->prepare("SELECT attempts, last_attempt FROM login_attempts WHERE identifier = :identifier");
$stmt->execute(['identifier' => $identifier]);
$attempt = $stmt->fetch();
if ($attempt) {
$timeDiff = time() - strtotime($attempt['last_attempt']);
if ($attempt['attempts'] >= $maxAttempts && $timeDiff < $lockoutTime) {
return [
'allowed' => false,
'remaining' => $lockoutTime - $timeDiff
];
}
if ($timeDiff >= $lockoutTime) {
$stmt = $pdo->prepare("DELETE FROM login_attempts WHERE identifier = :identifier");
$stmt->execute(['identifier' => $identifier]);
}
}
return ['allowed' => true];
}
function recordLoginAttempt($identifier, $success) {
if ($success) {
$stmt = $pdo->prepare("DELETE FROM login_attempts WHERE identifier = :identifier");
$stmt->execute(['identifier' => $identifier]);
} else {
$stmt = $pdo->prepare("
INSERT INTO login_attempts (identifier, attempts, last_attempt)
VALUES (:identifier, 1, NOW())
ON DUPLICATE KEY UPDATE attempts = attempts + 1, last_attempt = NOW()
");
$stmt->execute(['identifier' => $identifier]);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
if (!$email || empty($password)) {
$error = "Please fill all fields";
} else {
$ip = $_SERVER['REMOTE_ADDR'];
$rateLimit = rateLimitCheck($ip);
if (!$rateLimit['allowed']) {
$minutes = ceil($rateLimit['remaining'] / 60);
$error = "Too many attempts. Try again in $minutes minutes.";
} else {
$stmt = $pdo->prepare("SELECT id, name, password_hash FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
$user = $stmt->fetch();
if ($user && verifyPassword($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
recordLoginAttempt($ip, true);
header('Location: dashboard.php');
exit();
} else {
recordLoginAttempt($ip, false);
$error = "Invalid credentials";
}
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<?php if (isset($error)): ?>
<p style="color: red;"><?php echo htmlspecialchars($error); ?></p>
<?php endif; ?>
<form method="POST">
<input type="email" name="email" placeholder="Email" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>
<?php
// register.php - تسجيل آمن مع تجزئة كلمة المرور
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
$errors = [];
if (strlen($name) < 2) {
$errors[] = "Name must be at least 2 characters";
}
if (!$email) {
$errors[] = "Valid email required";
}
if (strlen($password) < 8) {
$errors[] = "Password must be at least 8 characters";
}
if (empty($errors)) {
$passwordHash = hashPassword($password);
try {
$stmt = $pdo->prepare("INSERT INTO users (name, email, password_hash) VALUES (:name, :email, :hash)");
$stmt->execute([
'name' => $name,
'email' => $email,
'hash' => $passwordHash
]);
echo "Registration successful!";
} catch(PDOException $e) {
if ($e->getCode() == 23000) {
echo "Email already exists";
} else {
echo "Error: " . $e->getMessage();
}
}
} else {
foreach ($errors as $error) {
echo "<p style='color: red;'>$error</p>";
}
}
}
?>
تمارين
- جزّئ كلمة مرور وتحقق من تطابقها مع الأصل.
- حاول تسجيل الدخول 6 مرات ببيانات خاطئة وراقب حد المحاولات.
- أنشئ مؤشر قوة كلمة المرور في JavaScript.
الواجب المنزلي
- نفّذ ميزة إعادة تعيين كلمة المرور برموز آمنة.
- اكتب تدقيقاً أمنياً لنظام المصادقة الخاص بك، مع تحديد 3 تحسينات محتملة.
اختبار قصير
- ما هي تجزئة كلمة المرور؟
- A) تشفير كلمات المرور
- B) تحويل كلمات المرور إلى سلاسل لا رجعة فيها
- C) ضغط كلمات المرور
- D) ترميز كلمات المرور
- الإجابة: B
- أي دالة PHP تجزّئ كلمات المرور؟
- A) md5()
- B) sha1()
- C) password_hash()
- D) crypt()
- الإجابة: C
- لماذا لا يجب تخزين كلمات المرور بنص واضح أبداً؟
- A) تستهلك مساحة كبيرة
- B) إذا تم اختراق قاعدة البيانات، تُكشف كلمات المرور
- C) بطيئة
- D) غير قانونية
- الإجابة: B
- ماذا يفعل bcrypt؟
- A) يشفّر البيانات
- B) يجزّئ كلمات المرور مع ملح (salt)
- C) يضغط الملفات
- D) يتحقق من البريد الإلكتروني
- الإجابة: B
- ما هو الحد من المحاولات (rate limiting)؟
- A) الحد من استعلامات قاعدة البيانات
- B) تقييد محاولات تسجيل الدخول
- C) الحد من رفع الملفات
- D) تقييد استدعاءات API
- الإجابة: B
الدرس 5: HTTPS وترويسات الأمان
- المدة: 60 دقيقة
- أهداف التعلم:
- فهم كيف يعمل HTTPS
- تفعيل HTTPS مع Let's Encrypt
- ضبط ترويسات الأمان
- اختبار الأمان باستخدام أدوات مطور المتصفح
ملاحظات للمدرب
HTTPS لم يعد اختيارياً. جوجل ترفع ترتيب مواقع HTTPS والمتصفحات تضع علامة "غير آمن" على مواقع HTTP. اعرض كيفية التحقق من أيقونة القفل في المتصفح.
مخطط العرض
- ما هو HTTPS؟
- شهادات SSL/TLS
- شهادات مجانية مع Let's Encrypt
- شرح ترويسات الأمان
- اختبار الأمان
العرض التوضيحي
- اعرض الفرق بين HTTP و HTTPS في المتصفح
- فعّل SSL في cPanel
- أضف ترويسات الأمان في .htaccess
- اختبر مع securityheaders.com
أوامر الذكاء الاصطناعي
- "ما هي ترويسات الأمان التي يجب أن يملكها كل موقع PHP؟ اعرض إعداد .htaccess."
- "كيف أحصل على شهادة SSL مجانية لموقعي؟ اشرح Let's Encrypt خطوة بخطوة."
- "كيف يمكنني اختبار أمان موقعي؟ اذكر 5 أدوات مجانية عبر الإنترنت."
الكود الابتدائي
<?php
// .htaccess - بدون ترويسات أمان
// ملف .htaccess فارغ أو غير موجود
?>
الكود النهائي
# .htaccess - إعداد ترويسات الأمان
# إجبار استخدام HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# ترويسات الأمان
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# سياسة أمان المحتوى
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https:; font-src 'self' https://fonts.googleapis.com https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none';"
# إخفاء إصدار PHP
Header always unset X-Powered-By
# منع الوصول للملفات الحساسة
<FilesMatch "^\.">
Require all denied
</FilesMatch>
<FilesMatch "\.(env|log|sql|txt)$">
Require all denied
</FilesMatch>
# تعطيل عرض محتويات المجلد
Options -Indexes
# إعدادات PHP
php_value upload_max_filesize 10M
php_value post_max_size 10M
php_value max_execution_time 60
<?php
// security-check.php - سكربت اختبار الأمان
$checks = [];
// فحص HTTPS
$checks['HTTPS'] = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443;
// فحص ترويسات الأمان
$headers = getallheaders();
$checks['X-Content-Type-Options'] = isset($headers['X-Content-Type-Options']);
$checks['X-Frame-Options'] = isset($headers['X-Frame-Options']);
$checks['Content-Security-Policy'] = isset($headers['Content-Security-Policy']);
// فحص إصدار PHP
$checks['PHP Version'] = version_compare(PHP_VERSION, '8.0.0', '>=');
// فحص عرض الأخطاء
$checks['Display Errors Off'] = ini_get('display_errors') == 0;
echo "<h1>Security Check Results</h1>";
foreach ($checks as $name => $status) {
echo $status ? "<p style='color: green;'>[PASS] $name</p>" : "<p style='color: red;'>[FAIL] $name</p>";
}
?>
تمارين
- ثبّت شهادة SSL على حساب الاستضافة الخاص بك.
- أضف ترويسات الأمان إلى ملف .htaccess الخاص بك.
- شغّل موقعك عبر securityheaders.com وأصلح أي مشاكل.
الواجب المنزلي
- أجبر استخدام HTTPS على موقعك واضبط تحويلاً تلقائياً من HTTP إلى HTTPS.
- اختبر موقعك مع SSLLabs.com. اكتب تقريراً بالنتائج.
اختبار قصير
- ماذا يعني اختصار HTTPS؟
- A) بروتوكول نقل النص التشعبي الآمن (HyperText Transfer Protocol Secure)
- B) بروتوكول نقل تقني عالٍ
- C) بروتوكول نقل النص التشعبي القياسي
- D) لا شيء
- الإجابة: A
- ما هو Let's Encrypt؟
- A) مزوّد استضافة
- B) سلطة شهادات SSL مجانية
- C) مسجّل نطاقات
- D) شبكة توصيل محتوى (CDN)
- الإجابة: B
- ماذا يفعل X-Frame-Options: DENY؟
- A) يمنع كل التضمين في إطارات
- B) يسمح بالتضمين
- C) يمنع السكربتات
- D) يفعّل HTTPS
- الإجابة: A
- ما هي سياسة أمان المحتوى (Content Security Policy)؟
- A) قائمة بالموارد المسموح بها
- B) سياسة كلمات المرور
- C) جدول نسخ احتياطي
- D) قاعدة جدار ناري
- الإجابة: A
- ماذا تعني أيقونة القفل في المتصفح؟
- A) الاتصال آمن (HTTPS)
- B) الموقع محفوظ في المفضلة
- C) JavaScript مفعّل
- D) الكوكيز مفعّلة
- الإجابة: A
العودة إلى ملف الوحدة