- 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
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.netTruyề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ý DNSSECLuồ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 đó
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
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*.pytrong mã nguồncloud-initCá 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
guestfishhoặ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ớiVớ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-credentialskhông thể truyền khóa host tạm thời, mà chỉ có credential cho~/.ssh/authorized_keysnhưssh.authorized_keys.rootThay 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 credentialsystemd.extra-unit.*, rồi kích hoạt bằng tùy chọn dòng lệnh kernelsystemd.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ờiLà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
systemdHOOKtrong/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 initrdKhi 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/hooksNhưng việc bật
systemd-networkingvàsystemd-resolvedtrong 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 filesystemDĩ 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àyMụ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