0

Joomla Bài học 18: Bảo mật Joomla toàn diện: Xác thực đa yếu tố (MFA/Passkeys), cấu hình .htaccess và HTTP Security Headers

Bảo mật không phải là một tính năng cài thêm ở cuối dự án, mà là một kiến trúc phòng thủ đa lớp (Defense in Depth). Trong Joomla Bài học 18, chúng ta sẽ thiết lập hàng rào bảo vệ toàn diện cho hệ thống Joomla 5 trên 4 mặt trận: Xác thực không mật khẩu (Passkeys / WebAuthn) và Đa yếu tố (MFA), gia cố hệ thống tệp tin qua .htaccess / Nginx, cấu hình HTTP Security Headers chống tấn công XSS/Clickjacking, và kỹ thuật chống CSRF ở tầng lập trình PHP.

1. Xác thực Đa yếu tố (MFA) và Đăng nhập không mật khẩu (Passkeys / WebAuthn)

Khác với các phiên bản cũ gọi là 2FA (Two-Factor Authentication), từ Joomla 4.2+ và hoàn thiện ở Joomla 5, hệ thống đã nâng cấp lên kiến trúc Multi-Factor Authentication (MFA) và tích hợp chuẩn W3C WebAuthn (Passkeys) ngay trong Core.

A. Sự khác biệt giữa MFA và Passkeys trong Joomla 5

  1. Passkeys (System - WebAuthn Plugin): Cho phép người dùng đăng nhập trực tiếp từ màn hình đầu tiên bằng vân tay (TouchID/Windows Hello), nhận diện khuôn mặt (FaceID) hoặc khóa cứng bảo mật (YubiKey) mà không cần gõ mật khẩu. Khóa bí mật (Private Key) nằm trong chip TPM/Secure Enclave của thiết bị, còn Joomla chỉ lưu khóa công khai (Public Key) trong bảng #__webauthn_credentials (yêu cầu website chạy trên HTTPS hoặc localhost).

  2. MFA (Nhóm plugin multifactorauth): Là lớp khóa thứ hai sau khi đã nhập đúng Username & Password. Joomla 5 tích hợp sẵn 5 phương thức:

    • Verification Code (TOTP): Dùng ứng dụng Google Authenticator, Authy hoặc Microsoft Authenticator (Phổ biến nhất).

    • WebAuthn / FIDO2: Dùng khóa cứng vật lý hoặc sinh trắc học làm bước xác thực thứ 2.

    • Code by Email: Gửi mã OTP 6 số qua email đăng ký.

    • Fixed Code: Mã tĩnh cố định (chỉ dùng cho môi trường dev/test).

    • Backup Codes: Bộ 10 mã dự phòng dùng 1 lần (tự động sinh ra khi bật MFA, dùng khi mất điện thoại).

B. Cách bắt buộc (Enforce) nhóm Quản trị viên phải bật MFA

Là kiến trúc sư hệ thống, bạn không nên để nhân sự tự giác bật MFA mà phải ép buộc tự động theo nhóm quyền:

  1. Truy cập System →\rightarrow Manage →\rightarrow Plugins →\rightarrow Lọc theo Type: multifactorauth và đảm bảo plugin Multi-factor Authentication - Verification Code (TOTP) đã được Enabled.

  2. Truy cập Users →\rightarrow Manage →\rightarrow Bấm nút Options (góc trên bên phải) →\rightarrow Chuyển sang tab Multi-factor Authentication.

  3. Tại mục Enforce Multi-factor Authentication, chọn các nhóm nhạy cảm như Manager, Administrator, Super Users.

  4. Bấm Save & Close. Từ lúc này, bất kỳ Admin nào đăng nhập vào /administrator mà chưa thiết lập MFA sẽ bị khóa ở màn hình bắt buộc quét mã QR trước khi được phép thao tác với hệ thống!

Lưu ý giải cứu khi mất điện thoại (Emergency Recovery qua Database):

Nếu Super User làm mất điện thoại lẫn Backup Codes và bị khóa ngoài trang Admin, với quyền truy cập MySQL trong Docker, bạn có thể giải cứu ngay bằng lệnh SQL xóa bản ghi MFA của user đó (ví dụ User ID = 42):

SQL

DELETE FROM j5_user_mfa WHERE user_id = 42;

