Hardening MySQL Server trên Linux: Quy trình siết bảo mật và cô lập quyền thực chiến

MySQL tutorial - IT technology blog
MySQL tutorial - IT technology blog

Quick start: Checklist siết bảo mật MySQL trong 5 phút

Vừa dựng xong một node MySQL trên Ubuntu hay Rocky Linux để chuẩn bị kết nối backend? Hãy chạy ngay 3 bước cơ bản này trước khi cấp tài khoản cho team hoặc đưa lên staging.

1. Chạy script dọn dẹp mặc định

sudo mysql_secure_installation

Tool chạy dạng wizard tương tác. Bạn nên chọn các thiết lập sau:

  • Bật component kiểm tra độ mạnh mật khẩu (VALIDATE PASSWORD) ở mức MEDIUM hoặc STRONG.
  • Xóa sạch Anonymous User (tài khoản vô danh có sẵn sau khi cài).
  • Khóa quyền login từ xa của tài khoản root (Disallow root login remotely).
  • Xóa database test mặc định và reload lại bảng phân quyền (privilege tables).

2. Ép MySQL chỉ lắng nghe IP nội bộ (Private Interface)

Mặc định ở một số bản phân phối, MySQL có thể lắng nghe trên mọi card mạng (0.0.0.0). Mở file cấu hình /etc/mysql/mysql.conf.d/mysqld.cnf (hoặc /etc/my.cnf trên RHEL/Rocky) và chỉnh lại dòng bind-address:

[mysqld]
# Chỉ app trên cùng localhost kết nối:
bind-address = 127.0.0.1

# Hoặc nếu app server nằm chung dải VPC nội bộ, bind vào IP private của card mạng:
# bind-address = 10.0.1.15

Lưu file và restart service để áp dụng:

sudo systemctl restart mysql

3. Chặn port 3306 ở tầng Firewall máy chủ

Cấu hình ứng dụng có thể bị sót khi sửa đổi hạ tầng. Hãy dựng thêm một chốt chặn ở firewall của hệ điều hành.

# Nhớ mở port SSH trước khi bật ufw kẻo bị lock out khỏi server!
sudo ufw allow 22/tcp
sudo ufw default deny incoming

# Chỉ cho phép dải subnet của cụm App Server (ví dụ 10.0.2.0/24) gọi vào 3306
sudo ufw allow from 10.0.2.0/24 to any port 3306 proto tcp
sudo ufw reload

Đi sâu: 3 lớp phòng thủ cho database production

Xong bước dọn dẹp bề mặt, ta cần thiết lập các rào cản kỹ thuật sâu hơn. Ý tưởng cốt lõi: nếu attacker chọc thủng được web server, họ vẫn bị mắc kẹt tại database.

Lớp 1: Khóa chặt Engine và chặn rò rỉ dữ liệu (Data Exfiltration)

Lợi dụng lỗ hổng SQL Injection để đọc trộm file hệ thống như /etc/passwd hoặc dump shell qua INTO OUTFILE là bài đánh rất phổ biến. Ta có thể bẻ gãy đòn này ngay ở tầng config MySQL.

Thêm các tham số này vào block [mysqld] trong file cấu hình:

[mysqld]
# Chặn client tự ý đẩy file local lên database
local_infile = 0

# Khóa cứng thư mục được phép import/export, hoặc set rỗng để cấm hẳn
secure_file_priv = /var/lib/mysql-files

# Tắt symbolic link để chống đọc chéo file ngoài datadir
skip_symbolic_links = 1

Khi secure_file_priv được chỉ định cụ thể, hacker dù chèn được SELECT ... INTO OUTFILE cũng không thể nhả web shell vào thư mục /var/www/html để gọi qua web.

Lớp 2: Chia tách tài khoản và cô lập đặc quyền (Least Privilege)

Lỗi phổ biến nhất của các team là gán luôn GRANT ALL PRIVILEGES cho user backend. Khi đó, nếu API bị dính một câu SQLi từ input người dùng, toàn bộ schema có thể bị DROP trong chớp mắt.

Giải pháp chuẩn là tách tối thiểu 2 role với quyền hạn riêng biệt:

-- 1. Tài khoản phục vụ runtime của Backend (NodeJS, Spring, Laravel...)
CREATE USER 'app_runtime'@'10.0.2.%' IDENTIFIED BY 'Tr0ng-P@ss#Prod2026';

