Container Escapes 101 - Who am I?
To start, let’s get an idea of how isolated we really are. We’re going to run a few containers with a shell.
This is a fact-finding mission, as the answer to much of this is “it depends” on the things we’re trying to find out. At this point, it’s not fundamentally different from any other systems recon.
Who am I?
We want to determine who we are inside our container, not outside of it (yet).
1
2
3
4
5
6
$ docker run -it redhat/ubi9:9.6
Unable to find image 'redhat/ubi9:9.6' locally
9.6: Pulling from redhat/ubi9
7810b4123c84: Pull complete
Digest: sha256:2e4eebec441e8bbc3459fcc83ddee0f7d3cfd219097b4110a37d7ff4fe0ff2e9
Status: Downloaded newer image for redhat/ubi9:9.6
Using the same Linux commands as we would on any other system, we can find this information easily.
1
2
3
4
5
6
7
8
[root@26efa09f765e /]# whoami
root
[root@26efa09f765e /]# echo $EUID
0
[root@26efa09f765e /]# echo $UID
0
[root@26efa09f765e /]# id
uid=0(root) gid=0(root) groups=0(root)
A few other things to note:
- In this case, the terminal prompt clearly says I’m
root
. It’s likely correct, but it’s also trivial to spoof. More frequently, it’s simply left out of the terminal prompt (as we’ll see in a bit). - Based on the “hostname” of a random 12-digit alphanumeric string, it’s likely I do not have the hostname of my host system shared into my container.
It is uncomfortably common to find containers running as
root
in production. Many “base images” are built with the assumption that the user will run privileged tasks inside of them to, say, install packages or compile code. The “then we run it non-privileged” part gets left off at the end.
What else do I have?
We now know that we’re
- running as root
- in a container
- with a shell
At this point, we have a choice on what to do next.
- Look for more binaries we have available to us, using the same basic enumeration we’d run on any Linux host.
- Assume we have something to work with with the shell we have.
In general, I’m going to pick that second one. If I’ve already got a shell, I can likely download anything else I’d want.
Doing better means the basics of minimalism, same in any other system. In container security, it means not running as a privileged user and having as little as possible inside of it. Ideally, a container has just enough to do the task it needs to do (no shell, no extra packages, etc.) and nothing more.
Exercise #1 - make it a little harder
Now let’s look at what happens when we have a shell, but we’re not root.
1
2
3
4
$ docker run -it ghcr.io/some-natalie/some-natalie/whoami:latest
Digest: sha256:784fec4db8f70cce37fa953e8e40e8ed6573093965c7465695923e61ace29f7b
Status: Downloaded newer image for ghcr.io/some-natalie/some-natalie/whoami:latest
c3ab714c6ecb:/$
Who are you?
answer
205b67283a55:/$ whoami
user
What’s your user ID?
answer
205b67283a55:/$ id
uid=1000(user) gid=1000(user) groups=1000(user)
What user permissions do you have?
Explore the permissions here as much or little as you’d like. There’s not much to be had here and that’s alright!
Exercise #2 - whoops, no shell (ish)
A reasonably common security practice is to have a no POSIX shell (eg, sh
or bash
). Instead, it’s possible to have a programming-language specific shell without many of the GNU utilities (ls
, etc) that we’d rely on. That doesn’t have to stop us from taking a look around.
1
2
3
4
$ docker run -it cgr.dev/chainguard/python:latest
Python 3.13.5 (tags/v3.13.5-1-gfc237eb-dirty:fc237eb, Jul 1 2025, 21:40:20) [GCC 15.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Let’s try to take a look around using ls
to find our flag at /boot/flag.txt
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> import subprocess
>>> subprocess.run(["ls", "-l", "/"], capture_output=True)
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
subprocess.run(["ls", "-l", "/"], capture_output=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/subprocess.py", line 554, in run
with Popen(*popenargs, **kwargs) as process:
~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/subprocess.py", line 1039, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pass_fds, cwd, env,
^^^^^^^^^^^^^^^^^^^
...<5 lines>...
gid, gids, uid, umask,
^^^^^^^^^^^^^^^^^^^^^^
start_new_session, process_group)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.13/subprocess.py", line 1972, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'ls'
Well, that’s inconvenient … at least there’s other ways to find out more about our system.
1
2
3
4
5
6
7
>>> import os
>>> os.name
'posix'
>>> os.environ
environ({'PATH': '/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin', 'HOSTNAME': '5f8c4e68f2fa', 'TERM': 'xterm', 'SSL_CERT_FILE': '/etc/ssl/certs/ca-certificates.crt', 'HOME': '/home/nonroot', 'LC_CTYPE': 'C.UTF-8'})
>>> os.listdir("/")
['var', 'usr', '.dockerenv', 'dev', 'etc', 'tmp', 'run', 'root', 'home', 'proc', 'sys', 'opt', 'bin', 'sbin', 'lib64', 'lib']
Neat, but no /boot
which means no /boot/flag.txt
.
There was no easy way to find the flag here as we didn’t mount it, you’re not stuck! 🤝
What else can we find out about this system?
1
2
3
4
5
6
7
8
9
10
11
12
>>> import sys
>>> print(sys.version)
3.13.5 (tags/v3.13.5-1-gfc237eb-dirty:fc237eb, Jul 1 2025, 21:40:20) [GCC 15.1.0]
>>> import platform
>>> print(platform.system())
Linux
>>> print(platform.release())
6.8.0-63-generic
>>> f = open('/proc/uptime', 'r')
>>> print(f.read())
6912.60 13747.54
>>> f.close()
From this, we’ve gathered that our host system is Linux, a bit of information about our kernel, and that the uptime of our host system is a bit under 2 hours (6912 seconds).
There’s way more to Python’s os
, sys
, and platform
modules than we could ever cover in a short workshop. Know that most interpreted languages will have similar ways to take a look around. Every time I’m here, I end up searching for the right modules … it’s alright to not know everything about every language.
📚 tl;dr - linux processes aren’t very isolated at all. Also, not having the regular shell-based enumeration tools isn’t the end of the world. Next up … what else can we tell about our system from a shared kernel?
Back to the index.