A Container Hacker’s Guide to Living Off of the Land

A Container Hacker’s Guide to Living Off of the Land
Cory Sabol
Author: Cory Sabol
Share:
 

Sometimes as a pentester you find yourself in tricky situations. Depending on the type of engagement, you might want to try to avoid making a lot of noise on the network if possible. This blog post is going to talk about two techniques to use to gather information on your target while avoiding making too much noise as they pertain to container hacking. But for these to be useful, some other things have to have happened first.

Reeve_and_Serfs-1-2

An artist’s rendering of IT professionals working under the guidance of their project manager.

Scenario 1 – It’s a Good Neighborhood. Why Lock the Door?

In this scenario, we assume that you, the pentester, have the ability to create containers on whatever your current target system is. It’s likely that you can do this thanks to some kind of container management interface that doesn’t have proper authentication mechanisms in place. Now, let’s suppose that you want to create and deploy a container through this interface so that you can potentially use said container to escape to backdoor the host system.

deploy_malicious_container-1-1
Here we utilize the exposed container management interface to deploy our own malicious container to the container host (not necessarily the same machine which hosts the container management interface).
image-Sep-25-2021-09-53-20-24-PM
Here you can see that the goal is to use the compromised container (red) to escape the container context and affect the host context in some manner.

It’s common for these types of interfaces to accept a JSON description of the container to be created.  So, naturally you supply something like the following:

{
  "Image": "ubuntu",
  "Cmd": [
    "/bin/sh"
  ],
  "DetachKeys": "Ctrl-p,Ctrl-q",
  "OpenStdin": true,
  "Mounts": [
    {
      "Type": "bind",
      "ReadOnly": false,
      "Source": "/",
      "Target": "/mnt/host"
    }
  ]
}

BUT WAIT!  Did you check to see if that ubuntu image was already on the host system?  No? Oh… Specifying an image that isn’t actually on the host system yet will typically cause the underlying container tooling – in this case Docker – to try and reach out to the Internet and pull down that image.  

Blue teams, beware! If your container hosts are reaching out for new images, this could be an indicator of compromise.

Many container management interfaces have some way or another that you could check to see what images are already cached on the host system.  In this particular scenario, an easy way to determine what images are available to you on the host is to inspect already deployed containers.  For example if you’ve ran across the Kubernetes dashboard (sPoOoOky) you could check the description of active pods to see what images they are deployed with.  This is also the case if you have gained access to the Marathon (eww) management interface.  You can simply click on a deployed application and view it’s configuration to see what image it was deployed with.  There are a ton of ways you could gather information on what images are being used within a target container environment – but that’s the subject of another blog post in the future (that’s code for I have more stuff to explore first ¯\_(ツ)_/¯).

After all, we are poking around in a container management interface.  Once you determine what images are being used on the host – let’s assume that it’s a debian image, not that ubuntu image you tried to specify earlier – you could now modify your payload by changing the image as follows:

{
  "Image": "debian",
  "Cmd": [
    "/bin/sh"
  ],
  "DetachKeys": "Ctrl-p,Ctrl-q",
  "OpenStdin": true,
  "Mounts": [
    {
      "Type": "bind",
      "ReadOnly": false,
      "Source": "/",
      "Target": "/mnt/host"
    }
  ]
}

This has the simple benefit of using an image that is already cached on the system, preventing the container run-time from calling out to the Internet to try and download the image.  Sneaky!  Of course attaching a shell to the freshly deployed container is left as an exercise for the reader.  This previous blog post of mine might help shed some light on that 🙂

Scenario 2 – SSH is the Only Way, Maaaan.

In this scenario, we assume that you’ve already spun up a container with the host system’s root directory mounted to it, and attached a shell to it.  In other words, you had just experienced scenario 1 OR you’ve compromised a container in some other manner which magically happened to have the host root directory conveniently mounted for you. Some people are so inviting >:).  Anyhow, at that point, you would probably start poking around through those host system files that you’ve conveniently mounted into your compromised container and you probably examined /mnt/host/etc/shadow for some sweet host system password hashed.  At that point you might have wondered something like – “How can I backdoor the host system without modifying any user’s passwords or adding my own user to the shadow and passwd files?”

Well, to answer that last question you likely pondered – you could try to backdoor the host systems ssh profile with your own public key.  That’s pretty simple, given the circumstances; just check the /mnt/host/etc/ssh/ssd_config file and see if the system allows root login, etc.  Now you can just do:

echo “YourPubKey” >> /mnt/host/root/.ssh/authorized_keys

Blue teams make sure that you are alerted when authorized_keys get added/changed on any of your managed systems. 

Awesome, you’ve backdoored that host system with your public key!  Did you check to see if ssh is actually running on the host system though? What, you didn’t?!  Well that’s okay, I’ve got you covered. Since we’re in the spirit of trying to make as little network noise as we can, it might not be too wise to do a port scan of the host (even if in this case we only really need to check two common ports: 22, 2222).  Instead we can use the following nugget:

grep “Name:” /mnt/host/proc/*/status

This will spit back a nice little list of the processes that are running on the host system!

root@cab01e2e9991:/# grep "Name:" /mnt/host/proc/*/status
/mnt/host/proc/1/status:Name:   systemd
/mnt/host/proc/10/status:Name:  migration/0
/mnt/host/proc/10185/status:Name:       kworker/u16:0
/mnt/host/proc/10221/status:Name:       kworker/5:0
/mnt/host/proc/10222/status:Name:       sshd
/mnt/host/proc/10224/status:Name:       systemd
/mnt/host/proc/10225/status:Name:       (sd-pam)
/mnt/host/proc/10317/status:Name:       sshd
/mnt/host/proc/10318/status:Name:       bash
/mnt/host/proc/10344/status:Name:       docker
/mnt/host/proc/10422/status:Name:       kworker/3:0
/mnt/host/proc/10423/status:Name:       kworker/3:1
/mnt/host/proc/10430/status:Name:       containerd-shim
/mnt/host/proc/10453/status:Name:       bash

… output truncated

This is really cool, because we were able to tell what processes are running on the host system simply by having the host’s root directory mounted to our containers file system.  No network noise for this one!  We could also check for sshd specifically with:

grep “sshd” /mnt/host/proc/*/status

This would give back a match if sshd is in fact running on the host system.

root@cab01e2e9991:/# grep "sshd" /mnt/host/proc/*/status
/mnt/host/proc/10222/status:Name:       sshd
/mnt/host/proc/10317/status:Name:       sshd
/mnt/host/proc/1208/status:Name:        sshd

At this point, you can in theory ssh into the host machine as the root user and begin the pentest process all over again.

Conclusion

Okay, so it’s true that in the scenarios described the final ssh connection is obviously going to make noise on the network.  That’s okay, we can’t always be perfectly quiet all the time.  However, we can try to minimize the amount of noise that we make by being as mindful as possible of the effects our actions have on the network and it’s hosts :D.

I hope that when you’re on a pentest and you find yourself in a set of extraordinary circumstances involving containers and container management interfaces (much as my coworkers and I recently did) the little bit of knowledge in this post helps you figure out the best course of action.

P.S. – I know some of the stuff in this post wasn’t super specific with respect to specific interfaces and specifically how you might compromise a container or container management interface.  That’s because the subjects around container security run deep and intertwine with all the things one should be doing with application and OS security in the first place.  But don’t fret!  I plan to do more deep dive posts as I build my own knowledge 😀

Diagram credit to my coworker Kate Vajda @vajkat 

Join the Professionally Evil newsletter