## 引言
端口扫描是最基础的网络信息收集技术之一。通过扫描目标主机的端口,可以了解目标开放了哪些服务、运行了什么版本的软件,进而发现潜在的安全漏洞。
本文将从**开发视角**教你如何用Python编写一个端口扫描器,从**测试视角**讲解如何将其应用于安全评估。
>**法律声明**:端口扫描仅应用于你自己拥有或获得书面授权的目标网络。未经授权对他人网络进行扫描可能违反《中华人民共和国网络安全法》及相关法规。
## 基础知识:什么是端口?
TCP/IP协议中,端口(Port)是应用程序与外部通信的逻辑端点,范围从0到65535。常见端口及其对应服务:
| 端口号 | 协议 | 服务 |
|--------|------|------|
| 21 | TCP | FTP(文件传输) |
| 22 | TCP | SSH(安全远程登录) |
| 23 | TCP | Telnet(不安全远程登录) |
| 25 | TCP | SMTP(邮件发送) |
| 80 | TCP | HTTP(网页服务) |
| 443 | TCP | HTTPS(安全网页服务) |
| 3306 | TCP | MySQL(数据库) |
| 3389 | TCP | RDP(远程桌面) |
## 第一部分:基础端口扫描器(TCP Connect)
最简单的扫描方式是直接尝试连接目标端口。
```python
import socket
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
classPortScanner:
"""端口扫描器 - 基于TCP Connect扫描"""
def__init__(self, target: str, max_workers: int = 100):
"""
初始化扫描器
Args:
target: 目标主机IP地址或域名
max_workers: 最大并发线程数
"""
self.target = target
self.max_workers = max_workers
self.open_ports = [] # 存储发现的开放端口
self.service_map = {
# 常见服务端口映射表
20: "FTP-Data", 21: "FTP", 22: "SSH",
23: "Telnet", 25: "SMTP", 53: "DNS",
80: "HTTP", 110: "POP3", 135: "MSRPC",
139: "NetBIOS", 143: "IMAP", 443: "HTTPS",
445: "SMB", 993: "IMAPS", 995: "POP3S",
1433: "MSSQL", 1521: "Oracle", 3306: "MySQL",
3389: "RDP", 5432: "PostgreSQL", 5900: "VNC",
6379: "Redis", 8080: "HTTP-Proxy", 8443: "HTTPS-Alt",
27017: "MongoDB"
}
def_scan_port(self, port: int, timeout: float = 2.0) -> tuple:
"""
扫描单个端口
Args:
port: 要扫描的端口号
timeout: 连接超时时间(秒)
Returns:
(端口号, 端口状态, 服务名称) 元组
"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((self.target, port))
sock.close()
if result == 0: # 端口开放
service = self.service_map.get(port, f"Unknown-{port}")
return (port, "OPEN", service)
else:
return (port, "CLOSED", None)
except socket.timeout:
return (port, "TIMEOUT", None)
except socket.gaierror:
return (port, "ERROR", "域名解析失败")
exceptExceptionas e:
return (port, "ERROR", str(e))
defscan_range(self, start_port: int = 1, end_port: int = 1024) -> list:
"""
扫描指定范围内的端口
Args:
start_port: 起始端口(默认1)
end_port: 结束端口(默认1024,已知端口范围)
Returns:
开放端口的列表
"""
print(f"[*] 开始扫描目标: {self.target}")
print(f"[*] 端口范围: {start_port} - {end_port}")
print(f"[*] 并发线程数: {self.max_workers}")
print("-" * 50)
start_time = time.time()
self.open_ports = []
# 生成要扫描的端口列表
ports_to_scan = range(start_port, end_port + 1)
# 使用线程池并发扫描
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
future_to_port = {
executor.submit(self._scan_port, port): port
for port in ports_to_scan
}
for future in as_completed(future_to_port):
port, status, service = future.result()
if status == "OPEN":
self.open_ports.append((port, service))
print(f" [+] OPEN : {port:5d} - {service}")
elapsed = time.time() - start_time
self._print_summary(elapsed)
returnself.open_ports
def_print_summary(self, elapsed: float):
"""打印扫描摘要报告"""
print("-" * 50)
print(f"[*] 扫描完成!")
print(f"[*] 耗时: {elapsed:.2f} 秒")
print(f"[*] 发现 {len(self.open_ports)} 个开放端口:")
for port, service insorted(self.open_ports):
print(f" 端口 {port:5d} ({service})")
defscan_top_ports(self, top_n: int = 1000) -> list:
"""
扫描最常见的N个端口(而非连续范围)
效率更高,因为大多数安全端口不会暴露在公网上
Args:
top_n: 扫描常见的前N个端口
Returns:
开放端口的列表
"""
# 常见端口列表(按出现频率排序)
common_ports = [
21, 22, 23, 25, 53, 80, 110, 135, 139, 143,
443, 445, 993, 995, 1433, 1521, 3306, 3389,
5432, 5900, 6379, 8080, 8443, 27017
]
# 补充更多端口
for i inrange(top_n):
iflen(common_ports) >= top_n:
break
port = i + 1
if port notin common_ports:
common_ports.append(port)
ports_to_scan = common_ports[:top_n]
print(f"[*] 快速模式: 扫描 {len(ports_to_scan)} 个常见端口")
print("-" * 50)
start_time = time.time()
self.open_ports = []
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
future_to_port = {
executor.submit(self._scan_port, port): port
for port in ports_to_scan
}
for future in as_completed(future_to_port):
port, status, service = future.result()
if status == "OPEN":
self.open_ports.append((port, service))
print(f" [+] OPEN : {port:5d} - {service}")
elapsed = time.time() - start_time
self._print_summary(elapsed)
returnself.open_ports
defmain():
"""命令行入口函数"""
import sys
import argparse
parser = argparse.ArgumentParser(
description="Python 端口扫描器 - 网络安全实战教程",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例用法:
python port_scanner.py 192.168.1.1 # 扫描常见端口
python port_scanner.py 10.0.0.1 -s 1 -e 65535 # 扫描全部端口
python port_scanner.py scanme.nmap.org -w 50 # 降低并发
警告: 仅扫描您拥有授权的目标!
"""
)
parser.add_argument("target", help="目标主机IP地址或域名")
parser.add_argument("-s", "--start", type=int, default=1,
help="起始端口 (默认: 1)")
parser.add_argument("-e", "--end", type=int, default=1024,
help="结束端口 (默认: 1024)")
parser.add_argument("-w", "--workers", type=int, default=100,
help="并发线程数 (默认: 100)")
parser.add_argument("--fast", action="store_true",
help="快速模式:只扫描常见端口")
parser.add_argument("-o", "--output", type=str,
help="输出结果到文件")
args = parser.parse_args()
scanner = PortScanner(args.target, max_workers=args.workers)
if args.fast:
open_ports = scanner.scan_top_ports()
else:
open_ports = scanner.scan_range(args.start, args.end)
# 可选:保存到文件
if args.output:
withopen(args.output, "w") as f:
f.write(f"# 端口扫描结果\n")
f.write(f"# 目标: {args.target}\n")
f.write(f"# 时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
for port, service in open_ports:
f.write(f"{port},{service}\n")
print(f"\n[*] 结果已保存到: {args.output}")
if__name__ == "__main__":
main()
```
## 第二部分:进阶 - TCP SYN扫描(半开放扫描)
TCP Connect扫描速度快,但容易被防火墙日志记录。**SYN扫描**(又称半开放扫描)只发送SYN包,不建立完整的三次握手,更加隐蔽。
```python
import struct
import ipaddress
classSYNScanner:
"""
TCP SYN 扫描器(半开放扫描)
原理:
1. 发送 TCP SYN 数据包到目标端口
2. 如果收到 SYN-ACK,说明端口开放
3. 收到 RST,说明端口关闭
4. 不发送最后的 ACK,保持扫描的隐蔽性
"""
def__init__(self, target: str):
self.target = target
self.ip_address = str(ipaddress.IPv4Address(target))
self.open_ports = []
@staticmethod
def_calculate_checksum(header: bytes) -> int:
"""计算TCP伪首部校验和"""
iflen(header) % 2 != 0:
header += b'\x00'
checksum = 0
for i inrange(0, len(header), 2):
word = (header[i] << 8) + header[i + 1]
checksum += word
checksum &= 0xFFFFFFFF
while checksum > 0xFFFF:
checksum = (checksum & 0xFFFF) + (checksum >> 16)
return ~checksum & 0xFFFF
defscan(self, port: int) -> str:
"""
扫描单个端口的SYN状态
Args:
port: 目标端口号
Returns:
端口状态: 'OPEN', 'CLOSED', 或 'FILTERED'
"""
try:
# 创建原始套接字(需要root权限)
raw_socket = socket.socket(
socket.AF_INET,
socket.SOCK_RAW,
socket.IPPROTO_TCP
)
raw_socket.settimeout(1.0)
# 构造TCP头部
tcp_header = self._build_tcp_header(port)
# 发送SYN包
raw_socket.sendto(
tcp_header,
(self.target, port)
)
# 接收响应
response, _ = raw_socket.recvfrom(1024)
tcp_response = response[20:40] # 跳过IP头部
# 分析响应
flags = tcp_response[13]
if flags & 0x12: # SYN-ACK 或 RST-ACK
return"OPEN"if flags & 0x02else"CLOSED"
elif flags & 0x14: # RST-ACK
return"CLOSED"
else:
return"FILTERED"
except socket.timeout:
return"FILTERED"
exceptPermissionError:
print("[!] 需要root/administrator权限运行SYN扫描")
return"PERMISSION_DENIED"
exceptExceptionas e:
returnf"ERROR: {e}"
finally:
raw_socket.close()
def_build_tcp_header(self, dest_port: int) -> bytes:
"""构建TCP头部"""
src_port = 54321# 随机源端口
seq_num = 1234567
ack_num = 0
# TCP头部字段
header = struct.pack(
'!HHLLBBHHH',
src_port, # 源端口
dest_port, # 目标端口
seq_num, # 序列号
ack_num, # 确认号
5 << 4, # 数据偏移 (5个32位字)
0x02, # SYN标志
0xFFFF, # 窗口大小
0, # 校验和(稍后计算)
0# 紧急指针
)
# 计算伪首部校验和
pseudo_header = struct.pack(
'!IBB4s',
socket.inet_aton(self.ip_address)[0], # 源IP
0, # 填充
socket.IPPROTO_TCP, # 协议
struct.pack('!H', len(header)) # TCP长度
)
checksum = self._calculate_checksum(pseudo_header + header)
header = header[:16] + struct.pack('!H', checksum) + header[18:]
return header
```
## 第三部分:端口扫描的应用场景
### 测试视角:安全评估流程
```python
defsecurity_assessment(target: str, output_file: str = None):
"""
安全评估流程示例
完整流程:
1. 发现存活主机(Ping扫描)
2. 端口扫描(识别开放服务)
3. 版本探测(确定软件版本)
4. 漏洞关联(匹配已知CVE)
5. 生成报告
"""
scanner = PortScanner(target)
# 第一步:快速扫描常见端口
print("=" * 60)
print("阶段1: 快速端口扫描")
print("=" * 60)
open_ports = scanner.scan_top_ports(1000)
# 第二步:对关键端口进行版本探测
print("\n" + "=" * 60)
print("阶段2: 服务版本探测")
print("=" * 60)
for port, service in open_ports:
version = probe_version(target, port)
print(f" {port}/{service} -> {version}")
# 第三步:关联漏洞数据库
print("\n" + "=" * 60)
print("阶段3: 漏洞关联分析")
print("=" * 60)
vulns = check_vulnerabilities(open_ports)
if vulns:
for vuln in vulns:
print(f" ⚠️ {vuln['severity']}: {vuln['description']}")
else:
print(" [+] 未发现已知漏洞")
# 第四步:生成报告
if output_file:
generate_report(output_file, open_ports, vulns)
print(f"\n[*] 报告已生成: {output_file}")
return open_ports, vulns
defprobe_version(target: str, port: int) -> str:
"""简单的服务版本探测"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2.0)
sock.connect((target, port))
banner = ""
try:
banner = sock.recv(1024).decode('utf-8', errors='ignore')[:50]
except (socket.timeout, ConnectionResetError):
pass
finally:
sock.close()
returnf"Banner: {banner.strip()}"if banner else"No banner available"
exceptExceptionas e:
returnf"Error: {e}"
defcheck_vulnerabilities(open_ports: list) -> list:
"""
检查端口对应的已知漏洞
注意: 实际环境中应接入 CVE/NVD 数据库或漏洞扫描工具
这里仅提供简化的示例映射
"""
known_vulns = {
21: [
{
"cve": "CVE-2021-xxx",
"severity": "HIGH",
"description": "FTP服务认证绕过漏洞"
}
],
23: [
{
"cve": "通用风险",
"severity": "MEDIUM",
"description": "Telnet未加密传输敏感信息"
}
],
3306: [
{
"cve": "常见风险",
"severity": "HIGH",
"description": "MySQL远程访问可能导致未授权访问"
}
],
6379: [
{
"cve": "CVE-2022-xxx",
"severity": "CRITICAL",
"description": "Redis无认证访问可导致代码执行"
}
],
27017: [
{
"cve": "常见风险",
"severity": "HIGH",
"description": "MongoDB默认无认证绑定0.0.0.0"
}
]
}
results = []
for port, service in open_ports:
if port in known_vulns:
results.extend(known_vulns[port])
return results
```
## 第四部分:实战练习
### 练习1:完善端口扫描器
为你的端口扫描器添加以下功能:
- 支持UDP端口扫描(UDP是无连接的,扫描逻辑有所不同)
- 添加进度条显示(可以使用`tqdm`库)
- 支持从文件读取目标IP列表进行批量扫描
### 练习2:端口扫描检测器
编写一个程序来检测是否正在遭受端口扫描:
- 监听网络接口上的SYN包
- 统计来自同一源IP的不同目标端口数量
- 如果超过阈值(如10个端口/秒),记录并告警
### 练习3:端口指纹识别
改进你的扫描器,使其能识别服务类型:
- 连接端口后发送特定协议的请求(如HTTP的GET请求)
- 根据响应特征判断服务器类型(Apache/Nginx/IIS等)
- 尝试获取服务器的精确版本号
### 练习4:构建扫描引擎框架
设计一个可扩展的扫描引擎:
- 定义统一的扫描器接口
- 实现多种扫描策略(TCP Connect、SYN、FIN、NULL、XMAS)
- 支持动态选择扫描策略
## 第五部分:避坑指南
### 常见问题
1.**权限问题**:SYN扫描需要原始套接字权限,在Linux上通常需要root
2.**速度控制**:过快扫描会被防火墙拦截,建议设置合理的超时和并发
3.**防火墙干扰**:某些端口可能被防火墙丢弃(DROP),导致超时而非拒绝
4.**目标漂移**:大规模扫描时可能出现结果错乱,需要妥善处理并发
5.**法律风险**:务必确认你有权限扫描目标
### 性能优化技巧
| 技巧 | 说明 |
|------|------|
| 端口优先级排序 | 先扫常见高危端口,提高检出率 |
| 智能并发控制 | 根据目标响应调整并发线程数 |
| 结果去重缓存 | 同一目标的扫描结果可缓存复用 |
| 分段扫描 | 大批量目标分批扫描,避免耗尽资源 |
## 总结
本篇教程涵盖了:
- ✅ TCP Connect端口扫描器的完整实现
- ✅ 线程池并发优化
- ✅ TCP SYN半开放扫描原理
- ✅ 服务版本探测方法
- ✅ 漏洞关联分析流程
- ✅ 安全评估实战框架
掌握了端口扫描器后,你可以进一步探索:
- 更隐蔽的扫描方式(FIN扫描、XMAS扫描)
- 更高效的协议(使用asyncio异步IO)
- 更智能的扫描策略(自适应端口优先级)
下一节我们将讨论:**目录遍历扫描器**——发现网站隐藏的文件和目录。
---
> ⚖️ **版权声明**:本教程仅供学习和研究用途。使用本文所述技术进行网络攻击属于违法行为,请务必遵守相关法律法规。