CVE-2022-0645 (published 2022-04-19T12:15Z)

Open redirect vulnerability via endpoint authorize_and_redirect/?redirect= in GitHub repository posthog/posthog prior to 1.34.1.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-601
cwe_name: URL Redirection to Untrusted Site ('Open Redirect')
description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
extended_description: An http parameter may contain a URL value and could cause the web application to redirect the request to the specified URL. By modifying the URL value to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts have a more trustworthy appearance. Whether this issue poses a vulnerability will be subject to the intended behavior of the application. For example, a search engine might intentionally provide redirects to arbitrary URLs.
         

Fix information:
repo_url: https://github.com/posthog/posthog
commit_details: repo_url: https://github.com/posthog/posthog
author: Marius Andra
author_date: 2022-03-29 15:29:57+02:00
msg: fix(toolbar): only redirect to hosts in app_urls (#9268)

* fix(toolbar): only redirect to hosts in app_urls

* fix mypy and regex
repository: repo_url: https://github.com/posthog/posthog
repo_name: PostHog/posthog
description: 🦔 PostHog provides open-source product analytics, session recording, feature flagging and A/B testing that you can self-host.
repo_language: Python
owner: posthog
file_changes: filename: decide.py
change_type: ModificationType.MODIFY
diff: @@ -20,23 +20,30 @@ from .utils import get_project_id
 
 
 def on_permitted_domain(team: Team, request: HttpRequest) -> bool:
+    origin = parse_domain(request.headers.get("Origin"))
+    referer = parse_domain(request.headers.get("Referer"))
+    return hostname_in_app_urls(team, origin) or hostname_in_app_urls(team, referer)
+
+
+def hostname_in_app_urls(team: Team, hostname: Optional[str]) -> bool:
+    if not hostname:
+        return False
+
     permitted_domains = ["127.0.0.1", "localhost"]
 
     for url in team.app_urls:
-        hostname = parse_domain(url)
-        if hostname:
-            permitted_domains.append(hostname)
+        host = parse_domain(url)
+        if host:
+            permitted_domains.append(host)
 
-    origin = parse_domain(request.headers.get("Origin"))
-    referer = parse_domain(request.headers.get("Referer"))
     for permitted_domain in permitted_domains:
         if "*" in permitted_domain:
-            pattern = "^{}$".format(permitted_domain.replace(".", "\\.").replace("*", "(.*)"))
-            if (origin and re.search(pattern, origin)) or (referer and re.search(pattern, referer)):
-                return True
-        else:
-            if permitted_domain == origin or permitted_domain == referer:
+            pattern = "^{}$".format(re.escape(permitted_domain).replace("\\*", "(.*)"))
+            if re.search(pattern, hostname):
                 return True
+        elif permitted_domain == hostname:
+            return True
+
     return False
 
 

programming_language: Python
filename: test_urls.py
change_type: ModificationType.MODIFY
diff: @@ -56,6 +56,15 @@ class TestUrls(APIBaseTest):
         self.assertEqual(response.status_code, status.HTTP_200_OK)
 
     def test_authorize_and_redirect_domain(self):
+        self.team.app_urls = ["https://domain.com", "https://not.com"]
+        self.team.save()
+
+        response = self.client.get(
+            "/authorize_and_redirect/?redirect=https://not-permitted.com", HTTP_REFERER="https://not-permitted.com"
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertTrue("Can only redirect to a permitted domain." in str(response.content))
+
         response = self.client.get(
             "/authorize_and_redirect/?redirect=https://domain.com", HTTP_REFERER="https://not.com"
         )

programming_language: Python
filename: urls.py
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,8 @@
-from typing import Any, Callable, List, Optional
+from typing import Any, Callable, List, Optional, cast
 from urllib.parse import urlparse
 
 from django.conf import settings
-from django.http import HttpResponse
+from django.http import HttpRequest, HttpResponse
 from django.urls import URLPattern, include, path, re_path
 from django.views.decorators import csrf
 from django.views.decorators.csrf import csrf_exempt
@@ -21,7 +21,9 @@ from posthog.api import (
     signup,
     user,
 )
+from posthog.api.decide import hostname_in_app_urls
 from posthog.demo import demo
+from posthog.models import User
 
 from .utils import render_template
 from .views import health, login_required, preflight_check, robots_txt, security_txt, sso_login, stats
@@ -50,15 +52,19 @@ def home(request, *args, **kwargs):
     return render_template("index.html", request)
 
 
-def authorize_and_redirect(request):
+def authorize_and_redirect(request: HttpRequest) -> HttpResponse:
     if not request.GET.get("redirect"):
         return HttpResponse("You need to pass a url to ?redirect=", status=401)
     if not request.META.get("HTTP_REFERER"):
         return HttpResponse('You need to make a request that includes the "Referer" header.', status=400)
 
+    current_team = cast(User, request.user).team
     referer_url = urlparse(request.META["HTTP_REFERER"])
     redirect_url = urlparse(request.GET["redirect"])
 
+    if not current_team or not hostname_in_app_urls(current_team, redirect_url.hostname):
+        return HttpResponse(f"Can only redirect to a permitted domain.", status=400)
+
     if referer_url.hostname != redirect_url.hostname:
         return HttpResponse(f"Can only redirect to the same domain as the referer: {referer_url.hostname}", status=400)
 

programming_language: Python