SSH như một kỹ thuật thay thế cho `sudo`
(whynothugo.nl)- Vì
sudovàdoasphụ thuộc vào binary setuid và leo thang đặc quyền, đây là một thử nghiệm giao chosshdcục bộ được bind vào Unix domain socket đảm nhiệm việc thực thi lệnh root - Mục tiêu là chỉ cho phép người dùng được cấp quyền chạy lệnh root đồng thời không để lại khả năng leo thang đặc quyền cho toàn bộ phiên người dùng
- Cách triển khai gồm một tệp khóa SSH chỉ dành cho root,
/run/sshd/sshd.sockvới quyền truy cập bị giới hạn, và một instancesshdriêng với các tùy chọnAuthorizedKeysFile,PermitRootLogin=yes - Do
sshkhông có tùy chọn truyền trực tiếp socket sẵn có, ban đầu tác giả dùngProxyCommandvàsocat, sau đó chuyển sang ProxyUseFdpass cùng một script Python ngắn để chuyển file descriptor của socket - Cách này hoạt động được và phần xử lý bảo mật chủ yếu dựa vào OpenSSH, nhưng để dùng hằng ngày thì phù hợp hơn nếu biến
passfd.pythành một file thực thi nhỏ và bọc toàn bộ lệnhsshbằng một wrapper
Những giới hạn mang tính cấu trúc của sudo và doas
sudovàdoasphụ thuộc vào binarysetuidvà leo thang đặc quyền để chạy lệnh với quyền root- Thiết kế này có một số hạn chế
- Toàn bộ phiên người dùng phải tiếp tục giữ khả năng thực hiện leo thang đặc quyền
- Không hoạt động khi toàn bộ phiên người dùng chạy bên trong một user namespace bị giới hạn
- Binary
setuidtạo ra các ràng buộc đối với cấu hình bảo mật của toàn hệ thống
s6-sudodlà một phương án thay thế, tách chương trình thành máy chủ có đặc quyền và máy khách không có đặc quyền
Mục tiêu của thử nghiệm
- Dùng
sshcục bộ để đảm nhận vai trò giốngsudo, nhưng instancesshdđó không bị lộ ra mạng - Có hai điều kiện cốt lõi
- Chỉ người dùng được cấp quyền mới có thể chạy lệnh với quyền root
- Không dùng leo thang đặc quyền
Cấu hình sshd cho đăng nhập root cục bộ
- Tạo một khóa SSH chuyên dụng chỉ dùng cho xác thực root, và lưu vào
/root/.ssh/local_keysthay vìauthorized_keysthông thường - Bind một instance
sshdriêng vào Unix domain socket, đồng thời giới hạn quyền của/run/sshd/để người dùng không được cấp quyền không thể truy cập socket - Ví dụ chạy là dùng
s6-ipcserverđể tạo/run/sshd/sshd.sockvà truyền chosshdcác tùy chọn sauAuthorizedKeysFile=/root/.ssh/local_keysPermitRootLogin=yes
- Không chỉnh sửa
/etc/ssh/sshd_config- Vì không muốn cho phép đăng nhập root trên
sshdhiện có đang bind ra mạng - Trong cấu hình hiện có vẫn giữ
PermitRootLogin no
- Vì không muốn cho phép đăng nhập root trên
Khóa tài khoản root và xử lý đăng nhập bằng mật khẩu
- Tài khoản root đã bị khóa để không thể đăng nhập theo bất kỳ cách nào, bằng cách thêm
!vào trước hash mật khẩu sshddiễn giải tiền tố!này là tài khoản bị khóa và không cho phép đăng nhập root- Giá trị mật khẩu của root trong
/etc/passwdđược đổi để thay!bằng*sshdkhông diễn giải*là một giá trị khóa đặc biệt*không khớp với bất kỳ hash mật khẩu nào nên đăng nhập bằng mật khẩu trên thực tế vẫn bị vô hiệu hóa
- Ngoài ra trong
sshd_configcòn đặtPasswordAuthentication no; cấu hình tắt xác thực bằng mật khẩu trongsshdlà an toàn
Kết nối vào Unix socket bằng ssh
sshdcó cờ-iđể nhận một socket có sẵn, nhưngsshkhông có cờ tương ứng như vậy- Ban đầu tác giả dùng
socatquaProxyCommandđể kết nối tới/run/sshd/sshd.sock - Lệnh kết nối gồm các thành phần sau
ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'- Chỉ định khóa root chuyên dụng bằng
-i .ssh/root-key.pub - Kết nối tới
root@root - Chuyển vào thư mục hiện tại rồi chạy login shell
- Khóa SSH được dùng là khóa ràng buộc phần cứng, nên phải chạm vào thiết bị vật lý để chấp thuận kết nối
- Một
ssh-agentchỉ công khai khóa sau khi có chấp thuận tường minh, chẳng hạnhissh-agent, cũng có thể là một lựa chọn thay thế
Overhead của socat và ProxyUseFdpass
socatđọc toàn bộ đầu vào củasshrồi mới ghi vào socket, nên overhead kết nối về cơ bản bị lặp đôiProxyUseFdpasscho phép lệnh gửi file descriptor của socket chosshquastdout, rồisshkết nối bằng chính socket đó- Dựa trên ví dụ dùng OpenSSH ProxyUseFdpass từ năm 2016, tác giả viết script Python
passfd.py- Kết nối tới Unix domain socket
/run/sshd/sshd.sock - Truyền file descriptor bằng
sendmsgvàSCM_RIGHTS
- Kết nối tới Unix domain socket
- Sau đó lệnh kết nối chuyển sang chỉ định
passfd.pytrongProxyCommandvà thêmProxyUseFdpass=yes nc -FU /run/sshd/sshd.sockcũng có vẻ khả thi, nhưng tài liệu hướng dẫn ghi rõ-Fkhông thể dùng cùng-U
Kết luận và cách dùng thực tế
- Kỹ thuật này hoạt động được, và phần xử lý bảo mật nhạy cảm chủ yếu được giao cho OpenSSH
- OpenSSH có thành tích tốt và có thể dùng nhiều phương thức xác thực, bao gồm khóa SSH dựa trên phần cứng
- Quá trình thiết lập trên máy mới không có bước phức tạp nào, và lệnh
ipcservercó thể được chạy thông qua trình quản lý dịch vụ hệ thống passfd.pygần như chỉ là một bản hack nhanh để phục vụ thử nghiệm- Để dùng hằng ngày, phù hợp hơn là tạo một file thực thi nhỏ làm cùng vai trò, đặt trong
/usr/local/bin, đồng thời bọc toàn bộ lệnhsshbằng một wrapper nhỏ
1 bình luận
Ý kiến trên Hacker News
Lý do phản đối lớn nhất đối với cách này là độ phức tạp tăng lên. Thay vì một binary
suidduy nhất đọc tệp cấu hình rồi gọiexec(), sẽ có một binary chạy bằng root và lắng nghe UNIX socket, cùng một binary khác nói chuyện với socket đó; cả hai còn phải xử lý cả mật mã bất đối xứngNếu phản biện cốt lõi với sudo/doas là “có một binary
suidmà mọi người dùng đều truy cập được, và nếu có lỗi thì có thể bị lợi dụng để leo thang đặc quyền”, thì có thể làm như sauchgrp wheel /usr/bin/sudochmod o-rwx /usr/bin/sudoNếu đưa người dùng sudo vào nhóm
wheel, chỉ những người dùng có thể dùng sudo mới đọc được byte của tệp từ đĩa và cũng có thể thực thi nó. Về mặt kiểm soát truy cập, mức an toàn gần như tương đương với cách sshd chỉ cho người dùngwheeltruy cập UNIX socket, nhưng độ phức tạp thấp hơn nhiềuHơn nữa, cách dùng sshd không thể hạn chế quyền truy cập root theo từng lệnh cụ thể như sudo, nên ngay cả khi có lỗi vượt qua giới hạn lệnh của sudo, nó cũng không cấp nhiều quyền hơn so với cách sshd
Nếu lo trình quản lý gói làm hỏng quyền của
/usr/bin/sudo, có thể dùng cron để định kỳ sửa lại, hoặc gỡ sudo hoàn toàn rồi tự cài thủ công từ mã nguồn vào một vị trí khác. Tất nhiên khi đó bạn cũng phải tự bảo trì và nâng cấp/etc. Dù là cài đặt/nâng cấp phần mềm hay thay đổi do con người thực hiện đều được ghi lại; khi nâng cấp lên bản phát hành mới cũng có thể tạo bản vá từ các thay đổi cục bộ, áp dụng lên một cài đặt sạch rồi kiểm tra xung đột bằng 3-way merge, rất hữu íchhttps://etckeeper.branchable.com/
wheelđể chỉ những người có thể dùng sudo mới đọc và thực thi được tệp nghe khá hợp lý. Tôi thắc mắc vì sao đây không phải cấu hình mặc định ở mọi nơi/usr/bin/sudothành immutable không? Như vậy có vẻ sẽ giúp ngăn trình quản lý gói động vào nóĐây chẳng phải là điều systemd run0 đang làm sao? systemd có một công cụ mới tên
run0, nhưng thực ra không hẳn là công cụ hoàn toàn mới; nó gọisystemd-runvốn đã có từ lâu thông qua symlink tênrun0, rồi hoạt động như một thứ thay thế sudoKhác biệt cốt lõi là nó thực sự không phải
SUID. Nó yêu cầu trình quản lý dịch vụ chạy một lệnh hoặc shell với UID của người dùng đích, cấp phát một PTY mới, rồi chuyển dữ liệu qua lại giữa TTY ban đầu và PTY nàyNói cách khác, lệnh đích không kế thừa ngữ cảnh của client mà chạy trong một ngữ cảnh thực thi cô lập mới được fork từ PID 1.
$TERMđược truyền qua, nhưng đó là ngoại lệ rõ ràng, gần với danh sách cho phép hơn là danh sách chặnỞ nhiều khía cạnh, có thể xem
run0có hành vi gần vớisshhơn làsudohttps://mastodon.social/@pid_eins/112353324518585654
Có phải tôi đang bỏ sót điều gì không? Tôi không hiểu đăng nhập ssh bằng root thì an toàn hơn dùng sudo ở điểm nào. Tôi luôn được dạy là không được cho phép việc đó, nên cũng không rõ thực tế nguy hiểm đến mức nào
Ở đây có vẻ đã tính đến việc chặn người dùng từ xa, nên tôi không nói đến khía cạnh bảo mật đó
Có thể liên quan đến hạn chế số 3 của sudo, nhưng ít nhất so với số 1 thì tôi không thấy ưu điểm nào
Tôi hiểu đây là một thử nghiệm, nhưng cảm giác không phải ít dễ tổn thương hơn sudo mà là dễ tổn thương hơn. Proxy socket mở trông có vẻ dễ bị tấn công trung gian
Dù vậy tôi đã học được vài kỹ thuật có thể làm bằng các công cụ cũ, và việc bài viết cho thấy điều mới mẻ là điểm tốt
sudolà suid root và là binary có đặc quyền, nên bị phơi trực tiếp cho người dùng không đáng tin cậy. Nếu bên trong sudo có gì đó sai, toàn bộ môi trường của người dùng sẽ trở thành bề mặt tấn công và có thể bị khai thácCách dùng ssh không phơi binary
suidmà dùng tầng mạng của ssh. Vì vậy nó không kém an toàn hơn việc truy cập ssh qua mạng, điều vốn được xem là khá an toànsudo bash, ta có thể chạy từng lệnh riêng lẻ bằng sudo để tăng khả năng kiểm toánDaemon đó cũng có thể có lỗi cấu hình hoặc lỗ hổng, và nó thu gọn vài lớp ủy quyền dựa trên người dùng xuống một mức root duy nhất
Vậy mà dường như vẫn được xem là an toàn hơn. Từ góc nhìn bảo mật hợp lý thì không thể coi là an toàn hơn, chỉ là một cách khác mà thôi
Trên thực tế, đăng nhập SSH thẳng bằng root nghiêm ngặt mà nói an toàn hơn so với đăng nhập vào SSH từ xa bằng user rồi dùng
sudoNếu
user@homessh vàoroot@server, thìroot@serverchỉ bị ảnh hưởng khiuser@homebị xâm nhậpNgược lại, nếu
user@homessh vàouser@serverrồi dùng sudo để trở thànhroot@server, thì chỉ cần một trong haiuser@homehoặcuser@serverbị xâm nhập là root đã bị ảnh hưởng. Đặc biệt trênuser@serverthường có các phần mềm khác chạy, như daemon hoặc tác vụ cronKhông nên trao cho người đã lây nhiễm thành công qua con đường đó một quyền leo thang lên root miễn phí, rồi cả di chuyển ngang vốn thường xảy ra do tái sử dụng mật khẩu
Tất nhiên điều này không áp dụng nếu sudo được dùng ở chế độ chỉ cho phép các lệnh trong danh sách cho phép, và không nhận mật khẩu hay thông tin xác thực có thể truy cập hoàn toàn từ máy chủ từ xa
Nếu ssh không khởi động khi boot thì sao? Tôi nhớ trong cấu hình thông thường nó được khởi động phụ thuộc vào mạng. Khi đó cũng không thể đăng nhập ở console failsafe
Tôi không rõ thực tế thu được gì so với sudo hay su. Thay vì tránh binary setuid, bạn lại chạy một dịch vụ mạng với quyền root, dù chỉ kết nối tới socket
Nếu SSD chết thì sao? Khi đó cũng không thể đăng nhập vào console failsafe
Trong 30 năm dùng Linux, số lần ổ cứng chết nhiều hơn số lần daemon sshd không khởi động rất nhiều; xét theo tỷ lệ thì gần như là chia cho 0
Nếu daemon sshd của hệ điều hành ngẫu nhiên không khởi động, tôi sẽ coi đó là dấu hiệu đã đến lúc chuyển sang một hệ điều hành ổn định hơn
Điều đạt được so với sudo hay su là khai thác leo thang đặc quyền cục bộ trở nên khó hơn nhiều
ttyS*của cổng serial và IPMI SoL, không dùng sudo hay suNhững console này dùng chương trình như
gettyhoặc trình quản lý cửa sổ, và chúng là các chương trình không suid được khởi động bằng rootNên thiết lập mật khẩu root cho đăng nhập console
kubectlthay cho sshNếu có thể đăng nhập thì tôi cho phép setuid thành root. Nếu là máy k8s thì đó là đội hạ tầng nền tảng, còn quyền truy cập dịch vụ bên trên đi qua nhà cung cấp quyền của k8s
Từ góc nhìn đội hạ tầng nền tảng, nếu chỉ cần metrics và log thì chúng đã ở bên ngoài máy; nếu cần kích hoạt tác vụ hay workflow nào đó thì dùng pipeline
Dù vậy, nếu ai đó đăng nhập và làm việc với quyền root thì tôi vẫn muốn có nhật ký kiểm toán
Tôi không nghĩ ra máy nào của mình mà người có quyền đăng nhập lại không có toàn quyền root
Tất nhiên tôi hiểu trường hợp dịch vụ thực hiện setuid, nhưng với dịch vụ thì thường systemd thực hiện setuid để hạ quyền chứ không phải nâng quyền
systems.unit=emergency.target,init=/bin/bash,rd.break=pre-pivot, hoặc boot vào môi trường live CD. Tất cả tùy chọn cứu hộ khẩn cấp thông thường đều hoạt độngVới tình huống khẩn cấp ít nghiêm trọng hơn, tôi cũng không thấy lý do gì để instance sshd này phải gắn với mạng
Hơi tiếc là bài không bao gồm hết các nhược điểm của cách tiếp cận này. sudo có thể kiểm soát nhóm nào được chạy lệnh nào, lệnh đó được nhận tham số gì, có cho phép tạo subshell hay không, v.v.
Cách này mất nhiều kiểm soát chi tiết như vậy, và phụ thuộc vào các khóa tin cậy khó quản lý hơn so với việc chỉnh file sudoers
Nếu muốn thấy những khả năng đáng kinh ngạc mà sudo làm được, tôi thật sự khuyên đọc sách Sudo Mastery
Đây là ý tưởng tương tự run0 của Systemd: https://news.itsfoss.com/systemd-run0/
Một vấn đề của ssh là tạo tiến trình không phải một phần của protocol. Và vì là protocol từ xa nên không thể truyền tài nguyên cục bộ cho tiến trình con
Vì vậy không thể truyền mảng tham số phân tách bằng null, truyền thêm file descriptor, hay chỉ định file thực thi
Thay vào đó, nó truyền một chuỗi duy nhất cho shell được cấu hình trên server. Khi đó cần shell escaping, và cũng phải biết phía server đang chạy shell nào
Để dùng SSH như một thay thế sudo đúng nghĩa, cần có extension với chức năng gần giống
posix_spawnhttps://bugzilla.mindrot.org/show_bug.cgi?id=2283
Hoàn toàn đồng ý. Tôi cũng đang làm điều tương tự, và trước đây từng giải thích trong một bình luận trên HN
Cách của tôi hơi khác. Tôi dùng một máy chuyên dụng làm console SSH vật lý, và máy này nằm trong một LAN riêng tách biệt với các máy khác trong nhà. Nó dùng switch thường chứ không phải switch managed, dùng cáp Ethernet và không có trunk
Chỉ có thể đăng nhập bằng SSH, và tôi gắn Yubikey vào đó
PC desktop có firewall riêng, chỉ cho phép traffic SSH đến từ địa chỉ IP/MAC của console SSH này. Điều đó chỉ áp dụng trên LAN riêng mà hai máy chia sẻ; trên LAN vật lý khác, desktop vẫn có thể truy cập Internet
Daemon sshd chỉ cho đăng nhập bằng cặp khóa công khai/riêng tư và chặn đăng nhập bằng mật khẩu
Khi cần root, tôi boot “console SSH”. Vì máy gần như không có gì nên bật lên rất nhanh. Đăng nhập, nhấn mũi tên lên để gọi lại dòng
ssh root@..., rồi nhấn Enter và chạm YubikeyConsole SSH đó và bàn phím ở trên bàn, luôn trong tầm tay
iptables/nftables + sshd trên một LAN riêng, lại còn tách vật lý khỏi LAN riêng khác, an toàn hơn hay kém an toàn hơn binary sudo hoặc su thì mỗi người tự đánh giá
Nếu hỏi “vì sao”, tôi sẽ trả lời “vì có thể làm được”. Tôi thiết lập đã lâu đến mức không còn nhớ là khi nào. Có lẽ khoảng 2 năm trước tôi bắt đầu nghịch ý tưởng này, và đã dùng từ đó đến giờ. Chưa gặp vấn đề gì cả
Đây là một giải pháp thanh lịch cho vấn đề này. Không cần đối xử với người dùng như trẻ con, nhưng đồng thời nên tránh các sai sót tiềm ẩn bằng các giá trị mặc định hợp lý
Nếu cần root thì đăng nhập root ở console là được, nên tôi nghĩ thậm chí
sucũng không cần. Cách này gần nhất có thể với việc đăng nhập root trên console ttyThứ nhất, khác với sudo, mọi người dùng đều phải biết mật khẩu root
Thứ hai, nếu tất cả cứ đăng nhập bằng root thì không có cách nào audit xem thực sự ai đã đăng nhập và đã làm gì
Tôi từng thử một thứ tương tự cách đây 10 năm. Khi đó không có phần UNIX socket; tôi chạy một
sshdriêng chỉ lắng nghe trên localhost, và cũng không cần xử lýSCM_RIGHTSKhông có kết quả nào đặc biệt tốt hay xấu; chỉ là tôi mất hứng nên đã không chuyển cấu hình sang máy tiếp theo