在我的 iptables 脚本中,我一直在尝试编写尽可能细粒度的规则。我限制哪些用户可以使用哪些服务,部分是为了安全,部分是作为学习练习。
在运行 3.6.2 内核的 Debian 6.0.6 上使用 iptables v1.4.16.2。
然而我遇到了一个我不太明白的问题......
所有用户的传出端口
这工作得很好。我没有任何通用的状态跟踪规则。
## 传出端口 81 $IPTABLES -A 输出 -p tcp --dport 81 -m conntrack --ctstate 新,已建立 -j 接受 $IPTABLES -A 输入 -p tcp --sport 81 -s $MYIP -m conntrack --ctstate 已建立 -j 接受
与用户匹配的传出端口
## 用户帐户的传出端口 80 $IPTABLES -A 输出 --match 所有者 --uid-owner useraccount -p tcp --dport 80 -m conntrack --ctstate 新,已建立 --sport 1024:65535 -j 接受 $IPTABLES -A 输入 -p tcp --sport 80 --dport 1024:65535 -d $MYIP -m conntrack --ctstate 已建立 -j 接受
这仅允许帐户“useraccount”使用端口 80,但 TCP 流量的此类规则存在问题。
## 默认传出日志+阻止规则 $IPTABLES -A 输出 -j 日志 --log-prefix "BAD OUTGOING" --log-ip-options --log-tcp-options --log-uid $IPTABLES -A 输出 -j 丢弃
问题
上面的工作,用户“useraccount”可以完美地获取文件。系统上的任何其他用户都无法与端口 80 建立传出连接。
useraccount@host:$ wget http://cachefly.cachefly.net/10mb.test
但上面的 wget 在我的系统日志中留下了 x7 删除的条目:
10 月 18 日 02:00:35 xxxx 内核:错误传出 IN= OUT=eth0 SRC=xx.xx.xx.xx DST=205.234.175.175 LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=12170 DF PROTO=TCP SPT=37792 DPT=80 SEQ=164520678 ACK=3997126942 WINDOW=979 RES=0x00 ACK URGP=0
对于 UDP 流量的类似规则,我没有得到这些丢弃信息。我已经制定了限制哪些用户可以发出 DNS 请求的规则。
丢弃的传出 ACK 数据包似乎来自 root 帐户(URGP=0),我不明白。即使我将用户帐户交换为root。
我相信 ACK 数据包被归类为新数据包,因为 conntrack 在 3 次握手的第 3 步之后开始跟踪连接,但为什么会被丢弃?
可以安全地忽略这些掉落吗?
编辑
所以我经常看到这样的规则,这些规则对我来说很有效:
$IPTABLES -A 输出 -s $MYIP -p tcp -m tcp --dport 80 -m state --state 新,已建立 -j 接受 $IPTABLES -A 输入 -p tcp -m tcp --sport 80 -d $MYIP -m 状态 --state 已建立 -j 接受
我将“-m state --state”替换为“-m conntrack --ctstate”,因为状态匹配显然已过时。
拥有通用状态跟踪规则是最佳实践吗?上述规则不被认为是正确的吗?
为了严格控制传出用户连接,这样的东西会更好吗?
$IPTABLES -A 输入 -m conntrack --ctstate 已建立 -j 接受 $IPTABLES -A 输出 -m conntrack --ctstate 已建立 -j 接受 $IPTABLES -A 输出 -p tcp --dport 80 -s $SERVER_IP_TUNNEL -m conntrack --ctstate NEW -m 所有者 --uid-owner useraccount -j ACCEPT $IPTABLES -A 输出 -p tcp --dport 80 -s $SERVER_IP_TUNNEL -m conntrack --ctstate NEW -m 所有者 --uid-owner otheraccount -j ACCEPT
答案1
长话短说,ACK 是在套接字不属于任何人时发送的。允许属于用户套接字x
发起的连接的数据包,而不是允许属于用户的套接字的数据包x
。
故事比较长。
要理解这个问题,有助于理解wget
HTTP 请求的一般工作方式。
在
wget http://cachefly.cachefly.net/10mb.test
wget
建立到 的 TCP 连接,一旦建立,就会在 HTTP 协议中发送一个请求,内容为:“请将( )cachefly.cachefly.net
的内容发送给我,顺便说一下,完成后请不要关闭连接 ( )。原因这样做是因为,如果服务器回复同一 IP 地址上的 URL 重定向,它可以重用该连接。/10mb.test
GET /10mb.test HTTP/1.1
Connection: Keep-alive
现在服务器可以回复“您请求的数据来了,注意它有 10MB 大 ( Content-Length: 10485760
),是的,好的,我将保持连接打开”。或者,如果它不知道数据的大小,“这是数据,抱歉,我无法保持连接打开,但我会告诉您何时可以通过关闭我的连接端来停止下载数据”。
在上面的 URL 中,我们处于第一种情况。
因此,一旦wget
获得响应的标头,它就知道一旦下载了 10MB 的数据,它的工作就完成了。
基本上,wget
就是读取数据直到收到 10MB 并退出。但到那时,还有更多工作要做。服务器呢?已被告知保持连接打开。
退出之前,wget
关闭(close
系统调用)套接字的文件描述符。此时,close
系统完成对服务器发送的数据的确认,并发送一条消息FIN
表示:“我不会再发送任何数据”。然后close
返回并wget
退出。不再有与 TCP 连接关联的套接字(至少不再由任何用户拥有)。然而它还没有完成。收到该消息后FIN
,HTTP 服务器会看到文件结尾当读取来自客户端的下一个请求时。在 HTTP 中,这意味着“不再请求,我将结束”。因此它也发送 FIN,表示“我也不会发送任何内容,该连接即将消失”。
收到 FIN 后,客户端发送“ACK”。但是,那时,wget
它早已不复存在,因此 ACK 不是来自任何用户。这就是它被防火墙阻止的原因。因为服务器没有收到 ACK,所以它会一遍又一遍地发送 FIN,直到放弃,您会看到更多丢弃的 ACK。这也意味着通过丢弃这些 ACK,您将不必要地使用服务器的资源(需要将套接字维持在 LAST-ACK 状态)相当长的一段时间。
如果客户端未请求“Keep-alive”或服务器未回复“Keep-alive”,则行为会有所不同。
正如已经提到的,如果您使用连接跟踪器,您要做的就是让处于 ESTABLISHED 和 RELATED 状态的每个数据包通过,并且只担心数据NEW
包。
如果您允许NEW
来自 user 的数据包x
但不允许来自 user 的数据包y
,则用户建立的连接的其他数据包x
将通过,并且因为用户无法建立连接y
(因为我们阻止了NEW
将建立连接的数据包),不会有任何用户y
连接数据包经过。
答案2
这仅允许帐户“useraccount”使用端口 80
- 好吧,至少你所展示的规则实际上并不意味着这一点。
还有一个建议空间 - 不要对 ESTABLISHED 流进行用户检查,而只对 NEW 进行检查。在检查传入 ESTABLISHED 时,我也没有看到检查源端口的意义,它的端口有什么区别,它已经从 conntrack 的 PoV 处于 ESTABLISHED 状态。防火墙应该尽可能简单但高效,所以奥卡姆剃刀方法是最合适的。