Vấn đề thực tế: Khi SSH Key không còn là lá chắn bất khả xâm phạm
Vào lúc 2:14 sáng một ngày thứ Ba của tháng 10 năm ngoái, tiếng chuông báo động trực ca từ PagerDuty đã đánh thức tôi. Máy tính xách tay của một lập trình viên junior bị nhiễm mã độc đánh cắp dữ liệu (infostealer) do dính bẫy lỗi chính tả (typo-squatting) từ một thư viện npm trong một dự án cá nhân cuối tuần. Chỉ trong vòng 90 giây, mã độc đã quét sạch mọi thứ trên hệ thống: cookie trình duyệt đang hoạt động, session token của AWS, và một private key Ed25519 256-bit không cài passphrase nằm chơ vơ tại ~/.ssh/id_ed25519.
Mười tám phút sau, các bot script tự động đã kết nối thành công tới ba node staging và máy chủ jump box production chính của chúng tôi (10.0.1.15). Hạ tầng của chúng tôi vốn đã áp dụng các tiêu chuẩn bảo mật cơ bản: tắt đăng nhập bằng mật khẩu, giám sát các cổng SSH tùy chỉnh và vô hiệu hóa đăng nhập bằng tài khoản root. Thế nhưng không có lấy một cảnh báo nào kích hoạt. Kết nối gửi đến mang theo chữ ký khóa hợp lệ và chuẩn xác về mặt mật mã. Đối với SSH daemon, kẻ tấn công trông chẳng khác nào một kỹ sư đáng tin cậy trong đội ngũ.
Kịch bản sự cố này vẫn lặp đi lặp lại ở nhiều tổ chức công nghệ mỗi tuần. Các đội ngũ kỹ thuật thường coi xác thực khóa công khai (public-key authentication) của SSH như một pháo đài bất khả xâm phạm. Nhưng một khi private key bị rò rỉ do mất laptop, vô tình commit nhầm lên public GitHub, hay thiết bị đầu cuối bị chiếm quyền điều khiển, pháo đài ấy sẽ biến thành cánh cửa mở toang chào đón kẻ đột nhập.
Phân tích nguyên nhân gốc rễ: Lỗ hổng xác thực một yếu tố của khóa bất đối xứng
Hầu hết các nhóm kỹ thuật đều mặc định xem SSH key là xác thực đa yếu tố. Họ tin rằng khóa đáp ứng đủ tiêu chuẩn “thứ bạn có” (file private key) và “thứ bạn biết” (passphrase bảo vệ khóa). Tuy nhiên trong môi trường production, giả định này rất thường xuyên sụp đổ:
- Khóa không mã hóa lưu trên ổ đĩa: Các kỹ sư thường bỏ qua việc thiết lập passphrase. Họ làm vậy để đỡ phải gõ mật khẩu hàng chục lần mỗi ngày khi chạy lệnh git, chạy script deploy tự động hay mở pane mới trên tmux.
- SSH agent duy trì phiên quá lâu: Ngay cả khi đặt passphrase mạnh, các tiến trình chạy nền như
ssh-agenthoặc GNOME Keyring vẫn lưu khóa đã giải mã trong RAM suốt nhiều giờ hoặc nhiều ngày. Kẻ tấn công chỉ cần giành được quyền thực thi ở mức người dùng thông thường trên laptop là đã có thể trỏ tới biến môi trườngSSH_AUTH_SOCKđang hoạt động để chiếm đoạt khóa mà không cần biết passphrase của bạn. - Không xác thực được ngữ cảnh phía client: Khi
sshdkiểm tra chữ ký mật mã, nó chỉ đối chiếu tính khớp nối toán học với fileauthorized_keyschứ không làm gì hơn. Daemon không thể phân biệt được chữ ký đó xuất phát từ một sysadmin kỳ cựu hay từ một con bot đang thực thi lệnh bên trong socket phiên bị đánh cắp.
Việc phụ thuộc vào duy nhất một file tĩnh lưu trữ trên hệ thống tệp của client tạo ra một điểm lỗi đơn lẻ (single point of failure) về mặt kiến trúc. Nếu thiết bị đầu cuối bị xâm nhập, mọi máy chủ tin cậy chiếc khóa đó cũng sẽ bị xâm nhập theo.
Đánh giá các chiến lược khắc phục: Hardware Token đối đầu TOTP PAM
Để bịt lỗ hổng bảo mật này, chúng ta cần bổ sung yếu tố xác thực thứ hai ở phía máy chủ trước khi cấp quyền mở phiên shell tương tác. Có hai phương án thực tế giải quyết được vấn đề:
Phương án A: Khóa bảo mật phần cứng (FIDO2 / U2F)
Các phiên bản OpenSSH hiện đại (từ 8.2 trở lên) hỗ trợ sẵn khóa bảo mật FIDO2 thông qua các kiểu khóa như ed25519-sk. Mỗi thao tác ký duyệt đều yêu cầu một cú chạm vật lý lên thiết bị phần cứng USB như dòng YubiKey 5.
- Ưu điểm: Kiến trúc chống tấn công giả mạo (phishing-resistant), private key nằm an toàn trong chip không thể trích xuất, và không cần phụ thuộc vào PAM module phía máy chủ.
- Nhược điểm: Chi phí trang bị phần cứng tốn kém ($45–$85 mỗi khóa cho đội ngũ hơn 50 lập trình viên), trở ngại giao nhận khi tuyển nhân sự từ xa, quy trình khôi phục tài khoản phức tạp khi làm mất khóa, và không tương thích với các hệ thống doanh nghiệp cũ chạy OpenSSH < 8.2 (chẳng hạn như CentOS 7).
Phương án B: Mật khẩu dùng một lần theo thời gian (TOTP) qua PAM
Linux dựa vào Pluggable Authentication Modules (PAM) để điều phối quá trình bắt tay xác thực. Bằng cách tích hợp module mã nguồn mở Google Authenticator PAM vào chuỗi xử lý SSH, máy chủ sẽ yêu cầu mã TOTP 6 chữ số động và có giới hạn thời gian. Người dùng có thể lấy mã này qua các ứng dụng xác thực trên điện thoại như Aegis, 1Password, Bitwarden hoặc Google Authenticator.
- Ưu điểm: Không tốn chi phí mua sắm phần cứng, tương thích rộng rãi trên mọi bản phân phối Linux phổ biến, và có thể triển khai cho đội ngũ 100 người chỉ trong chưa đầy một giờ bằng các công cụ quản lý cấu hình tiêu chuẩn.
- Nhược điểm: Dễ bị tấn công dạng man-in-the-middle reverse proxy theo thời gian thực nếu sử dụng như một yếu tố xác thực duy nhất (tuy nhiên rủi ro này được loại bỏ hoàn toàn khi kết hợp cùng xác thực bằng SSH key).
Giải pháp tối ưu: Bắt buộc xác thực đa yếu tố (SSH Key + TOTP)
Đối với các máy ảo cloud tiêu chuẩn và máy chủ vật lý, việc kết hợp SSH key với Google Authenticator PAM mang lại mô hình phòng thủ theo chiều sâu (defense-in-depth) hiệu quả nhất. Kẻ xâm nhập dù có nắm trong tay private key bị đánh cắp cũng sẽ đụng phải bức tường kiên cố vì không có trình tạo mã TOTP vật lý trên điện thoại của bạn.
Thông tin đăng nhập dự phòng vững chắc vẫn rất cần thiết cho các phương thức truy cập console ngoài băng thông (out-of-band) như AWS EC2 Serial Console hay Hetzner KVM. Để tạo thông tin đăng nhập root khẩn cấp khó đoán, các công cụ tạo mật khẩu offline trên trình duyệt như toolcraft.app/vi/tools/security/password-generator sẽ giúp mật khẩu không bao giờ bị truyền qua đường truyền mạng. Còn đối với truy cập SSH hàng ngày, chúng ta sẽ bắt buộc phải có cả khóa mật mã lẫn mã TOTP 6 chữ số xoay vòng liên tục.
Bước 1: Cài đặt PAM Module
Cài đặt gói module Google Authenticator PAM thông qua trình quản lý gói của hệ thống. Trên Ubuntu (22.04 / 24.04 LTS) và Debian:
sudo apt update
sudo apt install -y libpam-google-authenticator
Đối với RHEL 9, Rocky Linux hoặc AlmaLinux, hãy tải binary từ kho EPEL:
sudo dnf install -y epel-release
sudo dnf install -y google-authenticator
Bước 2: Tạo Secret Key và mã khôi phục cho người dùng
Chuyển sang tài khoản người dùng thông thường (non-root) cần truy cập SSH. Chạy trình hướng dẫn thiết lập trực tiếp trên terminal của bạn:
google-authenticator
Lần lượt trả lời các câu hỏi tương tác theo cấu hình tăng cường bảo mật dưới đây:
- Do you want authentication tokens to be time-based (y/n)? Nhập
yđể áp dụng token theo thời gian chuẩn RFC 6238. - Công cụ sẽ hiển thị mã QR dạng ASCII ngay trên terminal cùng với một secret key dạng base32, một mã xác minh ban đầu và 5 mã dự phòng khẩn cấp gồm 8 chữ số. Quét mã QR vào ứng dụng xác thực trên điện thoại. Hãy lưu ngay 5 mã khôi phục này vào trình quản lý mật khẩu của công ty ở chế độ offline.
- Do you want me to update your “~/.google_authenticator” file (y/n)? Nhập
yđể ghi thông tin cấu hình vào ổ đĩa với quyền truy cập0400. - Do you want to disallow multiple uses of the same authentication token (y/n)? Nhập
y. Tùy chọn này giúp ngăn chặn việc tái sử dụng cùng một token trong cửa sổ thời gian 30 giây (chống tấn công replay). - By default, tokens are good for 30 seconds… Increase window (y/n)? Nhập
n. Giữ giới hạn sai lệch thời gian ở mức nghiêm ngặt chỉ trong 3 mã hợp lệ (±1 chu kỳ, tổng cộng 90 giây) để tránh nới rộng khoảng thời gian dễ bị tấn công phát lại. - Do you want to enable rate-limiting (y/n)? Nhập
y. Tùy chọn này giới hạn tối đa 3 lần đăng nhập thất bại mỗi 30 giây, giúp chặn đứng các đợt tấn công dò mật khẩu (brute-force) tự động ngay từ đầu.
Bước 3: Cấu hình PAM cho SSH
Chỉ định Linux PAM yêu cầu mã TOTP khi có đăng nhập tương tác. Mở file /etc/pam.d/sshd bằng trình soạn thảo:
sudo nano /etc/pam.d/sshd
Tìm chỉ thị xác thực mật khẩu tiêu chuẩn. Trên Debian/Ubuntu, tìm dòng @include common-auth. Trên các bản phân phối RHEL, tìm dòng auth substack password-auth. Hãy vô hiệu hóa dòng đó bằng cách thêm dấu # ở đầu để người dùng bỏ qua bước nhập mật khẩu Unix truyền thống. Sau đó, thêm chỉ thị authenticator vào cuối file:
# @include common-auth
auth required pam_google_authenticator.so nullok
Tham số nullok đóng vai trò như chiếc lưới bảo hiểm an toàn tạm thời trong quá trình triển khai cho toàn công ty. Những người dùng chưa kịp chạy lệnh google-authenticator vẫn có thể đăng nhập bình thường chỉ bằng SSH key. Khi tất cả các tài khoản trong nhóm đã hoàn tất Bước 2, hãy xóa bỏ chữ nullok để bắt buộc áp dụng MFA trên mọi tài khoản của máy chủ.
Bước 4: Cập nhật cấu hình OpenSSH Daemon
Tiếp theo, cấu hình OpenSSH để thực thi tuần tự cả hai giai đoạn xác thực. Mở file cấu hình daemon tại /etc/ssh/sshd_config (hoặc tạo file cấu hình riêng tại /etc/ssh/sshd_config.d/mfa.conf):
sudo nano /etc/ssh/sshd_config
Kiểm tra và áp dụng các thông số sau:
# Kích hoạt PAM và cơ chế tương tác xác thực (interactive challenge)
UsePAM yes
KbdInteractiveAuthentication yes
# Tiếp tục vô hiệu hóa xác thực mật khẩu dạng plaintext
PasswordAuthentication no
# Bắt buộc chuỗi xác thực đa yếu tố: SSH Key TRƯỚC, sau đó đến lời nhắc TOTP
AuthenticationMethods publickey,keyboard-interactive
Hãy đặc biệt lưu ý đến dòng AuthenticationMethods publickey,keyboard-interactive. Cú pháp phân tách bằng dấu phẩy này mang tính then chốt: nó đòi hỏi cả hai bước kiểm tra theo thứ tự nghiêm ngặt. OpenSSH phải xác thực thành công public key của client trước. Chỉ sau khi bước kiểm tra khóa vượt qua, máy chủ mới kích hoạt lời nhắc nhập mã TOTP keyboard-interactive từ PAM.
Bước 5: Kiểm tra cấu hình an toàn, tránh bị khóa khỏi máy chủ
Tuyệt đối không đóng phiên terminal hiện tại của bạn. Trước hết, hãy chạy lệnh kiểm tra cú pháp cấu hình OpenSSH để phát hiện các lỗi chính tả nếu có:
sudo sshd -t
Nếu lệnh trên trả về mã thoát 0 mà không in ra bất kỳ thông báo lỗi nào, bạn có thể yên tâm tải lại hoặc khởi động lại dịch vụ:
sudo systemctl restart sshd
Hãy giữ nguyên cửa sổ terminal đang mở làm phao cứu sinh khẩn cấp. Mở một cửa sổ terminal hoàn toàn mới trên máy local của bạn và thực hiện kết nối thử nghiệm với chế độ chi tiết (verbose):
ssh -v username@your-server-ip
Quan sát đầu ra gỡ lỗi chi tiết. Bạn sẽ thấy quá trình trao đổi public key hoàn tất, tiếp theo ngay sau đó là phản hồi xác thực một phần từ OpenSSH:
Authenticated with partial success.
Verification code:
Nhập mã TOTP 6 chữ số hiển thị trên điện thoại của bạn. Bạn sẽ vào thẳng môi trường bash shell. Nếu chẳng may ngày mai có kẻ tấn công trích xuất được SSH private key của bạn, kết nối tự động của chúng cũng sẽ bị khựng lại ngay tại dấu nhắc xác minh—hoàn toàn bế tắc nếu không có điện thoại của bạn trong tay.

