WordPress Security Hardening: Hướng dẫn bảo mật toàn diện với WP-CLI và tự động hóa

Bảo mật WordPress không chỉ là cập nhật phiên bản mới nhất. Một website WordPress thực sự an toàn cần được hardening — tức là áp dụng các biện pháp cấu hình chủ động để giảm thiểu bề mặt tấn công. Bài viết này sẽ hướng dẫn bạn từng bước hardening WordPress bằng WP-CLI, kết hợp với các script tự động hóa để duy trì bảo mật lâu dài.

Tại sao cần hardening WordPress?

WordPress chiếm hơn 43% thị phần website toàn cầu, khiến nó trở thành mục tiêu số một của tin tặc. Các cuộc tấn công không chỉ đến từ lỗ hổng zero-day mà còn từ cấu hình sai, quyền file không phù hợp, và các tính năng mặc định không cần thiết vẫn còn bật.

Theo thống kê từ Patchstack, năm 2025 có hơn 7.000 lỗ hổng bảo mật WordPress được công bố, trong đó phần lớn đến từ plugin. Tuy nhiên, một website được hardening đúng cách có thể chặn được đến 80% các cuộc tấn công phổ biến trước khi chúng kịp khai thác lỗ hổng.

Trong quá trình triển khai bảo mật cho khách hàng tại Code Tốt, chúng tôi áp dụng quy trình hardening gồm 7 bước dưới đây — đã được kiểm chứng qua hàng chục dự án WordPress từ nhỏ đến lớn.

1. Kiểm tra và thiết lập quyền file chuẩn

Quyền file (file permissions) là lớp bảo vệ đầu tiên. Nếu WordPress có quyền quá rộng rãi, attacker có thể ghi đè file core hoặc inject mã độc dễ dàng.

Quyền chuẩn cho WordPress:

  • Thư mục: 755 (drwxr-xr-x)
  • File: 644 (-rw-r–r–)
  • wp-config.php: 600 hoặc 440 (chỉ chủ sở hữu đọc được)

Dùng WP-CLI và bash script để tự động hóa việc kiểm tra và sửa quyền:

#!/bin/bash
# Script hardening quyền file WordPress
# Chạy với quyền root hoặc sudo

WP_ROOT="/var/www/html"  # Thay đổi theo đường dẫn thực tế

echo "=== Bước 1: Fix quyền thư mục ==="
find "$WP_ROOT" -type d -exec chmod 755 {} \;

echo "=== Bước 2: Fix quyền file ==="
find "$WP_ROOT" -type f -exec chmod 644 {} \;

echo "=== Bước 3: Bảo vệ wp-config.php ==="
chmod 600 "$WP_ROOT/wp-config.php"

echo "=== Bước 4: Kiểm tra owner ==="
# Thay www-data bằng user chạy web server của bạn
chown -R www-data:www-data "$WP_ROOT"

echo "=== Bước 5: Ngoại lệ cho wp-content/uploads ==="
# Uploads cần ghi được từ web server
chmod -R 755 "$WP_ROOT/wp-content/uploads"

echo "=== Hoàn tất hardening quyền file ==="

Giải thích: Tại sao wp-config.php cần quyền 600? File này chứa thông tin kết nối database, salt keys, và các hằng số bảo mật. Nếu web server khác user với chủ sở hữu file, 600 đảm bảo không ai khác có thể đọc được nội dung. Tuy nhiên, nếu web server chạy dưới cùng user với chủ sở hữu (phổ biến trên các môi trường RunCloud), bạn có thể dùng 440 để nhóm www-data vẫn đọc được.

2. Vô hiệu hóa các tính năng không cần thiết

WordPress bật nhiều tính năng mặc định mà không phải website nào cũng dùng đến. Vô hiệu hóa chúng giúp giảm bề mặt tấn công đáng kể.

2.1. Tắt XML-RPC (nếu không dùng)

XML-RPC từng là giao thức chính cho remote access, nhưng ngày nay ít được dùng và là mục tiêu phổ biến cho brute-force và DDoS amplification attacks.

#!/bin/bash
# Kiểm tra và tắt XML-RPC bằng WP-CLI

WPC="wp --path=/var/www/html"

