Database Security Best Practices

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

บทความนี้รวบรวมแนวทางปฏิบัติด้านความปลอดภัยที่ครอบคลุมทั้ง MySQL/MariaDB, PostgreSQL และ MongoDB ตั้งแต่การจัดการ Authentication, Authorization, การเข้ารหัสข้อมูล, การตรวจสอบ Audit Log ไปจนถึงการป้องกัน SQL Injection และการวางแผน Hardening เซิร์ฟเวอร์

Authentication — จัดการการยืนยันตัวตน

Authentication เป็นด่านแรกของการรักษาความปลอดภัย ต้องมั่นใจว่าเฉพาะผู้ที่มีสิทธิ์เท่านั้นที่เข้าถึงฐานข้อมูลได้ การตั้งรหัสผ่านที่แข็งแรง ปิด Default Account และจำกัดวิธีเชื่อมต่อเป็นสิ่งจำเป็น

MySQL/MariaDB

# 1. ตรวจสอบ User ทั้งหมดในระบบ
SELECT user, host, plugin, authentication_string != '' AS has_password
FROM mysql.user;

# 2. ลบ Anonymous User (ถ้ามี)
DROP USER IF EXISTS ''@'localhost';
DROP USER IF EXISTS ''@'%';

# 3. ตั้งรหัสผ่านที่แข็งแรง (อย่างน้อย 16 ตัวอักษร ผสมตัวพิมพ์ใหญ่-เล็ก ตัวเลข อักขระพิเศษ)
ALTER USER 'admin'@'localhost' IDENTIFIED BY 'K9#mP$vL2x@wQ7nR';

# 4. บังคับใช้ Password Policy (MySQL 8.0+)
INSTALL COMPONENT 'file://component_validate_password';
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 16;
SET GLOBAL validate_password.mixed_case_count = 1;
SET GLOBAL validate_password.number_count = 1;
SET GLOBAL validate_password.special_char_count = 1;

# ตรวจสอบ Policy ที่ตั้งไว้
SHOW VARIABLES LIKE 'validate_password%';

# 5. ใช้ caching_sha2_password (MySQL 8.0+) หรือ ed25519 (MariaDB 10.4+)
ALTER USER 'admin'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'K9#mP$vL2x@wQ7nR';

# 6. ตั้ง Password Expiry
ALTER USER 'developer'@'%' PASSWORD EXPIRE INTERVAL 90 DAY;

# 7. จำกัด Failed Login Attempts (MySQL 8.0.19+)
ALTER USER 'developer'@'%' FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;

PostgreSQL

# 1. ตรวจสอบ User ทั้งหมด
SELECT usename, usesuper, valuntil FROM pg_user;

# 2. ตั้ง Password ที่แข็งแรง
ALTER USER postgres WITH PASSWORD 'K9#mP$vL2x@wQ7nR';

# 3. ตั้งค่า pg_hba.conf — ควบคุมวิธี Authentication ตาม IP/User/Database
# ไฟล์: /etc/postgresql/16/main/pg_hba.conf

# TYPE  DATABASE  USER       ADDRESS         METHOD
# local all       postgres                   peer
# local all       all                        scram-sha-256
# host  all       all        127.0.0.1/32    scram-sha-256
# host  mydb      appuser    10.0.0.0/24     scram-sha-256
# host  all       all        0.0.0.0/0       reject

# 4. บังคับใช้ SCRAM-SHA-256 (แทน MD5)
# ใน postgresql.conf:
# password_encryption = scram-sha-256

# 5. Reload หลังแก้ไข
sudo systemctl reload postgresql

# 6. ตั้ง Password Expiry ด้วย VALID UNTIL
ALTER ROLE developer VALID UNTIL '2027-01-01';

# 7. ตรวจสอบ Connection Limit
ALTER ROLE appuser CONNECTION LIMIT 20;

MongoDB

# 1. เปิด Authentication (mongod.conf)
# security:
#   authorization: enabled

# 2. สร้าง Admin User
mongosh --eval "
  use admin;
  db.createUser({
    user: 'adminUser',
    pwd: 'K9#mP\$vL2x@wQ7nR',
    roles: ['userAdminAnyDatabase', 'readWriteAnyDatabase']
  });"

