2

tl;dr: accessing 0.0.0.0:port (eg. curl http://0.0.0.0:443) gets redirected(internally) to 127.0.0.1:port (where port is any port number) (eg. the previous curl command is the same as curl http://127.0.0.1:443); why does this happen and how to block connections destined to 0.0.0.0 ?

UPDATE2: I've found a way to block it by patching the Linux kernel (version 6.0.9):

 --- .orig/usr/src/linux/net/ipv4/route.c +++ /usr/src/linux/net/ipv4/route.c @@ -2740,14 +2740,17 @@ struct rtable *ip_route_output_key_hash_ } if (!fl4->daddr) { - fl4->daddr = fl4->saddr; + rth = ERR_PTR(-ENETUNREACH); + goto out; + /* commenting out the rest: + fl4->daddr = fl4->saddr; // if you did specify src address and dest is 0.0.0.0 then set dest=src addr if (!fl4->daddr) - fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK); + fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK); // if you didn't specify source address and dest address is 0.0.0.0 then make them both 127.0.0.1 dev_out = net->loopback_dev; fl4->flowi4_oif = LOOPBACK_IFINDEX; res->type = RTN_LOCAL; flags |= RTCF_LOCAL; - goto make_route; + goto make_route; END of COMMENTed out block */ } err = fib_lookup(net, fl4, res, 0); 

Result: Where do packets sent to IP 0.0.0.0 go?:

$ ip route get 0.0.0.0 RTNETLINK answers: Network is unreachable 

...they don't!

A client attempts to connect from 127.1.2.18:5000 to 0.0.0.0:80

$ nc -n -s 127.1.2.18 -p 5000 -vvvvvvvv -- 0.0.0.0 80 (UNKNOWN) [0.0.0.0] 80 (http) : Network is unreachable sent 0, rcvd 0 

