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!