Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CVE-2022-24439: <gitpython::clone> 'ext::sh -c touch% /tmp/pwned' for remote code execution #1515

Closed
mmuehlenhoff opened this issue Dec 6, 2022 · 29 comments · Fixed by #1521

Comments

@mmuehlenhoff
Copy link

This appeared in the CVE feed today: https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858

Not sure if this was reported to you before or not, reporting it here just in case.

@Byron
Copy link
Member

Byron commented Dec 6, 2022

I am aware, have been informed and we can track it here. Thanks a lot for setting up this issue so timely.

@Byron Byron changed the title CVE-2022-24439 CVE-2022-24439: <gitpython::clone> 'ext::sh -c touch% /tmp/pwned' for remote code execution Dec 6, 2022
@ajakk
Copy link

ajakk commented Dec 6, 2022

@Byron, had Snyk or Sam Wheating (@SamWheating?) contacted you previously about this or did you learn about it independently? I'm curious given there's no reference to any upstream report in the above Snyk report.

@SamWheating
Copy link

I reached out to Snyk, who I believe got in touch with the maintainers.

@ajakk
Copy link

ajakk commented Dec 6, 2022

I reached out to Snyk, who I believe got in touch with the maintainers.

What makes you think that? Again, just curious given there doesn't seem to be any indication of that happening according to their report. Also, why did you go to Snyk rather than to upstream?

@Byron
Copy link
Member

Byron commented Dec 6, 2022

Snyk did reach out to me by email, to my mind all this went pretty well. By publishing the issue the community can contribute a mitigation.

@ajakk
Copy link

ajakk commented Dec 6, 2022

Yeah, seems fine to me. It would have been nice to have an existing public report to go with the public release of the CVE so that all the people who handle CVEs (myself, and the reporter of this issue, for example) would know that the issue is already known to upstream and we don't have to spend time extracting that information via issues like this.

Thank you for the insight.

@stsewd
Copy link
Contributor

stsewd commented Dec 6, 2022

Looks like this same vulnerability has been reported in another git library (simple-git), https://www.cve.org/CVERecord?id=CVE-2022-25912.

They added an option to opt-in in to the insecure behavior

And this should probably check for https://www.cve.org/CVERecord?id=CVE-2022-24433 too (git clone file:///tmp/zero123 /tmp/example-new-repo --upload-pack='touch /tmp/pwn')

https://github.com/steveukx/git-js/blob/fc10fbe1cba2dedfccd8e5507b61fc979addb252/simple-git/src/lib/tasks/clone.ts#L46-L49

@SamWheating
Copy link

SamWheating commented Dec 6, 2022

What makes you think that? Again, just curious given there doesn't seem to be any indication of that happening according to their report. Also, why did you go to Snyk rather than to upstream?

Snyk's vulnerability program is fantastic - you can report an issue to them and they will review it, triage it, try to get in contact with the maintainers and then register the CVE if applicable. It eliminates a lot of the overhead on my end and helps to ensure that a vulnerability is handled appropriately.

https://docs.snyk.io/more-info/disclosing-vulnerabilities/disclose-a-vulnerability-in-an-open-source-package

I didn't have a direct line to the maintainers and I didn't want to open a public issue explaining a potentially sensitive vulnerability. In this case it sounds like Snyk was able to get in touch on my behalf and handle this disclosure responsibly.

@jacwalte
Copy link

jacwalte commented Dec 8, 2022

Just thought I would ping to keep this issue active. This is a critical issue in my org. Can we get a status update? Is a fix expected soon?

@Byron
Copy link
Member

Byron commented Dec 8, 2022

No fix is planned I don't plan to work on this directly, and this issue is triaged as 'help wanted'. Indirectly I am working on it by answering here and following up on the PR which might alleviate the problem.

s-t-e-v-e-n-k added a commit to s-t-e-v-e-n-k/GitPython that referenced this issue Dec 13, 2022
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 gitpython-developers#1515
s-t-e-v-e-n-k added a commit to s-t-e-v-e-n-k/GitPython that referenced this issue Dec 15, 2022
Since the URL is passed directly to git clone, and the remote-ext helper
will happily execute shell commands, so by default disallow URLs that
contain a "::" unless a new unsafe_protocols kwarg is passed.
(CVE-2022-24439)