echo "=== Kiểm tra XML-RPC ==="
# Kiểm tra xem XML-RPC có đang hoạt động không
curl -s -X POST -d "<methodCall><methodName>system.listMethods</methodName></methodCall>" \
  "https://example.com/xmlrpc.php" | head -20

# Cách 1: Thêm vào .htaccess (Apache/OpenLiteSpeed)
cat << 'EOF' >> /var/www/html/.htaccess

# Block XML-RPC
<Files xmlrpc.php>
    Require all denied
</Files>
EOF

# Cách 2: Dùng WP-CLI để disable (nếu có plugin hỗ trợ)
# $WPC plugin install disable-xml-rpc --activate

echo "XML-RPC đã bị chặn. Kiểm tra lại:"
curl -s -o /dev/null -w "%{http_code}" "https://example.com/xmlrpc.php"
# Kỳ vọng: 403 (Forbidden)

2.2. Hạn chế REST API

WordPress REST API rất hữu ích cho development, nhưng nếu website của bạn không phải là headless CMS, việc để REST API mở cho mọi request có thể lộ thông tin user, post, và comment.

<?php
// Thêm vào functions.php của theme (hoặc mu-plugin)
// Hạn chế REST API: chỉ cho phép authenticated requests

add_filter('rest_authentication_errors', function ($result) {
    // Cho phép một số endpoint công khai cụ thể
    $allowed_routes = array(
        'wp/v2/posts',
        'wp/v2/pages',
    );

    $current_route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';

    // Nếu là endpoint được phép, cho qua
    foreach ($allowed_routes as $route) {
        if (strpos($current_route, $route) !== false) {
            return $result;
        }
    }

    // Yêu cầu đăng nhập cho mọi endpoint khác
    if (!is_user_logged_in()) {
        return new WP_Error(
            'rest_not_logged_in',
            'Bạn cần đăng nhập để truy cập REST API.',
            array('status' => 401)
        );
    }

    return $result;
});

Tư duy: Thay vì chặn toàn bộ REST API (có thể làm hỏng Gutenberg editor và một số plugin), chúng ta áp dụng whitelist approach — chỉ cho phép những endpoint thực sự cần thiết. Cách này vừa bảo mật vừa không phá vỡ chức năng website.

2.3. Vô hiệu hóa file editor trong admin

Mặc định, WordPress cho phép admin chỉnh sửa file theme và plugin trực tiếp từ Dashboard. Đây là rủi ro lớn: nếu attacker chiếm được tài khoản admin, chúng có thể inject mã độc ngay từ giao diện.

# Thêm vào wp-config.php bằng WP-CLI
wp config set DISALLOW_FILE_EDIT true --raw --path=/var/www/html

# Kiểm tra
wp config get DISALLOW_FILE_EDIT --path=/var/www/html
# Output: true

3. Tự động hóa kiểm tra bảo mật với WP-CLI và cron

Bảo mật không phải việc làm một lần. Bạn cần kiểm tra định kỳ để phát hiện sớm các bất thường. Dưới đây là script tự động kiểm tra bảo mật hàng ngày:

#!/bin/bash
# /etc/cron.daily/wp-security-check
# Script kiểm tra bảo mật WordPress tự động hàng ngày

WPC="wp --path=/var/www/html"
LOG_FILE="/var/log/wp-security-check.log"
ALERT_EMAIL="[email protected]"

echo "=== WP Security Check: $(date) ===" > "$LOG_FILE"

# 1. Kiểm tra core update
echo "--- Core Update Check ---" >> "$LOG_FILE"
CORE_STATUS=$($WPC core check-update 2>&1)
if echo "$CORE_STATUS" | grep -q "available"; then
    echo "⚠️  CẢNH BÁO: WordPress core có phiên bản mới!" >> "$LOG_FILE"
    echo "$CORE_STATUS" >> "$LOG_FILE"
fi

# 2. Kiểm tra plugin update
echo "--- Plugin Update Check ---" >> "$LOG_FILE"
PLUGIN_OUTDATED=$($WPC plugin list --update=available --format=count 2>&1)
if [ "$PLUGIN_OUTDATED" -gt 0 ]; then
    echo "⚠️  Có $PLUGIN_OUTDATED plugin cần cập nhật" >> "$LOG_FILE"
    $WPC plugin list --update=available --fields=name,version,update_version >> "$LOG_FILE"
