1 điểm bởi GN⁺ 5 giờ trước | 1 bình luận | Chia sẻ qua WhatsApp
  • ssh-init-vm ngăn tấn công trung gian trong lần SSH đầu tiên vào VM mới bằng cách dùng cloud-init để chèn khóa riêng host SSH tạm thời, và chỉ tin cậy khóa này trong lúc tạo hoặc lấy khóa host dài hạn
  • Hoạt động cả trên VPS hoặc cloud không có tính năng bảo vệ kết nối chuyên dụng như Hetzner Cloud; thứ cần thiết chỉ là cloud-init được hỗ trợ rộng rãi
  • Với Trust On First Use thông thường, nếu nhập yes vào câu hỏi của ssh “The authenticity of host [...] can't be established”, kẻ tấn công có thể proxy lưu lượng hoặc cung cấp một máy trông giống VM của người dùng
  • Nếu chèn trực tiếp khóa riêng host SSH dài hạn vào cloud-init userdata thì có thể giúp xác thực lần kết nối đầu tiên, nhưng dữ liệu khóa nhạy cảm có thể bị lộ qua dịch vụ metadata, SSRF, hệ thống của nhà cung cấp cloud hoặc máy trạm quản trị
  • ssh-init-vm đặt khóa tạm trong thư mục tạm thời và không đưa nó vào ~/.ssh/known_hosts, không ghi nguyên xi đầu ra của VM, mà dựa vào xoay vòng khóa host của OpenSSH để ghi lại khóa dài hạn

Vấn đề lộ cloud-init userdata

  • Nếu chèn khóa riêng host SSH dài hạn bằng cloud-init thì có thể đưa khóa công khai vào ~/.ssh/known_hosts để xác thực lần kết nối đầu tiên, nhưng khóa riêng có thể rò rỉ qua nhiều đường
  • Một tiến trình tùy ý bên trong VM thường có thể lấy userdata từ dịch vụ metadata có thể đọc được; trên VM của Hetzner, nội dung cloud-init có thể xuất hiện tại http://169.254.169.254/hetzner/v1/userdata
  • Kẻ tấn công có thể dùng SSRF để buộc tiến trình làm lộ metadata, và kiểu chặn này không phải lúc nào cũng được áp dụng kể cả trong môi trường có cơ chế bảo vệ chuyên dụng
  • Userdata cũng có thể bị lộ trên các hệ thống khác của nhà cung cấp cloud; Hetzner ghi rõ trong tài liệu API tạo server rằng không nên dùng nó để lưu “passwords or other sensitive information”
  • Máy trạm quản trị cũng có thể là nơi cloud-init userdata được lưu lại hoặc đi qua, nên cách đưa khóa riêng dài hạn vào đó tạo ra rủi ro lộ khóa trong suốt thời gian khóa còn hiệu lực

Phân tích bảo mật và mô hình đe dọa

  • Giả định là giao thức và triển khai OpenSSH là đáng tin cậy, và không phụ thuộc vào khả năng phát hiện tấn công của quản trị viên
  • Bảo vệ trước kẻ tấn công trên mạng

    • Đối tượng được bảo vệ là tính toàn vẹn của máy trạm quản trị và VM
    • Kẻ tấn công là bên trung gian kiểm soát hoàn toàn mạng, và có thể biết cloud-init userdata vào một thời điểm nào đó sau khi script kết thúc thành công hoặc thất bại
    • Việc bảo vệ có hiệu lực vì kẻ tấn công không biết dữ liệu khóa vào lúc nó còn giá trị sử dụng
    • Script lưu khóa host SSH tạm thời trong thư mục tạm để tránh việc vô tình sử dụng, và không đưa khóa host SSH tạm thời vào ~/.ssh/known_hosts
  • Khi máy trạm quản trị bị xâm nhập

    • Phạm vi được bảo vệ chỉ giới hạn ở VM và khóa riêng host SSH dài hạn của VM
    • Giả định kẻ tấn công kiểm soát hoàn toàn mạng và máy trạm quản trị, nhưng không truy cập vào VM thực tế
    • Khóa riêng host SSH dài hạn chưa từng nằm trên máy trạm quản trị, và vì kẻ tấn công không truy cập VM thực tế nên không lấy được khóa host dài hạn của VM
    • Nếu kẻ tấn công truy cập được VM thực tế thì rất có thể sẽ biết khóa host SSH, ví dụ bằng ssh root@<VM> cat /etc/ssh/ssh_host_*
  • Khi VM hoặc nhà cung cấp bị xâm nhập

    • Phạm vi được bảo vệ chỉ giới hạn ở tính toàn vẹn của máy trạm quản trị
    • Kẻ tấn công kiểm soát hoàn toàn mạng và cũng có thể kiểm soát hoàn toàn VM hoặc nhà cung cấp
    • Ngay cả trong trường hợp này, tính toàn vẹn của máy trạm quản trị vẫn được bảo vệ nhờ giả định OpenSSH là an toàn
    • Như một lớp phòng thủ bổ sung, script không ghi nguyên đầu ra của VM vào ~/.ssh/known_hosts, mà dựa vào xoay vòng khóa của OpenSSH để thêm khóa host SSH dài hạn
    • Cách này ngăn host đã bị xâm nhập nhồi dữ liệu độc hại vào parser known_hosts, và đảm bảo chỉ những khóa mà VM thực sự kiểm soát mới được ghi vào ~/.ssh/known_hosts
    • Các tùy chọn OpenSSH như HashKnownHosts và các tùy chọn liên quan trong tương lai cũng có thể được xử lý đúng cách

