
1. 90%的入侵入口都在这张端口清单上做运维和应急这几年我复盘过的入侵事件里有相当一部分的起点朴素得让人无奈一台机器6379 对着公网没密码或者密码是 123456。攻击者甚至不需要什么高级手段连上来写个计划任务就完事了。所以端口自查与加固这件事本质上不是高级攻防而是一份必须定期拿出来核对的账本——哪些端口在监听、监听在哪个地址、对应哪个进程、谁在用、该不该暴露。从 80 到 6379这条线性路径上分布着 Web 服务、远程管理、数据库、缓存、容器面板这些最常见的角色每一个的暴露代价都不一样80 端口敞开是业务需要3306 敞开就是事故苗头6379 敞开基本等于把门钥匙插在锁上。这篇内容面向三类人一是刚接手几台云主机、还没建立端口管理意识的新手二是在做等保、渗透测试整改、上云迁移需要一份可落地清单的运维三是开发同学本地跑个 Docker 就把端口全发布出去想知道怎么不给自己挖坑。我会按先看清、再分级、再动手、最后沉淀机制的顺序讲所有命令和配置都可以直接抄参数为什么这么设也会讲明白。不涉及任何攻击手法只谈防守侧的自我核查与收敛。1.1 先纠正一个误区端口不是越少越好新手常走两个极端。第一种是全关看着 ss 输出清爽了结果业务半夜挂了第二种是先开着回头再说回头就是永远。真正合理的状态叫最小必要暴露外部可达的端口集合应该精确等于业务确实需要被公网访问的端口加上明确用于运维的受控通道其余全部收进内网或本机回环。判断一个端口该不该对外我习惯问三个问题。第一这个服务的客户端从哪里来如果是同一台机器上的应用绑 127.0.0.1 就够如果是同 VPC 内的几台应用服务器绑内网网卡 IP 加安全组白名单就够只有浏览器直接访问的 Web 入口才真正需要 0.0.0.0。第二这个服务自己有没有认证Redis、Memcached、早期版本的 MongoDB、没开认证的 Elasticsearch默认是不设防的把它们对公网开放等于把数据库管理权限交给扫描器。第三暴露之后有没有第二道闸如果你的答案是有安全组白名单那风险可控如果答案是应该没人会扫吧那就得马上处理。注意端口收敛的第一原则是服务自身绑定地址优先于防火墙拦截。绑定地址是从源头上不让它听见外面的敲门声防火墙是听见了再决定开不开门。两层都做才叫稳只做一层迟早出问题。1.2 把风险分个级别眉毛胡子一把抓我一般把常见端口分三档这个分法直接决定了后面加固的优先级和投入。风险档位典型端口暴露后的实际后果处理时限极高危2375/2376、6379、27017、9200、11211、未认证的 15672直接获得数据读写、容器控制权甚至宿主机 root发现即处理当天高危3306、5432、1433、22、3389、21、23、445可被暴力破解、弱口令撞库成功后横向移动一周内收敛中低危80、443、8080、8000、9000 等业务/面板端口风险主要来自应用层漏洞、默认页面、后台弱口令纳入常规巡检2375 排在最前面不是夸张。Docker daemon 的未加密 TCP 端口一旦对外对方可以直接创建挂载宿主机根目录的容器等同于拿到 root shell中间不需要任何漏洞利用。11211 这个 Memcached 端口单独提一句它在 UDP 上被大量用于反射放大型流量你的机器会变成别人攻击的跳板带宽账单和投诉都会找上你。至于 22 端口很多人第一反应是换个端口就安全了。说实话改端口只是把日志里的噪音降低 80%扫描器照样能找到。真正的加固是把密码登录关掉只留密钥再限制来源 IP。2. 自查第一层从外面看自己别只信本机输出自查最容易犯的错是在机器上敲一遍ss -lntp看没什么异常就收工了。问题在于本机视角看不到三样东西云平台安全组的放通规则、NAT 与负载均衡的转发关系、以及某些应用自己监听在 0.0.0.0 但你本地测的时候恰好走的是回环。所以我坚持外部视角优先。2.1 外部探测站在攻击者的位置数一遍门找一台不在同一安全组白名单里的机器同区域最低配的临时实例就够对目标公网 IP 做一次全端口探测。命令上nmap 的-Pn -sT组合最通用不需要 raw socket 权限也不会被误判为异常行为# 全端口 TCP 探测只看开放状态不做版本识别速度快 nmap -Pn -sT -p- --min-rate 2000 --max-retries 1 203.0.113.10 # 对已确认开放的端口做服务与版本识别便于核对是什么服务在跑 nmap -Pn -sV -p 80,443,22,6379,3306 203.0.113.10-p-是 1-65535 全扫别只扫常见端口我见过把管理后台开在 18080、把数据库开在 13306 的就赌没人扫全段。--min-rate控制发包速率2000 对单机来说比较温和不会把对方打挂一次全扫大约一两分钟。如果你拿不到第二台机器用nc -zv逐个验证也行就是慢for p in 22 80 443 3306 6379 9200 27017; do timeout 2 bash -c echo /dev/tcp/203.0.113.10/$p 2/dev/null \ echo OPEN $p || echo close $p done这里有个细节值得强调UDP 也要抽查。161SNMP、11211、123 这些 UDP 服务TCP 扫描完全看不到。用nmap -sU --top-ports 50快速过一遍不用全扫UDP 全扫太慢且容易被限速。2.2 本机视角把端口和进程、业务对上号外部扫描告诉你开了什么本机视角要回答是谁开的、谁在用。这两份数据必须能一一对应对不上的就是黑盒黑盒就是隐患。# 列出所有监听中的 TCP/UDP 端口及其所属进程需要 root 才能看到全部进程名 ss -lntup # 只看监听在 0.0.0.0 和 :: 上的也就是对外可达的 ss -lntup | grep -E 0\.0\.0\.0:|\*:|\[::\]: # 拿到 pid 后反查完整命令行确认是不是你以为的那个服务 ps -fp PIDss输出里的Local Address:Port那一列是重点。127.0.0.1:6379和0.0.0.0:6379是两个完全不同的世界前者只有本机能连后者所有网卡都听。很多人加固完 Redis 以为搞定了其实配置文件里写的是bind 0.0.0.0改了密码但没改绑定风险只是从零门槛降到一道门槛。顺带说一句如果发现某个端口对应的进程是java却看不到具体服务名用ss -lntup拿 PID 后ls -l /proc/PID/cwd看工作目录基本能定位到是哪个应用。这个技巧在清理不知道谁起的服务时特别好用。2.3 三层防火墙别在一层上做完就以为完事从公网到你的进程流量通常要穿过三道闸云安全组 / 网络 ACL → 主机防火墙firewalld、ufw、nftables→ 服务自身的绑定与认证。任何一层单独做都可能被绕过尤其是第二层有几个经典的坑第一Docker 发布的端口会绕过 ufw。这是真实存在且高频的问题。当你docker run -p 6379:6379 redis时Docker 直接往 nat 表写 DOCKER 链的 DNAT 规则流量在 PREROUTING 阶段就被转发到容器根本不走 ufw 的 INPUT 链。所以你 ufw 里写着 deny 6379nmap一扫还是 open。解决方案有两个方向发布时显式指定绑定地址-p 127.0.0.1:6379:6379或者在 DOCKER-USER 链里加规则做限制。# 在 DOCKER-USER 链里限制外部访问容器端口只允许指定网段 iptables -I DOCKER-USER -p tcp --dport 6379 ! -s 10.0.1.0/24 -j DROP第二安全组和主机防火墙的默认策略不一致。安全组放通了 22主机 firewalld 又是默认拒绝结果就是连不上排查半天。我的做法是安全组做粗粒度放通按业务分组主机防火墙做细粒度限制按来源 IP、按服务两边都记在台账里。第三firewalld 的 zone 搞混。网卡绑在publiczone你却在trustedzone 里加规则等于没加。用firewall-cmd --get-active-zones先确认网卡归属再动手。3. 逐个击破从 80 到 6379 的加固动作前面是方法论这一节全是可直接执行的配置。我按端口 → 典型风险 → 具体动作 → 验证方式的顺序讲每个都给出验证命令改完立刻能确认有没有生效。3.1 80 和 443端口本身没问题问题在它背后的东西80/443 是唯一一个应该对外开放的端口所以加固方向不在端口收敛而在应用层收口。我巡检时常查这几项默认首页/示例页面有没有删掉很多框架装完自带一个欢迎页那是个明显的指纹、版本号有没有回显在响应头里、备份文件.bak、.zip、.sql有没有躺在 Web 目录里、上传目录有没有被配置成可执行、管理后台是不是走的默认路径。Nginx 侧几个低成本高收益的配置server { listen 443 ssl; server_tokens off; # 不在错误页和响应头暴露版本号 autoindex off; # 关闭目录列表防止直接遍历文件 client_max_body_size 10m; # 限制上传体积避免大文件撑爆磁盘 # 拦截常见的敏感文件探测直接返回 404 减少信息泄露 location ~* \.(bak|sql|zip|tar\.gz|env|git)$ { return 404; } # 上传目录禁止执行脚本 location ^~ /uploads/ { location ~* \.(php|jsp|py|sh)$ { deny all; } } }TLS 部分也别忽略。还在用 TLS 1.0/1.1、或者开着一堆弱加密套件扫描报告上会被直接标出来。ssl_protocols TLSv1.2 TLSv1.3;基本够用配合 HSTS 头。证书到期时间记得加监控到期当天业务全红的事故我见过不止一次。8080、8000、9000 这类端口要格外留意。它们往往挂着 Actuator、Swagger、管理控制台、对象存储控制台。这类面板的特征是一旦可达往往不需要登录就能看到大量内部信息环境变量、配置项、接口列表。处理方式很直接——安全组只放通办公出口 IP或者干脆只允许通过跳板机访问。3.2 22 端口把改端口换成关密码SSH 加固我列一个优先级从高到低的清单前三项做完安全性提升 90%# /etc/ssh/sshd_config 关键项 PermitRootLogin prohibit-password # 禁止 root 用密码登录密钥仍可用 PasswordAuthentication no # 彻底关闭密码登录 PubkeyAuthentication yes MaxAuthTries 3 # 认证失败 3 次断开压制爆破 ClientAliveInterval 300 ClientAliveCountMax 2 AllowUsers deploy ops # 只允许指定账号登录 LoginGraceTime 30改完sshd -t先做语法检查再systemctl reload sshd。重载而不是重启这样已有的会话不会被断。这里有个必须强调的操作纪律改远程登录配置时永远保持一个已登录的会话不关另开一个窗口测试新连接确认能连上再关掉旧会话。我踩过的坑是改完AllowUsers忘了把自己加进去reload 之后新连接被拒靠云控制台的 VNC 才救回来。注意如果你的密钥是 RSA 且长度只有 1024 位新版 OpenSSH 默认会拒绝。改成 ed25519 或者至少 3072 位 RSA。生成方式ssh-keygen -t ed25519 -a 100 -C deployops-a 100是 KDF 轮数让私钥的暴力破解成本更高。爆破防护可以再加一层。用 fail2ban 之类的工具监听认证日志连续失败若干次就临时拉黑来源 IP配置不复杂对降低日志噪音效果明显。但别把它当成主要防线它只是拖时间的。3.3 数据库端口 3306 / 5432 / 27017绑定 账号粒度数据库加固的核心就两句话不要让数据库听得见公网不要用 root 连业务。绑定地址是第一道。MySQL 的my.cnf里bind-address 127.0.0.1或者内网 IPPostgreSQL 的postgresql.conf里listen_addresses localhost再配合pg_hba.conf做来源控制MongoDB 的mongod.conf里bindIp: 127.0.0.1。改完记得重启服务并复查ss -lntp | grep 3306确认绑定地址真的变了。账号粒度是第二道。业务账号只授予必要的库和表权限来源限制到具体网段-- 只允许内网应用网段连接且只给业务库的增删改查 CREATE USER app_rw10.0.1.% IDENTIFIED BY 强密码; GRANT SELECT, INSERT, UPDATE, DELETE ON biz_db.* TO app_rw10.0.1.%; FLUSH PRIVILEGES; -- 确认没有意外的远程 root SELECT user, host FROM mysql.user WHERE host NOT IN (localhost, 127.0.0.1);最后那条查询一定要跑我见过太多次root%安静地躺在 user 表里。顺手把test库和匿名账号删掉这些是老版本安装脚本遗留的。MongoDB 补一句2.x/3.x 时代默认不启用认证很多人装完直接用几年后才发现。现在用security.authorization: enabled开启认证建业务库专属账号别用 admin 跑应用。3.4 6379改 bind、开密码、重命名危险命令Redis 是我见过被开放得最多的服务因为它太容易跑起来了——docker run -d -p 6379:6379 redis两秒钟公网可访问无密码。所以这一段写得细一点按顺序做# redis.conf bind 127.0.0.1 10.0.1.15 # 只监听回环和内网网卡不要写 0.0.0.0 protected-mode yes # 没有 bind 且没有密码时拒绝外部连接兜底用 port 6379 requirepass 一个32位以上的随机串 maxmemory 2gb maxmemory-policy allkeys-lru # 把高危管理命令改名或禁用降低被滥用的可能 rename-command CONFIG rename-command FLUSHALL rename-command FLUSHDB rename-command KEYS rename-command SHUTDOWN requirepass的成本很低但它有一个绕不开的问题Redis 认证没有失败次数限制密码强度不够就是纯靠时间扛。所以密码必须够长够随机别用Redis2024这类。生产上更推荐用 Redis 6 之后的 ACL 机制给不同应用分配独立的用户名和权限集出问题能定位到具体是谁ACL SETUSER app_reader on 强密码 ~cache:* read -write -dangerousrename-command CONFIG 这一条要单独说明理由。CONFIG 命令可以动态改配置包括改dir和dbfilename再配合SAVE就能向任意路径写文件。禁掉它就砍掉了这条链路。不过要注意禁用之后你就不能在线改配置了得改配置文件重启这个代价要提前权衡。如果用了云托管的 Redis 或者依赖配置中心动态下发先确认没有自动化脚本依赖 CONFIG。改完的验证方式本机redis-cli -h 127.0.0.1 -a 密码 ping应该返回 PONG从外部机器nc -zv 公网IP 6379应该是超时或拒绝。如果外部还是能连八成是安全组没改或者前面说的 Docker 端口发布绕过了防火墙。3.5 容器与中间件2375、9200、11211 这几个必须清零2375前面说了等于 root。如果你确实需要远程管理 Docker走 2376 并配置双向 TLS 证书同时安全组只允许运维网段。绝大多数场景根本不需要开这个端口——需要跨机管理就用编排平台别裸奔 daemon。9200Elasticsearch 在 7.x/8.x 默认开启安全认证了但很多老集群是 6.x 时期搭的xpack.security.enabled: false一直在跑。检查方式很直接curl -s http://127.0.0.1:9200/_cat/indices如果能直接出结果说明没认证。加固要么开 xpack 安全要么用 Nginx 反代加 Basic Auth 挡在前面配合安全组白名单。11211Memcached 加固三件事-l 127.0.0.1绑定回环、-U 0关闭 UDPUDP 是反射放大的载体、-m限制内存防止被打爆。它本身没有认证机制所以只能靠网络层隔离这一点没有别的办法。15672 / 5672RabbitMQ 管理台默认账号 guest 只允许 localhost 登录很多人为了图方便改成了允许远程又用了弱密码。要么走内网加安全组要么建独立账号并设成管理员之外的只读角色给监控用。4. 从这次改完到长期不复发单个端口改掉不难难的是三个月后新上的服务又把 6379 开到公网。所以最后这一步是把动作变成机制否则前面所有工作都是临时的。4.1 建一份端口台账比什么工具都管用我给每个接手的环境都维护一张表字段固定端口、协议、绑定地址、进程/容器名、负责人、暴露范围公网/内网/本机、安全组规则编号、最后核对日期。这张表的价值在变更时体现——新服务上线先查表看端口有没有冲突、有没有必要对外出问题时看表能立刻定位是谁的责任。表格不用什么高级工具一个 Markdown 文件放在仓库里每次变更走一次提交历史记录天然就有。配套的动作是定期复扫。我一般设成每月一次自动扫描结果和台账比对出现表里没有但实际开放的端口就告警。这一步能抓住大部分临时开了一下忘了关的情况比如为了导数据临时放通的 3306、为了调试临时开的 8080。4.2 变更要有窗口、有回滚、有验证端口加固本身就是一次变更而且是可能中断业务的变更。我的标准流程是四步第一步确认影响面。改 bind 之前先看ss -tnp state established ( dport :6379 )看现在有哪些连接来自哪里。如果有外部 IP 正在连说明确实有跨机调用直接改 bind 会打断它。第二步灰度。先在安全组层面收紧这步可秒级回滚观察一个业务周期至少覆盖一次定时任务执行确认没报错再改服务配置。第三步验证。从外部扫一遍、从应用侧跑一次核心链路、看错误日志有没有连接超时。第四步留后路。改 SSH 或防火墙之前确保有 VNC / 串口 / 云助手这类带外通道可用。这一条不是形式主义我见过把默认策略改成 DROP 又忘了放通自己 IP 的最后靠控制台救援。# 改默认策略前先确认当前规则并把现有规则备份 iptables-save /root/iptables-backup-$(date %F).rules # 设置默认拒绝先不动 OUTPUT避免自己把出站也断了 iptables -P INPUT DROP iptables -P FORWARD DROP # 放通已建立的连接和回环这两条必须先加否则现有 SSH 立刻断 iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT # 再按需放通 iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT顺序很关键ESTABLISHED,RELATED和lo这两条要在设置 DROP 之前或紧跟其后加上否则你当前这条 SSH 会话会立刻断掉。4.3 用配置管理固化避免手工漂移机器一多手工改必然漂移。可行的做法是把关键配置项纳入版本管理Redis 的bind和requirepass、MySQL 的bind-address、sshd 的关键开关都写成模板由配置管理工具下发。这样新机器默认就是安全的而不是新机器先上线回头再加固。对容器环境建议在镜像构建阶段就把默认配置改好比如基础镜像里redis.conf默认bind 127.0.0.1。跑起来之后docker run的-p参数强制要求写明绑定地址不允许出现裸的-p 6379:6379。这条规矩可以在 CI 里做静态检查扫 Dockerfile 和 compose 文件里的端口映射命中就拦下。5. 踩坑实录与排查速查表这一节是我自己踩过、以及帮别人排查过的典型问题按现象 → 原因 → 处理整理遇到时可以直接对照。5.1 五个高频故障现场现象一改完 bind 之后同机应用连不上了。原因通常是应用连的是公网 IP 或主机名而主机名解析到了内网 IPbind 只写了 127.0.0.1。处理应用侧改成127.0.0.1或者在 bind 里同时写上内网 IP两者用空格分隔。现象二防火墙规则加了从外部还是能连。三个方向排查。一是规则加在了错误的链或错误的 zone二是 Docker 的 DNAT 绕过了 INPUT 链要去 DOCKER-USER 链查三是云安全组优先级高于主机防火墙安全组放通了就先进来了。用iptables -t nat -L -n -v看有没有意外转发能快速定位第二种。现象三端口显示被占用但ss看不到进程。常见于进程已经退出但 socket 还在 TIME_WAIT或者进程在另一个网络命名空间容器里。前者等 60 秒自然释放或用ss -tan state time-wait确认后者需要进容器内ss或者用nsenter。现象四改 SSH 端口后彻底失联。顺序错了。正确顺序是先在 sshd 配置里新增一个端口Port 22和Port 2222并存reload确认 2222 能连上再删掉 22再 reload。这样任何一步出错都还有退路。同时安全组要在第一步就先放通新端口。现象五加固完成后业务偶发超时日志里是连接被重置。检查是不是MaxAuthTries、MaxStartups设得太紧或者 fail2ban 的阈值配置过激把正常的健康检查请求当成爆破拉黑了。把监控探针的源 IP 加进白名单。5.2 端口自查与加固速查表端口服务典型风险首选动作验证命令22SSH密码爆破、弱口令关闭密码登录仅密钥 来源白名单ssh -o PreferredAuthenticationspassword -o PubkeyAuthenticationno userip80/443Web默认页、备份文件、版本泄露关目录列表、隐藏版本、清理备份curl -I https://ip看响应头3306MySQLroot 远程、弱密码bind 内网 账号 Host 粒度SELECT user,host FROM mysql.user5432PostgreSQL信任认证收窄pg_hba.conf的 CIDRpsql -h ip -U user从外部应失败6379Redis无密码、CONFIG 滥用bind requirepass rename-commandredis-cli -h ip ping外部应超时27017MongoDB未启认证authorization: enabled bindIpmongosh ip:27017应要求认证9200Elasticsearch未启安全模块开 xpack 或反代加认证curl ip:9200/_cat/indices应 4012375Docker daemon等同宿主机 root直接关闭改走 2376 双向 TLScurl ip:2375/version应拒绝11211MemcachedUDP 反射放大-l 127.0.0.1 -U 0nmap -sU -p 11211 ip21/23FTP/Telnet明文传输凭据下线改用 SFTP/SSH端口扫描确认 closed161SNMPpublic 团体名泄露信息改团体名或升 v3限制来源snmpwalk -v2c -c public ip应失败445/139SMB勒索软件横向非必要直接关或仅内网nmap -p 139,445 ip最后再补一个自查脚本可以放到定时任务里每天把 0.0.0.0 监听的端口和基线对比有新增就发通知。逻辑很简单ss -lntup抓出所有对外监听和一份允许清单做差集差集非空就告警。这个脚本二十行就能写完但能挡住临时起个服务忘了关这类最常见的疏漏。#!/usr/bin/env bash # 只会检查 0.0.0.0 / :: 上的监听和基线文件对比 BASELINE/etc/port-baseline.txt # 每行一个端口如 22、80、443 CURRENT$(ss -lntu | awk NR1 {print $5} | grep -E ^(0\.0\.0\.0|\*|\[::\]): \ | sed s/.*:// | sort -un) DIFF$(comm -13 (sort -un $BASELINE) (echo $CURRENT | sort -un)) if [ -n $DIFF ]; then echo 发现基线外端口$DIFF 2 # 这里接告警渠道 exit 1 fi这套东西我前后在几个不同规模的环境里跑过最深的体会是端口加固真正难的不是技术是知道自己开了什么。命令就那么几条配置也就那么几行难在有没有人定期核对、有没有在变更时想起它。我现在接手新环境的头一件事就是跑一遍全端口外部扫描把结果存成基线之后所有变更都对着基线看。这个动作花不到十分钟但它能让你在半夜被叫起来之前先把问题找出来。