Full Report
The author of this post has a strict policy on when they will use a product or not for a strict 24-hour research window. This is a hands-on source code review to see how the product would behave in their environment. This is why they were looking into this product in the first place. The architecture has a UI that makes calls to a backend Django server. The server will trigger Celery tasks that get executed on ClicHouse for SQL. There is a PostgresDB for storing status information about the Celery task as well. PostHog supports thousands of external integrations used for CRMs, support, billing systems, and other purposes. The promise is to analyze product/customer data, wherever it is generated. To an attacker, this sounds like a SSRF vulnerability waiting to happen. They found a bypass for CVE-2023-46746 by directly calling a PATCH request to store the endpoint used for a webhook. There's also a TOCTOU bug in the verification. Now, we can set the domain to be localhost. SSRF is excellent, but each one of them has unique exploitation constraints. The vulnerability creates an incoming POST request. Using a 302 redirect, it's possible to change the request method to GET. This is a reasonably powerful SSRF as a result. The ClickHouse database runs on port 8123 via HTTP; this is enabled by default on localhost. GET requests operate as READ ONLY calls and POST for data modifications. This gives us a primitive for executing SQL queries against the underlying Clickhouse datastore. Using the SSRF, it's possible to extract large amounts of data. ClickHouse has a ffeature called Table Functions; these are temporary and query scoped tables that only exist during the duration of the query. While reviewing the escaping functionality of preventing SQL injection, they noticed a Postgres-specific flaw: backslashes don't escape. The code would turn 'posthog_table' into 'posthog_table\'', but the backslash is a literal here. So, we now have SQL injection on the incoming query that can be used to write on a GET request as well. The exploit payload is pretty slick after the SQL injection. First, the internal query must be completed by adding ;END, which makes it read-only. Next, execute a command using cmd_exec and put the results into a payload. An important trick is to use $$ instead of single quotes within the payload. Otherwise, the single quotes would have been escaped once again. This appears to require permissions on PostHog. It'd be weird to be able to add arbitrary webhooks as an anonymous user. Still, the flow control bypass to get the SSRF and the SQL injection within the ClickHouse endpoint were both great finds!
Analysis Summary
# Vulnerability: PostHog RCE Chain via SSRF, SQL Injection, and Default Credentials
## CVE Details
- **CVE ID:** CVE-2024-9710 (SSRF), CVE-2025-1522 (SSRF), CVE-2025-1521 (SSRF), ZDI-25-099, ZDI-25-097, ZDI-25-096
- **CVSS Score:** 9.8 (Critical - Estimated as RCE chain)
- **CWE:** CWE-918 (SSRF), CWE-89 (SQL Injection)
## Affected Systems
- **Products:** PostHog (Self-hosted analytics platform)
- **Versions:** Software versions prior to December 2025/January 2026 patches. Specific versions include those vulnerable to the CVE-2023-46746 bypass.
- **Configurations:** Self-hosted deployments using the default architecture (Django, Celery, ClickHouse, and PostgreSQL).
## Vulnerability Description
This exploit represents a multi-stage attack chain:
1. **SSRF Bypass:** Researchers bypassed previous fixes (CVE-2023-46746) via a logic flaw in the webhook validation. By using a direct `PATCH` request to store an endpoint, attackers could bypass the initial validation check. A TOCTOU (Time-of-Check to Time-of-Use) bug allowed setting the domain to `localhost`.
2. **Protocol Smuggling:** The SSRF creates an incoming POST request. By utilizing a 302 redirect, the attacker converts the POST into a GET request reaching internal services.
3. **SQL Injection (ClickHouse/PostgreSQL):** The researcher identified a PostgreSQL-specific escaping flaw where backslashes were treated as literals rather than escape characters (transforming `table` into `table\'`). This allows SQL injection into internal queries.
4. **Remote Code Execution (RCE):** By chaining the SSRF to reach the internal ClickHouse HTTP interface (port 8123) and using the SQL injection primitive, an attacker can execute administrative commands (e.g., `cmd_exec`) to achieve full system compromise.
## Exploitation
- **Status:** PoC available (documented by Trend Micro ZDI and Mehmet Ince).
- **Complexity:** Medium (Requires chaining multiple primitives and understanding internal escaping logic).
- **Attack Vector:** Network (Authenticated/Authorized user permissions required to add webhooks).
## Impact
- **Confidentiality:** Total (Full access to underlying ClickHouse and Postgres databases).
- **Integrity:** Total (Ability to modify data and system state).
- **Availability:** Total (RCE allows for complete system shutdown or disruption).
## Remediation
### Patches
- Users should update to the latest version of PostHog (issued late 2025/early 2026).
- Ensure ClickHouse and PostgreSQL components are updated to versions that correctly handle backslash escaping in SQL queries.
### Workarounds
- **Network Segmentation:** Ensure the ClickHouse (8123) and PostgreSQL ports are not reachable by the PostHog application server's outbound requests OR implement strict egress filtering.
- **Credential Rotation:** Change all default credentials for the underlying PostgreSQL and ClickHouse instances immediately.
## Detection
- **Indicators of Compromise:**
- Unexpected outbound connections from the PostHog server to `127.0.0.1` or `localhost`.
- HTTP 302 redirects in logs originating from webhook validation endpoints.
- ClickHouse logs showing queries containing unexpected characters like `$$` or `;END;`.
- **Detection Methods:** Monitor for `PATCH` requests to `/api/projects/@current/webhooks/` followed by immediate attempts to trigger the webhook.
## References
- Mehmet Ince Advisory: hxxps://mehmetince[.]net/inside-posthog-how-ssrf-a-clickhouse-sql-escaping-0day-and-default-postgresql-credentials-formed-an-rce-chain-zdi-25-099-zdi-25-097-zdi-25-096/
- PostHog Webhook Documentation: hxxps://posthog[.]com/docs/webhooks
- ZDI Advisories: ZDI-25-099, ZDI-25-097, ZDI-25-096