usd-2021-0019 | Zulip
Advisory ID: usd-2021-0019
Affected Product: Zulip
Affected Version: <= Zulip Server 4.7
Vulnerability Type: CWE-918: Server-Side Request Forgery (SSRF)
Security Risk: Medium
Vendor URL: https://zulip.com/
Vendor Status: Fixed
With the release of Zulip 4.0 an optional mitigation against SSRF was introduced, which was disabled by default until the release of Zulip 4.8. It is highly recommended to update to the latest version of Zulip, because older versions (in their default configuration) may be vulnerable to SSRF due to their configuration. Also, the configuration of Smokescreen was optional.
For Zulip <= 4.7 (default configuration), it is possible to use the outgoing webhook feature of bots in order to scan open ports of the Zulip server (localhost) and other systems on the internal network.
The application is vulnerable to server-side request forgery (SSRF) attacks and can therefore be made to perform requests to other services. From the perspective of those other services it looks like the requests originated from the vulnerable application. It is possible to use the outgoing webhook feature of bots in order to scan open ports of the Zulip server (localhost) and other systems on the internal network. An open port can be differentiated from a closed one by observing the time delay between talking to a bot and receiving the "Failure! Bot is unavailable" error message.
Proof of Concept (PoC)
The SSRF to localhost can be proven as follows:
1. Create two new bots at https://<your-zulip-instance>/#settings/your-bots. Name one of the bots "openPortLocalhost" and set the Endpoint URL to http://localhost:8765:
Name the other bot "closedPortLocalhost" and set the endpoint to http://localhost:<known closed port>.
2. Open a port on localhost:
$ nc -lvp 8765
3. Trigger openPortLocalhost for which you opened a port on localhost:
4. Observe the incoming HTTP request:
5. Wait for a couple of minutes before killing the Netcat process with Ctrl+C.
6. Observe the time difference between `openPortLocalhost` was called and the "Failure! Bot is unavailable" error message was received.
7. Trigger closedPortLocalhost:
8. Observe the time difference between `closedPortLocalhost` was called and the "Failure! Bot is unavailable" error message was received.
9. Compare the response times of calling the two different bots. The error message for openPortLocalhost is received shortly after killing the Netcat process (step 5), while the error message for closedPortLocalhost is received almost instantly.
The following screenshots illustrate the blind SSRF to the internal network:
With the release of Zulip 4.0 an optional mitigation against SSRF was introduced. Zulip now allows to configure Smokescreen, "an open-source proxy developed by Stripe to protect a service with outgoing webhooks from being used to make SSRF attacks against other services". Since Zulip 4.8 Smokescreen is enabled by default. Thus, it is highly recommended to update to the most-recent version of Zulip.
Generally, it is recommended to evaluate the need to make server-side requests. If server-side requests are absolutely necessary, the corresponding function should be ideally restricted by a whitelisting approach.
2021-04-30: vulnerability identified by Marcus Nilsson.
2021-05-05: Initial contact via firstname.lastname@example.org.
2021-05-06: Vulnerability details submitted.
2021-05-18: Status update requested.
- 2021-05-20: Zulip responds and asks to verify the findings against the recently published version 4.0 including the introduced Smokescreen setup.
- 2021-07-23: usd confirms that the Smokescreen setup mitigates the described SSRF vulnerability.
- 2021-12-01: Zulip 4.8 is released which is not vulnerable by default against the described vulnerability anymore.
- 2022-02-25: Security advisory released by usd AG.
This security vulnerability was found by Marcus Nilsson of usd AG.