در یکی از پروژه‌های فروشگاهی، سایتی که فایروال و افزونه امنیتی داشت، هک شد. تیم پشتیبانی هاست هشدار داد که دیتابیس MySQL (سیستم مدیریت دیتابیس متن‌باز) از یک IP ناشناس درخواست دریافت کرده. وقتی لاگ‌ها را بررسی کردیم، متوجه شدیم رمز کاربر دیتابیس از سه سال پیش تغییر نکرده بود و به‌صورت متن ساده در فایل بکاپ قدیمی ذخیره شده بود. آن پرونده یادآوری شد که در امنیت سایت، دیتابیس، لایه‌ای است که اغلب فراموش می‌شود، درحالی‌که حاوی باارزش‌ترین داده‌هاست. در این راهنما، همان چارچوب پنج‌لایه‌ای را می‌گویم که در همه پروژه‌ها استفاده می‌کنم.

اگر با مفاهیم پایه دیتابیس آشنا نیستید، ابتدا بهینه‌سازی جدول‌های MySQL و تأثیر دیتابیس بر سرعت سایت را بخوانید. برای درک کلی امنیت، راهنمای امنیت وردپرس پیش‌نیاز است.

امنیت دیتابیس: لایه‌ای که فراموش می‌شود

در پروژه‌های امنیتی، سه لایه اصلی داریم:

  1. لایه سایت: افزونه امنیتی، فایروال، 2FA.
  2. لایه سرور: SSH، فایروال شبکه، به‌روزرسانی سیستم.
  3. لایه دیتابیس: اعتبارنامه، دسترسی، پاکسازی، بکاپ.

لایه سوم، همیشه در سایه دو لایه اول است. درحالی‌که در تجربه من، بیشترین حملات موفق پس از نفوذ، در لایه دیتابیس آسیب وارد می‌کنند. سه دلیل:

  • دیتابیس حاوی اطلاعات حساس (کاربران، سفارشات، پرداخت‌ها).
  • بیشتر مدیران سایت، از دیتابیس فقط برای بکاپ استفاده می‌کنند و به امنیتش فکر نمی‌کنند.
  • دسترسی به دیتابیس، معمولاً در فایل‌های پیکربندی ذخیره می‌شود که خودش هدف حمله است.
سایت امن، دیتابیس ناامن، مثل خانه‌ای است که درب ورودی‌اش قفل است ولی پنجره‌های طبقه دومش باز. سارق از پنجره وارد می‌شود.

چارچوب پنج‌لایه امنیت دیتابیس

در پروژه‌های واقعی، پنج لایه را جداگانه امن می‌کنم. هر لایه، هدف و ابزار خودش را دارد:

لایههدفابزار اصلی
اعتبارنامهرمز قوی و یکتامدیریت رمز، تولید تصادفی
کاربران MySQLحداقل دسترسی لازمGRANT با محدوده
دسترسی شبکهمحدودسازی اتصالbind-address، فایروال سرور
پاکسازی دادهجلوگیری از تزریقPrepared Statement، validation
رمزنگاری و بکاپحفاظت از داده در حال انتقال و ذخیرهSSL، بکاپ رمزگذاری‌شده

بخش‌های بعدی، هر لایه را با جزئیات باز می‌کنند.

لایه اول: اعتبارنامه‌های امن

اعتبارنامه دیتابیس، شامل چهار جزء است:

  1. نام کاربری دیتابیس: نه root، نه admin.
  2. رمز عبور: حداقل ۳۲ کاراکتر، تصادفی.
  3. نام دیتابیس: با پیشوند یکتا، نه wordpress.
  4. هاست: localhost نه %.

تولید رمز قوی

رمز دیتابیس باید کاملاً تصادفی باشد. از ابزارهای تولید رمز استفاده کنید:

# در لینوکس
openssl rand -base64 48

# یا در PHP
bin2hex(random_bytes(32))