# 3. สร้าง Application User (จำกัดเฉพาะ Database ที่ต้องใช้)
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use myapp;
  db.createUser({
    user: 'appUser',
    pwd: 'AppP@ss2026!Str0ng',
    roles: [{role: 'readWrite', db: 'myapp'}]
  });"

# 4. ใช้ SCRAM-SHA-256 (MongoDB 4.0+)
# ใน mongod.conf:
# setParameter:
#   authenticationMechanisms: SCRAM-SHA-256

# 5. ตรวจสอบ User ทั้งหมด
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use admin;
  db.getUsers();"

Authorization — จัดการสิทธิ์การเข้าถึง

หลัก Principle of Least Privilege กำหนดว่าทุก User ควรได้รับสิทธิ์เท่าที่จำเป็นสำหรับงานของตนเท่านั้น ไม่ควรให้ Application ใช้ Root หรือ Superuser เชื่อมต่อฐานข้อมูล เพราะถ้าแอปพลิเคชันถูกโจมตี ผู้โจมตีจะมีสิทธิ์ทำทุกอย่างกับฐานข้อมูลได้

MySQL/MariaDB — จัดการ Privilege

# 1. สร้าง User สำหรับ Application (จำกัดเฉพาะ Database ที่ต้องการ)
CREATE USER 'webapp'@'10.0.0.%' IDENTIFIED BY 'WebApp@2026!Secure';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'webapp'@'10.0.0.%';

# 2. สร้าง Read-Only User สำหรับ Reporting
CREATE USER 'reporter'@'10.0.0.%' IDENTIFIED BY 'R3port!2026@Read';
GRANT SELECT ON mydb.* TO 'reporter'@'10.0.0.%';

# 3. สร้าง Backup User
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'Bkup@2026!S3cure';
GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION CLIENT, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';

# 4. ตรวจสอบ Privilege ของ User
SHOW GRANTS FOR 'webapp'@'10.0.0.%';

# 5. ถอน Privilege ที่ไม่จำเป็น
REVOKE DROP, ALTER, CREATE ON mydb.* FROM 'webapp'@'10.0.0.%';

# 6. ห้ามให้ Application ใช้ GRANT OPTION
# ❌ อย่าทำ: GRANT ALL PRIVILEGES ON *.* TO 'webapp'@'%' WITH GRANT OPTION;

# 7. ลบ Privilege ระดับ Global ที่ไม่จำเป็น
REVOKE SUPER, FILE, PROCESS ON *.* FROM 'webapp'@'10.0.0.%';
FLUSH PRIVILEGES;

PostgreSQL — จัดการ Role และ Privilege

# 1. สร้าง Role สำหรับ Application
CREATE ROLE app_role;
GRANT CONNECT ON DATABASE mydb TO app_role;
GRANT USAGE ON SCHEMA public TO app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_role;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_role;

# ให้สิทธิ์กับตารางที่สร้างในอนาคตด้วย
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_role;

# 2. สร้าง User และ Assign Role
CREATE USER appuser WITH PASSWORD 'AppUs3r@2026!Pg';
GRANT app_role TO appuser;

# 3. สร้าง Read-Only Role
CREATE ROLE readonly_role;
GRANT CONNECT ON DATABASE mydb TO readonly_role;
GRANT USAGE ON SCHEMA public TO readonly_role;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO readonly_role;

CREATE USER reporter WITH PASSWORD 'R3port!2026@Pg';
GRANT readonly_role TO reporter;

# 4. Row-Level Security (RLS) — จำกัดข้อมูลระดับแถว
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY orders_tenant_policy ON orders
  USING (tenant_id = current_setting('app.tenant_id')::int);

# ตั้ง tenant_id ก่อนใช้งาน
SET app.tenant_id = '42';
SELECT * FROM orders;  -- เห็นเฉพาะ tenant_id = 42

# 5. ตรวจสอบ Privilege
SELECT grantee, privilege_type, table_name
FROM information_schema.role_table_grants
WHERE grantee = 'appuser';

MongoDB — จัดการ Role

# 1. Built-in Roles ที่ใช้บ่อย
# read           — อ่านอย่างเดียว
# readWrite      — อ่าน-เขียน
# dbAdmin        — จัดการ Database (Index, Statistics)
# userAdmin      — จัดการ User
# clusterMonitor — Monitor Replica Set/Sharded Cluster