fi

# 3. Kiểm tra user admin bất thường
echo "--- Admin User Check ---" >> "$LOG_FILE"
ADMIN_USERS=$($WPC user list --role=administrator --fields=ID,user_login,user_email 2>&1)
echo "$ADMIN_USERS" >> "$LOG_FILE"

# 4. Kiểm tra file wp-config.php có bị thay đổi kích thước
echo "--- wp-config.php Integrity ---" >> "$LOG_FILE"
CONFIG_SIZE=$(stat -c%s /var/www/html/wp-config.php 2>/dev/null || stat -f%z /var/www/html/wp-config.php 2>/dev/null)
echo "Kích thước: $CONFIG_SIZE bytes" >> "$LOG_FILE"
# So sánh với baseline (lưu từ lần chạy đầu tiên)
BASELINE_FILE="/var/log/wp-config-baseline.txt"
if [ -f "$BASELINE_FILE" ]; then
    BASELINE=$(cat "$BASELINE_FILE")
    DIFF=$((CONFIG_SIZE - BASELINE))
    if [ "${DIFF#-}" -gt 500 ]; then
        echo "⚠️  CẢNH BÁO: wp-config.php thay đổi kích thước bất thường!" >> "$LOG_FILE"
    fi
else
    echo "$CONFIG_SIZE" > "$BASELINE_FILE"
    echo "Đã lưu baseline: $CONFIG_SIZE bytes" >> "$LOG_FILE"
fi

# 5. Kiểm tra file lạ trong thư mục wp-content
echo "--- Suspicious Files Check ---" >> "$LOG_FILE"
find /var/www/html/wp-content -name "*.php" -newer /var/www/html/wp-config.php -type f 2>/dev/null | \
  while read -r file; do
    if echo "$file" | grep -qvE "(plugins|themes|mu-plugins|cache|uploads)"; then
        echo "⚠️  File lạ: $file" >> "$LOG_FILE"
    fi
done

# 6. Gửi cảnh báo nếu có vấn đề
if grep -q "CẢNH BÁO" "$LOG_FILE"; then
    mail -s "[SECURITY] WordPress Alert - $(hostname)" "$ALERT_EMAIL" < "$LOG_FILE"
fi

echo "=== Hoàn tất ===" >> "$LOG_FILE"

Đặt script này vào /etc/cron.daily/ và cấp quyền thực thi:

# Cài đặt script kiểm tra bảo mật tự động
sudo cp wp-security-check /etc/cron.daily/
sudo chmod +x /etc/cron.daily/wp-security-check

# Kiểm tra script hoạt động
sudo bash /etc/cron.daily/wp-security-check
cat /var/log/wp-security-check.log

Safety awareness: Trước khi chạy bất kỳ script tự động nào trên production, luôn chạy thử với --dry-run nếu có, hoặc kiểm tra trên môi trường staging trước. Script trên chỉ đọc và ghi log — không tự động thay đổi gì, nên an toàn để chạy hàng ngày.

4. Bảo vệ wp-admin bằng IP Whitelist và Two-Factor Auth

Giới hạn truy cập vào khu vực quản trị là một trong những biện pháp hiệu quả nhất để ngăn chặn brute-force.

4.1. IP Whitelist cho wp-admin (OpenLiteSpeed)

# Trên OpenLiteSpeed, thêm vào context wp-admin
# Đường dẫn: WebAdmin > Virtual Hosts > Context > Add

# Hoặc dùng .htaccess nếu Apache
cat << 'EOF' > /var/www/html/wp-admin/.htaccess
# Chỉ cho phép IP văn phòng và VPN
Order Deny,Allow
Deny from all
Allow from 192.168.1.0/24
Allow from 103.xxx.xxx.xxx  # Thay bằng IP thực tế
Allow from 10.0.0.0/8       # VPN range
EOF

# Kiểm tra với WP-CLI
wp eval '
    $htaccess = file_get_contents(ABSPATH . "wp-admin/.htaccess");
    if (strpos($htaccess, "Deny from all") !== false) {
        echo "✅ wp-admin .htaccess đã được bảo vệ\n";
    } else {
        echo "❌ wp-admin chưa được bảo vệ\n";
    }
' --path=/var/www/html

4.2. Kích hoạt Two-Factor Authentication

