Bảo mật website không chỉ là cài SSL — đó chỉ là bước 1 trong 15 bước hardening cần thiết. Theo OWASP (2025), 94% website có ít nhất 1 lỗ hổng bảo mật, và 74% developer thừa nhận không biết hết các kỹ thuật hardening. Báo cáo của Sucuri cho thấy 53% website bị hack do lỗi cấu hình mà developer bỏ qua — không phải do lỗ hổng zero-day phức tạp.
Bài viết này liệt kê 15 bước hardening cụ thể với code mẫu — từ đơn giản đến nâng cao.
Lead magnet: Tải miễn phí Website Security Hardening Checklist — checklist 50 điểm kiểm tra bảo mật cho website.
Bước 1: Security Headers
Security headers là dòng lệnh HTTP server gửi cho browser, chỉ định cách browser xử lý nội dung. Đa số website thiếu headers quan trọng nhất.
Cấu hình Nginx
# Thêm vào server block
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
| Header | Chức năng | Nếu thiếu |
|---|---|---|
| X-Frame-Options | Chặn website bị nhúng trong iframe | Clickjacking attack |
| X-Content-Type-Options | Ngăn browser đoán MIME type | MIME-type sniffing attack |
| X-XSS-Protection | Kích hoạt XSS filter của browser | Cross-site scripting |
| Referrer-Policy | Kiểm soát thông tin referrer | Leak URL nhạy cảm |
| Permissions-Policy | Kiểm soát browser APIs | Camera/mic hijacking |
| HSTS | Bắt buộc HTTPS | SSL stripping attack |
Bước 2: Content Security Policy (CSP)
CSP là security header mạnh nhất nhưng phức tạp nhất — kiểm soát browser được phép load resources từ đâu.
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://www.google-analytics.com;
frame-src 'self' https://www.youtube.com;
object-src 'none';
base-uri 'self';
form-action 'self';
" always;
Mẹo: Bắt đầu với Content-Security-Policy-Report-Only (chỉ báo cáo, không block) để tìm lỗi trước khi enforce.
Bước 3: Rate Limiting
Ngăn brute-force attack và DDoS:
# Giới hạn request rate
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
server {
# Giới hạn chung: 10 requests/giây
location / {
limit_req zone=general burst=20 nodelay;
}
# Giới hạn login: 5 lần/phút
location /wp-login.php {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
}
# Giới hạn API: 30 requests/giây
location /api/ {
limit_req zone=general burst=50 nodelay;
}
}
Bước 4: Ẩn thông tin server
# Nginx: Tắt hiển thị version
server_tokens off;
# PHP: Tắt expose PHP version
# php.ini
expose_php = Off
Trước: Server: nginx/1.24.0, X-Powered-By: PHP/8.3.0
Sau: Server: nginx, không có X-Powered-By
Hacker dùng version information để tìm lỗ hổng đã biết → ẩn version giảm attack surface.
Bước 5: Bảo vệ file nhạy cảm
# Chặn truy cập file nhạy cảm
location ~ /\. {
deny all; # .env, .git, .htaccess
}
location ~ /(wp-config\.php|readme\.html|license\.txt) {
deny all;
}
location ~ /xmlrpc\.php {
deny all; # WordPress XML-RPC (thường bị exploit)
}
# Chặn truy cập thư mục
location ~ /(uploads|includes|wp-includes)/.*\.php$ {
deny all; # Không cho chạy PHP trong thư mục upload
}
Bước 6: Database Security
-- Không dùng root cho ứng dụng
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'StrongPassword';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'localhost';
-- KHÔNG cấp DROP, ALTER, CREATE cho app user
-- Bind MySQL chỉ localhost
-- /etc/mysql/mysql.conf.d/mysqld.cnf
-- bind-address = 127.0.0.1
Prepared Statements (chống SQL Injection)
// Sai: Vulnerable to SQL injection
$query = "SELECT * FROM users WHERE email = '$email'";
// Đúng: Prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
Bước 7: Input Validation & Sanitization
// Validate email
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
// Sanitize HTML (chống XSS)
$comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
// Validate phone (Việt Nam)
if (!preg_match('/^(0[3|5|7|8|9])+([0-9]{8})$/', $_POST['phone'])) {
throw new ValidationException('Số điện thoại không hợp lệ');
}
// Validate file upload
$allowed_types = ['image/jpeg', 'image/png', 'image/webp'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['upload']['tmp_name']);
if (!in_array($mime, $allowed_types)) {
throw new ValidationException('File type not allowed');
}
Bước 8: Secure Cookie Settings
// PHP session cookie settings
ini_set('session.cookie_secure', 1); // Chỉ gửi qua HTTPS
ini_set('session.cookie_httponly', 1); // JavaScript không đọc được
ini_set('session.cookie_samesite', 'Lax'); // Chống CSRF
ini_set('session.use_strict_mode', 1); // Reject uninitialized session ID
# Nginx: Set cookie flags
proxy_cookie_path / "/; HTTPOnly; Secure; SameSite=Lax";
Bước 9: CSRF Protection
// Tạo CSRF token
session_start();
$csrf_token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $csrf_token;
// Trong form HTML
echo '<input type="hidden" name="csrf_token" value="' . $csrf_token . '">';
// Verify khi submit
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('CSRF token mismatch');
}
Bước 10: File Upload Security
$upload_dir = '/var/www/uploads/';
$max_size = 5 * 1024 * 1024; // 5MB
// Kiểm tra kích thước
if ($_FILES['file']['size'] > $max_size) {
die('File too large');
}
// Kiểm tra MIME type thực sự (không tin extension)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
if (!in_array($mime, $allowed)) {
die('File type not allowed');
}
// Đổi tên file (tránh path traversal)
$filename = bin2hex(random_bytes(16)) . '.' . pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
// Upload vào thư mục không có execute permission
move_uploaded_file($_FILES['file']['tmp_name'], $upload_dir . $filename);
Bước 11: Password Hashing
// Sai: MD5, SHA1 — crackable trong giây
$hash = md5($password); // KHÔNG DÙNG
$hash = sha1($password); // KHÔNG DÙNG
// Đúng: bcrypt hoặc Argon2
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
// Verify
if (password_verify($input_password, $stored_hash)) {
// Login success
}
Bước 12: Two-Factor Authentication (2FA)
Thêm 2FA cho admin accounts:
- WordPress: Plugin “Two Factor Authentication”
- Laravel:
laravel/fortify+ Google Authenticator - Custom: Dùng thư viện TOTP (Time-based One-Time Password)
Admin account + 2FA = giảm 99.9% rủi ro bị hack qua password.
Bước 13: Regular Updates
| Component | Tần suất kiểm tra | Tự động? |
|---|---|---|
| OS (Ubuntu) security patches | Hàng ngày | Có (unattended-upgrades) |
| CMS (WordPress) | Hàng tuần | Minor: có, Major: không |
| Plugins/packages | Hàng tuần | Kiểm tra, update thủ công |
| PHP version | Mỗi 6 tháng | Không (cần test) |
| SSL certificate | Tự động (Let’s Encrypt) | Có |
| Dependencies (npm, composer) | Hàng tháng | npm audit, composer audit |
Bước 14: Logging & Monitoring
# Nginx: Detailed access log
log_format security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/security.log security;
Log những gì cho security:
- Failed login attempts
- 4xx/5xx errors
- Unusual request patterns
- File upload attempts
- Admin panel access
- Database query errors
Bước 15: Web Application Firewall (WAF)
Cloudflare WAF (miễn phí — cơ bản)
Cloudflare Free Plan tự động bảo vệ khỏi:
- SQL injection cơ bản
- XSS attacks
- Known exploit patterns
ModSecurity (miễn phí — nâng cao)
sudo apt install libmodsecurity3 libnginx-mod-security -y
# Thêm OWASP Core Rule Set
git clone https://github.com/coreruleset/coreruleset /etc/nginx/modsecurity/crs
Bảng tổng hợp 15 bước
| # | Bước | Thời gian | Độ khó | Impact |
|---|---|---|---|---|
| 1 | Security Headers | 10 phút | Dễ | Cao |
| 2 | CSP | 30 phút | Trung bình | Cao |
| 3 | Rate Limiting | 15 phút | Dễ | Cao |
| 4 | Ẩn server info | 5 phút | Dễ | Thấp |
| 5 | Bảo vệ file nhạy cảm | 10 phút | Dễ | Cao |
| 6 | Database security | 20 phút | Trung bình | Cao |
| 7 | Input validation | 1–4 giờ | Trung bình | Cao |
| 8 | Secure cookies | 10 phút | Dễ | Trung bình |
| 9 | CSRF protection | 30 phút | Trung bình | Cao |
| 10 | File upload security | 1 giờ | Trung bình | Cao |
| 11 | Password hashing | 30 phút | Dễ | Cao |
| 12 | 2FA | 30 phút | Dễ | Cao |
| 13 | Regular updates | Ongoing | Dễ | Cao |
| 14 | Logging | 30 phút | Trung bình | Trung bình |
| 15 | WAF | 1 giờ | Trung bình | Cao |
FAQ — Câu hỏi thường gặp
Bước nào quan trọng nhất?
Nếu chỉ làm 3 bước: (1) Security Headers + HSTS, (2) Rate Limiting cho login, (3) Regular Updates. Ba bước này ngăn được 70% attacks phổ biến.
Bảo mật website tốn bao nhiêu?
15 bước trong bài viết đều miễn phí (chỉ tốn thời gian). Nếu thuê chuyên gia hardening: 5–15 triệu VND (one-time). Ongoing security monitoring: 3–8 triệu VND/tháng. So với thiệt hại khi bị hack (50–500 triệu VND), đây là đầu tư nhỏ.
WordPress có cần tất cả 15 bước không?
Có. WordPress là CMS phổ biến nhất → cũng là target phổ biến nhất. 70% WordPress bị hack do: plugin outdated, password yếu, hoặc thiếu security headers. 15 bước này đặc biệt quan trọng cho WordPress.
Kết luận
Bảo mật website không phải là “1 lần rồi xong” — đó là quy trình liên tục. 15 bước trong bài viết này là foundation cần thiết. Bắt đầu từ bước 1 (security headers — 10 phút), và từ từ implement các bước còn lại.
Nếu bạn cần security audit hoặc dịch vụ bảo mật website chuyên nghiệp, hãy liên hệ Trinh Digital — chúng tôi audit và hardening website theo chuẩn OWASP.