From 03753c0437dd6cf20a10c0f21d04401e9b69ff61 Mon Sep 17 00:00:00 2001 From: Steve Kowalik Date: Tue, 13 Dec 2022 19:11:47 +1100 Subject: [PATCH] Quote URLs in Repo.clone_from() Since the URL is passed directly to git clone, and the remote-ext helper will happily execute shell commands, so by default qoute URLs unless a (undocumented, on purpose) kwarg is passed. (CVE-2022-24439) Fixes #1515 --- git/repo/base.py | 3 +++ test/test_repo.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/git/repo/base.py b/git/repo/base.py index c49c61184..0d2a92712 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -8,6 +8,7 @@ import os import re import shlex +import urllib.parse import warnings from gitdb.db.loose import LooseObjectDB @@ -1272,6 +1273,8 @@ def clone_from( git = cls.GitCommandWrapperType(os.getcwd()) if env is not None: git.update_environment(**env) + if 'unsafe_protocols' not in kwargs: + url = urllib.parse.quote(url) return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) def archive( diff --git a/test/test_repo.py b/test/test_repo.py index 703dbb438..2d4e24316 100644 --- a/test/test_repo.py +++ b/test/test_repo.py @@ -13,6 +13,7 @@ import pickle import sys import tempfile +import uuid from unittest import mock, skipIf, SkipTest import pytest @@ -263,6 +264,18 @@ def test_leaking_password_in_clone_logs(self, rw_dir): to_path=rw_dir, ) + @with_rw_repo("HEAD") + def test_clone_from_quotes_urls_by_default(self, repo): + bad_filename = f'{tempfile.gettempdir()}/{uuid.uuid4()}' + bad_url = f'ext::sh -c touch% {bad_filename}' + try: + repo.clone_from( + bad_url, 'tmp', multi_options=["-c protocol.ext.allow=always"] + ) + except GitCommandError: + pass + self.assertFalse(pathlib.Path(bad_filename).is_file()) + @with_rw_repo("HEAD") def test_max_chunk_size(self, repo): class TestOutputStream(TestBase):