SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

พอร์ตคืออะไรและวิธีตรวจสอบ Linux Service ที่กำลังรันอยู่

พอร์ตคือช่องทางรับส่งข้อมูลของเซิร์ฟเวอร์ เรียนรู้วิธีใช้คำสั่ง ss เพื่อตรวจสอบว่ามีบริการใดกำลังเปิดพอร์ตใช้งานอยู่บ้าง พร้อมวิธีแยกแยะพอร์ตภายในและพอร์ตสาธารณะอย่างถูกต้อง

พอร์ตคืออะไร

พอร์ตคือตัวเลขที่ช่วยให้เซิร์ฟเวอร์หนึ่งเครื่องสามารถรันหลายบริการได้พร้อมกันโดยที่ข้อมูลไม่ปะปนกัน VPS ของคุณมี IP address เพียงหนึ่งเดียว แต่อาจกำลังรันเว็บเซิร์ฟเวอร์, SSH เซิร์ฟเวอร์ และฐานข้อมูลไปพร้อมกันได้ เมื่อมีแพ็กเก็ตข้อมูลเข้ามา ระบบปฏิบัติการจำเป็นต้องทราบว่าข้อมูลนั้นส่งถึงบริการใด ซึ่งตัวเลขพอร์ตคือคำตอบของเรื่องนี้ โดยปกติแล้วข้อมูล HTTPS จะวิ่งไปที่พอร์ต 443 และ SSH ไปที่พอร์ต 22 ตัว IP address ทำหน้าที่นำแพ็กเก็ตมาส่งที่เซิร์ฟเวอร์ของคุณ ส่วนพอร์ตทำหน้าที่นำส่งข้อมูลไปยังโปรแกรมที่ถูกต้องบนเซิร์ฟเวอร์นั้น

พอร์ตเป็นตัวเลขขนาด 16-bit ซึ่งให้ค่าตั้งแต่ 0 ถึง 65535 โดยเลข 0 ถูกสงวนไว้ ดังนั้นในทางปฏิบัติพอร์ตจะเริ่มตั้งแต่ 1 ถึง 65535 พอร์ตที่มีเลขต่ำกว่า 1024 เรียกว่า "well-known ports" ซึ่งโดยค่าเริ่มต้น Linux จะอนุญาตให้เฉพาะ root (หรือโปรแกรมที่ได้รับสิทธิ์ CAP_NET_BIND_SERVICE) เท่านั้นที่เปิดใช้งานได้ นี่คือเหตุผลว่าทำไมบริการมาตรฐานจึงอยู่ที่พอร์ตเหล่านี้ เช่น 22 สำหรับ SSH, 80 สำหรับ HTTP, 443 สำหรับ HTTPS และ 53 สำหรับ DNS ค่า net.ipv4.ip_unprivileged_port_start ใน sysctl สามารถปรับเปลี่ยนขอบเขตนี้ได้ แต่ค่าเริ่มต้นคือ 1024 พอร์ตตั้งแต่ 1024 ขึ้นไปถือเป็นพอร์ตที่โปรแกรมทั่วไปสามารถใช้งานได้

พอร์ตมีสองประเภทคือ TCP และ UDP ซึ่งหมายเลขพอร์ตเดียวกันในแต่ละประเภทจะถือเป็นคนละพอร์ตกัน TCP เป็นโปรโตคอลแบบเน้นการเชื่อมต่อที่บริการส่วนใหญ่ใช้งาน เช่น SSH, HTTP และฐานข้อมูล ส่วน UDP เป็นแบบไม่เน้นการเชื่อมต่อและใช้สำหรับงานอย่าง DNS และ VPN บางประเภท เมื่อคุณเปิดใช้งานไฟร์วอลล์ คุณมักจะต้องระบุประเภทของโปรโตคอลด้วย เช่น 22/tcp

การฟัง (Listening) หมายถึงโปรแกรมกำลังรอการเชื่อมต่อที่พอร์ต

โปรแกรมที่ต้องการรับการเชื่อมต่อจะร้องขอให้ kernel ทำการ "listen" บนพอร์ตใดพอร์ตหนึ่ง นับจากวินาทีนั้นพอร์ตจะถูกเปิดใช้งานบนเครื่อง และโปรแกรมจะตอบสนองต่อทุกสิ่งที่เข้ามาตามกฎของ firewall พอร์ตที่ไม่มีโปรแกรมใดรอรับการเชื่อมต่ออยู่จะปฏิเสธการเชื่อมต่อโดยตรง ดังนั้นคำถามแรกในการตรวจสอบความปลอดภัยคือ: มีอะไรกำลังฟังอยู่บ้าง และฟังที่ไหน

คำสั่งสำหรับตรวจสอบเรื่องนี้คือ ss:

sudo ss -tlnp

แฟล็กต่างๆ หมายถึง TCP (t), เฉพาะ socket ที่กำลังฟังอยู่ (l), พอร์ตแบบตัวเลข (n เพื่อให้คุณเห็น 22 แทนที่จะเป็น ssh) และกระบวนการที่เป็นเจ้าของ (p) ผลลัพธ์ทั่วไปจะเป็นดังนี้:

State   Recv-Q  Local Address:Port   Process
LISTEN  0       0.0.0.0:22           sshd
LISTEN  0       127.0.0.1:5432       postgres
LISTEN  0       [::]:80              nginx

คอลัมน์ Process จะบอกคุณว่าโปรแกรมใดเป็นเจ้าของพอร์ตนั้นๆ ซึ่งเป็นวิธีที่คุณใช้ติดตามตรวจสอบสิ่งที่คุณไม่ได้ตั้งใจให้ทำงานค้างไว้

คอลัมน์ Local Address คือส่วนที่สำคัญ

โปรดอ่านที่อยู่หน้าพอร์ตแต่ละพอร์ตอย่างละเอียด เพราะที่อยู่นี้จะเป็นตัวกำหนดว่าใครสามารถเข้าถึงบริการนั้นได้บ้าง โดยคุณจะพบกรณีหลักๆ 3 รูปแบบดังนี้

0.0.0.0:22 หมายถึง "ให้ฟังที่ทุก IPv4 address ที่เครื่องนี้มี" รวมถึง IP สาธารณะด้วย บริการนี้จะสามารถเข้าถึงได้จากอินเทอร์เน็ตผ่าน IPv4 หากไฟร์วอลล์อนุญาต

[::]:80 หมายถึงความหมายเดียวกันสำหรับ IPv6 คือให้ฟังที่ทุก IPv6 address รวมถึง IP สาธารณะ โปรแกรมจำนวนมากผูก (bind) กับที่อยู่นี้เป็นค่าเริ่มต้น และบน Linux นั้น socket แบบ :: มักจะยอมรับการเชื่อมต่อ IPv4 เข้ามาด้วย

127.0.0.1:5432 หมายถึง "ให้ฟังเฉพาะที่ loopback เท่านั้น" 127.0.0.1 คือที่อยู่ที่เครื่องใช้สื่อสารกับตัวเอง และภายใต้การตั้งค่าเริ่มต้นของเคอร์เนล ที่อยู่นี้จะไม่สามารถถูกเข้าถึงจากภายนอกได้ บริการที่ผูกกับที่อยู่นี้จะเข้าถึงได้จากภายในเซิร์ฟเวอร์เครื่องเดียวกันเท่านั้น ไม่สามารถเข้าถึงจากอินเทอร์เน็ตได้แม้ว่าไฟร์วอลล์ของคุณจะเปิดพอร์ตไว้ก็ตาม บรรทัดของ Postgres ที่กล่าวถึงข้างต้นจึงมีความปลอดภัยโดยค่าเริ่มต้น ข้อยกเว้นคือการตั้งค่าเคอร์เนลที่เรียกว่า route_localnet ซึ่งปกติจะปิดอยู่ แต่เครื่องมือบางอย่างอาจเปิดใช้งาน (เช่น kube-proxy ของ Kubernetes) ซึ่งหากเปิดใช้งานร่วมกับการตั้งค่า routing หรือ NAT ที่ถูกต้อง ก็อาจทำให้ traffic จาก loopback เข้ามาจากภายนอกได้ sysctl -a | grep route_localnet ควรแสดงค่า = 0 ในทุกบรรทัด

กฎในทางปฏิบัติที่ได้จากเรื่องนี้คือ: ฐานข้อมูล, แคช หรือแผงควบคุมผู้ดูแลระบบที่ใช้เฉพาะแอปพลิเคชันของคุณเอง ควรผูกไว้ที่ 127.0.0.1 ไม่ใช่ 0.0.0.0 หากบริการไม่เคยฟังที่ public address ก็จะไม่มีช่องทางให้ผู้โจมตีเข้าถึงได้

การฟัง (Listening) ไม่เหมือนกับการเข้าถึงได้ (Reachable)

มีอีกหนึ่งความแตกต่างที่ช่วยลดความสับสนได้มาก การที่พอร์ตเปิดอยู่บนเครื่อง หมายความว่ามีโปรแกรมกำลังรอรับการเชื่อมต่อ (listening) แต่การที่พอร์ตนั้นเข้าถึงได้จากภายนอกหรือไม่ ขึ้นอยู่กับว่าไฟร์วอลล์อนุญาตหรือไม่ ทั้งสองเงื่อนไขต้องเป็นจริงเพื่อให้ไคลเอนต์จากระยะไกลเชื่อมต่อได้ ความล้มเหลวทั้งสองแบบแสดงผลต่างกันจากฝั่งไคลเอนต์: การเชื่อมต่อที่ถูกปฏิเสธ (refused) มักหมายความว่าไม่มีโปรแกรมใดรอรับการเชื่อมต่ออยู่ ในขณะที่การเชื่อมต่อที่ค้างและหมดเวลา (timed out) มักหมายความว่าไฟร์วอลล์ได้ทิ้งแพ็กเก็ตนั้นไป ซึ่งนั่นคือสิ่งที่ ข้อผิดพลาด SSH สองแบบนั้นกำลังบอกคุณ