Điều kiện để một cuộc tấn công trung gian thực sự thành công

  • Việc tấn công trung gian có thành công hay không phụ thuộc vào việc người dùng có thực sự nhận ra rằng ngay từ đầu mọi kết nối đều đang đi tới sai máy hay không, có từ chối nhập mật khẩu hay không, và có bật chuyển tiếp agent hoặc X11 của ssh hay không
  • Theo các điều kiện đơn giản hóa dựa trên ssh-mitm, khả năng thành công sẽ cao nếu kẻ tấn công có thể đánh lừa người dùng bằng cách cho quyền truy cập vào một máy do mình kiểm soát thay vì host đích thực sự
  • Cuộc tấn công cũng thành công nếu kẻ tấn công lừa được người dùng cung cấp thông tin cho phép đăng nhập vào host thật
    • Nếu người dùng đăng nhập vào máy của kẻ tấn công bằng mật khẩu, kẻ tấn công có thể thành công
    • Nếu người dùng đăng nhập bằng bất kỳ phương thức xác thực nào rồi nhập mật khẩu ở dấu nhắc, kẻ tấn công có thể thành công
    • Nếu người dùng đăng nhập bằng bất kỳ phương thức xác thực nào và chuyển tiếp kết nối ssh-agent, kẻ tấn công có thể thành công
  • Nếu không có các điều kiện trên, kẻ tấn công sẽ cần truy cập host thật để lừa người dùng, nhưng có khả năng sẽ thất bại vì không thể đăng nhập vào host thật chỉ bằng dữ liệu người dùng nhập vào
  • Nếu người dùng chuyển tiếp kết nối X11 thì kẻ tấn công cũng có thể thành công trong việc tấn công máy trạm quản trị bất kể phương thức xác thực

