ทำไมถึงอยากเขียน port scanner?
การเขียน port scanner เป็นวิธีดีๆ เรียนรู้พื้นฐานของโปรโตคอล TCP ซึ่งเป็น transport layer ที่โปรโตคอลอินเทอร์เน็ตส่วนใหญ่ใช้ (รวม HTTP และ SSH)
มันยังเป็น exercise ดีสำหรับเรียนรู้ network programming ใน Ruby
มาเริ่มด้วยการพูดถึง port!
Port คืออะไร?
พูดถึง port แล้วเราพูดถึงอะไรอยู่ดี? Port ที่ระดับ OS คือแค่ "file descriptor" ที่เชื่อมโยงกับ process
File descriptor คือตัวเลขที่ใช้ reference ไปยัง I/O channel ที่เปิดอยู่ เช่น stdout (standard output), network socket หรือไฟล์
เมื่อ OS รับ TCP/IP packet มันจะดู destination port และพยายามหา process ที่กำลัง listen บน port นั้น
ถ้ามี process ที่ listen packet จะถูกส่งต่อไป
ช่วงของ Port (Port Ranges)
มี port ทั้งหมด 65,535 port แต่ในทางปฏิบัติไม่ได้ใช้ทั้งหมด
Port แบ่งได้ 3 กลุ่ม:
| ช่วง | ชื่อ | ตัวอย่าง |
|---|---|---|
1-1023 |
Well-known ports | 22 (SSH), 80 (HTTP), 443 (HTTPS) |
1024-49151 |
Registered ports | 3306 (MySQL), 5432 (PostgreSQL) |
49152-65535 |
Ephemeral ports | Chrome, Firefox |
Port ในช่วงที่ 2 ถูกจัดสรรโดย IANA (Internet Assigned Numbers Authority)
ช่วงที่ 3 ใช้สำหรับ port "dynamic" หรือ "ephemeral" คือ port ที่ฝั่ง client ใช้รับข้อมูลจาก server
พื้นฐานการสื่อสาร TCP
ตอนนี้คุณคุ้นเคยกับ port แล้ว แต็ยังไม่พร้อมเขียน port scanner
ก่อนหน้านี้ ต้องเข้าใจว่าการที่ port "เปิด" หมายถึงอะไร จากนั้นตรวจสอบพฤติกรรมของ port เปิดและ port ปิดที่ระดับเครือข่าย เพื่อแยกแยะได้
ล่ะจะรู้ได้ไงว่า port เปิดอยู่?
Port เปิดหมายถึงมีแอปพลิเคชัน listen อีกฝั่ง และเราเข้าถึงได้ (ไม่ถูกบล็อกโดย firewall)
มา看看 TCP connection ใหม่เริ่มต้นอย่างไร

TCP connection ใหม่เริ่มด้วย packet SYN ซึ่งเป็นสัญลักษณ์การเริ่ม connection ใหม่
มี 3 ผลลัพธ์ที่เป็นไปได้:
- Server ตอบด้วย
SYN/ACK— หมายถึงพร้อมสร้าง connection - Server ตอบด้วย packet
RST— หมายถึง connection ถูกปฏิเสธ - Server ไม่ตอบเลย (บาง firewall ทำแบบนี้ เรียกว่า DROP policy)
ถ้า client รับ SYN/ACK ก็สามารถสร้าง connection เสร็จโดยส่ง packet ACK
สิ่งนี้เรียกว่า "three-way handshake"
หมายเหตุ: วิธีดูสิ่งนี้เกิดขึ้นจริงคือใช้เครื่องมือเครือข่ายอย่าง tcpdump หรือ wireshark ทำให้เห็นทุก packet ที่เข้า-ออกระบบ
ตัวอย่าง connection จาก tshark (command-line interface ของ wireshark):

ตอนนี้คุณเข้าใจพื้นฐานการสร้าง TCP connection แล้ว มาเขียน port scanner 簡単กัน
มาเขียน Port Scanner กัน!
วิธีง่ายที่สุดคือเปิด TCP connection ใหม่ด้วย TCPSocket` แล้วพึ่งพาข้อเท็จจริงว่า connection ที่ถูกปฏิเสธจะ raise exception Errno::ECONNREFUSED
โค้ด:
require 'socket'
PORT = ARGV[0] || 22
HOST = ARGV[1] || 'localhost'
begin
socket = TCPSocket.new(HOST, PORT)
status = "open"
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
status = "closed"
end
puts "Port #{PORT} is #{status}."
โค้ดนี้มีข้อจำกัด:
สแกนได้ทีละ port แต่บ่อยครั้งอยากสแกนช่วง port เพื่อดู service ที่เปิดอยู่บน host
ข้อจำกัดอีกอย่าง: port ที่ไม่ตอบสนอง (ผลลัพธ์ที่ 3) จะทำให้รอประมาณ 20 วินาทีจน timeout
แก้ได้ด้วย combo ของ connect_nonblock, IO.select และ Socket (แทน TCPSocket ที่ connect ทันทีเมื่อสร้าง object)
ตัวอย่าง:
require 'socket'
TIMEOUT = 2
def scan_port(port)
socket = Socket.new(:INET, :STREAM)
remote_addr = Socket.sockaddr_in(port, 'www.example.com')
begin
socket.connect_nonblock(remote_addr)
rescue Errno::EINPROGRESS
end
_, sockets, _ = IO.select(nil, [socket], nil, TIMEOUT)
if sockets
p "Port #{port} is open"
else
# Port is closed
end
end
PORT_LIST = [21,22,23,25,53,80,443,3306,8080]
threads = []
PORT_LIST.each { |i| threads << Thread.new { scan_port(i) } }
threads.each(&:join)
IO.select จะรอจนกว่า socket จะพร้อมรับข้อมูล (หมายถึงเปิด) หรือจนกว่า TIMEOUT จะหมด แล้วค่อยสรุปว่า port ปิดหรือไม่ตอบ
ทั้งหมดนี้ดีสำหรับการเรียนรู้ แต่ถ้าต้องการ port scanner จริง ควรใช้ nmap
Nmap เป็น open source และพัฒนามามากกว่า 15 ปี
นี่คือ output ของ nmap:

ข้อมูลเยอะมาก ลองเขียนคอมเมนต์ 2 สิ่งที่คุณเรียนรู้ใหม่จากบทความนี้ 🙂
สรุป
ในบทความนี้ คุณเรียนรู้ว่า port คืออะไร ช่วง port ต่างๆ การสร้าง TCP connection (three-way handshake) และการเขียน port scanner แบบพื้นฐานด้วย Ruby
ถ้าชอบบทความนี้ อย่าลืมแชร์ให้เพื่อนๆ ได้อ่านด้วยนะครับ 🙂