Kotchasan PHP Framework

เทคนิคการจัดเก็บรูปภาพหรือไฟล์อย่างปลอดภัย

การจัดเก็บรูปภาพหรือไฟล์บน Server เมื่อต้องการเรียกดูหรือดาวน์โหลด เราจะสามารถเรียกผ่าน URL ได้ เช่น domain.tld/path/to/images/image.jpg ซึ่งหมายความว่า ไฟล์ image.jpg จัดเก็บอยู่ที่ไดเร็คทอรี่ /path/to/images/ จากตัวอย่างนี้ ใครก็ตามที่รู้เส้นทางนี้ สามารถเข้าถึงไฟล์ที่ต้องการได้โดยตรง
ข่าวร้ายก็คือ ถ้าเรารู้ ผู้ไม่ประสงค์ดีก็รู้ได้เช่นกัน อาจด้วยการสุ่มหรือคาดเดาก็ได้ วันนี้เราจะมาพูดถึงการป้องกันการเข้าถึงไฟล์โดยตรงด้วย PHP กัน

ว่าด้วยเรื่องวิธีการจัดเก็บกันก่อน

1. จัดเก็บไฟล์นอกโฟลเดอร์ที่เข้าถึงได้จากเว็บเซิร์ฟเวอร์
คุณสามารถจัดเก็บไฟล์ในไดเร็กทอรีที่อยู่นอกโฟลเดอร์ที่เว็บเซิร์ฟเวอร์สามารถเข้าถึงได้โดยตรง ตัวอย่างเช่น

/project
    /public_html  <- โฟลเดอร์ที่เว็บเซิร์ฟเวอร์เข้าถึงได้
    /images       <- โฟลเดอร์ที่ใช้เก็บรูปภาพ

คุณสามารถเก็บไฟล์ไว้ที่ images/ ได้ ซึ่งไฟล์ที่อยู่ในโฟลเดอร์นี้จะไม่สามารถเรียกใช้งานได้ผ่าน URL

2. ใช้ .htaccess เพื่อป้องกันการเข้าถึง
ถ้าคุณต้องการเก็บไฟล์ในไดเร็กทอรีที่สามารถเข้าถึงได้จากเว็บเซิร์ฟเวอร์ คุณสามารถใช้ไฟล์ .htaccess เพื่อป้องกันการเข้าถึงโดยตรงได้ เช่น
สร้างไฟล์ .htaccess ในไดเร็กทอรีที่เก็บไฟล์รูปภาพ

Order Allow,Deny
Deny from all

วิธีนี้จะป้องกันการเข้าถึงไฟล์ในไดเร็กทอรีนั้นโดยตรงจาก URL ได้

3. เข้ารหัสชื่อไฟล์
อีกวิธีหนึ่งคือการเข้ารหัสชื่อไฟล์หรือเก็บไฟล์ในโฟลเดอร์ที่มีชื่อแบบสุ่ม ซึ่งจะทำให้การเดา URL ของไฟล์ทำได้ยากขึ้น ตัวอย่างเช่น

<?php
// สร้างชื่อไฟล์สุ่ม
$file_name = md5(uniqid()) . '.jpg';

// บันทึกไฟล์ลงในโฟลเดอร์ที่ตั้งชื่อสุ่ม
move_uploaded_file($_FILES['image']['tmp_name'], '/path/to/images/' . $file_name);
?>

การสุ่มชื่อไฟล์จะทำให้การคาดเดาชื่อของไฟล์ทำได้ยากขึ้นและเสียเวลามากขึ้น เช่น idcard.jpg หรือ ased2236445w.jpg ชื่อแบบหลังจะทำให้การสุ่มเรียกชื่อไฟล์ทำได้ยากกว่า

การนำไฟล์ที่มีการป้องกันมาใช้งาน

