- SSH không chỉ là công cụ truy cập từ xa mà còn bao quát cả chuyển tiếp cổng, jump host, tệp cấu hình và quản lý khóa; các ví dụ với máy chủ web cũng có thể áp dụng nguyên vẹn cho các dịch vụ khác như RDP·SQL
- Chuyển tiếp cổng cục bộ·từ xa·động lần lượt được cấu hình bằng
-L, -R, -D, và khác biệt cốt lõi là cổng được mở ở phía nào và lưu lượng đi theo hướng nào
- Trong các mạng không cho phép truy cập trực tiếp, có thể dùng jump host
-J và ProxyJump để nối nhiều điểm trung chuyển SSH rồi đi vào đích cuối
ssh-agent và agent forwarding -A giúp dùng khóa cục bộ ngay cả trên máy chủ từ xa, rất tiện, nhưng cần kiểm tra trước các rủi ro bảo mật do việc lạm dụng agent
- Khi dùng cùng
~/.ssh/config, ssh-copy-id, ssh-keygen, và console SSH ~?·~C, bạn có thể giảm việc nhập lặp lại các tùy chọn, thêm forwarding trong phiên, phân phối khóa công khai, cũng như tạo·kiểm tra khóa dễ hơn
Những điều cần biết trước để hiểu SSH port forwarding
- Chỉ nhìn sơ đồ thì khá khó nắm được luồng của SSH port forwarding, nên sẽ dễ hiểu hơn nếu xem cùng các lệnh thực tế và kịch bản mạng
- Ví dụ dựa trên việc truy cập máy chủ web, nhưng cách làm tương tự có thể áp dụng cho RDP, SQL và gần như mọi dịch vụ khác
- Các tùy chọn dùng lặp lại có ý nghĩa như sau
-N: không thực thi lệnh trên máy chủ từ xa và cũng không mở shell
-f: đưa SSH xuống chạy nền
root@host: đăng nhập bằng người dùng và host sẽ dùng để tạo tunnel
Chuyển tiếp cổng cục bộ -L
- Chuyển tiếp cổng cục bộ chuyển một cổng trên máy cục bộ tới một cổng trên máy chủ từ xa
- Tình huống ví dụ
internal-web.int đang chạy một trang web chỉ truy cập được qua giao diện loopback
- Có thể SSH từ
campfire.int tới internal-web.int
- Muốn truy cập máy chủ web của
internal-web.int thông qua một cổng cục bộ trên campfire.int
- Lệnh sử dụng
ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int
- Giải thích tùy chọn
-L: chỉ định chuyển tiếp cổng cục bộ
1337:127.0.0.1:80: bind cổng cục bộ 1337 tới 127.0.0.1:80 ở phía từ xa
- Sau khi tạo tunnel, có thể gửi yêu cầu tới cổng cục bộ
1337 trên campfire.int để tương tác với cổng 80 của internal-web.int
- Điều cần nhớ là trong
-L, cổng cục bộ nằm ở bên trái của địa chỉ
Chuyển tiếp cổng từ xa -R
- Chuyển tiếp cổng từ xa làm lộ ra trên máy chủ từ xa một cổng mà phía cục bộ có thể truy cập được
- Tình huống ví dụ
internal-web.int đang chạy một trang web chỉ truy cập được qua loopback
campfire.int không thể truy cập trực tiếp internal-web.int do tường lửa
vuln-server.int có thể được truy cập từ cả campfire.int lẫn internal-web.int
- Lệnh sử dụng
ssh -N -f -R 3000:127.0.0.1:80 root@vuln-server.int
- Giải thích tùy chọn
-R: chỉ định chuyển tiếp cổng từ xa
3000:127.0.0.1:80: bind cổng 3000 trên vuln-server.int tới 127.0.0.1:80 ở phía cục bộ
- Sau đó, nếu gửi yêu cầu
curl tới vuln-server.int:3000, có thể truy cập trang web nội bộ đang chạy trên cổng 80 của internal-web.int
- Điều cần nhớ là trong
-R, cổng cục bộ nằm ở bên phải của địa chỉ
Chuyển tiếp cổng động -D và proxy SOCKS
- Chuyển tiếp cổng động dùng tùy chọn
-D để tạo một proxy SOCKS và gửi lưu lượng qua điểm trung chuyển SSH
- Tình huống ví dụ
internal-web.int đang chạy một ứng dụng web chỉ truy cập được từ mạng nội bộ
vuln-server.int nằm trong cùng mạng nội bộ và có thể truy cập internal-web.int
- Muốn proxy lưu lượng từ
campfire.int thông qua vuln-server.int
- Cấu hình
/etc/proxychains.conf phải khớp với cổng trong lệnh SSH
socks5: để proxychains dùng SOCKS5
127.0.0.1: dùng localhost
8080: phải trùng với cổng đã chỉ định cho SSH -D
- Lệnh sử dụng
ssh -N -f -D 8080 root@vuln-server.int
- Sau khi tạo forwarding, nếu cấu hình
socks5 127.0.0.1 8080 thì có thể truy cập trang web nội bộ bằng proxychains curl 192.168.1.185
- DNS qua SOCKS đôi khi hoạt động không ổn tùy môi trường, nên trong ví dụ dùng địa chỉ IP thay vì hostname
- Trong Firefox cũng có thể dùng proxy SOCKS bằng cấu hình proxy thủ công
- Đường dẫn: Settings → Privacy & Security → Network Settings
- Chọn Manual proxy configuration
- Tích “Proxy DNS when using SOCKS V5”
- Đặt SOCKS host là
127.0.0.1, port là 8080
Jump host -J
- Jump host cho phép kết nối tới một đích không thể truy cập trực tiếp từ máy hiện tại bằng cách đi qua nhiều điểm trung chuyển SSH
- Chuỗi ví dụ là
campfire.int → vuln-server.int → internal-web.int → dns.int
- Lệnh sử dụng
ssh -J root@vuln-server.int,root@internal-web.int root@dns.int
- Nhiều điểm nhảy được phân tách bằng dấu phẩy
Agent forwarding -A
ssh-agent cho phép thêm private key hoặc identity trên máy cục bộ bằng ssh-add <private_key_file>
- Có thể kiểm tra các khóa đã thêm bằng
ssh-add -l
- Khi thêm khóa vào
ssh-agent, bạn có thể SSH bằng khóa đó mà không cần nhập lại mật khẩu, hữu ích cho cả con người lẫn tài khoản dịch vụ
- Agent forwarding
-A cho phép dùng các khóa trong agent cục bộ ngay cả trên máy từ xa mà bạn đã kết nối tới
- Trước khi dùng, hãy xem rủi ro bảo mật tại Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement
- Lệnh ví dụ
ssh -A -J root@vuln-server.int root@internal-web.int
- Lệnh này kết nối tới
internal-web.int qua vuln-server.int, đồng thời cho phép dùng các khóa trong SSH agent của campfire.int
- Sau đó, từ
internal-web.int, khi chạy ssh root@dns.int, bạn có thể đăng nhập mà không cần chỉ định private key hay nhập thông tin xác thực
Gán lệnh TTY -t
- Tùy chọn
-t hữu ích khi cần nhanh chóng chạy một lệnh tương tác trên máy chủ từ xa
- Ví dụ là các lệnh cần TTY như
Vim hoặc top
- Lệnh sử dụng
ssh root@internal-web.int -t top
- Khi thực thi, bạn sẽ nhận được một TTY trên máy chủ từ xa với lệnh
top
-g để host bên ngoài cũng có thể kết nối vào cổng local forwarding
- Tùy chọn
-g cho phép các host từ xa kết nối tới cổng được local forwarding
- Nó tương tự local port forwarding
-L, nhưng khác ở chỗ các máy bên ngoài cũng có thể dùng cổng “cục bộ” đó
- Tình huống ví dụ
- Có quyền truy cập shell vào
vuln-server.int
- Muốn proxy các kết nối đến cổng
2222 của vuln-server.int sang cổng 22 của internal-web.int
- Lệnh sử dụng
ssh -N -f -g -L 2222:localhost:22 root@internal-web.int
- Giải thích tùy chọn
-g: cho phép host từ xa kết nối tới cổng local forwarding
-L: chỉ định local forwarding
- Dù SSH vào cổng
2222 của vuln-server.int, shell thực tế vẫn sẽ nằm trên internal-web.int
Console SSH ~? và forwarding trong phiên
- Console SSH là một tính năng ẩn cho phép điều khiển chính SSH mà không tương tác trực tiếp với hệ thống từ xa
- Nó hữu ích khi shell bị lỗi hoặc khi cần điều khiển chính phiên SSH
- Có thể mở console trợ giúp bằng
~?
- Một số tùy chọn hữu ích
~.: kết thúc phiên SSH hiện tại
~C: mở console SSH để thêm các tùy chọn forwarding
- Ngay cả khi đã kết nối tới
vuln-server.int bằng lệnh ssh thông thường, bạn vẫn có thể nhấn ~C rồi nhập -D 8080 để biến phiên đó thành một phiên dynamic forwarding
- Nếu
/etc/proxychains.conf trên campfire.int đã được cấu hình dùng cổng 8080, thì có thể dùng proxychains như thể bạn đã bắt đầu bằng ssh -D ngay từ đầu
Tệp cấu hình SSH ~/.ssh/config
- Tệp cấu hình SSH nằm tại
~/.ssh/config, giúp lưu các tùy chọn SSH thường nhập lặp lại để tiết kiệm thời gian
- Khi SSH kết nối, nó sẽ phân tích tệp này và nếu có cấu hình
host khớp với đích kết nối thì sẽ dùng các tùy chọn tương ứng
- Đối số trên dòng lệnh có độ ưu tiên cao hơn tệp cấu hình
- Ví dụ, dù trong tệp cấu hình có đặt người dùng cho
internal-web.int là root, nếu chạy ssh graham@internal-web.int thì SSH sẽ thử đăng nhập bằng graham
- Ví dụ cấu hình cơ bản
# You can put comments with a `#` at the beginning of the line only.
host internal-web.int
User root
IdentityFile /home/smores/ssh_agent/internal-web-no-pw
Port 2222
- Luồng xử lý khi chạy
ssh internal-web.int
- So khớp
internal-web.int trên dòng lệnh với host internal-web.int trong ~/.ssh/config
- Nếu khớp, lấy các tùy chọn chưa được chỉ định trên dòng lệnh từ tệp cấu hình
- Nếu không khớp, chỉ dùng các tùy chọn đã định nghĩa trên dòng lệnh
Các từ khóa SSH config thường dùng
IdentityFile /path/to/private_key
- Chỉ định private key sẽ dùng cho host
- Có vai trò tương tự
ssh -i
ForwardAgent
ProxyJump root@internal-web.int
- Chỉ định máy chủ sẽ dùng để proxy lưu lượng
- Có vai trò tương tự tùy chọn
-J
- Trong ví dụ, nó yêu cầu xác thực với
vuln-server.int trước, cho thấy lưu lượng đi qua host đó
Match
- Áp dụng các từ khóa SSH config theo điều kiện
- Trong ví dụ, nếu mã thoát của lệnh
export | grep PROXYME=TRUE là 0 thì dùng ProxyJump dưới khối Match
- Nếu không có biến môi trường
PROXYME thì chỉ dùng khối host internal-web.int thông thường
- Sau khi đặt
export PROXYME=TRUE, nếu chạy cùng lệnh ssh internal-web.int thì trước tiên sẽ xác thực qua vuln-server.int rồi mới nhận shell của internal-web.int
scp và một số tiện ích dựa trên SSH khác cũng thường có thể dùng SSH config
- Trong môi trường không tự động dùng, có thể chỉ rõ bằng
-F ~/.ssh/config
Sao chép public key ssh-copy-id
ssh-copy-id là một tiện ích nhỏ để nhanh chóng tải public key lên máy chủ
- Lệnh sử dụng
ssh-copy-id -i internal-web root@internal-web.int
- Giải thích tùy chọn
-i internal-web: chỉ định tên private key sẽ dùng để xác thực với máy chủ
root@internal-web.int: chỉ định máy chủ sẽ tải public key lên
Tạo và kiểm tra khóa bằng ssh-keygen
ssh-keygen là tiện ích để tạo cặp private key·public key
- Thông thường khuyến nghị dùng tùy chọn
-b để chỉ định kích thước khóa lớn hơn
- Kích thước khóa mặc định trong môi trường ví dụ là
3072
- Thuật toán mặc định là RSA, nhưng có thể chỉ định thuật toán khác bằng cờ
-t
- Ví dụ:
ssh-keygen -t ecdsa -b 521
- Có thể kiểm tra fingerprint và kích thước byte của khóa bằng lệnh sau
ssh-keygen -lf <file-name>
1 bình luận
Ý kiến trên Hacker News
Ở đây thiếu một chỉ thị đơn giản đến đáng ngạc nhiên: chỉ cần cấu hình trong
sshd_configkiểuAuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %u, rồi trong script lấyhttps://github.com/{$user}.keystừ GitHub là đượcTất nhiên đây không phải mã đạt chất lượng production, nhưng nó cho thấy ý chính
Sau khi kiểm tra người dùng có thuộc tổ chức/nhóm GitHub hay không, nếu người dùng tồn tại và được ánh xạ bằng thứ như
nss-ato, bạn có thể cho phép đăng nhập vào máy chủKhi onboarding/offboarding nhân sự, chỉ cần thêm/xóa họ khỏi nhóm GitHub là có thể cấp hoặc thu hồi quyền truy cập thiết bị, giảm bớt phiền phức
Trên Amazon Linux 2 trở xuống, nó gọi dòng lệnh
opensslđể kiểm tra định dạng từng khóa trong fileauthorized_keys, nhưng lại hard-code RSA, nên dù phiên bản OpenSSH có hỗ trợed25519, bạn vẫn không thể xác thực bằnged25519trên host Amazon Linux 2Về lý thuyết, điều này tốt vì cho phép một tính năng¹ khá hay, nhưng trên thực tế nó làm hỏng cả chức năng cơ bản đối với những người không dùng tính năng đó, khiến tôi kém tin tưởng Amazon Linux hơn
Lần đầu gặp vấn đề này là khi tôi đang cố SSH vào một máy do một đồng nghiệp DevOps theo hướng cloud-first sở hữu, và vì không thể trực tiếp đụng vào máy nên rất khó chẩn đoán
Anh ấy rành AWS nhưng kém rành Linux hơn nên không biết phải xem ở đâu; anh chọn Amazon Linux vì nghĩ rằng đây là bản phân phối do chủ sở hữu nền tảng cloud tạo ra nên sẽ “tương thích hơn”, nhưng “tương thích hơn” ở đây thực ra nghĩa là “có nhiều bất ngờ ngớ ngẩn hơn”
¹ https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-...
Vì “mạng không chạy đúng” là phạm vi công việc, nên tôi bỏ lỡ những tính năng hay ho như thế này
Có lẽ nhiều người không biết rằng parser cấu hình của OpenSSH bỏ qua các chỉ thị trùng lặp, và chỉ chỉ thị đầu tiên trong số các chỉ thị giống nhau mới có hiệu lực
Thông thường trong parser cấu hình hoặc rule engine, chỉ thị xuất hiện sau sẽ ghi đè chỉ thị trước, nên điều này khá trái trực giác
Nghe có vẻ nhỏ nhặt, nhưng khi thay đổi các giá trị mặc định như
/etc/ssh/sshd_config, cả con người lẫn phần mềm thường có xu hướng thêm thay đổi vào cuối file hoặc cuối khối chỉ thị, và kỳ vọng nó sẽ được áp dụngCác công ty bảo mật, tổ chức, và nhiều sản phẩm bastion SSH cũng hiểu sai điểm này; các khuyến nghị CIS Benchmarks và hầu hết công cụ audit CIS của bên thứ ba cũng không xét đến thứ tự ưu tiên hoặc xử lý sai
Để xác nhận các chỉ thị cấu hình OpenSSH có được định nghĩa đúng như kỳ vọng hay không, đừng đọc lướt file cấu hình trực tiếp; hãy dump cấu hình nội bộ đã được suy diễn bằng
sshd -Thoặcssh -Gsudoerscũng hoạt động theo cách tương tựVới phần mềm xác thực/ủy quyền người dùng, tôi cho rằng cách này là đáng mong muốn
Vì nó giúp dễ tạo ra các thiết lập không thể bị ghi đè chỉ bằng cách thêm file mới vào thư mục
whatever.conf.dChỉ cần định nghĩa thiết lập đó trong file cấu hình chính trước khi nạp
whatever.conf.d/*, rồi đặt bảo vệ đặc biệt cho file đóNgay cả khi không có ai cố vượt qua kiểm soát, điều này vẫn có lợi cho quản lý cấu hình, vì nếu một người mới chưa nắm toàn bộ bối cảnh thêm file mới, hoặc một gói nào đó cài các mặc định kỳ quặc cho dịch vụ của nó, thì cấu hình chuẩn vẫn giữ được ưu tiên
Lý do trong các bối cảnh khác ta thường thấy hành vi ngược lại là vì thứ mong muốn không phải “cấu hình chuẩn”, mà là giá trị mặc định theo nghĩa chặt chẽ, dùng khi người dùng/nhà phát triển/quản trị viên không thiết lập rõ ràng
AllowUsersTuy vậy, hôm qua NixOS đột nhiên đổi thứ tự merge, khiến
AllowUserstrong file của tôi bị đẩy xuống dướiMatchcủa file khác và bị kẹt ở đóCâu trong bài nói rằng với
-Lthì phía local nằm bên trái của địa chỉ trong local forwarding, còn với-Rthì port local nằm bên phải của địa chỉ trong remote forwarding là câu quan trọng và súc tích nhấtNgay từ đầu tôi đã nhầm lẫn
-Lvà-R, và việc instance port nào là local lại thay đổi theo L/R khá phiềnTôi hiểu rằng
-Lvà-Rthay đổi hướng chủ ý, tức bên khởi tạo và bên phản hồi nằm ở đâu, nhưng có lẽ cũng ổn nếuport:address:portluôn mang nghĩalocal:binding:remote, còn-L/-Rquyết định phía nào là bên lắng nghe và phía nào là bên gửi-Llắng nghe trên port local là con số xuất hiện ngay sau nó, còn-Rlắng nghe trên port remote là con số xuất hiện ngay sau nóPhần
host:portcòn lại chỉ là định dạng thông thường cho biết sẽ kết nối tới đâuVì đây là port forwarding qua đường hầm SSH, nên cũng tự nhiên suy ra rằng host sẽ được liên hệ từ phía đối diện đường hầm so với nơi có port đang lắng nghe
Trong SSH có một tính năng ít được biết đến nhưng hữu ích là ghép kênh kết nối
Thay vì tạo một kết nối TCP mới và thực hiện lại quy trình xác thực, có thể cấu hình để tái sử dụng kết nối hiện có
Bản thân giao thức có khái niệm kênh, và mỗi khung dữ liệu được gắn metadata để phân biệt các luồng khác nhau; tính năng này tận dụng cơ chế đó
Lợi ích lớn là các phiên sau không cần xác thực đầy đủ lại từ đầu
Đặc biệt hữu ích khi máy từ xa không có
tmuxhay công cụ tương tự và bạn dùng nhiều cửa sổ terminal như nhiều panel; nếu xác thực có bước mất vài giây như passphrase hoặc chạm HSM thì càng cảm nhận rõ hơnCũng có thiết lập “duy trì kết nối”, nên khi qua lại giữa vài máy chủ sẽ không phải xác thực mỗi lần
Nhìn chung đây là tính năng có thì tốt, nhưng không đến mức thay đổi cuộc đời
Một số máy chủ tắt tính năng này; khi bị tắt thì sự thiếu vắng của nó lại dễ nhận ra hơn so với khi được bật
Xem thêm: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing
SSH là một giao thức khá “nói nhiều”, không giống TLS vốn được tối ưu để giảm số vòng trao đổi, và tùy chọn ghép kênh này gần như là ngoại lệ duy nhất
ProxyJumpNếu có nhiều host trong
~/.ssh/config, có thể dùng chỉ thịIncludehỗ trợ wildcard để tránh làm file quá rốiVí dụ, trong
~/.ssh/configđặtInclude config.d/*.conf, rồi tách các thiết lậphost,hostname,uservào~/.ssh/config.d/work.confhoặcclient1.confHostcũng hỗ trợ wildcardVí dụ, có thể thêm
host *_workvà đặt các thiết lập chung cho mọi host dùng cho công việc nhưhost1_workIncludebên trong chỉ thịHost/MatchVí dụ, nếu trong
~/.ssh/configcóHost proj1.*.corpvàInclude ~/.ssh/proj1.conf, bạn có thể đặt các match theo dự án ở gần phía trên, đồng thời giảm việc phải lục qua rất nhiều file riêng lẻ khi rà soátKhi forwarding, hầu như không dùng
-fNó có thể trở thành khẩu súng dưới chân mình, vì khiến khó biết forwarding nào vẫn đang mở và đang hoạt động
-tlà một mẹo hay, và là tính năng trước đây tôi không biếtMột điểm quan trọng dễ bị bỏ sót trong danh sách lệnh escape
~là ngay cả trong phiên lồng nhau cũng có thể lồng escapeĐiều này hữu ích khi vì lý do nào đó không dùng
-JDanh sách này khớp với những thứ hữu ích, và tôi cũng học thêm được vài điều
-trất tuyệt. Tôi dùng kiểussh -t my-dev-vps 'tmux new-session -A -s main'để mỗi lần chạy là quay thẳng về vị trí trước đó trong phiên tmuxSẽ tốt hơn nếu SSH cung cấp trạng thái forwarding theo một cách hợp lý, thay vì phải tự đào trong danh sách tiến trình
Hiện có một pull request đang thêm hỗ trợ
AF_UNIX; nếu được đưa vào, nó sẽ mở ra đủ loại forwarding thú vịVì khi đó sẽ dễ proxy kết nối SSH qua một tiến trình cục bộ tùy ý, và tiến trình đó có thể tự xử lý việc chuyển dữ liệu đến đầu xa theo ý muốn
https://github.com/openssh/openssh-portable/pull/431
-DdùngAF_UNIX, nhưng việc mọi thứ có thể hoạt động trênAF_UNIXcũng rất hayCó vẻ từ khoảng một năm trước,
curlđã có thể dùng SOCKS quaAF_UNIXthông qua cú phápALL_PROXYlàsocks5://localhost/pathhoặcsocks5hCó lẽ tính năng này được thêm vì Tor dùng proxy SOCKS
AF_UNIXSẽ tốt nếu có thể cấu hình quyền truy cập mạng bằng quyền Unix tiêu chuẩn; cá nhân tôi còn muốn có thể đẩy TCP/IP hoàn toàn ra khỏi kernel thì càng tốt
Lần đầu nhìn thấy console của SSH tôi đã rất sốc
Đồng nghiệp cho tôi xem
~#, và cảm giác như vừa phát hiện ra menu cheat bí mật kiểu có thể xuất hiện trong game SEGA GenesisVì sao lại là dấu ngã? Vì
rlogin,rshđã dùng nóVì sao
rlogin,rshdùng dấu ngã? Vìcuđã dùng nóVì sao là
cu? Khi có modem hoặc đường serial, người ta giao tiếp bằngcu, và phải gửi mã Hayes; nhưng nếu dùng chuỗi escape của mã Hayes thì sẽ thoát vào modem, nên cần một tín hiệu riêng để thoát khỏicuVì sao không phải
^[? Vì đó là telnetVì vậy nếu bạn telnet vào một host rồi dùng
cuđể kết nối tới modem, bạn cần một cú pháp escape riêng để quay lạicumà không thoát về telnetRốt cuộc đây là một cấu trúc các cú pháp escape chồng chất vô tận
Và thực ra không phải là dấu ngã, mà là dấu ngã
CR, dấu ngã, rồi.; chẳng lẽ bấy lâu nay tôi dùng sai sao?Phần
ssh-copy-idban đầu giải thích rằng lệnh này tải public key lên, rồi đột nhiên chuyển sang nói tải private key lên; có vẻ là lỗi đánh máyNgoài ra lệnh này không chỉ đơn giản là tải key lên, mà còn nối thêm vào
~/.ssh/authorized_keys, nên hữu ích hơn nhiềuCuối cùng, ở phần
ssh-keygen, theo những tài liệu tôi đọc gần đây thìed25519được ưu tiên hơnecdsa