1 bình luận

 
Ý kiến trên Lobste.rs
  • Khá hay vì có thể tự động hóa. Với cách thủ công, tôi vẫn xác minh dấu vân tay SSH của máy chủ qua một kênh riêng từ console của nhà cung cấp cloud
    Tôi không quản lý quá nhiều instance cloud, nên có vài bước thủ công trong quá trình provisioning cũng không sao

    • Cách đó cũng hiệu quả. Chỉ là nhà cung cấp tôi dùng không nối sẵn phần đó, và lúc đó tôi cũng đang viết script tự động hóa cấu hình
  • Nếu bạn đã tự động hóa vùng DNS, thì có thể dùng một cách khác: tạo một token dùng một lần với phạm vi cực hẹp, ví dụ chỉ cho phép tạo đúng một record tại my-server-hostname.example.net
    Truyền token đó vào máy chủ bằng cloud-init, rồi để máy chủ đăng khóa SSH công khai của nó lên DNS dưới dạng bản ghi SSHFP. Sau đó có thể cấu hình để SSH client tự động xác minh bản ghi SSHFP, và vùng DNS phải được ký DNSSEC
    Luồng này cho phép máy chủ giữ khóa host SSH riêng tư mà vẫn tránh phải xoay khóa. Hầu hết nhà cung cấp DNS không hỗ trợ token truy cập dùng một lần chi tiết đến mức đó, nhưng bạn có thể đặt một dịch vụ web nội bộ đơn giản để xác minh token rồi thay mặt nó gọi API bằng một token lâu dài nhưng không giới hạn phạm vi. Máy chủ SSH sẽ không thể truy cập token lâu dài đó

    • Cách này khá sáng tạo, và với token dùng một lần cho dịch vụ tùy chỉnh thì có vẻ đủ linh hoạt để hoạt động tốt
      Tuy vậy, cá nhân tôi có lẽ sẽ thích tạo chứng chỉ SSH hơn là ghi vào một domain DNSSEC, và từ điểm đó trở đi thì vấn đề là giải pháp nào hợp với từng môi trường hơn
      Tôi cũng tò mò không biết có phần mềm hay nhà cung cấp nào cho phép tạo token linh hoạt như vậy không, hay là vẫn cần tự phát triển ở mức độ nào đó
  • Khá gọn gàng. Nhưng có vẻ tháng và ngày trong ngày ghi trên bài đã bị đảo ngược

    • Làm việc với OpenSSH lúc nào cũng vui, và tôi đã sửa lại cách ghi tháng/ngày
  • Trước đây tôi từng làm một dịch vụ thử nghiệm để khám phá chính bài toán con gà quả trứng của SSH này
    Nó tạo bản ghi SSHFP trên DNS khi có yêu cầu, ai quan tâm có thể xem tại https://github.com/tedb/sshfp

  • Rất vui khi thấy bài viết đề cập đến dịch vụ metadata 169.254.169.254 khá nhất quán giữa các nhà cung cấp VM. Có thể thấy điều đó qua các mục cloudinit/source/DataSource*.py trong mã nguồn cloud-init
    Cá nhân tôi ngày càng thấy mệt mỏi với thiết kế và các giới hạn của cloud-init. Tôi quan tâm đến việc hợp nhất cấu hình hệ thống trên VM QEMU cục bộ, máy từ xa, container và phần cứng vật lý
    arch-boxes project cho thấy cách image cloud-init image của ArchLinux được tạo ra, và thực chất chỉ là một bộ script shell rất đơn giản. Nếu áp dụng cách này với guestfish hoặc µvm, bạn có thể dùng cùng một bộ script cho image tương thích OCI, image đĩa cho QEMU hoặc nhà cung cấp cloud, và cả provisioning máy vật lý mới
    Với thêm vài cờ QEMU, có thể tái tạo cùng cách tiếp cận mà không cần phụ thuộc vào cloud-init. Theo tôi biết thì systemd.system-credentials không thể truyền khóa host tạm thời, mà chỉ có credential cho ~/.ssh/authorized_keys như ssh.authorized_keys.root
    Thay vào đó, bạn có thể tạo một unit file chạy ở giai đoạn initrd hoặc cùng với systemd-firstboot.service. Unit file này có thể được đưa sẵn vào image hoặc tiêm tạm thời bằng credential systemd.extra-unit.*, rồi kích hoạt bằng tùy chọn dòng lệnh kernel systemd.wants=…. Trong QEMU, có thể giả lập sự tồn tại của dịch vụ metadata bằng mục -netdev user,id=metadata,net=169.254.0.0/16,dhcpstart=169.254.0.15,guestfwd=tcp:169.254.169.254:80-cmd:…. Tuy vậy, nhiều khả năng bạn sẽ phải kích hoạt interface được tạo ra, và việc đó cũng có thể nên xử lý bằng một unit file tạm thời
    Làm như vậy sẽ mang lại độ linh hoạt đáng kể với mức độ phức tạp tương đối thấp, khi bạn cần cấu hình hệ thống nhất quán trên nhiều loại “máy” khác nhau. Trên thực tế, riêng cho bài toán này thì có vẻ cách tốt nhất là công cụ tạo image tạo ra image máy với khóa host cố định, rồi cài một script xoay khóa host tùy chỉnh dưới dạng dịch vụ SystemD để chạy ở lần reboot đầu tiên hoặc khi tắt máy

    • Trên ArchLinux, nếu bật systemd HOOK trong /etc/mkinitcpio.conf, bạn có thể, và thực tế gần như nên, viết unit file SystemD cho các tác vụ cần thực hiện trong initrd
      Khi dùng thực tế thì nó chỉ phiền hơn một chút so với việc viết {/etc,/usr/lib}/initcpio/hooks
      Nhưng việc bật systemd-networkingsystemd-resolved trong initrd lại khá dễ, nên initrd có thể đảm nhận trách nhiệm khởi động hệ thống và lên lịch công việc trước khi chuyển sang root filesystem
      Dĩ nhiên, trên phần cứng vật lý như laptop thì có thể không phù hợp vì kết nối Wi‑Fi cần thứ như NetworkManager, nhưng với VM QEMU và VM được lưu trữ thì rất hợp, và nhiều tác vụ khởi động hệ thống tự nhiên nằm trong không gian này
      Mục tiêu là không phụ thuộc vào cloud-init, không bị khóa vào một nhà cung cấp cloud cụ thể, đạt được tính nhất quán giữa máy vật lý, container, VM cục bộ và VM được lưu trữ, đồng thời giảm phụ thuộc về cơ bản xuống còn SystemD
    • Với một công cụ rất nhỏ chỉ cần mở rộng đúng phần tính năng bạn muốn, có lẽ thứ như https://github.com/the-maldridge/shinit/ cũng khả thi