การจัดเก็บไฟล์ตามวิธีที่ 1 และ 2 จะทำให้เราสูญเสียการเข้าถึงไฟล์โดยตรงผ่าน URL ส่วนในวิธีที่ 3 อาจต้องอาศัยการจัดเก็บทีอยู่ของไฟล์จริงๆลงฐานข้อมูล เพื่อไว้ใช้อ้างอิงเมื่อต้องการเรียกใช้ไฟล์ ซี่งเมื่อได้ที่อยู่จริงของไฟล์มาแล้ว เราจะยังคงเรียกใช้ไฟล์ผ่าน URL ได้ตามปกติ หรือ อาจใช้วิธีจัดเก็บไฟล์แบบสุ่มลงในโฟลเดอร์ที่เกี่ยวข้องไว้ก็ได้ เช่นการสร้างไดเร็คทอรี่ที่เก็บไฟล์เป็น ID และเก็บไฟล์ที่เกี่ยวข้องแบบสุ่มลงในโฟลเดอร์นั้นๆ การเรียกใช้งานก็ใช้วิธีแสกนโฟลเดอร์เอาไฟล์ออกมาเลย ซึ่งผมใช้วิธีนี้กับการจัดเก็บไฟล์ทั่วๆไป เช่นไฟล์รูปของสินค้า (หนึ่งสินค้ามีหลายรูป) ซึ่งประเด็นหลักก็คือมันสามารถเรียกไฟล์ผ่าน URL ได้ตามปกติ แต่มีการป้องกันโดยการตั้งชื่อไฟล์แบบสุ่ม

ในกรณีของไฟล์ที่มีความอ่อนไหว เช่นบัตรประชาชน การจัดเก็บตามวิธีที่ 3 อาจไม่ค่อยปลอดภัยนัก วิธีที่ 1 และ 2 จะปลอดภัยกว่า เพราะมันจะทำให้เราสามารถให้สิทธิ์รวมถึงควบคุมการใช้งานไฟล์ได้ โดยการเรียกใช้ไฟล์ผ่าน PHP ดังโค้ดตัวอย่างด้านล่าง

<?php
session_start();

// เช็คสิทธิ์ผู้ใช้ (ปรับให้เหมาะสมกับระบบของคุณ)
if (!isset($_SESSION['login'])) {
    die('Access denied.');
}

// รับชื่อไฟล์รูปภาพจากพารามิเตอร์ GET
$image = basename($_GET['image']);

// กำหนดเส้นทางไปยังโฟลเดอร์ที่เก็บรูปภาพ
$file = '/path/to/protected/images/' . $image;

if (file_exists($file)) {
    // กำหนด Content-Type ตามประเภทของรูปภาพ
    $mime_type = mime_content_type($file);
    header('Content-Type: ' . $mime_type);
    header('Content-Length: ' . filesize($file));
    readfile($file);
    exit;
} else {
    // ถ้าไฟล์ไม่พบ
    header("HTTP/1.0 404 Not Found");
    echo "File not found.";
}
?>

ตัวอย่างโค้ดด้านบนมีชื่อไฟล์ว่า image.php เมื่อมีการเรียกไฟล์นี้ จะมีการตรวจสอบสิทธิ์โดย SESSION ก่อน จากนั้นจะอ่านชื่อรูปภาพจาก $_GET['image']  นำไปรวมกันกับ path จริงที่จัดเก็บไฟล์ (นอก Server หรือที่ได้รับการป้องกันไว้) ซึ่ง PHP จะสามารถโหลดไฟล์และนำมาแสดงผลเหมือนกับการโหลดไฟล์หรือรูปภาพตรงๆได้

ใน HTML คุณสามารถใช้ <img> แท็กเพื่อแสดงรูปภาพโดยการเรียกใช้ไฟล์ image.php พร้อมกับพารามิเตอร์ GET ที่มีชื่อไฟล์รูปภาพ เช่น

<!DOCTYPE html>
<html>
<head>
    <title>Protected Image</title>
</head>
<body>
    <h1>Protected Image</h1>
    <img src="image.php?image=ased2236445w.jpg" alt="Protected Image">
</body>
</html>

โค้ดต่างๆในบทความนี้เป็นเพียงตัวอย่างพื้นฐานเท่านั้นนะครับ ในการนำไปใช้งานจริงอาจต้องปรับปรุงโค้ดให้เหมาะสมกับโปรเจ็คของตัวเองเพิ่มเติม