Nmap 并不总是发送你以为自己请求的那个数据包。当原始套接字不可用时,它会把默认的 SYN 扫描替换成 TCP connect 扫描,因此以普通用户身份运行和以 sudo 运行,可能对同一个端口回答不同的问题。结果看起来像权限问题,实际上通常是方法的改变。把方法明确说清楚,才是两次运行可以相互比较的唯一前提。

两种探测,一个端口,不同的代码路径

SYN 扫描自己构造 TCP 首部,从原始套接字发出,并在内核 TCP 栈记录下任何完整连接之前就从网络上读取回应。connect 扫描则请操作系统打开一个普通套接字,并报告 connect() 返回了什么。两者都能把端口标为开放,但只有一种会留下半开尝试,也只有一种受与发送进程绑定的套接字层策略约束。

原始路径需要 CAP_NET_RAW,所以缺少这项能力时,默认扫描会悄悄降级。Nmap 自己的扫描技术参考记录了这一替换:connect 扫描之所以是后备,是因为它完全不需要任何特殊权限。在大多数运行中,这种替换不会给出任何警告,这正是它容易被误读的原因。一旦知道存在这种后备,“以 root 是开放、以用户是关闭”就不再像权限缺陷,而开始像两次不同的测量。

sudo 在扫描中真正改变了什么

提升权限会恢复原始套接字路径,而这改变的不只是结论。源端口的选择、数据包的时序、重传行为,以及过滤响应的分类方式,全都来自不同的代码。限制 SYN 速率的中转设备,即使目标主机完全相同,也会不平等地对待这两次运行。

目标侧的日志记录同样不同。完成的握手对服务和任何套接字计数都可见,而被重置的半开探测往往看不见。如果你比较的是那台你同时会读日志的主机,那么你看到的一部分差异,就是观测本身。

在两次运行中保持方法不变

在两次调用中强制使用同一种技术,比较才开始有意义。-sT 明确要求 connect 扫描,-Pn 去掉主机发现,-n 去掉名称解析,而单个 -p 去掉端口列表的差异。

nmap -sT -Pn -n -p 8443 198.51.100.77
sudo nmap -sT -Pn -n -p 8443 198.51.100.77

把地址和端口换成你有权测试的主机;上面的文档网段只是示例,不是目标。如果两次运行现在一致,原来的差异就来自扫描方法。如果它们仍然不一致,那就是别的东西在分类流量,下一节正是要看的地方。

账户上下文不只是一个权限标志

VPN 部署可以按 UID、按 cgroup,或按绑定到打开套接字那个进程的规则来分类数据包。于是 root 流量和用户流量会走不同的路由表,或命中不同的规则集,而任何扫描方法上的纪律都无法把它们调和。

要检查策略,而不是权限。在 Linux 上,nftables 用 meta skuid 表达这一点,iptables 则用属主匹配,两者都能把单个 UID 送入隧道,而另一个继续走物理默认路由。

sudo nft list ruleset | grep -n skuid
sudo iptables-save | grep -n uid-owner
ip rule show
ip route get 198.51.100.77

在下结论之前先确认实际运行的是哪种探测

在下结论之前,先证明实际运行的是哪种探测。Nmap 会在自己的输出里点明技术,而 --reason 会给出每个状态背后的证据,正是这一部分告诉你握手是否真的完成。

nmap --reason -sT -Pn -n -p 8443 198.51.100.77
sudo nmap --reason -sS -Pn -n -p 8443 198.51.100.77
getcap "$(command -v nmap)"
capsh --print | grep -i net_raw

同样状态、不同原因的两次运行并不矛盾;不同状态、同一原因的两次运行指向的是路由或策略,而不是扫描。让目标、端口、命名空间和时序保持一致,然后一次只改一个变量。以 root 运行服务并不是策略不匹配的修复办法;通过服务属主把预期策略表达出来,才是。

相关文章