(if you didn't apply kernel patch, you will need a server like the following for the above client to be able to successfully connect: (as root, in bash)while true; do nc -n -l -p 80 -s 127.1.2.18 -vvvvvvvv -- 127.1.2.18 5000; echo "------------------$(date)";sleep 1; done)

Patched ping(ie. a ping that doesn't set destination address to be the same as the source address when destination address is 0.0.0.0, ie. comment out the 2 lines under // special case for 0 dst address that you see here):

$ ping -c1 0.0.0.0 ping: connect: Network is unreachable 

instant. However, if specifying source address, it takes a timeout(of 10 sec) until it finishes:

$ ping -I 127.1.2.3 -c1 -- 0.0.0.0 PING 0.0.0.0 (0.0.0.0) from 127.1.2.3 : 56(84) bytes of data. --- 0.0.0.0 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms 

UPDATE1:

The why part is explained here but I'm expecting a little bit more details as to why does this happen, for example(thanks to user with nickname anyone on liberachat #kernel channel):

$ ip route get 0.0.0.0 local 0.0.0.0 dev lo src 127.0.0.1 uid 1000 cache <local> 

This shows that somehow packets destined for 0.0.0.0 get routed to the localhost interface lo and they get source ip 127.0.0.1 (if I'm interpreting this right) and because that route doesn't appear in this list:

$ ip route list table local local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 local 169.254.6.5 dev em1 proto kernel scope host src 169.254.6.5 broadcast 169.254.6.255 dev em1 proto kernel scope link src 169.254.6.5 local 192.168.0.17 dev em1 proto kernel scope host src 192.168.0.17 broadcast 192.168.255.255 dev em1 proto kernel scope link src 192.168.0.17 

it means that it must be somehow internal to the Linux kernel. ie. hardcoded

To give you an idea, here's how it looks for an IP that's on the internet (I used quad1 as an example IP):

$ ip route get 1.1.1.1 1.1.1.1 via 192.168.1.1 dev em1 src 192.168.0.17 uid 1000 cache 

where 192.168.1.1 is my gateway, ie.:

$ ip route default via 192.168.1.1 dev em1 metric 2 169.254.6.0/24 dev em1 proto kernel scope link src 169.254.6.5 192.168.0.0/16 dev em1 proto kernel scope link src 192.168.0.17 

Because iptables cannot be used to sense (and thus block/drop) such connections destined to 0.0.0.0 that get somehow routed to 127.0.0.1, it might prove difficult to find a way to block them... but I'll definitely try to find a way, unless someone already knows one.

@Stephen Kitt (in the comments) suggested a way to block hostnames that reside in /etc/hosts, so instead of:
0.0.0.0 someblockedhostname
you can have
127.1.2.3 someblockedhostname
127.1.2.3 someOTHERblockedhostname
(anything other than 127.0.0.1, but you can use the same IP for every blocked hostname, unless you want to differentiate)
which IP you can then block using iptables.

However if your DNS resolver (ie. NextDNS, or 1.1.1.3) returns 0.0.0.0 for blocked hostnames (instead of NXDOMAIN) then you cannot do this (unless, of course, you want to add each host manually in /etc/hosts, because /etc/hosts takes precedence - assuming you didn't change the line hosts: files dns from /etc/nsswitch.conf)


OLD: (though edited)

On Linux (I tried Gentoo and Pop OS!, latest) if you have this line in /etc/hosts:

0.0.0.0 somehosthere 

and you run this as root (to emulate a localhost server listening on port 443)
# nc -l -p 443 -s 127.0.0.1
then you go into your browser (Firefox and Chrome/Chromium tested) and put this in address bar:
https://somehosthere
or
0.0.0.0:443
or
https://0.0.0.0

then the terminal where you started nc(aka netcat) shows a connection attempt (some garbage text including the plaintext somehosthere if you used it in the url)

or instead of the browser, you can try:
curl https://somehosthere
or if you want to see the plaintext request:
curl http://somehosthere:443

This doesn't seem to be mitigable even when using dnsmasq as long as that 0.0.0.0 somehosthere is in /etc/hosts, but when using dnsmasq and your DNS resolver (ie. NextDNS or Cloudflare's 1.1.1.3) returns 0.0.0.0 instead of NXDOMAIN (true at the time of this writing) and that hostname isn't in your /etc/hosts(AND in what you told dnsmasq is the /etc/hosts to use) then there are two ways to mitigate it(either or both will work):

  1. use dnsmasq arg --stop-dns-rebind
 --stop-dns-rebind Reject (and log) addresses from upstream nameservers which are in the private ranges. This blocks an attack where a browser behind a firewall is used to probe machines on the local network. For IPv6, the private range covers the IPv4-mapped addresses in pri‐ vate space plus all link-local (LL) and site-local (ULA) ad‐ dresses. 
  1. use line bogus-nxdomain=0.0.0.0 in /etc/dnsmasq.conf which makes dnsmasq itself return NXDOMAIN for any hostname that resolved to 0.0.0.0 (except, once again, if that hostname was in /etc/hosts (bypasses dnsmasq) and what you told dnsmasq to use as /etc/hosts (if you did))

So, the second part of this question is how to disallow accesses to 0.0.0.0 from being redirected to 127.0.0.1 ? I want this because when using NextDNS (or cloudflare's 1.1.1.3) as DNS resolver, it returns 0.0.0.0 for blocked hostnames, instead of NXDOMAIN, thus when loading webpages, parts of them(that are located on blocked hostnames) will try to access my localhost server running on port 443 (if any) and load pages from it instead of just being blocked.

Relevant browser-specific public issues being aware of this(that 0.0.0.0 maps to 127.0.0.1):
Chrome/Chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=1300021 Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1672528#c17

15
  • Connecting to IP 0.0.0.0 succeeds. How? Why? explains why this happens. Commented Nov 18, 2022 at 12:59
  • Thanks! It seems that this translation from 0.0.0.0 to 127.0.0.1 happens inside the linux kernel and it doesn't reach iptables; somewhere inside tcp_v4_connect() maybe the nexthop thing. Commented Nov 18, 2022 at 13:57
  • @ilkkachu it kind of answer the "why" but not the "how to disallow it" part of the question. It is the same link that Stephen Kitt posted before. I'm still looking for some way to prevent this from happening, even if it means recompiling the kernel. Commented Nov 18, 2022 at 17:49
  • @StephenKitt I don't want blocked hosts(because they're resolved to 0.0.0.0 by NextDNS and by 1.1.1.3) to redirect to my localhost(imagine I host a https server locally) while browsing in my browser. Btw, someone on liberachat irc on #kernel realized that it's a routing thing by looking at the output of ip r g 0.0.0.0 and yet not in the local table ip r l table local thus likely hardcoded in the kernel source or something. Commented Nov 18, 2022 at 18:27
  • @correabuscar ah, right, I block DNS entries by resolving them to a specific address on my network which blocks everything apart from port 80 (where it displays a ”blocked” page). And yes, this is implemented somewhere in the kernel, I remember finding where when I wrote up my answer to the linked question, but I can’t remember where it was :-(. Commented Nov 18, 2022 at 18:30

2 Answers 2

2

Why this happens is explained in Connecting to IP 0.0.0.0 succeeds. How? Why? — in short, packets with no destination address (0.0.0.0) have their source address copied into their destination address, and packets with no source or destination have their source and destination addresses set to the loopback address (INADDR_LOOPBACK, 127.0.0.1); the resulting packet is sent out on the loopback interface.

As you determined, this behaviour is hard-coded in the Linux kernel’s IPv4 networking stack, and the only way to change it is to patch the kernel:

diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 795cbe1de912..df15a685f04c 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -2740,14 +2740,8 @@ struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4, } if (!fl4->daddr) { - fl4->daddr = fl4->saddr; - if (!fl4->daddr) - fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK); - dev_out = net->loopback_dev; - fl4->flowi4_oif = LOOPBACK_IFINDEX; - res->type = RTN_LOCAL; - flags |= RTCF_LOCAL; - goto make_route; + rth = ERR_PTR(-ENETUNREACH); + goto out; } err = fib_lookup(net, fl4, res, 0); 

This patch shows the original implementation, explaining the “why?” part above: if the packet has no destination address (i.e. it’s 0.0.0.0):

  • the source address is copied to the destination address
  • if the packet still has no destination address, i.e. it also has no source address, both addresses are set to the loopback address (127.0.0.1);
  • in all cases, the outgoing device is set to the loopback device, and the route is constructed accordingly.

The patch changes this behaviour to return a “network unreachable” error instead.

0

The short answer is it doesn't. It is much, much, worse than that.

0.0.0.0 maps to the IP address of any interface on your system. So if netstat or ip say that 0.0.0.0:22 is open, then your system is accepting incoming SSH connections over loopback, and ethernet, and wifi, and whatever other network interface you may have up. It's just that loopback is the only one listening on your port, or gets priority among the interfaces.

2
  • 2
    You're referring to the "binding" part of this answer, but I'm strictly referring to the "target" part, ie. when 0.0.0.0 is the target host, for example when you try to curl http://0.0.0.0:443 whilst you're already running the server on 127.0.0.1:443 via eg. nc -l -p 443 -s 127.0.0.1 (as root) Commented Nov 18, 2022 at 23:18
  • I don't think there's a difference in Linux. Even local packets are going to get routed, just to see which NIC to send them out on. Localhost can answer for 0.0.0.0 by definition, and probably has a better metric than anything else. Commented Nov 28, 2022 at 17:40

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.