红联Linux门户
Linux帮助

使用iptables为何不能将外部进入的包NAT到127.0.0.1

发布时间:2014-11-05 15:19:36来源:linux网站作者:dog250

如有下面的iptables规则:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:1234
你觉得会成功吗?试一下就知道,不会成功。这是为什么呢?奇怪的是,不但没有返回包,即使本地没有1234这个端口在监听,也不会发送reset,这说明数据包根本就没有到达传输层,通过forward统计信息来看,它也没有经过forward,那么数据包到哪里去了呢?执行conntrack -E,发现没有任何新的conntrack生成的事件,并且/proc/net/ip_conntrack中也没有任何甚至是转化前的到达80端口的连接。


通过IP安全规则,得知实际上是不允许外部进来的包以loopback地址为目标的,否则攻击就太容易了,比方说我在局域网上放一个目标地址为127.0.0.1的IP包,修改其MAC目标地址为全1,这样所有的机器都将可以收到这样的包,因此路由模块负责丢弃这样的数据包。从表象上上,数据包被丢弃了,如果从统计数据来看,执行rtstat命令,将会看到下面一列:
|rt_cache|
|in_marti|
|  an_dst|
|      34| 


这说明34个这样的包被丢弃了,在哪里被丢了呢?肯定在路由模块,毕竟这是IP的策略,查看源码,在ip_route_input_slow里面发现:

if (ipv4_is_lbcast(daddr) || ipv4_is_zeronet(daddr) || 
ipv4_is_loopback(daddr)) 
goto martian_destination; 
... 
martian_destination: 
RT_CACHE_STAT_INC(in_martian_dst);


我们看到,这种情况的目的地址是一个“火星地址”,被丢弃了,同时还记录了一条统计信息。这说明,环回地址只能通过本机来访问,在本机出去的包经过ip_route_output后,其dst字段即路由结果已经被设置,如果是访问127.0.0.1,那么将不会再到ip_route_input,详见ip_rcv_finish:

if (skb_dst(skb) == NULL) { 
int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, 
skb->dev); 
... 
}


理解了数据包在哪里被丢弃了之后,对于出现的现象就很好理解了。为何当发起到80端口的访问且途径设置了前面iptables规则的机器时/proc/net/ip_conntrack中没有任何关于端口80的连接呢?这是因为该连接的数据包既没有被forward到网卡,也没有被input到本地,因此该连接始终没有被confirm,而我们知道ip_conntrack机制只有在confirm时才会将连接加入到hash链表中并且报告事件。


本文描述的是一个关于漏洞的问题,经典的《专家们公认的20个最危险的安全漏洞》一文中有下面的论述:
G5 – 没有过滤地址不正确的包
G5.1描述
IP地址欺诈是黑客经常用来隐藏自己踪迹的一种手段。例如常见的smurf攻击就利用了路由的特性向数以千记的机器发出了一串数据包。每一个数据包都假冒了一个受害主机的IP地址作为源地址,于是上千台主机会同时向这个受害的主机返回数据包,导致该主机或网络崩溃。对流进和流出你网络的数据进行过滤可以提供一种高层的保护。过滤规则如下:
1,任何进入你网络的数据包不能把你网络内部的地址作为源地址。
2,任何进入你网络的数据包必须把你网络内部的地址作为目的地址。
3,任何离开你网络的数据包必须把你网络内部的地址作为源地址。
4,任何离开你网络的数据包不能把你网络内部的地址作为目的地址。
5,任何进入或离开你网络的数据包不能把一个私有地址(private address)或在RFC1918中
列出的属于保留空间(包括10.x.x.x/8, 172.16.x.x/12 或192.168.x.x/16 和网络回送地址
127.0.0.0/8.)的地址作为源或目的地址。
6,阻塞任意源路由包或任何设置了IP选项的包。


可见,阻塞这种目标为环回地址的包是合理的,然而这件事到底应该有谁来做,这是一个问题,到底应该由防火墙软件来做呢,还是操作系统协议栈本身来做?Linux是在IP路由模块中做的,我认为这样不是很合理,有时候我真的需要这个功能,总不能重新编译一下内核吧,我觉得要么做到Netfilter中,要么就像rp_filter那样,做成可以配置的开关。