Dev.to Security 🔐 Cybersecurity 👁 0 📖 8 min read

Hardening OpenSSH Server in 2026: Kill Password Auth, Use Certificates, and Verify Your Config Like an Attacker

Tuần này trên Hacker News có hai bài về SSH cùng lúc lên top: một advisory của Alpine Linux về lỗi bypass password authentication trên OpenSSH-server, và một bài khác cũng nói về SSH được vote hơn 1000 điểm. Đọc comment

Tuần này trên Hacker News có hai bài về SSH cùng lúc lên top: một advisory của Alpine Linux về lỗi bypass password authentication trên OpenSSH-server, và một bài khác cũng nói về SSH được vote hơn 1000 điểm. Đọc comment thì thấy nhiều người hỏi cùng một câu: "Server của tôi có bị ảnh hưởng không?". Câu trả lời thật ra đơn giản: nếu server của bạn không cho phép đăng nhập bằng password ngay từ đầu, thì cả một nhóm lỗi kiểu này không đụng tới bạn được.

Mình làm vận hành cho vài chục VPS và cluster nhỏ đã nhiều năm. Bài này là checklist mình thực sự dùng để harden sshd, kèm cách kiểm tra config như một attacker nhìn vào, chứ không chỉ sửa file rồi hy vọng là nó chạy đúng.

1. Vì sao password authentication là bề mặt tấn công thừa

Mỗi phương thức xác thực bạn bật trong sshd là thêm một đoạn code có thể có bug. Password auth đi qua PAM, qua keyboard-interactive, qua nhiều nhánh xử lý khác nhau tùy distro patch. Lỗi ở advisory của Alpine là ví dụ điển hình: lỗ hổng nằm ở chỗ kết hợp giữa bản build/patch của distro với cơ chế xác thực, chứ không hẳn ở lõi OpenSSH upstream.

Trong khi đó, public key authentication có logic đơn giản hơn nhiều: server kiểm tra chữ ký bằng public key đã biết trước. Không có gì để brute-force, không có gì để đoán.

flowchart LR
    A[Client kết nối] --> B{Phương thức auth}
    B -->|password| C[PAM / keyboard-interactive]
    C --> D[Brute-force, credential stuffing, bug của distro patch]
    B -->|publickey| E[Xác minh chữ ký]
    E --> F[Không có secret để đoán]
    B -->|certificate| G[Xác minh chữ ký CA + thời hạn]
    G --> F

Nguyên tắc của mình: chỉ bật đúng một phương thức, và đó là publickey (hoặc certificate).

2. Config sshd tối thiểu nhưng chặt

Từ OpenSSH 8.2 trở đi, hầu hết distro (Ubuntu 22.04+, Debian 12, Alpine, Rocky 9) đều hỗ trợ thư mục /etc/ssh/sshd_config.d/. Đừng sửa file gốc, hãy tạo một file drop-in để dễ quản lý bằng Ansible và dễ diff khi upgrade package.

# /etc/ssh/sshd_config.d/10-hardening.conf

# Chỉ cho phép public key
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey

# Không login trực tiếp bằng root
PermitRootLogin no

# Giới hạn user được phép
AllowGroups ssh-users

# Giảm thời gian và số lần thử
LoginGraceTime 20
MaxAuthTries 3
MaxStartups 10:30:60

# Tắt những thứ không dùng
X11Forwarding no
AllowAgentForwarding no
PermitTunnel no

# Chỉ dùng thuật toán hiện đại
HostKeyAlgorithms ssh-ed25519,[email protected]
KexAlgorithms [email protected],curve25519-sha256
Ciphers [email protected],[email protected]

Vài điểm hay bị bỏ qua:

  • AuthenticationMethods publickey là dòng quan trọng nhất. Ngay cả khi một file config khác (do cloud-init hay package nào đó tạo ra) bật lại PasswordAuthentication yes, dòng này vẫn bắt buộc phải qua publickey.
  • Thứ tự file drop-in có ý nghĩa: với sshd, giá trị đầu tiên được đọc sẽ thắng, không phải giá trị cuối. Ubuntu cloud image hay có file 50-cloud-init.conf bật password. Đặt tên file của bạn là 10-... để nó được đọc trước.
  • OpenSSH 9.8+ có PerSourcePenalties bật mặc định, tự động phạt các IP kết nối hỏng liên tục. Nếu distro của bạn đã có bản này thì không cần fail2ban cho SSH nữa.

Trước khi reload, luôn test config và giữ một session SSH đang mở:

sudo sshd -t && sudo systemctl reload ssh   # Debian/Ubuntu
sudo sshd -t && sudo rc-service sshd reload  # Alpine (OpenRC)

3. Kiểm tra config như attacker: đừng tin file, hãy tin sshd -T

Đây là phần mình thấy nhiều team bỏ qua nhất. Bạn sửa file, nhưng giá trị thực tế sshd đang dùng có thể khác do Match block, do include, do default của distro. Lệnh sshd -T in ra config đã được resolve hoàn chỉnh:

sudo sshd -T | grep -Ei 'passwordauth|kbdinteractive|authenticationmethods|permitrootlogin'

