diff --git a/airflow/www/views.py b/airflow/www/views.py
index 1e488cda1e00e..640adf84dffea 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -34,7 +34,7 @@
from json import JSONDecodeError
from operator import itemgetter
from typing import Any, Callable
-from urllib.parse import parse_qsl, unquote, urlencode, urlparse
+from urllib.parse import unquote, urljoin, urlsplit
import configupdater
import flask.json
@@ -155,27 +155,21 @@ def truncate_task_duration(task_duration):
def get_safe_url(url):
"""Given a user-supplied URL, ensure it points to our web server"""
- valid_schemes = ['http', 'https', '']
- valid_netlocs = [request.host, '']
-
if not url:
return url_for('Airflow.index')
- parsed = urlparse(url)
-
# If the url contains semicolon, redirect it to homepage to avoid
# potential XSS. (Similar to https://github.com/python/cpython/pull/24297/files (bpo-42967))
if ';' in unquote(url):
return url_for('Airflow.index')
- query = parse_qsl(parsed.query, keep_blank_values=True)
-
- url = parsed._replace(query=urlencode(query)).geturl()
-
- if parsed.scheme in valid_schemes and parsed.netloc in valid_netlocs:
- return url
+ host_url = urlsplit(request.host_url)
+ redirect_url = urlsplit(urljoin(request.host_url, url))
+ if not (redirect_url.scheme in ("http", "https") and host_url.netloc == redirect_url.netloc):
+ return url_for('Airflow.index')
- return url_for('Airflow.index')
+ # This will ensure we only redirect to the right scheme/netloc
+ return redirect_url.geturl()
def get_date_time_num_runs_dag_runs_form_data(www_request, session, dag):
diff --git a/tests/www/views/test_views.py b/tests/www/views/test_views.py
index 0899badfc4a40..79d42b869e4b7 100644
--- a/tests/www/views/test_views.py
+++ b/tests/www/views/test_views.py
@@ -167,7 +167,13 @@ def test_task_dag_id_equals_filter(admin_client, url, content):
"test_url, expected_url",
[
("", "/home"),
+ ("javascript:alert(1)", "/home"),
+ (" javascript:alert(1)", "http://localhost:8080/ javascript:alert(1)"),
("http://google.com", "/home"),
+ ("google.com", "http://localhost:8080/google.com"),
+ ("\\/google.com", "http://localhost:8080/\\/google.com"),
+ ("//google.com", "/home"),
+ ("\\/\\/google.com", "http://localhost:8080/\\/\\/google.com"),
("36539'%3balert(1)%2f%2f166", "/home"),
(
"http://localhost:8080/trigger?dag_id=test&origin=36539%27%3balert(1)%2f%2f166&abc=2",
diff --git a/tests/www/views/test_views_trigger_dag.py b/tests/www/views/test_views_trigger_dag.py
index bc578f8ea74d9..e443d5b50f730 100644
--- a/tests/www/views/test_views_trigger_dag.py
+++ b/tests/www/views/test_views_trigger_dag.py
@@ -149,14 +149,14 @@ def test_trigger_dag_form(admin_client):
("36539'%3balert(1)%2f%2f166", "/home"),
(
'">