# 2. สร้าง Custom Role (เฉพาะ Collection)
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use myapp;
  db.createRole({
    role: 'orderManager',
    privileges: [
      {
        resource: {db: 'myapp', collection: 'orders'},
        actions: ['find', 'insert', 'update']
      },
      {
        resource: {db: 'myapp', collection: 'products'},
        actions: ['find']
      }
    ],
    roles: []
  });"

# 3. Assign Custom Role ให้ User
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use myapp;
  db.createUser({
    user: 'orderApp',
    pwd: 'Ord3r@App2026!',
    roles: [{role: 'orderManager', db: 'myapp'}]
  });"

# 4. ตรวจสอบ Role ของ User
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use myapp;
  db.getUser('orderApp');"

Network Security — ป้องกันระดับเครือข่าย

การจำกัดการเข้าถึงระดับเครือข่ายช่วยลดพื้นที่โจมตีได้มาก ฐานข้อมูลไม่ควรเปิดรับการเชื่อมต่อจากทุก IP โดยเฉพาะจากอินเทอร์เน็ตสาธารณะ การ Bind เฉพาะ IP ที่จำเป็น ร่วมกับ Firewall Rules จะช่วยป้องกันการเข้าถึงโดยไม่ได้รับอนุญาต

Bind Address — จำกัด IP ที่รับ Connection

# MySQL (my.cnf)
[mysqld]
bind-address = 127.0.0.1          # รับเฉพาะ localhost
# bind-address = 10.0.0.10        # หรือเฉพาะ Private IP
# bind-address = 0.0.0.0          # ❌ ห้ามใช้ใน Production

# PostgreSQL (postgresql.conf)
listen_addresses = 'localhost'     # รับเฉพาะ localhost
# listen_addresses = '10.0.0.10'  # หรือเฉพาะ Private IP
# listen_addresses = '*'           # ❌ ห้ามใช้ใน Production

# MongoDB (mongod.conf)
net:
  bindIp: 127.0.0.1               # รับเฉพาะ localhost
  # bindIp: 127.0.0.1,10.0.0.10   # หรือเพิ่ม Private IP
  # bindIp: 0.0.0.0               # ❌ ห้ามใช้ใน Production

Firewall Rules

# UFW — อนุญาตเฉพาะ IP ที่กำหนด

# MySQL (port 3306)
sudo ufw deny 3306
sudo ufw allow from 10.0.0.5 to any port 3306 comment "App Server"
sudo ufw allow from 10.0.0.6 to any port 3306 comment "Backup Server"

# PostgreSQL (port 5432)
sudo ufw deny 5432
sudo ufw allow from 10.0.0.5 to any port 5432 comment "App Server"

# MongoDB (port 27017)
sudo ufw deny 27017
sudo ufw allow from 10.0.0.5 to any port 27017 comment "App Server"

# ตรวจสอบ Rules
sudo ufw status numbered

# iptables — สำหรับ Control ละเอียดกว่า
# อนุญาตจาก Subnet เฉพาะ
sudo iptables -A INPUT -p tcp --dport 3306 -s 10.0.0.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP

เปลี่ยน Default Port

การเปลี่ยน Port จาก Default ไม่ใช่มาตรการรักษาความปลอดภัยที่แท้จริง (Security through Obscurity) แต่ช่วยลด Automated Attack และ Port Scanning ที่โจมตี Port มาตรฐานได้

# MySQL — เปลี่ยนจาก 3306
[mysqld]
port = 33060

# PostgreSQL — เปลี่ยนจาก 5432
# postgresql.conf
port = 54320

# MongoDB — เปลี่ยนจาก 27017
# mongod.conf
net:
  port: 27018

# อย่าลืมอัพเดต Firewall Rules ตาม Port ใหม่
sudo ufw allow from 10.0.0.5 to any port 33060

Encryption — เข้ารหัสข้อมูล

การเข้ารหัสข้อมูลมี 2 ระดับหลัก คือ Encryption in Transit (เข้ารหัสระหว่างส่งข้อมูล) และ Encryption at Rest (เข้ารหัสข้อมูลที่เก็บบน Disk) ทั้งสองอย่างควรเปิดใช้งานเพื่อป้องกันการดักฟังและการเข้าถึงข้อมูลกรณีเซิร์ฟเวอร์ถูกขโมยหรือถูก Access ผิดที่

Encryption in Transit (SSL/TLS)

# === MySQL SSL Setup ===
# 1. สร้าง Certificate (ถ้ายังไม่มี)
sudo mysql_ssl_rsa_setup --uid=mysql

# 2. ตรวจสอบ SSL Status
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"
# ssl_ca   = /var/lib/mysql/ca.pem
# ssl_cert = /var/lib/mysql/server-cert.pem
# ssl_key  = /var/lib/mysql/server-key.pem

# 3. บังคับให้ User ต้องใช้ SSL
ALTER USER 'webapp'@'10.0.0.%' REQUIRE SSL;

# บังคับใช้ Certificate เฉพาะ
ALTER USER 'webapp'@'10.0.0.%' REQUIRE X509;

# 4. เชื่อมต่อด้วย SSL
mysql -u webapp -p -h 10.0.0.10 \
  --ssl-ca=/path/to/ca.pem \
  --ssl-cert=/path/to/client-cert.pem \
  --ssl-key=/path/to/client-key.pem

# === PostgreSQL SSL Setup ===
# 1. ตั้งค่า postgresql.conf
# ssl = on
# ssl_cert_file = '/etc/ssl/certs/server.crt'
# ssl_key_file = '/etc/ssl/private/server.key'

# 2. บังคับ SSL ใน pg_hba.conf
# hostssl  mydb  appuser  10.0.0.0/24  scram-sha-256
# (ใช้ hostssl แทน host)

# 3. เชื่อมต่อด้วย SSL
psql "host=10.0.0.10 dbname=mydb user=appuser sslmode=verify-full sslrootcert=/path/to/ca.crt"

# === MongoDB SSL Setup ===
# mongod.conf:
# net:
#   tls:
#     mode: requireTLS
#     certificateKeyFile: /etc/ssl/mongodb.pem
#     CAFile: /etc/ssl/ca.pem

# เชื่อมต่อด้วย TLS
mongosh --tls --tlsCAFile /path/to/ca.pem \
  --tlsCertificateKeyFile /path/to/client.pem \
  -u appUser -p 'password' --authenticationDatabase admin

Encryption at Rest

# === MySQL — InnoDB Tablespace Encryption ===
# 1. ตั้งค่า Keyring Plugin (my.cnf)
[mysqld]
early-plugin-load=keyring_file.so
keyring_file_data=/var/lib/mysql-keyring/keyring

# 2. เปิด Encryption สำหรับตาราง
ALTER TABLE orders ENCRYPTION='Y';

# 3. เปิด Encryption สำหรับ Tablespace ทั้งหมด (MySQL 8.0.16+)
ALTER TABLESPACE mysql ENCRYPTION='Y';

# ตรวจสอบ
SELECT TABLE_SCHEMA, TABLE_NAME, CREATE_OPTIONS
FROM information_schema.tables
WHERE CREATE_OPTIONS LIKE '%ENCRYPTION%';

# === PostgreSQL — ใช้ pgcrypto Extension ===
CREATE EXTENSION IF NOT EXISTS pgcrypto;

# เข้ารหัสข้อมูลที่ละเอียดอ่อน (Column-Level)
CREATE TABLE customers (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL,
  credit_card BYTEA  -- เก็บข้อมูลที่เข้ารหัสแล้ว
);

# เข้ารหัส
INSERT INTO customers (name, email, credit_card)
VALUES ('John', '[email protected]',
  pgp_sym_encrypt('4111-1111-1111-1111', 'encryption_key_here'));

# ถอดรหัส
SELECT name, pgp_sym_decrypt(credit_card, 'encryption_key_here') AS card
FROM customers WHERE id = 1;

# === MongoDB — Encrypted Storage Engine ===
# mongod.conf (Enterprise Edition):
# security:
#   enableEncryption: true
#   encryptionKeyFile: /etc/mongodb/encryption-key

# สร้าง Encryption Key
openssl rand -base64 32 > /etc/mongodb/encryption-key
chmod 600 /etc/mongodb/encryption-key
chown mongodb:mongodb /etc/mongodb/encryption-key

Audit Logging — บันทึกการเข้าถึง