این دستورات، رشته‌های طولانی و تصادفی تولید می‌کنند. رمز را در ابزار مدیریت رمز ذخیره کنید، نه در فایل متنی روی دسکتاپ.

فایل wp-config.php

چهار ثابت مربوط به دیتابیس در فایل wp-config.php ذخیره می‌شوند:

define( 'DB_NAME', 'wp_a7f3b9c' );
define( 'DB_USER', 'wp_d8e2f4a' );
define( 'DB_PASSWORD', 'یک-رمز-بسیار-طولانی-و-تصادفی' );
define( 'DB_HOST', 'localhost' );

راهنمای سخت‌سازی این فایل در امن‌سازی فایل wp-config.

تغییر دوره‌ای رمز

تغییر رمز دیتابیس، پروژه‌ای است که در تجربه من هرگز بدون بکاپ انجام نمی‌شود:

  1. بکاپ کامل دیتابیس.
  2. تغییر رمز در پنل هاست یا phpMyAdmin.
  3. به‌روزرسانی رمز در wp-config.php.
  4. تست سایت و پیشخوان.
  5. حذف بکاپ پس از تأیید.

در پروژه‌هایی که تیم‌های چندنفره دارند، تغییر دوره‌ای رمز دیتابیس هر ۶ ماه توصیه می‌شود.

ابزارهای مدیریت رمز

مدیریت رمز دیتابیس به‌صورت دستی، ریسک دارد. ابزارهای توصیه‌شده:

  • Bitwarden: رایگان، متن‌باز.
  • 1Password: پولی، حرفه‌ای.
  • KeePass: متن‌باز، آفلاین.

مسیر تفصیلی در مدیریت رمز عبور امن.

لایه دوم: کاربران و دسترسی‌های MySQL

دیتابیس MySQL (Structured Query Language)، سیستم مجوزدهی پیچیده‌ای دارد. استفاده اشتباه از آن، بزرگ‌ترین ریسک است.

قاعده حداقل دسترسی

کاربری که وردپرس استفاده می‌کند، باید حداقل دسترسی لازم را داشته باشد. اکثر هاست‌ها به‌صورت پیش‌فرض، کاربر را با دسترسی کامل ALL PRIVILEGES می‌سازند. این خوب نیست.

دسترسی‌های لازم برای وردپرس:

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX
ON wp_database.*
TO 'wp_user'@'localhost';

دسترسی‌های غیرضروری:

  • FILE: اجازه خواندن/نوشتن فایل روی سرور (برای وردپرس لازم نیست).
  • PROCESS: مشاهده فرآیندهای دیگر کاربران (لازم نیست).
  • SUPER: بالاترین سطح دسترسی (هرگز).
  • GRANT OPTION: اجازه اعطای دسترسی به دیگران (هرگز).

حذف کاربران اضافی

در دیتابیس‌های قدیمی، ممکن است کاربران اضافی وجود داشته باشند:

SELECT User, Host FROM mysql.user;

هر کاربری که نمی‌شناسید، حذف کنید:

DROP USER 'unknown_user'@'localhost';

محدودسازی کاربر root

کاربر root MySQL، بالاترین دسترسی را دارد. سه کار برای محدودسازی:

  1. رمز قوی: حداقل ۳۲ کاراکتر تصادفی.
  2. دسترسی محدود: فقط از localhost، نه از هر IP.
  3. عدم استفاده در وردپرس: وردپرس هرگز با کاربر root کار نکند.
# در MySQL
SELECT User, Host FROM mysql.user WHERE User = 'root';

اگر خروجی نشان داد root از % دسترسی دارد، این دسترسی را محدود کنید:

UPDATE mysql.user SET Host = 'localhost' WHERE User = 'root' AND Host = '%';
FLUSH PRIVILEGES;
کاربر root که از هر IP دسترسی دارد، مثل کلید طلایی است که روی درب خانه آویزان شده. حتماً محدودش کنید.

لایه سوم: محدودسازی دسترسی شبکه

