public inbox for passt-user@passt.top
 help / color / mirror / Atom feed
* Guest namespace can access host ports via secondary interface addresses
@ 2024-05-10  3:13 Kangjing Huang
  2024-05-10 10:45 ` Stefano Brivio
  0 siblings, 1 reply; 2+ messages in thread
From: Kangjing Huang @ 2024-05-10  3:13 UTC (permalink / raw)
  To: passt-user

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/) ...

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

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? I believe this is a security escape in
the container context, since containerized services can gain access to
unintended resources.


Thanks,
Chaser Huang

--
Kangjing "Chaser" Huang

^ permalink raw reply	[flat|nested] 2+ messages in thread

* 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

end of thread, other threads:[~2024-05-10 10:52 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-05-10  3:13 Guest namespace can access host ports via secondary interface addresses Kangjing Huang
2024-05-10 10:45 ` Stefano Brivio

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).