Fixes gitpython-developers#1515
s-t-e-v-e-n-k added a commit to s-t-e-v-e-n-k/GitPython that referenced this issue Dec 15, 2022
Since the URL is passed directly to git clone, and the remote-ext helper
will happily execute shell commands, so by default disallow URLs that
contain a "::" unless a new unsafe_protocols kwarg is passed.
(CVE-2022-24439)

Fixes gitpython-developers#1515
@stsewd
Copy link
Contributor

stsewd commented Dec 15, 2022

To avoid part of the problem, gitpython should also avoid interpreting positional arguments as option arguments.
First, I was thinking in adding -- after the option args

args_list = opt_args + ext_args

but there are commands like git checkout that make special use of --, git mentions the --end-of-options option https://git-scm.com/docs/gitcli/ as an alias for -- for cases like that, but that option isn't available for git checkout 🙃

Other options could be:

  • To have a list of commands (like checkout) to not add --
  • Check all command calls and add -- manually to each one, for example

    GitPython/git/repo/base.py

    Lines 1170 to 1180 in 17ff263

    proc = git.clone(
    multi,
    Git.polish_url(str(url)),
    clone_path,
    with_extended_output=True,
    as_process=True,
    v=True,
    universal_newlines=True,
    **add_progress(kwargs, git, progress),
    )
    if progress:

    that would be git.clone(multi, '--', ...)

@Byron
Copy link
Member

Byron commented Dec 16, 2022

Am I the only one confused by trying to fix a problem in the underlying git executable by trying to prevent all misuse in the layer above?

Trying to do that, to my mind, will not deter an attacker who wants to find a bypass, and is much more likely to break downstream legitimate code, or use-cases for the now deemed unsafe features. There is clearly a tradeoff that seems impossible to get right.

git by now has a history of making backwards incompatible changes by forbidding certain protocols or checking for ownership of repositories on shared file systems. Thus my preference here is to let it fix itself.

Those who pass user-input to GitPython or the git binary for that matter are obliged to validate it, and I would be all for such code to be shared in GitPython as well. Validating or sanitizing a URL seems useful, for example. If it's most definitely not breaking current legitimate uses, this could be the default like attempted in #1516 which also allows to opt-out from the potentially breaking feature.

@Anthony-Mckale
Copy link

Anthony-Mckale commented Dec 16, 2022

we're using this in eze

https://github.com/RiverSafeUK/eze-cli

def sanitise_repo_url(repo_url: str) -> str:
    """
    CVE-2022-24439: sanitization of url to prevent bash script injection attacks in underlying GitPythion implementation

    warning: git clone has bug https://github.com/gitpython-developers/GitPython/issues/1515
    """
    cleaned_url = re.sub(" ", "_", repo_url)
    cleaned_url = re.sub("--", "__", cleaned_url)
    return cleaned_url


url_test_data = [
    ("Do Nothing", "https://clean.already.com/repo", "https://clean.already.com/repo"),
    (
        "Remove Injection attack",
        "https://clean.already.com/repo --hahaha='exploit town'",
        "https://clean.already.com/repo___hahaha='exploit_town'",
    ),
    (
        "Remove Double Injection attack",
        "https://clean.already.com/repo --hahaha1='exploit town' --hahaha2='exploit town'",
        "https://clean.already.com/repo___hahaha1='exploit_town'___hahaha2='exploit_town'",
    ),
]


@pytest.mark.parametrize("title,test_input,expected_output", url_test_data)
def test_sanitise_repo_url(title, test_input, expected_output):
    output = sanitise_repo_url(test_input)
    assert output == expected_output

@Byron
Copy link
Member

Byron commented Dec 16, 2022

Thanks for sharing!

Have you validated that URL injection works? After all the shell seems to be disabled by default and isn't enabled when calling clone either.

Without shell in the middle a url like this should be just a single string passed to git as URL and it clearly detects something is up.

✦ ❯ git clone  "https://clean.already.com/repo --hahaha='exploit town'",
Cloning into 'repo --hahaha='exploit town','...
fatal: unable to access 'https://clean.already.com/repo --hahaha='exploit town',/': URL using bad/illegal format or missing URL

