พอร์ตคืออะไรและวิธีตรวจสอบ Linux Service ที่กำลังรันอยู่
พอร์ตคือช่องทางรับส่งข้อมูลของเซิร์ฟเวอร์ เรียนรู้วิธีใช้คำสั่ง ss เพื่อตรวจสอบว่ามีบริการใดกำลังเปิดพอร์ตใช้งานอยู่บ้าง พร้อมวิธีแยกแยะพอร์ตภายในและพอร์ตสาธารณะอย่างถูกต้อง
พอร์ตคืออะไรในทางปฏิบัติ
พอร์ตคือตัวเลขที่ช่วยให้เซิร์ฟเวอร์หนึ่งเครื่องสามารถรันหลายบริการพร้อมกันได้โดยที่ข้อมูลไม่ปะปนกัน VPS ของคุณมี IP address เพียงหนึ่งหมายเลข แต่อาจกำลังรันเว็บเซิร์ฟเวอร์, SSH server และฐานข้อมูลไปพร้อมกัน เมื่อมีแพ็กเก็ตข้อมูลเข้ามา ระบบปฏิบัติการจำเป็นต้องทราบว่าข้อมูลนั้นมีไว้สำหรับบริการใด หมายเลขพอร์ตคือคำตอบของเรื่องนี้ ทราฟฟิกของเว็บจะถูกส่งไปยังพอร์ต 443 ส่วน SSH จะไปที่พอร์ต 22 ตัว IP address ทำหน้าที่นำแพ็กเก็ตมาส่งถึงเซิร์ฟเวอร์ของคุณ ส่วนพอร์ตทำหน้าที่นำส่งข้อมูลไปยังโปรแกรมที่ถูกต้องบนเซิร์ฟเวอร์นั้น
พอร์ตเป็นตัวเลขขนาด 16-bit ซึ่ง 16 bits ให้ค่าตั้งแต่ 0 ถึง 65535 โดยที่ 0 ถูกสงวนไว้ ดังนั้นในทางปฏิบัติพอร์ตจึงมีตั้งแต่ 1 ถึง 65535 พอร์ตที่มีหมายเลขต่ำกว่า 1024 เรียกว่า "well-known" ports และต้องใช้สิทธิ์ root ในการเปิดใช้งาน ซึ่งเป็นเหตุผลว่าทำไมบริการมาตรฐานจึงทำงานที่พอร์ตเหล่านี้ เช่น 22 สำหรับ SSH, 80 สำหรับ HTTP, 443 สำหรับ HTTPS และ 53 สำหรับ DNS ส่วนพอร์ตที่สูงกว่า 1024 สามารถใช้งานได้โดยโปรแกรมทั่วไป
พอร์ตมีสองประเภทคือ TCP และ UDP โดยหมายเลขพอร์ตของประเภทหนึ่งจะแยกจากหมายเลขเดียวกันในอีกประเภทหนึ่ง TCP เป็นโปรโตคอลแบบเชื่อมต่อที่บริการส่วนใหญ่ใช้งาน เช่น SSH, HTTP และฐานข้อมูล ส่วน UDP เป็นแบบไม่เชื่อมต่อและใช้สำหรับงานอย่าง DNS และ VPN บางประเภท เมื่อคุณเปิดใช้งาน firewall คุณมักจะต้องระบุประเภทของพอร์ตด้วย เช่น 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 ที่เครื่องนี้มี" รวมถึงที่อยู่สาธารณะด้วย บริการนี้จะสามารถเข้าถึงได้จากอินเทอร์เน็ตผ่าน IPv4 หาก firewall อนุญาต
[::]:80 หมายถึงกรณีเดียวกันสำหรับ IPv6 คือให้ฟังที่ทุก IPv6 address รวมถึงที่อยู่สาธารณะ โปรแกรมจำนวนมากผูกกับที่อยู่นี้เป็นค่าเริ่มต้น และบน Linux นั้น socket แบบ :: มักจะยอมรับการเชื่อมต่อ IPv4 ด้วยเช่นกัน
127.0.0.1:5432 หมายถึง "ให้ฟังที่ loopback เท่านั้น" 127.0.0.1 คือที่อยู่ที่เครื่องใช้เพื่อสื่อสารกับตัวเอง และไม่สามารถกำหนดเส้นทาง (routable) มาจากที่อื่นได้ บริการที่ผูกไว้ที่นี่จะเข้าถึงได้จากเซิร์ฟเวอร์เดียวกันเท่านั้น ไม่สามารถเข้าถึงได้จากอินเทอร์เน็ตไม่ว่า firewall ของคุณจะตั้งค่าไว้อย่างไรก็ตาม บรรทัดของ Postgres ด้านบนจึงมีความปลอดภัยโดยโครงสร้างของมันเอง
กฎในทางปฏิบัติที่ได้จากเรื่องนี้คือ: ฐานข้อมูล, แคช หรือแผงควบคุมผู้ดูแลระบบที่แอปพลิเคชันของคุณใช้งานเองเท่านั้น ควรผูกไว้ที่ 127.0.0.1 ไม่ใช่ 0.0.0.0 หากบริการไม่เคยฟังที่ที่อยู่สาธารณะ ก็จะไม่มีช่องทางให้ผู้โจมตีเข้าถึงได้เลย
การฟังพอร์ตกับการเข้าถึงได้จริงนั้นต่างกัน
ข้อแตกต่างอีกประการหนึ่งช่วยลดความสับสนได้มาก การที่พอร์ตเปิดอยู่บนเครื่อง หมายความว่ามีโปรแกรมกำลังรอรับการเชื่อมต่อ (listening) ซึ่งต่างจากการที่พอร์ตนั้นสามารถเข้าถึงได้จากภายนอก ซึ่งหมายความว่าไฟร์วอลล์อนุญาตให้ผ่านได้ ทั้งสองเงื่อนไขต้องเป็นจริงเพื่อให้ไคลเอนต์จากระยะไกลเชื่อมต่อได้ ความล้มเหลวทั้งสองรูปแบบนี้แสดงผลต่างกันในฝั่งไคลเอนต์: การเชื่อมต่อที่ถูกปฏิเสธ (refused) หมายความว่าไม่มีโปรแกรมใดรอรับการเชื่อมต่ออยู่ ในขณะที่การเชื่อมต่อที่ค้างและหมดเวลา (timed out) มักหมายความว่าไฟร์วอลล์ทิ้งแพ็กเก็ตนั้นไป ซึ่งเป็นสิ่งที่ ข้อผิดพลาด SSH สองแบบนั้นกำลังบอกคุณอยู่
ดังนั้นจึงมีการควบคุมสองชั้น คุณเป็นผู้กำหนดว่าอะไรจะรอรับการเชื่อมต่อ โดยการตั้งค่า bind address ของแต่ละบริการ และคุณเป็นผู้กำหนดว่าโลกภายนอกจะเข้าถึงอะไรได้บ้างด้วยไฟร์วอลล์ เซิร์ฟเวอร์ที่จัดการอย่างดีจะใช้ทั้งสองอย่าง: บริการจะ bind เฉพาะจุดที่จำเป็นเท่านั้น และไฟร์วอลล์จะปฏิเสธทุกอย่างที่คุณไม่ได้อนุญาตไว้อย่างชัดเจน การตรวจสอบว่ามีอะไรกำลังรอรับการเชื่อมต่ออยู่ด้วย ss คือขั้นตอนแรก ส่วนการตัดสินใจว่าไฟร์วอลล์จะอนุญาตอะไรบ้างคือขั้นตอนที่สอง ซึ่งครอบคลุมอยู่ใน พื้นฐานไฟร์วอลล์ด้วย UFW
มีรายละเอียดเล็กน้อยที่มักทำให้ผู้ใช้พลาดในจุดนี้ IPv4 และ IPv6 นั้นแยกจากกัน และไฟร์วอลล์ที่ครอบคลุมเฉพาะ IPv4 จะปล่อยให้บริการ [::] เปิดรับการเชื่อมต่อบน IPv6 ได้ กับดักเฉพาะจุดนี้มีบทความแยกต่างหาก: กับดักไฟร์วอลล์ IPv6
FAQ
ฉันจะดูได้อย่างไรว่าพอร์ตใดเปิดอยู่บนเซิร์ฟเวอร์ Linux ของฉัน?
ให้รันคำสั่ง sudo ss -tlnp สำหรับ TCP หรือ sudo ss -ulnp สำหรับ UDP คำสั่งเหล่านี้จะแสดงรายการ socket ที่กำลังรอรับการเชื่อมต่อ (listening) ทั้งหมด รวมถึงพอร์ต, ที่อยู่ภายใน (local address) ที่ผูกอยู่ และกระบวนการ (process) ที่เป็นเจ้าของ ให้ดูที่คอลัมน์ 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 ซึ่งเป็นที่อยู่ที่เครื่องใช้สื่อสารกับตัวเอง และไม่สามารถกำหนดเส้นทาง (route) มาจากที่อื่นได้ ดังนั้นบริการที่ผูกกับที่อยู่นี้จะเข้าถึงได้จากภายในเครื่องเท่านั้น ควรผูกบริการภายในเข้ากับ 127.0.0.1 เพื่อไม่ให้ถูกเปิดเผยสู่ภายนอก
ฉันจำเป็นต้องเปิดพอร์ตในไฟร์วอลล์เพื่อให้บริการทำงานได้หรือไม่?
จำเป็นเฉพาะบริการที่ต้องเข้าถึงได้จากเครื่องอื่นเท่านั้น บริการที่ผูกกับ 127.0.0.1 ไม่จำเป็นต้องมีกฎไฟร์วอลล์เพราะไม่มีสิ่งใดจากภายนอกเข้าถึงได้อยู่แล้ว ส่วนบริการที่อยู่บน 0.0.0.0 หรือ [::] จำเป็นต้องมีกฎไฟร์วอลล์เพื่ออนุญาตให้ทราฟฟิกผ่านเข้ามาได้ และควรตั้งค่าปฏิเสธการเชื่อมต่อไว้เป็นค่าเริ่มต้นจนกว่าคุณจะเพิ่มกฎอนุญาต
พอร์ต TCP และพอร์ต UDP แตกต่างกันอย่างไร?
TCP เป็นโปรโตคอลแบบเน้นการเชื่อมต่อ (connection-based) ซึ่งบริการส่วนใหญ่ใช้งาน เช่น SSH, เว็บเซิร์ฟเวอร์ และฐานข้อมูล ส่วน UDP เป็นโปรโตคอลแบบไม่เน้นการเชื่อมต่อ (connectionless) ซึ่งใช้โดย DNS และ VPN บางประเภท พอร์ตหมายเลขเดียวกันใน TCP และ UDP ถือเป็นคนละพอร์ตกัน ดังนั้น 53/tcp และ 53/udp จึงเป็นคนละพอร์ต เมื่อคุณเขียนกฎไฟร์วอลล์ คุณต้องระบุโปรโตคอลด้วย เช่น 22/tcp