2. Cấu hình HTTP Security Headers bằng Plugin tích hợp sẵn

Các trình duyệt hiện đại hỗ trợ một loạt HTTP Response Headers giúp ngăn chặn hiệu quả tấn công XSS (Cross-Site Scripting), Clickjacking (nhúng trang Admin vào thẻ <iframe> ẩn để lừa click) và MIME-Sniffing.

Joomla 5 cung cấp sẵn plugin System - HTTP Headers cực kỳ mạnh mẽ mà bạn không cần phải cài thêm extension:

  1. Truy cập System →\rightarrow Manage →\rightarrow Plugins →\rightarrow Tìm và mở System - HTTP Headers (chuyển Status sang Enabled).

  2. Thiết lập các thông số chuẩn an toàn thông tin:

    • X-Frame-Options: Chọn SAMEORIGIN (Chỉ cho phép nhúng iframe trên cùng tên miền của bạn, chặn đứng Clickjacking từ web lạ).

    • Referrer-Policy: Chọn strict-origin-when-cross-origin (Bảo vệ thông tin URL nội bộ khi người dùng click sang link ngoài).

    • Cross-Origin-Opener-Policy: Chọn same-origin.

    • Strict-Transport-Security (HSTS): Khi website đã chạy HTTPS trên Production, bật Enabled với Max-Age = 31536000 (1 năm) để ép trình duyệt luôn kết nối qua HTTPS.

    • Content-Security-Policy (CSP): Công cụ mạnh nhất để chống XSS bằng cách quy định trình duyệt chỉ được tải JS/CSS/Image từ các nguồn được cấp phép (Whitelist). Mẹo thực tế: Hãy bật chế độ Report-Only trước khi áp dụng cứng để kiểm tra trên Console F12 xem có làm chặn nhầm Google Analytics hay Font chữ bên ngoài không.

3. Gia cố tầng Web Server (.htaccess & Nginx) và Phân quyền File

Một lỗ hổng kinh điển của các hệ thống CMS là kẻ tấn công lợi dụng sơ hở ở form upload để đẩy một file shell (.php) lên thư mục /images hoặc /tmp rồi gọi URL thực thi trực tiếp. Chúng ta phải chặn đứng hành vi thực thi PHP ở các thư mục tĩnh ngay từ tầng Web Server và khóa quyền ghi file cấu hình.

A. Phân quyền File (CHMOD) và khóa configuration.php

  • Quy tắc chuẩn trên Linux/Docker: Toàn bộ thư mục đặt quyền 755 (drwxr-xr-x) và tệp tin đặt quyền 644 (-rw-r--r--).

  • Riêng file configuration.php: Sau khi Joomla lưu cấu hình toàn cục, hệ thống tự động chuyển quyền file này về 444 (-r--r--r-- - Read-only cho tất cả mọi người) để ngăn các script độc hại chỉnh sửa thông số kết nối Database.

B. Cấu hình chặn thực thi PHP trong thư mục /images, /media, /tmp

  • Đối với Nginx (Cập nhật vào docker/nginx/default.conf của Bài 2): Đặt khối location này phía trên khối location ~ \.php$ để vô hiệu hóa hoàn toàn việc chạy file .php trong các thư mục chứa tài nguyên upload:

    Nginx

    # Chặn tuyệt đối thực thi mã PHP trong các thư mục chứa file tĩnh/upload
    location ~* ^/(images|media|tmp|logs|cache)/.*\.(php|pl|py|jsp|asp|sh|cgi)$ {
        return 403;
    }
    
    # Bảo vệ thư mục /administrator bằng giới hạn IP hoặc Basic Auth (Tùy chọn trên Production)
    # location ^~ /administrator/ {
    #     allow 115.78.x.x; # IP văn phòng
    #     deny all;
    # }
    
    
  • Đối với Apache (Thêm vào file .htaccess ở thư mục gốc):

    Apache

    # Chặn truy cập trực tiếp vào các file cấu hình XML, INI, LOG, SQL
    <FilesMatch "\.(xml|ini|log|sql|bak|dist|md)$">
        Require all denied
    </FilesMatch>
    
    # Chặn chạy script PHP trong thư mục /images và /tmp
    RewriteRule ^(images|tmp|cache|logs)/.*\.(php|phps|phtml)$ - [F,L]
    
    

4. Theo dõi hành vi quản trị với User Actions Log