# Kiểm tra cho một user/IP cụ thể (để test Match block)
sudo sshd -T -C user=deploy,host=example.com,addr=203.0.113.10 | grep -i password

Nhưng kiểm tra từ phía server vẫn chưa đủ. Mình viết một script nhỏ bằng Python chạy từ máy CI, hỏi thẳng server xem nó thực sự quảng bá những phương thức auth nào. Đây chính là thứ attacker nhìn thấy:

#!/usr/bin/env python3
# check_ssh_auth.py - yêu cầu: pip install paramiko==3.5.0
import sys
import paramiko

def allowed_methods(host: str, port: int = 22) -> list[str]:
    sock = paramiko.Transport((host, port))
    try:
        sock.start_client(timeout=10)
        try:
            # Gửi auth 'none' để server trả về danh sách method được phép
            sock.auth_none('probe-user')
        except paramiko.BadAuthenticationType as e:
            return list(e.allowed_types)
        return ['none']  # Rất nguy hiểm nếu tới được đây
    finally:
        sock.close()

if __name__ == '__main__':
    failed = False
    for host in sys.argv[1:]:
        methods = allowed_methods(host)
        bad = [m for m in methods if m != 'publickey']
        status = 'FAIL' if bad else 'OK'
        print(f'{status:4} {host:30} {methods}')
        failed |= bool(bad)
    sys.exit(1 if failed else 0)

Chạy python3 check_ssh_auth.py 10.0.0.11 10.0.0.12 bastion.internal trong pipeline hằng đêm. Nếu ai đó vô tình bật lại password (rất hay xảy ra khi rebuild image), CI sẽ đỏ ngay. Bổ sung thêm ssh-audit (bản 3.3.0) để kiểm tra thuật toán yếu:

pipx install ssh-audit
ssh-audit -l warn bastion.internal

4. Nâng cấp lên SSH certificate thay vì quản lý authorized_keys

Khi team lên tới 10+ người và 30+ server, việc copy public key vào authorized_keys trở thành ác mộng: người nghỉ việc vẫn còn key, không ai biết key nào của ai. Giải pháp là SSH certificate, tính năng có sẵn trong OpenSSH từ lâu nhưng ít người dùng.

sequenceDiagram
    participant Dev
    participant CA as SSH CA (offline/Vault)
    participant Server
    Dev->>CA: Gửi public key + xác thực SSO
    CA-->>Dev: Certificate ký, hạn 8 giờ
    Dev->>Server: ssh với certificate
    Server->>Server: Kiểm tra chữ ký CA, principal, thời hạn
    Server-->>Dev: Cho phép đăng nhập

Các bước cơ bản:

# 1. Tạo CA (giữ private key thật kỹ, tốt nhất là offline hoặc trong HSM/Vault)
ssh-keygen -t ed25519 -f ssh_user_ca -C 'user-ca-2026'

# 2. Ký key cho dev, hạn 8 giờ, principal là 'deploy'
ssh-keygen -s ssh_user_ca -I 'andy@laptop' -n deploy -V +8h ~/.ssh/id_ed25519.pub
# -> sinh ra ~/.ssh/id_ed25519-cert.pub

# 3. Trên server, tin CA này
echo 'TrustedUserCAKeys /etc/ssh/ssh_user_ca.pub' | sudo tee /etc/ssh/sshd_config.d/20-ca.conf

Lợi ích thực tế: certificate tự hết hạn sau 8 giờ, nên khi có người nghỉ việc bạn chỉ cần ngừng ký cho họ. Không cần đi dọn authorized_keys trên từng máy. Nếu không muốn tự vận hành CA, HashiCorp Vault có SSH secrets engine làm đúng việc này.

Kết luận

Mỗi lần có advisory về OpenSSH, thay vì hoảng loạn đi kiểm tra version, hãy tự hỏi: "Phương thức xác thực bị lỗi đó có đang bật trên server của mình không?". Nếu bạn đã làm theo checklist dưới đây, câu trả lời gần như luôn là không:

  1. Tắt hoàn toàn password và keyboard-interactive, khóa bằng AuthenticationMethods publickey.
  2. Dùng drop-in sshd_config.d/10-*.conf và nhớ quy tắc "giá trị đầu tiên thắng".
  3. Luôn chạy sshd -t trước khi reload, và giữ một session đang mở để tránh tự khóa mình ngoài server.
  4. Kiểm tra từ bên ngoài: dùng sshd -T, script probe auth_none và ssh-audit trong CI hằng đêm.
  5. Team lớn thì chuyển sang SSH certificate với thời hạn ngắn, bỏ việc quản lý authorized_keys thủ công.
  6. Cập nhật OpenSSH lên 9.8+ để có PerSourcePenalties, và theo dõi security advisory của distro bạn dùng, vì nhiều lỗi nằm ở patch của distro chứ không phải upstream.

Giảm bề mặt tấn công luôn hiệu quả hơn chạy theo từng CVE. Hãy dành 30 phút tuần này để audit lại SSH trên các server của bạn.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.