Audit Log ช่วยติดตามว่าใครเข้าถึงข้อมูลอะไร เมื่อไหร่ และทำอะไร เป็นสิ่งจำเป็นทั้งสำหรับการตรวจสอบความปลอดภัยและการปฏิบัติตามข้อกำหนดต่าง ๆ เช่น PDPA, GDPR หรือ PCI-DSS

MySQL — General Log และ Audit Plugin

# 1. เปิด General Log (บันทึกทุก Query — ใช้ชั่วคราวเท่านั้น เพราะกระทบ Performance)
SET GLOBAL general_log = ON;
SET GLOBAL general_log_file = '/var/log/mysql/general.log';

# ปิดเมื่อไม่ใช้
SET GLOBAL general_log = OFF;

# 2. ใช้ Audit Plugin (แนะนำสำหรับ Production)
# MySQL Enterprise Audit (ต้องมี License)
# หรือ MariaDB Audit Plugin (ฟรี ใช้ได้กับ MariaDB)

# MariaDB — เปิด Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
SET GLOBAL server_audit_file_path = '/var/log/mysql/audit.log';

# ตั้งค่าถาวรใน my.cnf
# [mysqld]
# server_audit_logging = ON
# server_audit_events = CONNECT,QUERY,TABLE
# server_audit_file_path = /var/log/mysql/audit.log

# 3. ตรวจสอบ Login ที่ล้มเหลว
SELECT * FROM performance_schema.host_cache
WHERE COUNT_AUTHENTICATION_ERRORS > 0;

PostgreSQL — pgAudit Extension

# 1. ติดตั้ง pgAudit
sudo apt install postgresql-16-pgaudit

# 2. ตั้งค่า postgresql.conf
# shared_preload_libraries = 'pgaudit'
# pgaudit.log = 'all'                    # บันทึกทุกอย่าง
# pgaudit.log = 'read, write, ddl'       # เฉพาะ read/write/DDL

# 3. Restart PostgreSQL
sudo systemctl restart postgresql

# 4. เปิดใช้งานใน Database
CREATE EXTENSION pgaudit;

# 5. ตั้ง Audit ระดับ Role (เฉพาะ User บางคน)
ALTER ROLE appuser SET pgaudit.log = 'write, ddl';

# 6. ตรวจสอบ Log
sudo tail -f /var/log/postgresql/postgresql-16-main.log | grep AUDIT

# ตัวอย่าง Log:
# AUDIT: SESSION,1,1,WRITE,INSERT,TABLE,public.orders,
#   "INSERT INTO orders (user_id, total) VALUES (1, 500)"

MongoDB — Audit Log

# MongoDB Enterprise — เปิด Audit Log
# mongod.conf:
# auditLog:
#   destination: file
#   format: JSON
#   path: /var/log/mongodb/audit.json
#   filter: '{ atype: { $in: ["authenticate", "createUser", "dropDatabase"] } }'

# MongoDB Community — ใช้ Profiler แทน
# เปิด Profiler (บันทึก Query ที่ช้ากว่า threshold)
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use myapp;
  db.setProfilingLevel(1, {slowms: 100});
  // 0 = ปิด, 1 = บันทึกเฉพาะ slow, 2 = บันทึกทั้งหมด"

# ดู Profiler Data
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  use myapp;
  db.system.profile.find().sort({ts: -1}).limit(5).pretty();"

# ตรวจสอบ Login Events จาก mongod.log
sudo grep "authenticate" /var/log/mongodb/mongod.log | tail -10

SQL Injection Prevention

SQL Injection ยังคงเป็นหนึ่งในช่องโหว่ที่พบบ่อยที่สุด การป้องกันต้องทำที่ระดับ Application เป็นหลัก แต่ฝั่ง Database ก็มีมาตรการเสริมที่ช่วยลดความเสี่ยงได้

ใช้ Prepared Statement เสมอ

# ❌ ผิด — ต่อ String ตรง ๆ (เสี่ยง SQL Injection)
query = "SELECT * FROM users WHERE email = '" + email + "'"

# ✅ ถูก — ใช้ Prepared Statement (Parameterized Query)

# Python (MySQL)
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

# Python (PostgreSQL / psycopg2)
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

# Node.js (MySQL)
# connection.query("SELECT * FROM users WHERE email = ?", [email])

# Node.js (PostgreSQL / pg)
# client.query("SELECT * FROM users WHERE email = $1", [email])

# PHP (PDO)
# $stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
# $stmt->execute(['email' => $email]);

# Go (database/sql)
# db.QueryRow("SELECT * FROM users WHERE email = $1", email)

มาตรการเสริมฝั่ง Database

# 1. จำกัด Privilege — App User ไม่ควรมีสิทธิ์ DROP, ALTER, GRANT
# ถ้า SQL Injection สำเร็จ ผลกระทบจะจำกัดเฉพาะ SELECT/INSERT/UPDATE/DELETE

# 2. ใช้ Stored Procedure แทน Dynamic SQL (ถ้าเหมาะสม)
DELIMITER //
CREATE PROCEDURE get_user_orders(IN p_user_id INT)
BEGIN
  SELECT * FROM orders WHERE user_id = p_user_id;
END //
DELIMITER ;

CALL get_user_orders(42);

# 3. MySQL — ปิด LOAD DATA LOCAL INFILE (ป้องกันอ่านไฟล์ผ่าน SQL Injection)
[mysqld]
local-infile = 0

# 4. ตรวจจาก Log ว่ามี Pattern SQL Injection หรือไม่
# ค้นหา Pattern ที่น่าสงสัย
sudo grep -iE "(union.*select|or.*1.*=.*1|drop.*table|sleep\(|benchmark\()" /var/log/mysql/general.log

Database Hardening Checklist

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

MySQL/MariaDB Hardening

# 1. รัน mysql_secure_installation (ทำทุกครั้งหลังติดตั้งใหม่)
sudo mysql_secure_installation
# — ตั้ง Root Password
# — ลบ Anonymous Users
# — ห้าม Root Login จาก Remote
# — ลบ Test Database
# — Reload Privileges

# 2. ปิด Symbolic Links (ป้องกันอ่านไฟล์ผ่าน Symlink)
[mysqld]
symbolic-links = 0

# 3. ปิด LOCAL INFILE
[mysqld]
local-infile = 0

# 4. ซ่อน Server Version
[mysqld]
# MariaDB
# version_comment = ''

# 5. จำกัด Connection ต่อ User
ALTER USER 'webapp'@'10.0.0.%' WITH MAX_CONNECTIONS_PER_HOUR 1000 MAX_USER_CONNECTIONS 50;

# 6. ตั้ง Timeout สำหรับ Idle Connection
[mysqld]
wait_timeout = 300
interactive_timeout = 600

# 7. ปิด SHOW DATABASES สำหรับ User ทั่วไป
# User ที่ไม่มี SHOW DATABASES Privilege จะเห็นเฉพาะ Database ที่มีสิทธิ์
REVOKE SHOW DATABASES ON *.* FROM 'webapp'@'10.0.0.%';

PostgreSQL Hardening

# 1. จำกัด Superuser — ใช้เฉพาะเมื่อจำเป็น
# ตรวจสอบว่ามี Superuser กี่คน
SELECT usename, usesuper FROM pg_user WHERE usesuper = true;
# ควรมีแค่ postgres เท่านั้น

# 2. ปิด Trust Authentication ใน pg_hba.conf
# ❌ ห้ามใช้:
# local  all  all  trust
# ✅ ใช้แทน:
# local  all  all  scram-sha-256

# 3. จำกัด Schema Permission
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON DATABASE mydb FROM PUBLIC;

# 4. ตั้ง Connection Timeout
# postgresql.conf:
# authentication_timeout = 30s
# idle_in_transaction_session_timeout = 60000  # 60 วินาที
# statement_timeout = 30000                     # 30 วินาที (ป้องกัน Query ค้าง)

# 5. ปิด Remote Login ของ postgres User
# pg_hba.conf:
# local  all  postgres  peer      ← เฉพาะ local login ด้วย OS user postgres
# host   all  postgres  0.0.0.0/0  reject  ← ห้าม remote

# 6. Log Connection และ Disconnection
# postgresql.conf:
# log_connections = on
# log_disconnections = on
# log_line_prefix = '%t [%p] %u@%d '

MongoDB Hardening

# 1. เปิด Authentication (ขั้นต่ำสุดที่ต้องทำ)
# mongod.conf:
# security:
#   authorization: enabled

# 2. ปิด JavaScript Execution (ถ้าไม่ใช้ $where หรือ mapReduce)
# mongod.conf:
# security:
#   javascriptEnabled: false