پیش‌فرض MySQL، به localhost گوش می‌دهد. ولی در بعضی تنظیمات سرور، این محدودیت برداشته می‌شود و هر IP می‌تواند به دیتابیس وصل شود. این خودش یک حفره امنیتی جدی است.

بررسی bind-address

در فایل my.cnf (یا mysqld.cnf)، تنظیم bind-address را ببینید:

[mysqld]
bind-address = 127.0.0.1

مقدار صحیح: 127.0.0.1 یا localhost. اگر 0.0.0.0 است، هر IP می‌تواند وصل شود. این تنظیم باید اصلاح شود.

فایروال سرور

اگر به هر دلیلی نیاز به دسترسی از راه دور دارید (مثلاً اتصال از یک ابزار مدیریت دیتابیس روی لپ‌تاپ شما)، پورت ۳۳۰۶ MySQL را فقط برای IP خودتان باز کنید:

# در UFW
ufw allow from 1.2.3.4 to any port 3306

# یا در iptables
iptables -A INPUT -p tcp --dport 3306 -s 1.2.3.4 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j DROP

راهنمای تفصیلی در فایروال نرم‌افزاری در سرور.

SSH Tunnel

راه امن‌تر برای دسترسی از راه دور به دیتابیس، استفاده از SSH Tunnel است. در این روش، پورت ۳۳۰۶ روی سرور بسته می‌ماند، ولی از طریق SSH به‌صورت رمزگذاری‌شده تونل ایجاد می‌شود:

ssh -L 3307:localhost:3306 user@server.com

سپس به‌جای اتصال به server.com:3306، به localhost:3307 وصل شوید. امن‌ترین روش برای دسترسی از راه دور.

لایه چهارم: پاکسازی و مدیریت داده

در ورودی‌های دیتابیس، تزریق SQL (SQL Injection) شایع‌ترین حمله است. در راهنمای SQL Injection چیست و چگونه جلوگیری کنیم به تفصیل پرداخته‌ام. اینجا خلاصه‌ای از اصول:

اصل اول: Prepared Statement

هرگز ورودی کاربر را مستقیم در کوئری SQL قرار ندهید. در وردپرس، از $wpdb->prepare استفاده کنید:

global $wpdb;
$sql = $wpdb->prepare(
    "SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s",
    $user_id,
    'publish'
);
$results = $wpdb->get_results( $sql );

پارامتر %d برای اعداد، %s برای رشته‌ها، %f برای اعشار.

اصل دوم: اعتبارسنجی ورودی

حتی با Prepared Statement، ورودی کاربر را اعتبارسنجی کنید:

$user_id = absint( $_GET['user'] );
if ( $user_id <= 0 ) {
    wp_die( 'شناسه کاربر نامعتبر است' );
}

$email = sanitize_email( $_POST['email'] );
if ( ! is_email( $email ) ) {
    wp_die( 'ایمیل نامعتبر است' );
}

مسیر تفصیلی در اعتبارسنجی داده‌ها در وردپرس و پاک‌سازی داده‌ها.

اصل سوم: نظارت بر ورودی‌های خودکار

در ورودی‌هایی که از فرم‌های عمومی می‌آیند (نظرات، فرم تماس، ثبت‌نام)، از CAPTCHA و محدودسازی نرخ استفاده کنید. یک ورودی نظری که ۱۰۰۰ کاراکتر تصادفی باشد، ممکن است حمله تزریق باشد.

اصل چهارم: حداقل داده

هر داده‌ای که در دیتابیس ذخیره می‌کنید، ریسک افشا دارد. اصل حداقل داده: فقط چیزی را ذخیره کنید که نیاز دارید.

  • شماره کارت بانکی ذخیره نکنید (اگر درگاه پرداخت خودش مدیریت می‌کند).
  • اطلاعات حساس کاربر را رمزگذاری کنید. راهنمای تفصیلی در رمزنگاری دیتابیس.
  • داده‌های قدیمی که نیاز ندارید را حذف کنید.