For a moment I thought maybe git doesn't validate enough and passes on abusive URLs to sub-programs through a shell, but that fortunately doesn't seem to be the case.

@stsewd
Copy link
Contributor

stsewd commented Dec 16, 2022

Those who pass user-input to GitPython or the git binary for that matter are obliged to validate it, and I would be all for such code to be shared in GitPython as well

Don't you agree that methods like clone that are expecting a path/url

path: PathLike,
should always treat those parameters as such? This is, if you pass --foo and that's interpreted as an option, that is unexpected/wrong.

@Byron
Copy link
Member

Byron commented Dec 16, 2022

I agree, and now I understand what the prior comment is about as well. And it seems not too unlikely that url and path could be used to construct a command-line which uses --upload-pack='attack vector'. Once URL is a little more validated, that should get harder as well. Using --upload-pack is only possible with the ssh scheme and I'd expect only personal-use grade servers to expose more than just git-upload-pack and git-receive-pack in their PATH.

I think once it can be demonstrated that one can indeed execute programs on the server by manipulating the URL or maybe even the URL + destination path, that would be an issue specific to GitPython and worth its own issue.

stsewd pushed a commit to stsewd/GitPython that referenced this issue Dec 23, 2022
Since the URL is passed directly to git clone, and the remote-ext helper
will happily execute shell commands, so by default disallow URLs that
contain a "::" unless a new unsafe_protocols kwarg is passed.
(CVE-2022-24439)

Fixes gitpython-developers#1515
@blade2005
Copy link

I'm not sure I have much to contribute to the conversation or to a fix. I did want to offer a lamentation that git doesn't have a C library to tie into to avoid having to execute the git cli instead of working against a library instead for this.

@Byron
Copy link
Member

Byron commented Dec 24, 2022

The library for git is on the way, it's called gitoxide and I will find a way to transition GitPython to using gitoxide's python bindings one day, when these exists. I definitely can't maintain GitPython for another decade if it stays like this 😅.

@mcepl
Copy link

mcepl commented Dec 24, 2022

I did want to offer a lamentation that git doesn't have a C library

Well, it does (https://libgit2.org/), doesn’t it?

@Byron
Copy link
Member

Byron commented Dec 24, 2022

I assume @blade2005 meant that there is no native git library that git itself would be using, hence git provides an API via the command-line exclusively. libgit2 is a separate effort which, by now, does not provide feature and performance parity with git itself anymore.

@nrpt-m
Copy link

nrpt-m commented Dec 28, 2022

@Byron Could you please confirm if this PR #1518 is fixing CVE-2022-24439 issue as it has been added to 3.1.30 ?

@stsewd
Copy link
Contributor

stsewd commented Dec 28, 2022

@nrpt-m that PR fixes a similar vulnerability which doesn't require passing any extra options to multi_options to be able to exploit it, #1521 will fix the vulnerability when users allow passing any options to multi_options.

@Byron
Copy link
Member

Byron commented Dec 29, 2022

@nrpt-m I second that.

@Byron
Copy link
Member

Byron commented Dec 29, 2022

A new release was created to incorporate many security related fixes.

A special thanks goes to @stsewd who was a driving force behind implementing the fixes, and to the fine folks who discovered it.

I hope this makes the upcoming year 2023 a little more secure for everyone 🎉.

@ajakk
Copy link

ajakk commented Dec 30, 2022

A new release was created to incorporate many security related fixes.

A special thanks goes to @stsewd who was a driving force behind implementing the fixes, and to the fine folks who discovered it.

I hope this makes the upcoming year 2023 a little more secure for everyone tada.

Can a release be made in Github? I imagine a nonzero number of people are watching for releases in this repository who aren't subscribed to this issue to be aware of the security impact of this release.

@Byron
Copy link
Member

Byron commented Dec 31, 2022

Thanks for the hint. That should be done now.

openstack-mirroring pushed a commit to openstack/openstack that referenced this issue Jan 10, 2023
* Update requirements from branch 'master'
  to 2aaf64dd91c63aa55f4cbe8c037a6f545e91b302
  - Merge "Bump GitPython to 3.1.30"
  - Bump GitPython to 3.1.30
    
    3.1.30 includes 2 sets of fixes for CVE-2022-24439:
      https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24439
      gitpython-developers/GitPython#1515
    
    PRs:
      gitpython-developers/GitPython#1518
      gitpython-developers/GitPython#1521
    
    Signed-off-by: Lon Hohberger <lhh@redhat.com>
    Change-Id: I0def2d9801f0b20fcc9b613165a29dbced1fd2d7
openstack-mirroring pushed a commit to openstack/requirements that referenced this issue Jan 10, 2023
3.1.30 includes 2 sets of fixes for CVE-2022-24439:
  https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24439
  gitpython-developers/GitPython#1515

PRs:
  gitpython-developers/GitPython#1518
  gitpython-developers/GitPython#1521

Signed-off-by: Lon Hohberger <lhh@redhat.com>
Change-Id: I0def2d9801f0b20fcc9b613165a29dbced1fd2d7
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this issue Jan 20, 2023
3.1.30
- Make injections of command-invocations harder or impossible for clone and others.
  See gitpython-developers/GitPython#1518 for details.
  Note that this might constitute a breaking change for some users, and if so please
  let us know and we add an opt-out to this.
- Prohibit insecure options and protocols by default, which is potentially a breaking change,
  but a necessary fix for gitpython-developers/GitPython#1515.
  Please take a look at the PR for more information and how to bypass these protections
  in case they cause breakage: gitpython-developers/GitPython#1521.
EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Nov 16, 2023
The tests of unsafe options are among those introduced originally
in gitpython-developers#1521. They are regression tests for gitpython-developers#1515 (CVE-2022-24439).
The unsafe options tests are paired: a test for the usual, default
behavior of forbidding the option, and a test for the behavior when
the option is explicitly allowed. Both tests use a payload that is
intended to produce the side effect of a file of a specific name
being created in a temporary directory.

All the tests work on Unix-like systems. On Windows, the tests of
the *allowed* cases are broken, and this commit marks them xfail.
However, this has implications for the tests of the default, secure
behavior, because until the "allowed" versions work on Windows, it
will be unclear if either are using a payload that is effective and
that corresponds to the way its effect is examined. (Fortunately,
all are working on other OSes, and the affected code under test
does not appear highly dependent on OS, so the fix is *probably*
fully working on Windows as well.)
EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Nov 16, 2023
The tests of unsafe options are among those introduced originally
in gitpython-developers#1521. They are regression tests for gitpython-developers#1515 (CVE-2022-24439).
The unsafe options tests are paired: a test for the usual, default
behavior of forbidding the option, and a test for the behavior when
the option is explicitly allowed. Both tests use a payload that is
intended to produce the side effect of a file of a specific name
being created in a temporary directory.

All the tests work on Unix-like systems. On Windows, the tests of
the *allowed* cases are broken, and this commit marks them xfail.
However, this has implications for the tests of the default, secure
behavior, because until the "allowed" versions work on Windows, it
will be unclear if either are using a payload that is effective and
that corresponds to the way its effect is examined. (Fortunately,
all are working on other OSes, and the affected code under test
does not appear highly dependent on OS, so the fix is *probably*
fully working on Windows as well.)
EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Nov 16, 2023
The tests of unsafe options are among those introduced originally
in gitpython-developers#1521. They are regression tests for gitpython-developers#1515 (CVE-2022-24439).
The unsafe options tests are paired: a test for the usual, default
behavior of forbidding the option, and a test for the behavior when
the option is explicitly allowed. In each such pair, both tests use
a payload that is intended to produce the side effect of a file of
a specific name being created in a temporary directory.

All the tests work on Unix-like systems. On Windows, the tests of
the *allowed* cases are broken, and this commit marks them xfail.
However, this has implications for the tests of the default, secure
behavior, because until the "allowed" versions work on Windows, it
will be unclear if either are using a payload that is effective and
that corresponds to the way its effect is examined. (Fortunately,
all are working on other OSes, and the affected code under test
does not appear highly dependent on OS, so the fix is *probably*
fully working on Windows as well.)
EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Nov 16, 2023
The tests of unsafe options are among those introduced originally
in gitpython-developers#1521. They are regression tests for gitpython-developers#1515 (CVE-2022-24439).
The unsafe options tests are paired: a test for the usual, default
behavior of forbidding the option, and a test for the behavior when
the option is explicitly allowed. In each such pair, both tests use
a payload that is intended to produce the side effect of a file of
a specific name being created in a temporary directory.

All the tests work on Unix-like systems. On Windows, the tests of
the *allowed* cases are broken, and this commit marks them xfail.
However, this has implications for the tests of the default, secure
behavior, because until the "allowed" versions work on Windows, it
will be unclear if either are using a payload that is effective and
that corresponds to the way its effect is examined.

Specifically, the "\" characters in the path seem to be treated as
escape characters rather than literally. Also, "touch" is not a
native Windows command, and the "touch" command in Git for Windows
maps disallowed occurrences of ":" in filenames to a separate code
point in the Private Use Area of the Basic Multilingual Plane.
EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Nov 16, 2023
The tests of unsafe options are among those introduced originally
in gitpython-developers#1521. They are regression tests for gitpython-developers#1515 (CVE-2022-24439).
The unsafe options tests are paired: a test for the usual, default
behavior of forbidding the option, and a test for the behavior when
the option is explicitly allowed. In each such pair, both tests use
a payload that is intended to produce the side effect of a file of
a specific name being created in a temporary directory.

All the tests work on Unix-like systems. On Windows, the tests of
the *allowed* cases are broken, and this commit marks them xfail.
However, this has implications for the tests of the default, secure
behavior, because until the "allowed" versions work on Windows, it
will be unclear if either are using a payload that is effective and
that corresponds to the way its effect is examined.

What *seems* to happen is this: The "\" characters in the path are
treated as shell escape characters rather than literally, with the
effect of disappearing in most paths since most letters lack
special meaning when escaped. Also, "touch" is not a native Windows
command, and the "touch" command provided by Git for Windows is
linked against MSYS2 libraries, causing it to map (some?)
occurrences of ":" in filenames to a separate code point in the
Private Use Area of the Basic Multilingual Plane. The result is a
path with no directory separators or drive letter. It denotes a
file of an unintended name in the current directory, which is never
the intended location. The current directory depends on GitPython
implementation details, but at present it's the top-level directory
of the rw_repo working tree. A new unstaged file, named like
"C\357\200\272UsersekAppDataLocalTemptmpc7x4xik5pwn", can be
observed there (this is how "git status" will format the name).

Fortunately, this and all related tests are working on other OSes,
and the affected code under test does not appear highly dependent
on OS. So the fix is *probably* fully working on Windows as well.
EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Nov 24, 2023
The tests of unsafe options are among those introduced originally
in gitpython-developers#1521. They are regression tests for gitpython-developers#1515 (CVE-2022-24439).
The unsafe options tests are paired: a test for the usual, default
behavior of forbidding the option, and a test for the behavior when
the option is explicitly allowed. In each such pair, both tests use
a payload that is intended to produce the side effect of a file of
a specific name being created in a temporary directory.

All the tests work on Unix-like systems. On Windows, the tests of
the *allowed* cases are broken, and this commit marks them xfail.
However, this has implications for the tests of the default, secure
behavior, because until the "allowed" versions work on Windows, it
will be unclear if either are using a payload that is effective and
that corresponds to the way its effect is examined.

What *seems* to happen is this: The "\" characters in the path are
treated as shell escape characters rather than literally, with the
effect of disappearing in most paths since most letters lack
special meaning when escaped. Also, "touch" is not a native Windows
command, and the "touch" command provided by Git for Windows is
linked against MSYS2 libraries, causing it to map (some?)
occurrences of ":" in filenames to a separate code point in the
Private Use Area of the Basic Multilingual Plane. The result is a
path with no directory separators or drive letter. It denotes a
file of an unintended name in the current directory, which is never
the intended location. The current directory depends on GitPython
implementation details, but at present it's the top-level directory
of the rw_repo working tree. A new unstaged file, named like
"C\357\200\272UsersekAppDataLocalTemptmpc7x4xik5pwn", can be
observed there (this is how "git status" will format the name).

Fortunately, this and all related tests are working on other OSes,
and the affected code under test does not appear highly dependent
on OS. So the fix is *probably* fully working on Windows as well.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
10 participants