Case study bảo mật website này ghi lại hành trình chúng tôi hardening website của một công ty luật tại TP.HCM — từ điểm F (0/100) lên A+ (125/100) trên Mozilla Observatory, và từ grade C lên A+ trên SSL Labs. Đây là case study chi tiết với từng bước thực hiện, giúp bất kỳ developer nào có thể áp dụng.
Bối cảnh
Khách hàng: Công ty luật với 15 luật sư, chuyên tư vấn doanh nghiệp và M&A.
Website: WordPress, host trên VPS DigitalOcean.
Tại sao cần hardening:
- Đối tác nước ngoài audit website trước khi ký hợp đồng → phát hiện điểm bảo mật F
- Khách hàng gửi thông tin nhạy cảm (hợp đồng, tài liệu pháp lý) qua website
- Yêu cầu tuân thủ Nghị định 13/2023 về bảo vệ dữ liệu cá nhân
Yêu cầu: Đạt A+ trên Mozilla Observatory, A+ trên SSL Labs, đáp ứng OWASP Top 10.
Đánh giá ban đầu
Mozilla Observatory: Grade F (Score: 0/100)
| Test | Kết quả | Điểm |
|---|---|---|
| Content Security Policy | Không có | -25 |
| Cookies | Không Secure, không HttpOnly | -20 |
| Cross-Origin Resource Sharing | Không cấu hình | 0 |
| HTTP Strict Transport Security | Không có | -20 |
| Redirection | Có redirect HTTP→HTTPS | +5 |
| Referrer Policy | Không có | -5 |
| Subresource Integrity | Không có | -5 |
| X-Content-Type-Options | Không có | -5 |
| X-Frame-Options | Không có | -20 |
| X-XSS-Protection | Không có | -10 |
SSL Labs: Grade C
| Vấn đề | Chi tiết |
|---|---|
| TLS 1.0 enabled | Deprecated, vulnerable |
| TLS 1.1 enabled | Deprecated |
| Weak cipher suites | DES-CBC3-SHA, RC4 |
| No HSTS | Không enforce HTTPS |
| No OCSP stapling | Slower SSL handshake |
Scan bổ sung
| Tool | Kết quả |
|---|---|
| Sucuri SiteCheck | 2 outdated plugins, jQuery vulnerable |
| WPScan | 5 known vulnerabilities |
| Nmap | Port 22, 80, 443, 3306 (MySQL exposed!) |
| Shodan | MySQL visible from internet |
12 bước hardening
Bước 1: Đóng port MySQL (5 phút)
MySQL port 3306 mở ra internet = bất kỳ ai đều có thể thử kết nối.
# Bind MySQL chỉ localhost
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
# bind-address = 127.0.0.1
# Update firewall
sudo ufw deny 3306
sudo systemctl restart mysql
Kết quả: MySQL không còn accessible từ internet.
Bước 2: SSL/TLS Hardening (15 phút)
# /etc/nginx/conf.d/ssl-hardening.conf
# Chỉ cho phép TLS 1.2 và 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites mạnh
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# SSL session caching
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# DH parameters (2048-bit)
ssl_dhparam /etc/nginx/dhparam.pem;
# Generate DH parameters
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Kết quả: SSL Labs: C → A+
Bước 3: Security Headers (10 phút)
# /etc/nginx/conf.d/security-headers.conf
# Chống clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Chống MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# XSS Protection
add_header X-XSS-Protection "1; mode=block" always;
# Referrer Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# HSTS (1 năm + subdomains + preload)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Permissions Policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
Kết quả: +75 điểm trên Mozilla Observatory.
Bước 4: Content Security Policy (30 phút)
CSP là header phức tạp nhất — cần test kỹ:
# Phase 1: Report-Only (1 tuần)
add_header Content-Security-Policy-Report-Only "
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://www.gstatic.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 https://www.google.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
upgrade-insecure-requests;
" always;
# Phase 2: Enforce (sau 1 tuần không có lỗi)
# Đổi Content-Security-Policy-Report-Only → Content-Security-Policy
Kết quả: +25 điểm trên Mozilla Observatory.
Bước 5: Secure Cookies (10 phút)
// wp-config.php — trước "/* That's all, stop editing! */"
// Force secure cookies
@ini_set('session.cookie_httponly', true);
@ini_set('session.cookie_secure', true);
@ini_set('session.cookie_samesite', 'Lax');
@ini_set('session.use_only_cookies', true);
define('COOKIE_DOMAIN', '.yourdomain.com');
define('COOKIEPATH', '/');
# Nginx: Thêm flags cho tất cả cookies
proxy_cookie_path / "/; HTTPOnly; Secure; SameSite=Lax";
Kết quả: +20 điểm.
Bước 6: Update WordPress + Plugins (30 phút)
| Plugin | Phiên bản cũ | Phiên bản mới | Lỗ hổng đã fix |
|---|---|---|---|
| Elementor | 3.12 | 3.19 | XSS, Privilege Escalation |
| Contact Form 7 | 5.6 | 5.9 | File Upload vulnerability |
| Yoast SEO | 20.1 | 22.3 | Information Disclosure |
| WooCommerce | 7.5 | 8.5 | SQL Injection |
| jQuery | 3.4.1 | 3.7.1 | XSS vulnerability |
# WP-CLI update tất cả
wp core update
wp plugin update --all
wp theme update --all
wp core update-db
Bước 7: WordPress Hardening (20 phút)
// wp-config.php
// Tắt file editing trong admin
define('DISALLOW_FILE_EDIT', true);
// Tắt file mods (cài plugin qua admin)
define('DISALLOW_FILE_MODS', true);
// Giới hạn post revisions
define('WP_POST_REVISIONS', 5);
// Custom database prefix (đã có từ cài)
// $table_prefix = 'wp_abc123_';
// Security keys (regenerate)
// https://api.wordpress.org/secret-key/1.1/salt/
# Nginx: WordPress-specific security
location /wp-admin/includes/ { deny all; }
location /wp-includes/theme-compat/ { deny all; }
location /wp-includes/js/tinymce/langs/ { deny all; }
location ~* /wp-config.php { deny all; }
location ~* /readme.html { deny all; }
location ~* /xmlrpc.php { deny all; }
# Chặn PHP execution trong uploads
location ~* /wp-content/uploads/.*\.php$ {
deny all;
}
Bước 8: Rate Limiting (10 phút)
limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m;
limit_req_zone $binary_remote_addr zone=contact:10m rate=5r/m;
location /wp-login.php {
limit_req zone=login burst=2 nodelay;
limit_req_status 429;
# Thêm: chỉ cho phép IP văn phòng
# allow 103.x.x.x;
# deny all;
}
location /wp-admin/admin-ajax.php {
limit_req zone=contact burst=10 nodelay;
}
Bước 9: 2FA cho Admin (15 phút)
Cài plugin Two Factor Authentication cho WordPress:
wp plugin install two-factor --activate
Bật 2FA bắt buộc cho tất cả admin accounts (Google Authenticator).
Bước 10: Backup & Recovery (20 phút)
Setup backup tự động theo quy tắc 3-2-1:
# Database backup hàng ngày → B2
# Files backup hàng tuần → B2
# Config backup hàng tháng → B2
Bước 11: Monitoring (15 phút)
- UptimeRobot: Kiểm tra uptime mỗi 5 phút
- SSL Monitor: Alert khi SSL sắp hết hạn
- Sucuri SiteCheck: Scan malware hàng tuần
Bước 12: Firewall & WAF (15 phút)
Cloudflare Free:
- DNS proxy → ẩn IP thật
- DDoS protection
- Basic WAF rules
- Rate limiting
Kết quả
Mozilla Observatory
| Test | Trước | Sau |
|---|---|---|
| Content Security Policy | Fail (-25) | Pass (+25) |
| Cookies | Fail (-20) | Pass (+5) |
| HSTS | Fail (-20) | Pass (+15) |
| Referrer Policy | Fail (-5) | Pass (+5) |
| X-Content-Type-Options | Fail (-5) | Pass (+5) |
| X-Frame-Options | Fail (-20) | Pass (+20) |
| X-XSS-Protection | Fail (-10) | Pass (+5) |
| Subresource Integrity | N/A | Pass (+5) |
| Tổng điểm | 0/100 (F) | 125/100 (A+) |
SSL Labs
| Test | Trước | Sau |
|---|---|---|
| Protocol Support | TLS 1.0, 1.1, 1.2 | TLS 1.2, 1.3 only |
| Key Exchange | Weak DH | ECDHE |
| Cipher Strength | Weak ciphers | Strong only |
| HSTS | No | Yes (preload) |
| OCSP Stapling | No | Yes |
| Grade | C | A+ |
Security scan
| Tool | Trước | Sau |
|---|---|---|
| Sucuri SiteCheck | 2 warnings | Clean |
| WPScan | 5 vulnerabilities | 0 vulnerabilities |
| Nmap (external) | 4 ports open | 2 ports open (80, 443) |
| SecurityHeaders.com | F | A+ |
Business impact
| Metric | Trước | Sau |
|---|---|---|
| Đối tác security audit | Fail | Pass |
| Trust score (khảo sát nội bộ) | 5.2/10 | 8.8/10 |
| SSL Labs badge | Không dám hiển thị | Hiển thị A+ trên website |
| Compliance NĐ 13/2023 | Chưa đạt | Đạt cơ bản |
Chi phí
| Hạng mục | Chi phí |
|---|---|
| Hardening (one-time) | 8 triệu VND |
| Cloudflare Free | 0 VND/tháng |
| 2FA plugin | 0 VND |
| Backup (Backblaze B2) | ~5.000 VND/tháng |
| Monitoring (UptimeRobot Free) | 0 VND |
| Tổng one-time | 8 triệu VND |
| Tổng hàng tháng | ~5.000 VND |
Timeline: 12 bước trong 1 ngày
| Thời gian | Bước | Kết quả |
|---|---|---|
| 9:00–9:05 | Đóng port MySQL | Port 3306 closed |
| 9:05–9:20 | SSL/TLS hardening | SSL Labs: A+ |
| 9:20–9:30 | Security headers | +75 điểm |
| 9:30–10:00 | CSP (Report-Only) | Monitoring |
| 10:00–10:10 | Secure cookies | +20 điểm |
| 10:10–10:40 | Update WP + plugins | 0 known vulnerabilities |
| 10:40–11:00 | WordPress hardening | Admin secured |
| 11:00–11:10 | Rate limiting | Login protected |
| 11:10–11:25 | 2FA | Admin 2FA enabled |
| 11:25–11:45 | Backup setup | 3-2-1 backup active |
| 11:45–12:00 | Monitoring | UptimeRobot active |
| 12:00–12:15 | Cloudflare WAF | DDoS protection active |
| Tổng | 3 giờ 15 phút | F → A+ |
FAQ — Câu hỏi thường gặp
Có cần thuê chuyên gia hay tự làm được?
12 bước trong bài viết đều có thể tự làm nếu bạn quen với Linux và Nginx. Tuy nhiên, CSP (bước 4) cần test kỹ vì có thể break website nếu cấu hình sai. Nếu không chắc chắn, thuê chuyên gia để tránh rủi ro.
A+ trên Mozilla Observatory có giữ được mãi không?
Không tự động — cần maintenance. Khi thêm third-party scripts (analytics, chat widget, ads), CSP cần update. Khi update WordPress/plugins, cần kiểm tra lại. Khuyến nghị: scan lại mỗi tháng 1 lần.
Kết luận
Từ F lên A+ trong 1 ngày — không khó, chỉ cần biết làm gì và làm đúng thứ tự. 12 bước trong case study này áp dụng được cho bất kỳ website nào: WordPress, Laravel, Node.js, hoặc static site.
Nếu bạn cần security audit hoặc dịch vụ hardening website chuyên nghiệp, hãy liên hệ Trinh Digital — chúng tôi đảm bảo A+ trên cả Mozilla Observatory và SSL Labs.