All Projects

ID Status Summary Opened by
 420 Closed Webmail Sessions Persist After Admin Panel Password and ...awais0x01 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 Closed Server-Side Request Forgery via Reverse Proxy Site Type ...awais0x01 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 Closed Cross-Tenant Data Exposure via Shared /tmp Directory awais0x01 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.

Showing tasks 1 - 3 of 3 Page 1 of 1

Available keyboard shortcuts

Tasklist

Task Details

Task Editing