-- Chỉ cấp quyền thao tác dữ liệu (DML). Cấm tuyệt đối DROP, ALTER, CREATE hay TRUNCATE
GRANT SELECT, INSERT, UPDATE, DELETE ON production_db.* TO 'app_runtime'@'10.0.2.%';

-- 2. Tài khoản chỉ dùng trong pipeline CI/CD để chạy Migration schema
CREATE USER 'app_migrator'@'10.0.2.50' IDENTIFIED BY 'Migrate_Secr3t#2026';
GRANT CREATE, ALTER, DROP, INDEX, REFERENCES ON production_db.* TO 'app_migrator'@'10.0.2.50';

FLUSH PRIVILEGES;

Tách rời như vậy giúp bạn giới hạn phạm vi thiệt hại. User runtime của app chỉ đọc/ghi record, hoàn toàn không đủ quyền can thiệp cấu trúc bảng.

Lớp 3: Đặt hạn mức tài nguyên trên từng user

Một con bot cào dữ liệu hay một loop query bị rò rỉ kết nối có thể nuốt trọn CPU của MySQL. Hãy khống chế hạn mức ngay trên tài khoản:

ALTER USER 'app_runtime'@'10.0.2.%' WITH
  MAX_QUERIES_PER_HOUR 500000
  MAX_UPDATES_PER_HOUR 100000
  MAX_CONNECTIONS_PER_HOUR 10000
  MAX_USER_CONNECTIONS 150;

Con số MAX_USER_CONNECTIONS = 150 nên được cân đối dựa trên cấu hình max_connections toàn cục (thường để 500-1000 tuỳ RAM server) và connection pool của app.

Quản trị Audit Log và lịch sử lệnh

Slow query làm ứng dụng chạy chậm, nhưng truy vấn trái phép trong đêm mới là thứ gây mất dữ liệu. Bạn cần ghi lại dấu vết truy cập để điều tra khi cần.

1. Giám sát các lượt đăng nhập bất thường

Với bản MySQL Community, cách nhanh nhất để soi các nỗ lực brute-force là nâng mức ghi nhận log lỗi:

[mysqld]
log_error = /var/log/mysql/error.log
log_error_verbosity = 3

Mức 3 (Verbose) sẽ ghi toàn bộ cảnh báo và các lần kết nối thất bại vào error log, kèm IP nguồn và lý do từ chối (Access Denied).

2. Vô hiệu hóa file ghi vết lịch sử MySQL client

Mỗi khi gõ lệnh trong CLI, MySQL client mặc định lưu tất cả vào ~/.mysql_history. Các câu lệnh tạo user kèm mật khẩu plaintext đều nằm trần trụi ở đây.

Triệt tiêu nguy cơ này bằng cách link file ra /dev/null:

# Xóa file cũ và chuyển hướng vĩnh viễn về /dev/null
rm -f ~/.mysql_history
ln -s /dev/null ~/.mysql_history

# Thiết lập biến môi trường áp dụng cho toàn hệ thống
echo "export MYSQL_HISTFILE=/dev/null" | sudo tee -a /etc/profile.d/mysql_history.sh
source /etc/profile.d/mysql_history.sh

Lưu ý vận hành trong thực tế

Vài điểm mấu chốt bạn nên rà soát định kỳ khi quản trị database:

  • Không cấp quyền SUPER hay FILE cho bất kỳ ai: Hai quyền này cho phép user sửa biến global của hệ thống và can thiệp file máy chủ. Chỉ giữ quyền này cho local root.
  • Bắt buộc mã hóa đường truyền bằng TLS: Kể cả khi nằm trong mạng nội bộ VPC, dữ liệu nhạy cảm vẫn có thể bị nghe lén nếu một node khác cùng dải mạng bị chiếm quyền. Bật require_secure_transport = ON trong block [mysqld] để từ chối mọi kết nối không dùng SSL/TLS.
  • Cấu hình xoay vòng log (logrotate): Kiểm tra cấu hình tại /etc/logrotate.d/mysql-server. Nếu không nén và dọn log định kỳ, file error log phình to sẽ làm đầy ổ cứng root (/), khiến MySQL tự động stop đột ngột vì hết dung lượng.
Share: