diff --git a/airflow/www/views.py b/airflow/www/views.py index ad7cc5104c3a2..b5bd317e8022b 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"), ( '">