4 điểm bởi GN⁺ 2024-06-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • sudodoas phụ thuộc vào binary setuid và leo thang đặc quyền, đây là một thử nghiệm giao cho sshd cụ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.sock với quyền truy cập bị giới hạn, và một instance sshd riêng với các tùy chọn AuthorizedKeysFile, PermitRootLogin=yes
  • Do ssh không có tùy chọn truyền trực tiếp socket sẵn có, ban đầu tác giả dùng ProxyCommandsocat, 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.py thành một file thực thi nhỏ và bọc toàn bộ lệnh ssh bằng một wrapper

Những giới hạn mang tính cấu trúc của sudodoas

  • sudodoas phụ thuộc vào binary setuidleo 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 setuid tạ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-sudod là 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 ssh cục bộ để đảm nhận vai trò giống sudo, nhưng instance sshd đó 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_keys thay vì authorized_keys thông thường
  • Bind một instance sshd riê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.sock và truyền cho sshd các tùy chọn sau
    • AuthorizedKeysFile=/root/.ssh/local_keys
    • PermitRootLogin=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 sshd hiện có đang bind ra mạng
    • Trong cấu hình hiện có vẫn giữ PermitRootLogin no

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
  • sshd diễ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 *
    • sshd khô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_config còn đặt PasswordAuthentication no; cấu hình tắt xác thực bằng mật khẩu trong sshd là an toàn

Kết nối vào Unix socket bằng ssh

  • sshd có cờ -i để nhận một socket có sẵn, nhưng ssh không có cờ tương ứng như vậy
  • Ban đầu tác giả dùng socat qua ProxyCommand để 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-agent chỉ công khai khóa sau khi có chấp thuận tường minh, chẳng hạn hissh-agent, cũng có thể là một lựa chọn thay thế

Overhead của socatProxyUseFdpass

  • socat đọc toàn bộ đầu vào của ssh rồi mới ghi vào socket, nên overhead kết nối về cơ bản bị lặp đôi
  • ProxyUseFdpass cho phép lệnh gửi file descriptor của socket cho ssh qua stdout, rồi ssh kế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 sendmsgSCM_RIGHTS
  • Sau đó lệnh kết nối chuyển sang chỉ định passfd.py trong ProxyCommand và thêm ProxyUseFdpass=yes
  • nc -FU /run/sshd/sshd.sock cũng có vẻ khả thi, nhưng tài liệu hướng dẫn ghi rõ -F khô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 ipcserver có thể được chạy thông qua trình quản lý dịch vụ hệ thống
  • passfd.py gầ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ệnh ssh bằng một wrapper nhỏ

1 bình luận

 
GN⁺ 2024-06-24
Ý 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 suid duy nhất đọc tệp cấu hình rồi gọi exec(), 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ứng
    Nếu phản biện cốt lõi với sudo/doas là “có một binary suid mà 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ư sau
    chgrp wheel /usr/bin/sudo
    chmod o-rwx /usr/bin/sudo
    Nế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ùng wheel truy cập UNIX socket, nhưng độ phức tạp thấp hơn nhiều
    Hơ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

    • Cá nhân tôi dùng etckeeper để theo dõi mọi thay đổi trong /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 ích
      https://etckeeper.branchable.com/
    • Việc đưa người dùng sudo vào nhóm 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
    • Xin thứ lỗi vì thiếu hiểu biết. Tôi muốn được giải thích nhóm wheel là gì và làm gì. Tôi biết việc này có thể mở ra một cuộc tranh luận phức tạp
    • Có thể đặt /usr/bin/sudo thà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ó
    • Về phần không thể hạn chế quyền truy cập root theo lệnh cụ thể thì có hạ tầng ForcedCommand
  • Đâ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ọi systemd-run vốn đã có từ lâu thông qua symlink tên run0, rồi hoạt động như một thứ thay thế sudo
    Khá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ày
    Nó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 run0 có hành vi gần với ssh hơn là sudo
    https://mastodon.social/@pid_eins/112353324518585654

    • Vì sao systemd cứ phải tạo lại mọi thứ vậy?
  • 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

    • Binary sudosuid 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ác
      Cách dùng ssh không phơi binary suid mà 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àn
    • Một trong những ưu điểm lớn của sudo là thay vì chỉ mở thứ như sudo bash, ta có thể chạy từng lệnh riêng lẻ bằng sudo để tăng khả năng kiểm toán
    • Đăng nhập ssh bằng root, trong bài viết, là nói đến một máy chủ SSH bổ sung lắng nghe Unix socket. Mô hình đe dọa thông thường khi phơi đăng nhập root ra Internet có thể không áp dụng ở đây
    • Cách này đang so sánh các lỗi cấu hình mang tính lý thuyết hoặc các lỗ hổng có thể có hoặc không, với một bề mặt tấn công mới do chạy daemon mới
      Daemon đó 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
    • Tôi hoài nghi cách tiếp cận trong bài được liên kết, nhưng câu “đăng nhập trực tiếp SSH từ xa bằng root là nguy hiểm” có phần bị cuốn theo sợ hãi, bất định và nghi ngờ
      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 sudo
      Nếu user@home ssh vào root@server, thì root@server chỉ bị ảnh hưởng khi user@home bị xâm nhập
      Ngược lại, nếu user@home ssh vào user@server rồi dùng sudo để trở thành root@server, thì chỉ cần một trong hai user@home hoặc user@server bị xâm nhập là root đã bị ảnh hưởng. Đặc biệt trên user@server thường có các phần mềm khác chạy, như daemon hoặc tác vụ cron
      Khô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ói từ góc độ người dùng cấu hình tương tự, tôi đang làm vậy trên desktop chính của mình. Nếu tình huống tệ nhất xảy ra thì vì mọi thứ đều có backup, tôi cài lại hệ thống
      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
    • Console Linux, tức tty hiện trên màn hình cục bộ hoặc KVM từ xa, các thiết bị ttyS* của cổng serial và IPMI SoL, không dùng sudo hay su
      Những console này dùng chương trình như getty hoặ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 root
      Nên thiết lập mật khẩu root cho đăng nhập console
    • Trường hợp của tôi thì dùng setuid/sudo vì nhật ký kiểm toán. Giờ tôi hầu như không vận hành các máy đa người dùng, đa dịch vụ nữa; những thứ multi-tenant phần lớn là k8s nên chỉ cần dùng endpoint kubectl thay cho ssh
      Nế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
    • Nếu có thể truy cập bootloader thì vẫn có thể đặt 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 động
      Vớ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

    • SSH cũng có thể làm một phần trong số đó bằng ForceCommand, nhưng tôi đồng ý là nó không linh hoạt hay chính xác bằng
  • Đây là ý tưởng tương tự run0 của Systemd: https://news.itsfoss.com/systemd-run0/

    • Và run0 không phải là giải pháp tạm tự chế. Nó đã được audit, và có lẽ tốt hơn thứ tự làm
  • 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_spawn

  • 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 Yubikey
    Console 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ả

    • Cấu hình bạn có không hoàn toàn giống, nhưng trông khá tương tự khái niệm bastion host
  • Đâ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í su cũ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 tty

    • Cách bảo rằng nếu cần root thì đăng nhập root ở console có hai vấn đề
      Thứ 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 sshd riêng chỉ lắng nghe trên localhost, và cũng không cần xử lý SCM_RIGHTS
    Khô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