- Status Closed
-
Assigned To
cbay - Private
Opened by zchill - 16.09.2026
Last edited by cbay - 21.09.2026
FS#481 - Any account can send fully authenticated email as any @alwaysdata.net address
Assets: webmail.alwaysdata.com (in scope) and the account submission service smtp-<account>.alwaysdata.net:465/587 (documented sending path, reached from the in-scope ssh-<account>.alwaysdata.net session). The vulnerable components are alwaysdata's own mail infrastructure: their submission relays (smtpout1.paris1.alwaysdata.com), their DKIM signer (d=alwaysdata.net, s=default) and their SPF ranges.
Class: CWE-290 (authentication bypass by spoofing) / CWE-863 (incorrect authorization)
CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N — because the impact is on every receiving system that trusts alwaysdata's mail authentication
Summary
An account as cheap as Free can send email as any address in the alwaysdata.net domain — ceo@, support@, any name — and the platform relays and DKIM-signs it. The forgery is not cosmetic: it verifies.
- The webmail UI (webmail.alwaysdata.com, in scope) lets the user add an identity with an arbitrary email address, with no verification of ownership, and then offers that identity as a first-class sender in the compose From dropdown. A message sent this way is delivered with the forged From and a valid DKIM signature.
- The submission service (smtp-<account>.alwaysdata.net:465, documented as the standard sending path) accepts any envelope sender from an authenticated account and relays it the same way.
- Verified facts on the delivered messages: the platform's relay signs them with DKIM d=alwaysdata.net s=default covering the attacker-chosen From header; the sending relay is inside alwaysdata.net's own SPF range; and DMARC for alwaysdata.net is strict (aspf=s; adkim=s) with an exact domain match — so the forgery passes SPF, DKIM and DMARC at any policy-enforcing receiver.
- End-to-end DKIM verification — header and body hash — passes on bytes fetched from alwaysdata's own IMAP storage (the platform's own mail store, no third-party mailbox involved): `dkim.verify(…) == True`.
Impact: any customer (or any phished/compromised account, or anyone willing to spend a Free signup) can send email that receiving providers authenticate as genuinely sent by alwaysdata.net — phishing alwaysdata's own customers with, for example, "your hosting account is about to be suspended" notifications that carry valid authentication. The same primitive lets the platform's outbound infrastructure be abused for sending under its authenticated domain at will.
Reproduction A — through the in-scope webmail UI (no tools beyond a browser)
1. Log in at https://webmail.alwaysdata.com/ with any account.
2. Settings → Identities → Create. Enter any email address in the alwaysdata.net domain, e.g.:
Display name: Alwaysdata Support
Email: ceo@alwaysdata.net
The form accepts it and offers no ownership verification; the identity is saved and listed.
3. Compose a message. The From dropdown now contains "Alwaysdata Support ceo@alwaysdata.net" alongside the account's own address. Select it, address the message anywhere, and send.
4. The send result reports "Message sent successfully". The message is delivered with:
From: Alwaysdata Support <ceo@alwaysdata.net>
Return-Path: <ceo@alwaysdata.net>
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=alwaysdata.net; s=default; h=Date:Message-Id:Subject:To:From: ...
5. Cryptographic verification of the delivered message, performed on the bytes fetched from alwaysdata's own IMAP server (the platform's mail store):
$ python3 -c "import dkim; print(dkim.verify(raw_message))"
True
Header signature and body hash both pass. In addition, alwaysdata's own inbound mail pipeline evaluated the forged message and recorded its verdict in the delivered headers:
X-alwaysdata-Spam-Report: ... DMARC_POLICY_ALLOW [alwaysdata.net, quarantine]
- DMARC permit policy
X-alwaysdata-Spam-Score: -4.87
That is the platform's own DMARC evaluation accepting the forged mail for the alwaysdata.net domain (the negative score makes it less spam-like than ordinary mail). The full cycle was then reproduced independently from a clean state — identity removed, re-created through the same UI steps, a new message sent and verified again with the same results (`dkim.verify == True`, `DMARC_POLICY_ALLOW`, score -3.21). SPF: the delivering relay 2a00:b6e0:1:a022::1 is inside alwaysdata.net's own published range:
$ dig +short TXT _spf.alwaysdata.com
"v=spf1 ip4:185.31.40.0/22 ip4:188.72.70.0/24 ip4:78.142.219.0/24
ip6:2a00:b6e0::/32 ..."
alwaysdata.net's published policy:
$ dig +short TXT _dmarc.alwaysdata.net
"v=DMARC1; p=quarantine; sp=quarantine; aspf=s; adkim=s; ..."
The From domain and the signing domain are both alwaysdata.net — an exact match — so strict alignment passes and the message satisfies DMARC. (The DKIM public key used for local verification: `dig +short TXT default._domainkey.alwaysdata.net`; the signature was additionally verified directly against it with `openssl dgst -sha256 -verify ... -> Verified OK`.)
Reproduction B — through the documented submission service (any script, any MUA)
1. Connect and authenticate with the account's own mailbox credentials (the service and hostname are documented at help.alwaysdata.com → "Configuring Thunderbird": smtp-<account>.alwaysdata.net, port 465):
python3 -c '
import smtplib,ssl
c=ssl.create_default_context(); c.check_hostname=False; c.verify_mode=ssl.CERT_NONE
s=smtplib.SMTP_SSL("smtp-zchill.alwaysdata.net",465,context=c)
s.login("zchill@alwaysdata.net","<account password>") # Authentication succeeded
'
2. Envelope probes — the server accepts any sender, authenticated as the tester's own mailbox:
MAIL FROM:<zchill@alwaysdata.net> -> 250 OK
MAIL FROM:<ceo@alwaysdata.net> -> 250 OK
MAIL FROM:<cbay@alwaysdata.com> -> 250 OK
MAIL FROM:<totally-random@example.org> -> 250 OK
(each RCPT → 250 Accepted; connection reset before DATA)
The same probes succeed from an external machine on port 587 (STARTTLS), confirming the vector is not restricted to the platform's internal network.
3. A message composed with From: Alwaysdata Support ceo@alwaysdata.net and envelope ceo@alwaysdata.net, sent to the tester's own mailbox on the platform, is delivered and verifies exactly as in Reproduction A step 5.
Control (what the service does correctly): an unauthenticated session is refused at message acceptance:
MAIL FROM:<ceo@alwaysdata.net> -> 250 OK
RCPT TO:<...> -> 250 Accepted
DATA -> 550 relay not permitted: you must be authenticated to send messages
So this is not an open relay: the failure is that once authenticated, no sender ownership is enforced and the platform signs what it is given.
On "working as intended"
- If arbitrary senders were intended, the platform would not maintain strict DMARC (aspf=s; adkim=s) and a dedicated abuse contact for its own domain, and the webmail would not have an identities subsystem at all. The customer-facing way to send as a custom domain is to add the domain after verification; @alwaysdata.net is never a customer domain.
- The expectation that recipients can trust alwaysdata.net mail is the entire purpose of SPF, DKIM and DMARC here — this finding is a sender-authentication bypass of that trust, performed by any account the platform itself issues.
- The service performs one check correctly (authentication required) and fails the next (sender authorization), so the control gap is precise and fixable.
Suggested fix
1. Enforce sender authorization at submission: the authenticated mailbox may send only as its own address plus addresses of domains it verifiably owns (the panel's own domain-ownership validation is the right registry). Reject other envelope senders at MAIL FROM, and cross-check the From header.
2. Validate identity emails in the webmail against that same registry (or restrict identity creation to the account's own addresses).
3. Do not apply the alwaysdata.net DKIM signature to messages whose From: is not an authorized alwaysdata.net identity; align the signer with the sender-authorization decision.
Loading...
Available keyboard shortcuts
- Alt + ⇧ Shift + l Login Dialog / Logout
- Alt + ⇧ Shift + a Add new task
- Alt + ⇧ Shift + m My searches
- Alt + ⇧ Shift + t focus taskid search
Tasklist
- o open selected task
- j move cursor down
- k move cursor up
Task Details
- n Next task
- p Previous task
- Alt + ⇧ Shift + e ↵ Enter Edit this task
- Alt + ⇧ Shift + w watch task
- Alt + ⇧ Shift + y Close Task
Task Editing
- Alt + ⇧ Shift + s save task
Hello,
Can you confirm that the bug is fixed?
Kind regards,
Cyril
Hey,
Yes it's fixed now.
Regards
Thanks, you can now claim your bounty by opening a support ticket.