ดังนั้นจึงมีการควบคุมสองชั้น คุณเป็นผู้กำหนดว่าบริการใดจะรอรับการเชื่อมต่อโดยการตั้งค่า bind address ของแต่ละบริการ และคุณเป็นผู้กำหนดว่าโลกภายนอกจะเข้าถึงอะไรได้บ้างด้วยไฟร์วอลล์ เซิร์ฟเวอร์ที่จัดการอย่างดีจะใช้ทั้งสองอย่าง: บริการจะผูกเฉพาะที่จำเป็น และไฟร์วอลล์จะปฏิเสธทุกอย่างที่คุณไม่ได้อนุญาตไว้อย่างชัดเจน การตรวจสอบว่ามีอะไรกำลังรอรับการเชื่อมต่ออยู่ด้วย ss คือขั้นตอนแรก และการตัดสินใจว่าไฟร์วอลล์อนุญาตอะไรบ้างคือขั้นตอนที่สอง ซึ่งครอบคลุมอยู่ใน พื้นฐานไฟร์วอลล์ด้วย UFW

มีจุดละเอียดอ่อนที่มักทำให้เกิดปัญหาคือ IPv4 และ IPv6 แยกจากกัน และไฟร์วอลล์ที่ครอบคลุมเฉพาะ IPv4 อาจทำให้บริการที่ [::] เปิดค้างอยู่บน IPv6 กับดักเฉพาะจุดนี้มีบทความแยกต่างหากคือ: กับดักไฟร์วอลล์ IPv6

FAQ

ฉันจะดูได้อย่างไรว่าพอร์ตใดเปิดอยู่บนเซิร์ฟเวอร์ Linux ของฉัน?

รัน sudo ss -tlnp สำหรับ TCP หรือ sudo ss -ulnp สำหรับ UDP คำสั่งนี้จะแสดงรายการ socket ทั้งหมดที่กำลังรอรับการเชื่อมต่อ, พอร์ต, local address ที่ผูกอยู่ และกระบวนการที่เป็นเจ้าของ ให้ดูที่คอลัมน์ Local Address: 0.0.0.0 หรือ [::] หมายความว่าบริการนั้นเปิดเผยต่อเครือข่าย ในขณะที่ 127.0.0.1 หมายความว่าบริการนั้นฟังเฉพาะบนเครื่องตัวเองเท่านั้น

0.0.0.0 และ 127.0.0.1 ต่างกันอย่างไร?

0.0.0.0 หมายถึง "ให้ฟังที่ทุก IPv4 address" รวมถึง IP สาธารณะ ดังนั้นบริการจึงสามารถเข้าถึงได้จากเครือข่าย 127.0.0.1 คือ loopback ซึ่งเป็นที่อยู่ที่เครื่องใช้สื่อสารกับตัวเอง และภายใต้การตั้งค่าเริ่มต้นของเคอร์เนล ที่อยู่นี้จะไม่สามารถถูกเข้าถึงจากภายนอกได้ ดังนั้นบริการที่ผูกกับที่อยู่นี้จึงเข้าถึงได้เฉพาะภายในเครื่องเท่านั้น ควรผูกบริการภายในไว้ที่ 127.0.0.1 เพื่อไม่ให้บริการเหล่านั้นออกไปอยู่บนเครือข่าย

ฉันจำเป็นต้องเปิดพอร์ตในไฟร์วอลล์เพื่อให้บริการทำงานได้หรือไม่?

จำเป็นเฉพาะบริการที่ต้องถูกเข้าถึงจากเครื่องอื่นเท่านั้น บริการที่ผูกไว้ที่ 127.0.0.1 ไม่จำเป็นต้องมีกฎอนุญาต เพราะด้วยการตั้งค่าเคอร์เนลเริ่มต้น ไม่มีสิ่งใดจากภายนอกสามารถเข้าถึงได้อยู่แล้ว บริการที่ผูกที่ 0.0.0.0 หรือ [::] จำเป็นต้องมีกฎอนุญาตหากไฟร์วอลล์ของคุณปฏิเสธการเชื่อมต่อขาเข้าเป็นค่าเริ่มต้น (ซึ่งควรจะเป็นเช่นนั้น) หากไม่มีไฟร์วอลล์ทำงานอยู่ หรือนโยบายไฟร์วอลล์ยอมรับทุกอย่าง บริการนั้นก็จะเข้าถึงได้โดยไม่ต้องมีกฎใดๆ

พอร์ต TCP และพอร์ต UDP ต่างกันอย่างไร?

TCP เป็นโปรโตคอลแบบเน้นการเชื่อมต่อและใช้โดยบริการส่วนใหญ่ เช่น SSH, เว็บเซิร์ฟเวอร์ และฐานข้อมูล ส่วน UDP เป็นแบบไม่เน้นการเชื่อมต่อและใช้โดย DNS และ VPN บางประเภท หมายเลขเดียวกันใน TCP และ UDP ถือเป็นคนละพอร์ตกัน ดังนั้น 53/tcp และ 53/udp จึงเป็นคนละพอร์ตกัน เมื่อคุณเขียนกฎไฟร์วอลล์ คุณต้องระบุโปรโตคอลด้วย เช่น 22/tcp