
Docker is vulnerable to a symlink-race attack - eyberg
https://seclists.org/oss-sec/2019/q2/131
======
raesene9
So from a reading of the advisory and pull request, this seems to affect a
specific set of scenarios, where a malicious image is running (attacker has
gained access either to the running container, or compromised the image before
distribution) and then a process goes to copy data out of the running
container (for example by using docker cp).

Not sure if there are other scenarios where this would hit as well.

One to be aware of, but as with most vulnerabilities, good to understand how
it can be exploited, when you're assessing mitigations...

~~~
chatmasta
Many services allow uploading arbitrary images. This is certainly a threat
they should mitigate against in their sandboxing strategies.

~~~
rtempaccount1
Would an arbitrary image upload alone allow exploitation of this, or would it
require an operation on the host along the lines of a 'docker cp' as well?

~~~
cpuguy83
No, the vulnerability is within the API for `docker cp`, specifically.

~~~
cyphar
The only _currently known and exploitable API_ is with `docker cp`, but
FollowSymlinkInScope is used all over the place. Unfortunately, fixing
FollowSymlinkInScope requires redesigning the API and then redesigning all the
callers so they stop passing around path strings blindly and instead pass
around handles (which are O_PATH fds).

But, as I mentioned in TFA, the plan is to rework
[https://github.com/cyphar/filepath-
securejoin](https://github.com/cyphar/filepath-securejoin) to have a sane API
that detects attacks on older kernels while using the new kernel bits (once
merged).

------
Bucephalus355
This is a known attack for quite sometime. The Apache Web Server, which I
love/hate, has for years never really been 100% secure because of issues with
how the Linux kernel handles FollowSymLinks and SymLinksIfOwnersMatch. You can
purchase special distributions of Linux that are patched against this
vulnerability, and also I believe this gentleman [1] has released an OS patch
that sort of fixes the issue.

Anyway I switched to Caddy [2] even though I deeply love Apache and it has an
amazing history behind it.

[1] [https://twitter.com/hanno](https://twitter.com/hanno)

[2] [https://caddyserver.com/](https://caddyserver.com/)

~~~
xrisk
Caddy is brilliant. Configuration is so simple and easy compared to Apache /
Nginx.

~~~
tdhz77
Have you tried Traefik ?

~~~
cargoshipit
Are you serious? Their kubernetes setup guide is longer than War and Peace

[https://docs.traefik.io/v1.4/user-
guide/kubernetes/](https://docs.traefik.io/v1.4/user-guide/kubernetes/)

~~~
tdhz77
Not sure what that has to do with anything.

------
kazinator
I think symlink attacks could, at least in some situations, be prevented by a
Boolean function (perhaps in the kernel) which validates a path. It returns
true if the path is immune to symlink attacks, false otherwise. Immune to
symlink attacks means this: the path is walked, and the permissions of each
directory are checked to make sure that it isn't open for anyone to create a
symlink (anyone whose effective ID is not either root, or the caller). If the
path is relative, then the current working directory is prepended to it for
the purposes of checking. (A nuance here that has to be taken into account is
sticky-bit directories like /tmp.)

If the path contains symlinks, then we have to validate that the real parent
directory of the symlink target doesn't allow that target to be replaced. E.g.
we are following /foo/bar and bar is a symlink to /xyzzy/pop. If /xyzzy is
writable to adversaries, pop can be replaced by a malicious symlink even if
pop itself isn't writable to other users.

Anyway, the idea is that if a path we would like to operate on has any
components vulnerable to manipulation, we fail the entire operation.

Things can be simplified if we canonicalize the path (so it is free of
symlinks) but that is often undesirable. Software should keep the paths it has
been given as-is; the symlink abstraction belongs to the user and should be
respected.

~~~
the8472
Or you can use the open + O_PATH + *at syscall family which can be used to get
a handle on a resolved directory relative to which you can manipulate with no
re-traversal happening for different operations on that handle.

This combo exists exactly to avoid these kinds of issues.

Another way would be temporarily joining the container's mount namespace to
obtain the source handle. But that can't really be done in go since goroutines
don't play nicely with per-thread operations.

Edit: After looking through the go standard library it seems that there is an
impedance mismatch. Go just does not expose the necessary pieces to do this
properly. A dedicated docker-cp tool in C or Rust could probably handle this
better. I could be wrong though, maybe it's just not part of the stdlib.

~~~
cyphar
Just using O_PATH won't completely save you. O_PATH can definitely help,
especially if you actually check that the path is what you think through
readlink(/proc/self/fd/$n), but doing it safely is quite a bit harder than
just using *at(2). As mentioned in TFA, I am working on some kernel patches
(which will probably result in an openat2(2)) which allow for restriction of
path resolution so you can block symlink crossings entirely or force symlinks
to resolve to the dirfd you passed.

~~~
the8472
Yeah, I have read about the precursors of those patches. But this has been
simmering for a long time.

The point is that one could do this correctly _today_ with the right rituals.
openat2 wouldn't save you if you were still doing plain realpath+open across a
security boundary even though openat+procfs or setns are available.

The new syscall implementation would still need a fallback impl. for older
kernels after all.

~~~
cyphar
> The new syscall implementation would still need a fallback impl. for older
> kernels after all.

Yup, this is why I'm planning on getting a sane API available in
<[https://github.com/cyphar/filepath-
securejoin>](https://github.com/cyphar/filepath-securejoin>) which projects
can use so that the correct thing is done with both old and new kernels. Right
now it has a (slighly) improved version of the code Docker has, but I'm
rewriting it.

It should be noted that there are lots of examples of interfaces which are
incredibly hard to make safe without openat2 -- such as O_CREAT.

------
bayareanative
"Docker is vulnerable" is also a valid statement at present, hopefully it will
improve by the forces of $$$ and tech fashionablism.

~~~
phoe-krk
It won't. Docker already has too much inertia to be stopped by a single CVE.
The "it's just another bug of an actively supported piece of software, we'll
just update tomorrow" behaviour will click.

------
ga-vu
Not that big of a deal after you read it, though

------
drogorbrn
[https://xkcd.com/1988/](https://xkcd.com/1988/)

~~~
keanebean86
This gave me an existential crisis.

We're told not to build our own since that's a waste of time and our version
won't be as good.

At the same time we're mocked for not understanding what we're using.

What's the happy medium?

~~~
mrguyorama
There is absolutely nothing wrong with building your own for personal
projects, especially fun ones. The "don't reinvent the wheel" is more about
how if you are ENGINEERING something, you should fully understand standards
and best practices, how to use them, any configuration if needed, and
composition of tools.

The professional will use and understand tools, it's the enthusiast who builds
his own.

~~~
hinkley
I'd say the difference is in what you do with the one you built.

In a number of disciplines, you might learn the basics of building a tool that
you use, but you elect to use that knowledge to buy a good tool rather than
mastering that problem domain.

So what do you do with the one you built? Can you bring yourself to 'build one
to throw away'? We struggle with this mightily. Once we've put effort into
something and we have a 'thing' to show for it, we have trouble walking away
from the thing. It's our form of hoarding.

------
MagicPropmaker
Why is MalwareBytes giving a warning for this site?

~~~
smpetrey
Because MalwareBytes is scareware?

~~~
MagicPropmaker
I'd still like to know why MalwareBytes is warning me about this page. If you
can't answer that, why mock someone?

~~~
pushpop
Edit: you’ve hacked your comment after I replied so much that this post no
long bares any context to the question it was originally responding to....

~~~
MagicPropmaker
And it's not true that MalwareBytes is "scareware". I'm sure the person saying
that is simply some 12-year-old who wants to show the Windows user that he
runs Linux and is a "l33t h4x0r" or something and knows better than my
(commercial) security software.

MalwareBytes is actually reporting that something on that page is trying to
make an outbound connection to port 50685 on ip address 45.33.49.119. I don't
know what to make of this, but it smells fishy. It's very odd that this
program will make a claim if there isn't something going on. I'm not going to
risk visiting that page.

~~~
apearson
45.33.49.119 is the same IP this website is hosted on.

I'm not sure on port 50685 but when I load the page there are no connections
on port 50685.

~~~
merb
well basically > 49k ports are private/dynamic. which means that if you open a
website at 80/443 it will open a port on your computer above 49k, so nothing
malicious.