# 3. ปิด HTTP Interface และ REST API
# mongod.conf:
# net:
#   http:
#     enabled: false

# 4. ปิด Wire Protocol Compression สำหรับ Sensitive Data
# (ป้องกัน CRIME/BREACH attack)

# 5. จำกัด Network Exposure
# mongod.conf:
# net:
#   bindIp: 127.0.0.1,10.0.0.10
#   maxIncomingConnections: 200

# 6. เปิด TLS/SSL (บังคับ)
# net:
#   tls:
#     mode: requireTLS

# 7. ตรวจสอบความปลอดภัยเบื้องต้น
mongosh -u adminUser -p 'K9#mP$vL2x@wQ7nR' --authenticationDatabase admin --eval "
  // ตรวจว่า Auth เปิดอยู่
  var status = db.adminCommand({getParameter: 1, authenticationMechanisms: 1});
  print('Auth mechanisms:', JSON.stringify(status.authenticationMechanisms));

  // ตรวจว่ามี User กี่คน
  use admin;
  var users = db.getUsers();
  print('Total users:', users.users.length);"

Backup Security

ไฟล์ Backup มักถูกมองข้ามเรื่องความปลอดภัย ทั้งที่เป็นสำเนาของข้อมูลทั้งหมดในฐานข้อมูล ถ้าผู้โจมตีเข้าถึง Backup ได้ ก็เท่ากับเข้าถึงข้อมูลทั้งหมด การเข้ารหัส Backup และจำกัดสิทธิ์การเข้าถึงจึงเป็นสิ่งจำเป็น

# 1. เข้ารหัส Backup ด้วย GPG
# สร้าง Key Pair
gpg --gen-key

# Backup + เข้ารหัส
mysqldump -u backup_user -p mydb | gzip | gpg --encrypt --recipient "[email protected]" > mydb_backup.sql.gz.gpg

# ถอดรหัส + Restore
gpg --decrypt mydb_backup.sql.gz.gpg | gunzip | mysql -u root -p mydb

# 2. เข้ารหัส Backup ด้วย OpenSSL
mysqldump -u backup_user -p mydb | gzip | openssl enc -aes-256-cbc -salt -pbkdf2 -out mydb_backup.sql.gz.enc

# ถอดรหัส
openssl enc -d -aes-256-cbc -pbkdf2 -in mydb_backup.sql.gz.enc | gunzip | mysql -u root -p mydb

# 3. ตั้ง Permission ไฟล์ Backup
chmod 600 /backup/mysql/*.sql.gz
chown root:root /backup/mysql/*.sql.gz

# 4. ป้องกัน Directory
chmod 700 /backup/mysql
chown root:root /backup/mysql

# 5. PostgreSQL — Backup เข้ารหัส
pg_dump -U postgres mydb | gzip | gpg --encrypt --recipient "[email protected]" > mydb.dump.gz.gpg

# 6. ลบ Backup เก่าอัตโนมัติ (เก็บ 30 วัน)
find /backup/mysql -name "*.gpg" -mtime +30 -delete
find /backup/postgresql -name "*.gpg" -mtime +30 -delete

Security Monitoring Script

การตรวจสอบความปลอดภัยควรทำเป็นประจำ Script ด้านล่างตรวจสอบประเด็นด้านความปลอดภัยที่สำคัญของ MySQL/MariaDB อัตโนมัติ สามารถตั้งเป็น Cron Job รันทุกวันได้

#!/bin/bash
# db_security_check.sh — Database Security Audit Script
set -euo pipefail

MYSQL_USER="root"
LOG="/var/log/db_security_audit_$(date +%Y%m%d).log"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG"; }

log "=== Database Security Audit ==="

# 1. ตรวจ Anonymous Users
ANON=$(mysql -u "$MYSQL_USER" -N -e "SELECT COUNT(*) FROM mysql.user WHERE user='';" 2>/dev/null)
if [ "$ANON" -gt 0 ]; then
    log "⚠️ พบ Anonymous User: $ANON — ควรลบทิ้ง"
else
    log "✅ ไม่มี Anonymous User"
fi

# 2. ตรวจ User ที่ไม่มี Password
NO_PASS=$(mysql -u "$MYSQL_USER" -N -e "SELECT COUNT(*) FROM mysql.user WHERE authentication_string='' OR authentication_string IS NULL;" 2>/dev/null)
if [ "$NO_PASS" -gt 0 ]; then
    log "⚠️ พบ User ไม่มี Password: $NO_PASS"
    mysql -u "$MYSQL_USER" -e "SELECT user, host FROM mysql.user WHERE authentication_string='' OR authentication_string IS NULL;" 2>/dev/null | tee -a "$LOG"
else
    log "✅ ทุก User มี Password"
fi

# 3. ตรวจ User ที่เข้าถึงได้จากทุก Host (%)
WILDCARD=$(mysql -u "$MYSQL_USER" -N -e "SELECT COUNT(*) FROM mysql.user WHERE host='%';" 2>/dev/null)
if [ "$WILDCARD" -gt 0 ]; then
    log "⚠️ พบ User ที่เปิดจากทุก Host (%): $WILDCARD"
    mysql -u "$MYSQL_USER" -e "SELECT user, host FROM mysql.user WHERE host='%';" 2>/dev/null | tee -a "$LOG"
else
    log "✅ ไม่มี User ที่เปิดจากทุก Host"
fi

# 4. ตรวจ Test Database
TEST_DB=$(mysql -u "$MYSQL_USER" -N -e "SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name='test';" 2>/dev/null)
if [ "$TEST_DB" -gt 0 ]; then
    log "⚠️ พบ test database — ควรลบ"
else
    log "✅ ไม่มี test database"
fi

# 5. ตรวจ SSL Status
SSL=$(mysql -u "$MYSQL_USER" -N -e "SHOW VARIABLES LIKE 'have_ssl';" 2>/dev/null | awk '{print $2}')
if [ "$SSL" = "YES" ]; then
    log "✅ SSL เปิดใช้งาน"
else
    log "⚠️ SSL ยังไม่เปิด"
fi

# 6. ตรวจ Bind Address
BIND=$(mysql -u "$MYSQL_USER" -N -e "SHOW VARIABLES LIKE 'bind_address';" 2>/dev/null | awk '{print $2}')
if [ "$BIND" = "0.0.0.0" ] || [ "$BIND" = "*" ]; then
    log "⚠️ bind-address = $BIND — เปิดรับทุก IP (ไม่ปลอดภัย)"
else
    log "✅ bind-address = $BIND"
fi

# 7. ตรวจ User ที่มี SUPER Privilege
SUPER=$(mysql -u "$MYSQL_USER" -N -e "SELECT COUNT(*) FROM mysql.user WHERE Super_priv='Y';" 2>/dev/null)
log "ℹ️ User ที่มี SUPER Privilege: $SUPER"

log "=== Audit Complete ==="

สรุป

การรักษาความปลอดภัยของฐานข้อมูลต้องทำหลายชั้น ตั้งแต่ Authentication ที่แข็งแรง การจัดการ Privilege ตาม Least Privilege การจำกัดการเข้าถึงระดับเครือข่ายด้วย Firewall และ Bind Address การเข้ารหัสทั้ง In Transit และ At Rest การบันทึก Audit Log และการป้องกัน SQL Injection ที่ระดับ Application ไม่มีมาตรการใดมาตรการเดียวที่ป้องกันได้ทุกอย่าง แต่การรวมหลายมาตรการเข้าด้วยกันจะสร้าง Defense in Depth ที่แข็งแรง สิ่งสำคัญคือต้องตรวจสอบความปลอดภัยเป็นประจำ อัพเดต Patch ทันที และทดสอบแผน Recovery อยู่เสมอ

แนะนำบริการ DE

การตั้งค่าความปลอดภัยของฐานข้อมูลตามที่กล่าวมาต้องการ Root Access เต็มรูปแบบเพื่อแก้ไข Configuration File ติดตั้ง SSL Certificate จัดการ Firewall Rules และตั้งค่า Audit Plugin Cloud VPS ของ DE ให้ Root Access พร้อม SSD NVMe ที่เร็วและเสถียร เหมาะสำหรับการรันฐานข้อมูลที่ต้องการความปลอดภัยระดับสูง

สำหรับโปรเจกต์ที่ต้องการความสะดวกและไม่ต้องการจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE มี Managed Infrastructure พร้อม SSL และ Firewall ที่ตั้งค่ามาให้เรียบร้อยแล้ว