لایه پنجم: رمزنگاری و بکاپ

لایه پنجم، حفاظت از داده در حال انتقال و در حالت ذخیره.

رمزنگاری در انتقال (Encryption in Transit)

اتصال بین وردپرس و MySQL، در حالت پیش‌فرض رمزگذاری نمی‌شود. اگر وردپرس و MySQL روی سرورهای جداگانه باشند، این ارتباط قابل شنود است. راه‌حل: SSL/TLS بین کلاینت و سرور MySQL.

در وردپرس، بعد از تنظیم سرور، فایل wp-config.php را به‌روز کنید:

define( 'MYSQL_CLIENT_FLAGS', MYSQL_CLIENT_SSL );

روی هاست‌های اشتراکی، این لایه معمولاً غیرقابل تنظیم است. ولی روی VPS یا سرور اختصاصی، توصیه می‌شود.

رمزنگاری در ذخیره (Encryption at Rest)

رمزنگاری کل دیتابیس، در بعضی سرورها از سطح سیستم‌عامل انجام می‌شود. ولی در سطح اپلیکیشن، می‌توان داده‌های حساس خاص را رمزگذاری کرد. مثلاً شماره تلفن کاربران:

$encrypted = openssl_encrypt(
    $phone,
    'AES-256-CBC',
    ENCRYPTION_KEY,
    0,
    $iv = openssl_random_pseudo_bytes(16)
);

update_user_meta( $user_id, 'encrypted_phone', base64_encode( $iv . $encrypted ) );

راهنمای تفصیلی در رمزنگاری دیتابیس.

بکاپ رمزگذاری‌شده

بکاپ دیتابیس، معمولاً حاوی تمام داده‌های حساس است. اگر بکاپ رمزگذاری نشده باشد، یک فایل دیتابیس در سرور FTP شما، یک نقص امنیتی جدی است.

# بکاپ رمزگذاری‌شده با GPG
mysqldump -u user -p database_name | \
    gpg --symmetric --cipher-algo AES256 \
    -o backup_$(date +%Y%m%d).sql.gpg

برای بازیابی:

gpg -d backup_20260916.sql.gpg | mysql -u user -p database_name

راهنمای تفصیلی در بکاپ امن دیتابیس و بکاپ دیتابیس وردپرس.

بکاپ رمزگذاری‌نشده، مثل گذاشتن کلید خانه در زیر پادری است. همه می‌دانند کجاست.

پایش و لاگ‌گیری

پایش منظم، لایه ششم دفاع است. چهار سیگنال که هفتگی بررسی می‌کنم:

سیگنال اول: لاگ اتصال‌های ناموفق

grep "Access denied" /var/log/mysql/error.log | tail -50

تلاش‌های مکرر برای اتصال با رمز نامعتبر، نشانه حمله brute force روی دیتابیس است.

سیگنال دوم: کوئری‌های حجیم

SHOW FULL PROCESSLIST;

کوئری‌های طولانی، ممکن است نشانه حمله DoS یا کوئری نامناسب در کد باشد.

سیگنال سوم: تغییرات در کاربران MySQL

SELECT User, Host, Account_locked FROM mysql.user;

هر کاربر ناشناس، مشکوک است. مخصوصاً اگر در تاریخ‌های اخیر اضافه شده باشد.

سیگنال چهارم: تغییرات در ساختار جداول

SELECT TABLE_NAME, CREATE_TIME, UPDATE_TIME
FROM information_schema.tables
WHERE TABLE_SCHEMA = 'wp_database'
ORDER BY CREATE_TIME DESC;

جدول‌های تازه، ممکن است توسط مهاجم ساخته شده باشند.

ابزارهای پایش تفصیلی در رفع مصرف بالای CPU و بررسی لاگ خطاهای سرور.

روز حادثه: چه کنیم؟

