PsiTransfer trusted user-supplied filenames a little too much. Attackers can upload files named like `../../.ssh/authorized_keys`. When a victim downloads these files as a ZIP or TAR, the server dutifully packs that malicious path. If the victim extracts this archive, the attacker's file escapes the download folder and overwrites critical system files, potentially leading to RCE on the client machine.
A critical Path Traversal vulnerability in PsiTransfer allows attackers to weaponize the 'Download Archive' feature. By uploading files with malicious filenames, attackers can generate archives that perform arbitrary file overwrites on the victim's machine upon extraction.
PsiTransfer is one of those delightful open-source tools that does exactly what it says on the tin: it lets you share files. No accounts, no complex cloud storage buckets, just a simple web interface to upload a file and get a link. It’s self-hosted, which implies a circle of trust. You trust the server, and the server trusts you. And therein lies the tragedy.
In the world of web security, trust is a four-letter word. PsiTransfer was designed to handle file uploads securely regarding storage. It renames uploaded files on the disk to unique IDs (SIDs) so that you can't accidentally upload a PHP shell and execute it on the server. Good job, developers. Secure design 101.
However, they forgot one crucial aspect: Metadata. When you upload a file, you aren't just sending bytes; you're sending a filename. PsiTransfer stored this filename in a database (JSONDB) to display it nicely to the next user. But what happens when that filename isn't just a name, but a roadmap to the victim's filesystem?
Most security researchers are familiar with the classic 'Zip Slip' vulnerability. Usually, that involves an application extracting a malicious ZIP and getting owned because it writes files outside the target directory. This vulnerability is the inverse: it is the creation of a malicious ZIP.
Here is the logic flow that doomed the application:
../../../../Users/Admin/.ssh/authorized_keys.files/12345.dat, but saves the name in the database exactly as provided: ../../../../Users/Admin/.ssh/authorized_keys.At this moment, the server dynamically generates an archive. It reads the safe content 12345.dat, but it labels the entry in the ZIP header using the stored malicious name. The server effectively constructs a weaponized archive on the fly and hands it to the victim. It is a Reflected File Download attack powered by Path Traversal.
The vulnerability lived in the way PsiTransfer handled the archiver and tar-stream libraries. It took the input directly from the metadata store without sanitization. Let's look at the logic before the patch.
In the vulnerable version, the code looked something like this (pseudocode based on the logic):
// Vulnerable Logic
archive.append(fileStream, { name: file.metadata.name });If file.metadata.name contains ../, the archiver library (compliant with the ZIP spec) writes those characters into the file header. The fix involved introducing a strict sanitizer. The patch was applied in commit 6c71bc0b8afa1ffa7aabd6c5fb28677651fd57b6.
Here is the remediation logic introduced in lib/utils.js:
const path = require('path');
module.exports.toSafeBasename = (str) => {
// 1. Remove control characters
str = str.replace(/[\x00-\x1f\x80-\x9f]/g, '');
// 2. Force basename (strips directory components)
str = path.posix.basename(str);
// 3. Fallback for empty strings or prohibited names
if (!str || str === '.' || str === '..') {
return 'renamed-by-system';
}
return str;
};This function is a sledgehammer. It uses path.posix.basename() which aggressively strips everything except the final file name. ../../etc/passwd becomes passwd. Simple, effective, and brutal.
Let's walk through a practical attack scenario. We aren't targeting the server here; we are using the server as a mule to target other users (perhaps an admin downloading logs or backup files).
The attacker prepares a legitimate-looking file (e.g., quarterly_report.pdf) but intercepts the upload request using Burp Suite or a simple Python script.
The attacker modifies the JSON payload in the upload request:
{
"metadata": {
"name": "../../../../../../home/victim/.bashrc",
"mime": "application/pdf"
}
}PsiTransfer accepts this with a smile (HTTP 200 OK).
The attacker sends the download link to the victim: "Hey, I uploaded the logs you asked for, just hit 'Download as TAR' to get them all at once."
The victim downloads archive.tar.gz. They open a terminal and run tar -xzvf archive.tar.gz.
Because standard tar (and many GUI tools) preserves paths by default or if flags are misused, the file extracts outside the current directory, overwriting the victim's .bashrc. The next time the victim opens a terminal... BOOM. The attacker's code executes.
The remediation is straightforward but highlights the importance of 'Defense in Depth'. The developers released version 2.3.1, which implements the sanitization logic described above.
[!TIP] For Developers: Never trust a filename. It is user input just like a SQL query or HTML body. Always generate your own filenames internally, or strip the input down to alphanumeric characters only.
If you are running a version of PsiTransfer older than 2.3.1, you are essentially hosting a malware distribution platform for anyone who can access your upload endpoint. Update immediately.
If you cannot update, your only defense is to disable public uploads or force users to download files one by one (avoiding the archive generation logic), though even single-file downloads can be risky if the Content-Disposition header is mishandled by the browser.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
PsiTransfer psi-4ward | < 2.3.1 | 2.3.1 |
| Attribute | Detail |
|---|---|
| Bug Class | Path Traversal / Zip Slip |
| Attack Vector | Network (Uploaded Metadata) |
| CVSS | 8.1 (High) |
| Impact | Client-Side Arbitrary File Write |
| Component | Archive Generator (lib/endpoints.js) |
| Exploit Status | Functional PoC Available |
The application allows user input to control paths used in filesystem operations, specifically within archive generation, allowing traversal outside the intended directory.
Get the latest CVE analysis reports delivered to your inbox.