某公司因未关闭端口被入侵 端口扫描实用技巧与系统漏洞防护指南
一场本可避免的灾难
2023年夏天,一家中型金融科技公司经历了噩梦般的48小时。黑客通过一个从未关闭的3389端口(RDP远程桌面)入侵了他们的内网,窃取了客户数据并勒索了巨额比特币。事后调查发现,这个问题早在三个月前的一次安全扫描中就已经被标记过,但由于运维团队忙于其他项目,这个”小问题”被无限期搁置。
这类事件每天都在发生。根据Verizon数据泄露调查报告,超过60%的入侵事件与暴露的端口和服务有关。今天,我们就来聊聊端口扫描的那些事儿,以及如何真正保护好你的系统。
端口是什么?用现实例子来理解
想象一下你的公司大楼有很多扇门——正门、侧门、消防通道、地下室入口、空调通风口……正常情况下,你只希望员工从正门进出,其他门都应该锁着。
端口就像这些门:
- 22端口(SSH):工程师的专用通道
- 80端口(HTTP):访客可以从这里进入网站
- 443端口(HTTPS):加密的安全通道
- 3306端口(MySQL):数据库的内部通道
- 3389端口(RDP):Windows远程桌面通道
问题出在哪里?很多公司就像那家出事的企业一样——所有门都开着,甚至包括一些根本不该存在的后门。
端口扫描:进攻方的视角
理解扫描的基本原理
端口扫描的本质就是”敲门”——向目标的各个端口发送探测数据包,观察哪些端口有回应,从而判断开放了哪些服务。
实用扫描工具
Nmap——扫描界的瑞士军刀
# 基础扫描:探测目标的主机和端口
nmap 192.168.1.1
# 扫描常见端口(默认1000个)
nmap -sS 192.168.1.1
# 扫描全部65535个端口
nmap -p- 192.168.1.1
# SYN扫描(隐蔽扫描,不完成三次握手)
nmap -sS -p 22,80,443,3389,8080 192.168.1.1
# 检测服务版本
nmap -sV -p 22,80,443 192.168.1.1
# 操作系统指纹识别
nmap -O 192.168.1.1
Python脚本:自己写一个扫描器
有时候,现成的工具不够灵活,或者你需要集成到自己的工作流程中。下面这个Python脚本可以实现基本的TCP端口扫描:
#!/usr/bin/env python3
"""
简易TCP端口扫描器
学习用途,请勿用于非法目的
"""
import socket
import threading
import argparse
from concurrent.futures import ThreadPoolExecutor
import time
class PortScanner:
def __init__(self, target, ports=None, threads=100):
self.target = target
# 默认扫描常用端口
self.ports = ports or [21, 22, 23, 25, 53, 80, 110, 135, 139,
443, 445, 993, 995, 1433, 3306, 3389,
5432, 5900, 8080, 8443, 9090]
self.threads = threads
self.open_ports = []
self.results = {}
def scan_port(self, port):
"""扫描单个端口"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.5) # 每个端口最多等待0.5秒
result = sock.connect_ex((self.target, port))
if result == 0:
return port, self.get_service_name(port)
return None, None
except Exception as e:
return None, None
finally:
sock.close()
def get_service_name(self, port):
"""根据端口号返回常见服务名称"""
service_map = {
21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP",
53: "DNS", 80: "HTTP", 110: "POP3", 135: "MSRPC",
139: "NetBIOS", 443: "HTTPS", 445: "SMB", 993: "IMAPS",
995: "POP3S", 1433: "MSSQL", 3306: "MySQL", 3389: "RDP",
5432: "PostgreSQL", 5900: "VNC", 8080: "HTTP-Proxy",
8443: "HTTPS-Alt", 9090: "WebADM"
}
return service_map.get(port, "Unknown")
def scan(self):
"""并发扫描所有端口"""
start_time = time.time()
print(f"[*] 开始扫描目标: {self.target}")
print(f"[*] 扫描端口数量: {len(self.ports)}")
print(f"[*] 使用线程数: {self.threads}")
print("=" * 50)
# 使用线程池并发扫描
with ThreadPoolExecutor(max_workers=self.threads) as executor:
futures = {executor.submit(self.scan_port, port): port
for port in self.ports}
for future in ThreadPoolExecutor.as_completed(futures):
port, service = future.result()
if port:
self.open_ports.append(port)
self.results[port] = service
print(f"[!] 发现开放端口: {port}/{service}")
elapsed = time.time() - start_time
print("=" * 50)
print(f"[*] 扫描完成!耗时: {elapsed:.2f}秒")
print(f"[*] 共发现 {len(self.open_ports)} 个开放端口")
if self.open_ports:
print("\n开放端口列表:")
for port in sorted(self.open_ports):
service = self.results.get(port, "Unknown")
print(f" {port:5d} {service}")
return self.results
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="TCP端口扫描器")
parser.add_argument("target", help="目标IP地址或域名")
parser.add_argument("-p", "--ports", type=int, nargs="+",
help="指定要扫描的端口")
parser.add_argument("-t", "--threads", type=int, default=100,
help="线程数(默认100)")
args = parser.parse_args()
scanner = PortScanner(args.target, args.ports, args.threads)
scanner.scan()
运行示例:
python scanner.py 192.168.1.100 -p 22 80 443 3389 8080
扫描完之后呢?如何发现真正的风险?
扫描发现开放端口只是第一步。真正的风险在于:开放的是什么服务、版本是否有漏洞、是否暴露在公网上。
常见危险端口速查表
| 端口 | 服务 | 风险等级 | 说明 |
|---|---|---|---|
| 21 | FTP | 高 | 明文传输,容易被爆破 |
| 23 | Telnet | 极高 | 明文协议,几乎不应该存在 |
| 135 | MSRPC | 高 | Windows远程执行漏洞高发区 |
| 139⁄445 | SMB | 极高 | 永恒之蓝等著名漏洞的入口 |
| 3389 | RDP | 高 | 容易被暴力破解 |
| 3306 | MySQL | 中 | 不应直接暴露在公网 |
| 5432 | PostgreSQL | 中 | 同上 |
| 5900 | VNC | 高 | 远程桌面,安全配置往往很弱 |
| 8080⁄8443 | 应用端口 | 中 | 可能是管理后台或API |
防御方的武器库
1. 最小化开放端口——最根本的防护
那家出事的公司,如果他们在服务器配置阶段就只开放必要的端口,整个悲剧都可以避免。
检查 Linux 系统开放端口的命令:
# 查看所有监听端口
sudo netstat -tlnp
# 更现代的替代方案(推荐)
sudo ss -tlnp
# 查看防火墙规则
sudo iptables -L -n -v
sudo ufw status verbose
示例输出解读:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN 5678/mysqld
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 9012/nginx
注意看:SSH(22)监听在所有接口(0.0.0.0),而MySQL(3306)只监听在本地。这是正确的做法!
2. 防火墙配置——你的第一道防线
UFW(Ubuntu防火墙)示例:
# 安装UFW
sudo apt install ufw
# 默认策略:拒绝所有入站,允许所有出站
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 只允许SSH(记得先设置!否则会把自己锁在外面)
sudo ufw allow from 10.0.0.0/8 proto tcp to any port 22
# 允许Web服务
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 禁止MySQL对外暴露
sudo ufw deny 3306/tcp
# 启用防火墙
sudo ufw enable
iptables高级配置——精确控制:
# 只允许特定IP访问SSH
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
# 允许HTTP/HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 丢弃所有其他入站连接
iptables -A INPUT -j DROP
3. 内网服务不要暴露在公网
很多公司犯的致命错误:把数据库、Redis、MongoDB等服务直接绑在0.0.0.0上,让公网可以直接访问。
正确的做法:
# MySQL配置(/etc/mysql/mysql.conf.d/mysqld.cnf)
# 只监听本地
bind-address = 127.0.0.1
# 或者只监听内网IP
bind-address = 192.168.1.100
# Redis配置(/etc/redis/redis.conf)
# 绑定本地
bind 127.0.0.1
# 禁用危险命令(防止Redis被利用)
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command EVAL ""
4. 定期扫描——像体检一样重要
不要等到出事才想起来检查。建议建立以下扫描机制:
#!/bin/bash
# 每日端口扫描脚本(可加入crontab)
TARGET=$(hostname -I | awk '{print $1}')
LOG_FILE="/var/log/port-scan-$(date +%Y%m%d).log"
PREV_LOG="/var/log/port-scan-$(date -d yesterday +%Y%m%d).log"
echo "=== 端口扫描报告 $(date) ===" > $LOG_FILE
# 检查所有监听端口
echo "[监听端口列表]" >> $LOG_FILE
ss -tlnp >> $LOG_FILE
# 检查是否有新增的异常端口
echo "" >> $LOG_FILE
echo "[安全检查]" >> $LOG_FILE
# 检查是否开放了危险端口
for port in 23 135 445 3389 5900; do
if ss -tlnp | grep -q ":${port} "; then
echo "[警告] 发现危险端口 ${port} 开放!" >> $LOG_FILE
fi
done
# 检查Telnet是否运行
if systemctl is-active --quiet telnet.socket; then
echo "[警告] Telnet服务正在运行!" >> $LOG_FILE
fi
# 如果有前一天的日志,对比差异
if [ -f "$PREV_LOG" ]; then
echo "" >> $LOG_FILE
echo "[端口变化检测]" >> $LOG_FILE
diff <(grep "LISTEN" $PREV_LOG) <(grep "LISTEN" $LOG_FILE) >> $LOG_FILE 2>&1
fi
添加定时任务:
# 每天凌晨2点自动扫描
crontab -e
0 2 * * * /usr/local/bin/daily-port-scan.sh
5. 入侵检测——你的监控摄像头
使用 OSSEC 或 Wazuh 进行实时监控:
# 安装Wazuh agent
curl -so /tmp/wazuhagent.sh https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/wazuh-agent_4.7.0-1_amd64.deb
sudo apt install ./wazuh-agent_*.deb
# 配置监听SSH登录
cat >> /var/ossec/etc/ossec.conf << 'EOF'
<localfile>
log_format=syslog
location=/var/log/auth.log
</localfile>
EOF
# 重启服务
sudo systemctl restart wazuh-agent
实战:从被入侵到快速恢复
回到开头的那家公司,如果他们做了这些,损失可以避免:
入侵前的防御清单:
□ 仅开放业务必需端口(80, 443, 22)
□ 22端口限制特定IP访问
□ 所有内网服务绑定127.0.0.1
□ 防火墙拒绝所有其他入站连接
□ 启用SSH密钥认证,禁用密码登录
□ 部署入侵检测和告警系统
□ 定期进行安全扫描和渗透测试
如果真的被入侵了,恢复步骤:
# 1. 立即隔离——断网!
sudo ifconfig eth0 down
# 2. 记录证据——不要急于重启
# 保存内存转储
sudo apt install dumpit
sudo dumpit -o /mnt/external/memory.dump
# 保存进程列表
sudo ps auxf > /tmp/processes.txt
# 保存网络连接
sudo netstat -tlnp > /tmp/connections.txt
# 保存登录历史
sudo last > /tmp/last_login.txt
sudo lastlog > /tmp/lastlog.txt
# 3. 检查异常进程
sudo ps aux | grep -E "(miner|kworker.*cpu|cryptonight)"
# 4. 检查异常网络流量
sudo netstat -an | grep ESTABLISHED
sudo ss -tn state established
# 5. 检查定时任务(黑客常用来持久化)
crontab -l
ls -la /etc/cron.d/
ls -la /var/spool/cron/crontabs/
给开发团队的安全建议
如果你是那个写代码、部署服务的人,这里有几个实用的检查点:
Docker容器的端口暴露:
# 错误的做法——暴露所有端口到宿主机
EXPOSE 3306
EXPOSE 6379
# 正确的做法——只暴露必要的,或者用网络隔离
# docker-compose.yml
services:
web:
ports:
- "80:80"
- "443:443"
networks:
- frontend
db:
# 不暴露任何端口到宿主机!
expose:
- "3306"
networks:
- backend
networks:
frontend:
backend:
internal: true # 内网隔离
Spring Boot应用的安全配置:
# application.yml
server:
port: 8080
# 管理端口放在不同的端口,限制访问
management:
server:
port: 8081
endpoints:
web:
exposure:
include: health,info # 只暴露必要端点
# 禁用不需要的端点
spring:
jmx:
enabled: false
Node.js应用的最小权限原则:
const http = require('http');
// 只监听本地回环
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello\n');
});
// 错误做法:server.listen(3000);
// 正确做法:只绑定本地
server.listen(3000, '127.0.0.1');
// 如果需要外网访问,通过反向代理(Nginx)
server.listen(3000, '127.0.0.1', () => {
console.log('应用仅在本地可访问');
});
最后说几句掏心窝的话
写这篇文章的时候,我想起一位安全工程师朋友说的话:”安全不是成本,是保险。你买保险不是为了出事,是因为出事之后你承受不起。”
那家被入侵的公司,最终损失了数百万美元,不是因为技术不够强,而是因为他们觉得”端口开着没什么”、”我们不会那么倒霉”。
端口扫描不可怕,可怕的是假装看不见扫描结果。
记住这三个原则:
- 该关的必须关——不必要的端口就是不必要的风险
- 能限制就限制——IP白名单、VPN前置,增加攻击难度
- 定期检查——安全不是一次性的工作,而是持续的过程
你的系统值得被认真对待。从今天开始,花十分钟检查一下你的端口吧。