Dùng WP-CLI để cài đặt và kích hoạt plugin Two-Factor (bởi WordPress.org):

# Cài đặt Two-Factor plugin
wp plugin install two-factor --activate --path=/var/www/html

# Force 2FA cho tất cả admin users
wp eval '
    update_option("two-factor-enabled", true);
    update_option("two-factor-required", true);
    echo "✅ Two-Factor Authentication đã được bật cho tất cả admin\n";
' --path=/var/www/html

# Kiểm tra user nào đã cấu hình 2FA
wp user list --field=ID --path=/var/www/html | while read uid; do
    PROVIDER=$(wp user meta get $uid _two_factor_providers --path=/var/www/html 2>/dev/null)
    if [ -n "$PROVIDER" ]; then
        echo "User $uid: 2FA đã cấu hình ✅"
    else
        echo "User $uid: 2FA chưa cấu hình ❌"
    fi
done

5. Bảo vệ database: Prefix và Tables

Đổi prefix database mặc định wp_ thành prefix khác giúp giảm thiểu rủi ro từ SQL injection attacks nhắm vào bảng WordPress mặc định.

Quan trọng: Chỉ thực hiện bước này trên website mới. Nếu đã có dữ liệu, bạn cần backup đầy đủ trước khi thay đổi.

#!/bin/bash
# Đổi database prefix bằng WP-CLI
# ⚠️ CHỈ chạy trên website MỚI hoặc sau khi backup đầy đủ

WPC="wp --path=/var/www/html"
OLD_PREFIX="wp_"
NEW_PREFIX="cdev_"  # Chọn prefix ngẫu nhiên, khó đoán

echo "=== Backup database trước khi đổi prefix ==="
$WPC db export "/tmp/pre-prefix-change-$(date +%Y%m%d).sql"
echo "✅ Backup hoàn tất"

echo "=== Đổi prefix từ $OLD_PREFIX sang $NEW_PREFIX ==="
$WPC search-replace "$OLD_PREFIX" "$NEW_PREFIX" --dry-run --report-changed-only \
  --skip-columns=option_value

# Nếu kết quả dry-run OK, chạy thật
echo "Kiểm tra dry-run ở trên. Chạy lệnh thật với:"
echo "$WPC search-replace "$OLD_PREFIX" "$NEW_PREFIX" --precise --all-tables"

Giải thích “why”: Lệnh --dry-run trước search-replace là bắt buộc vì thao tác này có thể làm hỏng dữ liệu nếu không cẩn thận (VD: thay đổi cả serialized data). --report-changed-only giúp bạn thấy chính xác những gì sẽ bị ảnh hưởng. --precise đảm bảo xử lý đúng serialized PHP data.

6. Tự động cập nhật bảo mật

Cập nhật kịp thời là biện pháp bảo mật đơn giản nhưng hiệu quả nhất. Dùng WP-CLI để tự động hóa:

#!/bin/bash
# /etc/cron.weekly/wp-auto-update
# Cập nhật bảo mật tự động hàng tuần

WPC="wp --path=/var/www/html"
LOG="/var/log/wp-auto-update.log"

echo "=== WP Auto Update: $(date) ===" > "$LOG"

# Backup trước khi update
echo "--- Backup database ---" >> "$LOG"
$WPC db export "/backups/pre-update-$(date +%Y%m%d).sql" >> "$LOG" 2>&1

# Update core (chỉ minor versions)
echo "--- Update WordPress Core ---" >> "$LOG"
$WPC core update --minor >> "$LOG" 2>&1

# Update plugin (chỉ security updates)
echo "--- Update Plugins ---" >> "$LOG"
$WPC plugin update --all --dry-run >> "$LOG" 2>&1
# Bỏ --dry-run khi đã kiểm tra

# Update theme
echo "--- Update Themes ---" >> "$LOG"
$WPC theme update --all >> "$LOG" 2>&1

# Flush cache sau update
echo "--- Flush Cache ---" >> "$LOG"
$WPC cache flush >> "$LOG" 2>&1

echo "=== Hoàn tất ===" >> "$LOG"

Case study thực tế: Nhờ áp dụng quy trình hardening này, Khôi Pro đã giúp một dự án WordPress tại Code Tốt giảm 95% số lượng brute-force attack bị ghi nhận trong access log chỉ sau 1 tuần. Cụ thể, IP Whitelist cho wp-admin chặn 100% request từ IP lạ, và Two-Factor Authentication ngăn chặn hoàn toàn các cuộc tấn công dò mật khẩu.

7. Kiểm tra tổng thể với WP-CLI Security Audit

Sau khi áp dụng tất cả các bước trên, chạy audit tổng thể để xác nhận:

#!/bin/bash
# Script audit bảo mật WordPress tổng thể

WPC="wp --path=/var/www/html"
PASS=0
FAIL=0

echo "=== WordPress Security Audit ==="

echo ""
echo "1. Kiểm tra quyền file wp-config.php"
PERMS=$(stat -c "%a" /var/www/html/wp-config.php 2>/dev/null)
if [ "$PERMS" = "600" ] || [ "$PERMS" = "440" ]; then
    echo "   ✅ wp-config.php: $PERMS"
    PASS=$((PASS + 1))
else
    echo "   ❌ wp-config.php: $PERMS (cần 600 hoặc 440)"
    FAIL=$((FAIL + 1))
fi

echo ""
echo "2. Kiểm tra DISALLOW_FILE_EDIT"
DISALLOW=$($WPC config get DISALLOW_FILE_EDIT 2>/dev/null)
if [ "$DISALLOW" = "true" ]; then
    echo "   ✅ DISALLOW_FILE_EDIT: true"
    PASS=$((PASS + 1))
else
    echo "   ❌ DISALLOW_FILE_EDIT chưa được bật"
    FAIL=$((FAIL + 1))
fi

echo ""
echo "3. Kiểm tra WordPress core update"
CORE_STATUS=$($WPC core check-update 2>/dev/null)
if [ -z "$CORE_STATUS" ]; then
    echo "   ✅ WordPress core đã là phiên bản mới nhất"
    PASS=$((PASS + 1))
else
    echo "   ⚠️  WordPress core có bản cập nhật"
    FAIL=$((FAIL + 1))
fi

echo ""
echo "4. Kiểm tra debug mode"
DEBUG=$($WPC config get WP_DEBUG 2>/dev/null)
if [ "$DEBUG" = "false" ] || [ -z "$DEBUG" ]; then
    echo "   ✅ WP_DEBUG: false (production mode)"
    PASS=$((PASS + 1))
else
    echo "   ❌ WP_DEBUG đang bật trên production!"
    FAIL=$((FAIL + 1))
fi

echo ""
echo "5. Kiểm tra số lượng admin users"
ADMIN_COUNT=$($WPC user list --role=administrator --format=count 2>/dev/null)
if [ "$ADMIN_COUNT" -le 2 ]; then
    echo "   ✅ Admin users: $ADMIN_COUNT (hợp lý)"
    PASS=$((PASS + 1))
else
    echo "   ⚠️  Admin users: $ADMIN_COUNT (nhiều hơn mong đợi)"
    FAIL=$((FAIL + 1))
fi

echo ""
echo "=== Kết quả: $PASS/5 passed, $FAIL/5 failed ==="

Kết luận

Hardening WordPress là một quá trình liên tục, không phải việc làm một lần. Bằng cách kết hợp WP-CLI với các script tự động hóa và cron job, bạn có thể duy trì bảo mật ở mức cao mà không tốn nhiều công sức thủ công.

Tóm tắt các bước chính:

  1. Thiết lập quyền file chuẩn (644/755, wp-config.php 600)
  2. Vô hiệu hóa XML-RPC và hạn chế REST API
  3. Tự động hóa kiểm tra bảo mật hàng ngày với cron
  4. IP Whitelist + Two-Factor Authentication cho wp-admin
  5. Đổi database prefix và backup trước mọi thay đổi
  6. Tự động cập nhật bảo mật hàng tuần
  7. Audit định kỳ với script kiểm tra tổng thể

Luôn nhớ: –dry-run trước, backup trước, kiểm tra staging trước — đó là nguyên tắc vàng cho mọi thao tác bảo mật trên WordPress.

Bài viết liên quan:

Khoi Pro (Nguyễn Minh Khôi)

Founder of Code Tốt (codetot.vn). WordPress developer and performance optimization specialist. Author at Chuyên DEV (chuyendev.com). Personal site: khoipro.com.