اگر متوجه شدید که دیتابیس شما نفوذ شده، این پنج حرکت سریع را انجام دهید:

  1. سایت را در حالت تعمیرات: قطع کامل لازم نیست، ولی سایت نباید به کاربران سرویس بدهد. راهنما در حالت تعمیرات وردپرس.
  2. بکاپ صحنه جرم: فوری یک نسخه کامل از دیتابیس بگیرید. حتی اگر آلوده است، برای تحلیل بعدی مفید خواهد بود.
  3. تغییر رمز دیتابیس: فوراً رمز کاربر دیتابیس را عوض کنید.
  4. محدودسازی دسترسی: دسترسی دیتابیس را از هر IP خارجی قطع کنید.
  5. گزارش به هاست: تیم پشتیبانی می‌تواند لاگ‌های سرور را بررسی کند و الگوهای حمله را تشخیص دهد.

مسیر کامل پاک‌سازی و بازیابی در راهنمای پاک‌سازی سایت وردپرسی هک‌شده و پاک‌سازی دیتابیس وردپرس آمده است.

اشتباهات رایج در امنیت دیتابیس

  1. استفاده از کاربر root برای وردپرس: بزرگ‌ترین اشتباه. کاربر وردپرس باید حداقل دسترسی داشته باشد.
  2. رمز کوتاه یا قابل‌حدس: رمز دیتابیس باید حداقل ۳۲ کاراکتر تصادفی باشد.
  3. باز گذاشتن پورت ۳۳۰۶ به همه: دسترسی از راه دور فقط برای IP مشخص.
  4. بکاپ رمزگذاری‌نشده: بکاپ دیتابیس، اطلاعات حساس دارد.
  5. عدم Prepared Statement: ورودی کاربر مستقیم در کوئری، حفره SQL Injection.
  6. نادیده گرفتن لاگ: بدون پایش، حملات مخفی می‌مانند.
  7. استفاده از یک کاربر برای همه سایت‌ها: اگر یک سایت هک شود، همه سایت‌ها آسیب می‌بینند.
  8. عدم تغییر رمز پس از خروج کارمند: کارمند سابق، ممکن است رمز را داشته باشد.
  9. ذخیره رمز در فایل‌های متنی: رمز باید در ابزار مدیریت رمز باشد، نه در Notepad.
  10. بی‌توجهی به به‌روزرسانی MySQL: نسخه‌های قدیمی MySQL، حفره‌های امنیتی شناخته‌شده دارند.

خط پایان

امنیت دیتابیس وردپرس، پنج لایه دارد: اعتبارنامه، کاربران MySQL، دسترسی شبکه، پاکسازی داده، رمزنگاری و بکاپ. هر لایه، بخشی از تصویر امنیتی را می‌سازد و نادیده گرفتن یکی، بقیه را بی‌اثر می‌کند. توصیه عملی من: از همین امروز، سه کار را انجام دهید — رمز کاربر دیتابیس را تغییر دهید، دسترسی از راه دور را محدود کنید، و بکاپ رمزگذاری‌شده بگیرید. این سه، ۸۰٪ ریسک‌ها را کاهش می‌دهند.

سه تجربه شخصی از پرونده‌های مختلف: اول، اکثر هک‌های موفق، از لایه دیتابیس شروع شده‌اند — نه از لایه سایت. دوم، بکاپ رمزگذاری‌نشده، یکی از رایج‌ترین عوامل لو رفتن اطلاعات در پرونده‌های من بوده. سوم، پایش هفتگی، حمله را در همان روز اول شناسایی می‌کند، حتی اگر تیم امنیتی نداشته باشید.

اگر در امنیت دیتابیس سایت خود به موقعیتی برخوردید که در این چارچوب نمی‌گنجد — مثلاً یک هاست خاص یا یک محدودیت قانونی — در دیدگاه‌ها بنویسید. تجربه‌های واقعی هر پروژه، این راهنما را دقیق‌تر می‌کند. 🔐