Khi hệ thống có nhiều nhân sự cùng vận hành, bạn cần một hộp đen (Audit Trail) ghi lại chính xác: Ai đã đăng nhập lúc mấy giờ? Ai đã xóa bài viết số 15? Ai vừa nâng quyền cho một tài khoản lạ?

  1. Truy cập Users →\rightarrow User Actions Log.

  2. Bấm vào nút Options (góc trên bên phải):

    • Events To Log: Chọn tất cả các thành phần (Articles, Categories, Users, Plugins, Global Configuration, Media...).

    • Log IP Address: Chọn Yes để lưu lại địa chỉ IP của người thực hiện thao tác vào bảng #__action_logs.

    • Hệ thống hỗ trợ xuất toàn bộ lịch sử thao tác ra file CSV để phục vụ kiểm toán bảo mật (Security Audit).

5. Giải phẫu Code PHP: Chống tấn công CSRF và XSS trong Joomla 5 (Góc nhìn Backend)

Khi bạn tự lập trình Form gửi dữ liệu (POST) hoặc viết API nội bộ cho Module/Component, kẻ tấn công có thể dùng kỹ thuật CSRF (Cross-Site Request Forgery) — lừa Admin đang đăng nhập bấm vào một đường link độc hại để âm thầm thực thi lệnh xóa dữ liệu.

Để bảo vệ ứng dụng ở tầng code PHP, bạn bắt buộc phải tuân thủ 3 nguyên tắc: CSRF Token, Input Filtering và Output Escaping:

PHP

use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Language\Text;

// =========================================================================
// 1. TẦNG GIAO DIỆN (VIEW / LAYOUT): Chèn CSRF Token vào mọi thẻ <form>
// =========================================================================
// Hàm này tự động sinh ra thẻ: <input type="hidden" name="a1b2c3d4e5..." value="1">
// Chuỗi tên name là mã băm gắn chặt với Session hiện tại của User!
echo HTMLHelper::_('form.token');


// =========================================================================
// 2. TẦNG XỬ LÝ (CONTROLLER): Kiểm tra CSRF Token trước khi xử lý DB
// =========================================================================
public function saveItem(): void
{
    // Kiểm tra token gửi lên qua POST (Nếu truyền qua GET hoặc Header thì truyền 'get' vào tham số)
    // Nếu Token sai hoặc hết hạn, Joomla tự động hủy tiến trình và báo lỗi 403 Invalid Token!
    Session::checkToken() or die(Text::_('JINVALID_TOKEN'));

    $app   = Factory::getApplication();
    $input = $app->getInput();

    // =====================================================================
    // 3. LỌC DỮ LIỆU ĐẦU VÀO (INPUT FILTERING): Tuyệt đối không dùng $_POST trực tiếp
    // =====================================================================
    $itemId   = $input->getInt('id', 0);                  // Ép kiểu số nguyên an toàn
    $cmdTask  = $input->getCmd('task', '');               // Chỉ giữ lại [A-Za-z0-9_.-]
    $title    = $input->getString('title', '');           // Lọc bỏ các thẻ HTML độc hại
    $safeHtml = $input->get('content', '', 'SAFEHTML');   // Giữ lại HTML an toàn, cắt bỏ <script>, onload=...

    // Nếu gọi qua AJAX (Header X-CSRF-Token), ở phía JS bạn lấy token bằng:
    // Joomla.getOptions('csrf.token') và kiểm tra bằng: Session::checkToken('request')
}

6. Tổng kết bài học

  • Triển khai thành công xác thực không mật khẩu (Passkeys / WebAuthn) và ép buộc sử dụng MFA (TOTP) cho đội ngũ quản trị viên.

  • Làm chủ plugin System - HTTP Headers để bật X-Frame-Options, HSTS và Content-Security-Policy (CSP).

  • Gia cố tầng máy chủ Nginx / .htaccess để chặn thực thi file .php trái phép trong thư mục upload, bật User Actions Log và nắm vững bộ công cụ HTMLHelper::_('form.token') + Session::checkToken() chống CSRF trong PHP.

Trong Bài học 19, chúng ta sẽ khám phá hệ thống tự động hóa tác vụ chạy ngầm hiện đại của Joomla 5: Quản lý tác vụ chạy ngầm tự động với Task Scheduler (Cronjobs nội bộ trên Joomla).


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí