* Re: Guest namespace can access host ports via secondary interface addresses
2024-05-10 3:13 Guest namespace can access host ports via secondary interface addresses Kangjing Huang
@ 2024-05-10 10:45 ` Stefano Brivio
0 siblings, 0 replies; 2+ messages in thread
From: Stefano Brivio @ 2024-05-10 10:45 UTC (permalink / raw)
To: Kangjing Huang; +Cc: passt-user
Hi Chaser,
Thanks for your report! See my answer inline, below.
On Thu, 9 May 2024 23:13:09 -0400
Kangjing Huang <huangkangjing@gmail.com> wrote:
> Hi there,
>
> I was tweaking around pasta and its usage with podman, and I realized
> that from pasta guest namespaces it is possible to access host ports
> through the address of secondary interfaces on the host.
>
> Say I have two interfaces on host, with eth0 connecting to a gateway
> and eth1 connected to another LAN:
>
> > $ # On host
> > $ ifconfig eth0
> > eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
> > inet 192.168.1.2 netmask 255.255.255.0 broadcast 192.168.1.255
> > ...
> > $ ifconfig eth1
> > eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
> > inet 192.168.110.1 netmask 255.255.255.0 broadcast 192.168.110.255
> > ...
> > $ ip route
> > default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.2 metric 1024
> > 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.2 metric 1024
> > 192.168.1.1 dev eth0 proto dhcp scope link src 192.168.1.2 metric 1024
> > 192.168.110.0/24 dev eth1 proto kernel scope link src 192.168.110.1
>
> If there is some service started on host:
>
> > $ python -m http.server
> > Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
This binds your server to "any" address, so this is expected, more
below.
> >From a pasta namespace, it is impossible to access the host ports by
> the address of the main interface:
>
> > $ pasta --config-net
> > $ # Now in pasta namespace
> > $ curl 192.168.1.2:8000
> > curl: (7) Failed to connect to 192.168.1.2 port 8000 after 0 ms: Couldn't connect to server
This fails simply because your container also has address 192.168.1.2,
by default, see:
https://passt.top/#addresses
You can use the address of the default gateway from the container to
use this connection path.
By the way, with default options (implying '--tcp-ns auto'), you can
actually connect to a loopback address to reach your server on the host:
$ python3 -m http.server 8082
Serving HTTP on 0.0.0.0 port 8082 (http://0.0.0.0:8082/) ...
[...]
and from the container:
# curl 127.0.0.1:8082
<!DOCTYPE html>
<html lang='en'>
[...]
this is also reflected in the man page:
auto Dynamically forward ports bound in the namespace.
The list of ports is periodically derived (every
second) from listening sockets reported by
/proc/net/tcp and /proc/net/tcp6, see proc(5).
and later, for --tcp-ns:
-T, --tcp-ns spec
Configure TCP port forwarding from target namespace to
init namespace. spec is as described above for TCP.
Default is auto.
note that this was instead considered an unexpected (hence insecure)
behaviour for Podman, which passes the '--tcp-ns none' option by
default, as well as '--no-map-gw'. See also this discussion, referring
to Podman integration, at:
https://lists.podman.io/archives/list/podman@lists.podman.io/message/OG5U6Y23X5HFYETKB67YG4E6D2G3BMFJ/
> However I found that it is possible to do so by the address of the
> secondary interface:
>
> > $ # In same pasta environment as above
> > $ curl 192.168.110.1:8000
> > <!DOCTYPE HTML>
> > <html lang="en">
> > ...
>
> Is this an expected behavior?
Yes, it is, so much that we even test for it:
https://passt.top/passt/tree/test/pasta/tcp?id=1ba76c9e8c14b21d8c2c7cb71abdaa85feb96605#n34
in those test cases, "via tap" means using the tap interface pasta
creates in the namespace, which is the same path you use by specifying
the address of a secondary interface such as in your example.
Sure, the host is in a different namespace from pasta's namespace, but
the host can route traffic between them, just like it can route traffic
from external hosts.
What difference would it make if the client in your namespace could
connect to an external proxy, that connects back to the host using your
secondary interface?
The only relevant bit for pasta's job here is to maintain the correct
source address:
$ python3 -m http.server 8082
Serving HTTP on 0.0.0.0 port 8082 (http://0.0.0.0:8082/) ...
1.2.3.1 - - [10/May/2024 12:13:17] "GET / HTTP/1.1" 200 -
and not make that connection appearing as if it comes from a loopback
device, because that would trick access control performed by services
running on the host, or even a specific address binding. If I do this:
$ python3 -m http.server -b 127.0.0.1 8082
Serving HTTP on 127.0.0.1 port 8082 (http://127.0.0.1:8082/) ...
I won't be able to connect from the container using the local address
of a non-remote interface, as expected:
# curl 1.2.3.1:8082
curl: (28) Failed to connect to 1.2.3.1 port 8082 after 129895 ms: Couldn't connect to server
See CVE-2021-20199 for why this matters (even though that's the reverse
case).
> I believe this is a security escape in
> the container context, since containerized services can gain access to
> unintended resources.
Note that pasta doesn't implement containerisation, it just detaches a
user and network namespace (again, by default) for convenience, but the
resulting shell will even have access to the host filesystem.
Podman implements containers, though, so it needs to care about this,
and it passes non-default options, such as '--no-map-gw'.
If it didn't, we would actually have what you describe, a container
being able to access unintended resources, such as a localhost-bound
server.
By the way, for the future, if you believe you found a security issue,
even if it's just a suspicion, please use the passt-sec@passt.top list
instead, so that we can implement responsible disclosure if needed:
https://passt.top/#security-and-vulnerability-reports
but in this case, I really don't see the need.
--
Stefano
^ permalink raw reply [flat|nested] 2+ messages in thread