Pwn2Own Ireland 2024: Hacking the TrueNAS Mini X
During Pwn2Own Ireland 2024, we demonstrated a SOHO Smash-up where we first compromised a QNAP QHora-322 router and then a TrueNAS Mini X. This blog post explains the vulnerability we found in the TrueNAS (CVE-2024-11944). Our write-up for our QNAP exploit can be read here.
See the Zero Day Initiative's post about this entry on Mastodon →
Background
The TrueNAS Mini X (despite being named "mini") is pretty expensive and shipping it to our office looked like it would be quite costly too. So before ordering it, we downloaded an image of its operating system (TrueNAS CORE) and installed it in a VM. This OS is open source and based on FreeBSD, so we hoped to find something here first before buying the actual device. We booted it and set it up, then started analysing it with port scans and by capturing traffic.
We noticed that soon after booting, it would attempt to download a number of .txz files (xz compressed tar files) over unencrypted HTTP connections. At first glance this looked like something part of FreeBSD, but it's not. TrueNAS supports plugins, based on a technology called iocage (as far as we can tell only used by TrueNAS), which uses FreeBSD jails in a similar way to Linux containers.
At boot, TrueNAS checks for plugin updates. To do that, it first git clones the repository https://github.com/freenas/iocage-ix-plugins. Then, for each json file in this repository, it would get the "packagesite" key, append "packagesite.txz" and download it.
Some of the packagesite links in that repository do not use an encrypted connection. For example:
{
"name": "syncthing",
"release": "12.2-RELEASE",
"artifact": "https://github.com/freenas/iocage-plugin-syncthing.git",
"official": false,
"properties": {
"nat": 1,
"nat_forwards": "tcp(80:8384),tcp(22000:22000),udp(22000:22000),udp(21027:21027)"
},
"pkgs": [
"syncthing",
"ca_root_nss",
"nginx"
],
"packagesite": "http://pkg.FreeBSD.org/${ABI}/latest",
"fingerprints": {
"iocage-plugins": [
{
"function": "sha256",
"fingerprint": "b0170035af3acc5f3f3ae1859dc717101b4e6c1d0a794ad554928ca0cbb2f438"
}
]
},
"revision": "0"
}
In this specific URL, the ${ABI} variable is replaced with its FreeBSD version, to get the URL in the Wireshark screenshot above.
Vulnerability
The vulnerability we found is that when the update check is performed, it extracts the entire tarfile using the method extractall (despite the fact that it only wants the "packagesite.yaml" file from the archive).
def download_parse_packagesite(packagesite_url):
package_site_data = {}
try:
with tempfile.TemporaryDirectory() as tmpdir:
packagesite_txz_path = os.path.join(tmpdir, 'packagesite.txz')
with requests.get(
f'{packagesite_url}/packagesite.txz', stream=True, timeout=300
) as r:
r.raise_for_status()
with open(packagesite_txz_path, 'wb') as f:
shutil.copyfileobj(r.raw, f)
with tarfile.open(packagesite_txz_path) as p_file:
p_file.extractall(path=tmpdir)
packagesite_path = os.path.join(tmpdir, 'packagesite.yaml')
if not os.path.exists(packagesite_path):
raise FileNotFoundError(f'{packagesite_path} not found')
with open(packagesite_path, 'r') as f:
for line in f.read().split('\n'):
searched = RE_PLUGIN_VERSION.findall(line)
if not searched:
continue
name = searched[0].rsplit('/', 1)[-1]
package_site_data[
name.rsplit('-', 1)[0]
] = iocage_lib.ioc_common.parse_package_name(name)
except Exception:
pass
return packagesite_url, package_site_data
TarFile.extractall() is documented to be vulnerable to path traversal, allowing files to be extracted outside of the destination directory:
Warning
Never extract archives from untrusted sources without prior inspection. It is possible that files are created outside of path, e.g. members that have absolute filenames starting with
"/"or filenames with two dots"..".
This means we could overwrite one or more files on the TrueNAS system by intercepting this HTTP connection and serving a malicious .txz file. The update check is running as root and outside of a jail, which gives a lot of potential files to overwrite that would allow us to get code execution on the device.
We opted to replace the SQLite3 database of the TrueNAS web interface (middlewared) with a database containing a root password hash for a password we know. Next, the web interface contains a websockets endpoint for authenticated users to get a shell, completing up our exploit chain. In a real-world scenario overwriting the complete database wouldn't be a great idea, as it locks out the real admin and may modify any number of configuration settings, but we wanted to have something that would work instantly and reliably during the competition, instead of slower solutions like replacing a cron file.
SOHO Smash-up
We had found this vulnerability after just a few days of looking at this target. For the competition, it had a big downside: it required a MitM setup, as we had to intercept a connection to the internet. During Pwn2Own, requiring a MitM setup normally isn't allowed or reduces the attempt to a partial win. However, for this edition, there was a SOHO Smash-up category. This means we compromise a router first and then use that to compromise another device connected to the router. This vulnerability fits that category perfectly, so after we found this we focussed on getting a router too.
A few days before the registration deadline, we had our QNAP QHora-322 exploit finished. Then we realised we maybe should test our TrueNAS exploit against the actual hardware... but then it turned out to be out of stock on Amazon! We couldn't find the right model anywhere that promised to ship it to us within the few days we had left, so in the end we went to Ireland with an exploit for a device we had never physically seen before.
We got unlucky in the drawing (again), making us the 7th SOHO Smash-up. Even worse, we were the 5th smash-up with exactly these two targets. As both vulnerabilities could be found by looking at Wireshark for 5 minutes, we were sure they would be collisions, which would have reduced the $100k reward to just $12.5k. To our surprise, all of the vulnerabilities we had were completely new during the competition, rewarding us $25k instead.
The fixes
This was addressed by the TrueNAS developers in two ways. First, it uses a filter while extracting files from the tar file to reject any attempts at path traversal:
https://github.com/truenas/iocage/commit/4989df6c26e53dbd68990d3928db81362cd68de7 https://github.com/truenas/iocage/commit/788d4b3093be168c48a7b8a36b5a73ba83bb7d7f https://github.com/truenas/iocage/commit/d8b3d7e11256db59a98c588259eec5637ec92123
Secondly, the URLs in the repository were updated to always use HTTPS:
https://github.com/truenas/iocage-ix-plugins/commit/b4fa47b4052f6c2ea7dad35a5b176f8603643e76
®