195 lines
6.3 KiB
Diff
195 lines
6.3 KiB
Diff
From c44088ecc4b0722636e0a305f9608d3047197282 Mon Sep 17 00:00:00 2001
|
|
From: Jonathan Nieder <jrnieder@gmail.com>
|
|
Date: Sat, 18 Apr 2020 20:54:13 -0700
|
|
Subject: [PATCH] credential: treat URL without scheme as invalid
|
|
|
|
libcurl permits making requests without a URL scheme specified. In
|
|
this case, it guesses the URL from the hostname, so I can run
|
|
|
|
git ls-remote http::ftp.example.com/path/to/repo
|
|
|
|
and it would make an FTP request.
|
|
|
|
Any user intentionally using such a URL is likely to have made a typo.
|
|
Unfortunately, credential_from_url is not able to determine the host and
|
|
protocol in order to determine appropriate credentials to send, and
|
|
until "credential: refuse to operate when missing host or protocol",
|
|
this resulted in another host's credentials being leaked to the named
|
|
host.
|
|
|
|
Teach credential_from_url_gently to consider such a URL to be invalid
|
|
so that fsck can detect and block gitmodules files with such URLs,
|
|
allowing server operators to avoid serving them to downstream users
|
|
running older versions of Git.
|
|
|
|
This also means that when such URLs are passed on the command line, Git
|
|
will print a clearer error so affected users can switch to the simpler
|
|
URL that explicitly specifies the host and protocol they intend.
|
|
|
|
One subtlety: .gitmodules files can contain relative URLs, representing
|
|
a URL relative to the URL they were cloned from. The relative URL
|
|
resolver used for .gitmodules can follow ".." components out of the path
|
|
part and past the host part of a URL, meaning that such a relative URL
|
|
can be used to traverse from a https://foo.example.com/innocent
|
|
superproject to a https::attacker.example.com/exploit submodule.
|
|
Fortunately a leading ':' in the first path component after a series of
|
|
leading './' and '../' components is unlikely to show up in other
|
|
contexts, so we can catch this by detecting that pattern.
|
|
|
|
Reported-by: Jeff King <peff@peff.net>
|
|
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
|
|
Reviewed-by: Jeff King <peff@peff.net>
|
|
---
|
|
credential.c | 7 ++++--
|
|
fsck.c | 47 +++++++++++++++++++++++++++++++++--
|
|
t/t5550-http-fetch-dumb.sh | 7 ++----
|
|
t/t7416-submodule-dash-url.sh | 32 ++++++++++++++++++++++++
|
|
4 files changed, 84 insertions(+), 9 deletions(-)
|
|
|
|
--- a/credential.c
|
|
+++ b/credential.c
|
|
@@ -360,8 +360,11 @@ int credential_from_url_gently(struct cr
|
|
* (3) proto://<user>:<pass>@<host>/...
|
|
*/
|
|
proto_end = strstr(url, "://");
|
|
- if (!proto_end)
|
|
- return 0;
|
|
+ if (!proto_end) {
|
|
+ if (!quiet)
|
|
+ warning(_("url has no scheme: %s"), url);
|
|
+ return -1;
|
|
+ }
|
|
cp = proto_end + 3;
|
|
at = strchr(cp, '@');
|
|
colon = strchr(cp, ':');
|
|
--- a/fsck.c
|
|
+++ b/fsck.c
|
|
@@ -1013,6 +1013,34 @@ static int submodule_url_is_relative(con
|
|
}
|
|
|
|
/*
|
|
+ * Count directory components that a relative submodule URL should chop
|
|
+ * from the remote_url it is to be resolved against.
|
|
+ *
|
|
+ * In other words, this counts "../" components at the start of a
|
|
+ * submodule URL.
|
|
+ *
|
|
+ * Returns the number of directory components to chop and writes a
|
|
+ * pointer to the next character of url after all leading "./" and
|
|
+ * "../" components to out.
|
|
+ */
|
|
+static int count_leading_dotdots(const char *url, const char **out)
|
|
+{
|
|
+ int result = 0;
|
|
+ while (1) {
|
|
+ if (starts_with_dot_dot_slash(url)) {
|
|
+ result++;
|
|
+ url += strlen("../");
|
|
+ continue;
|
|
+ }
|
|
+ if (starts_with_dot_slash(url)) {
|
|
+ url += strlen("./");
|
|
+ continue;
|
|
+ }
|
|
+ *out = url;
|
|
+ return result;
|
|
+ }
|
|
+}
|
|
+/*
|
|
* Check whether a transport is implemented by git-remote-curl.
|
|
*
|
|
* If it is, returns 1 and writes the URL that would be passed to
|
|
@@ -1059,15 +1087,30 @@ static int check_submodule_url(const cha
|
|
return -1;
|
|
|
|
if (submodule_url_is_relative(url)) {
|
|
+ char *decoded;
|
|
+ const char *next;
|
|
+ int has_nl;
|
|
+
|
|
/*
|
|
* This could be appended to an http URL and url-decoded;
|
|
* check for malicious characters.
|
|
*/
|
|
- char *decoded = url_decode(url);
|
|
- int has_nl = !!strchr(decoded, '\n');
|
|
+ decoded = url_decode(url);
|
|
+ has_nl = !!strchr(decoded, '\n');
|
|
+
|
|
free(decoded);
|
|
if (has_nl)
|
|
return -1;
|
|
+
|
|
+ /*
|
|
+ * URLs which escape their root via "../" can overwrite
|
|
+ * the host field and previous components, resolving to
|
|
+ * URLs like https::example.com/submodule.git that were
|
|
+ * susceptible to CVE-2020-11008.
|
|
+ */
|
|
+ if (count_leading_dotdots(url, &next) > 0 &&
|
|
+ *next == ':')
|
|
+ return -1;
|
|
}
|
|
|
|
else if (url_to_curl_url(url, &curl_url)) {
|
|
--- a/t/t5550-http-fetch-dumb.sh
|
|
+++ b/t/t5550-http-fetch-dumb.sh
|
|
@@ -321,11 +321,8 @@ test_expect_success 'git client does not
|
|
'
|
|
|
|
test_expect_success 'remote-http complains cleanly about malformed urls' '
|
|
- # do not actually issue "list" or other commands, as we do not
|
|
- # want to rely on what curl would actually do with such a broken
|
|
- # URL. This is just about making sure we do not segfault during
|
|
- # initialization.
|
|
- test_must_fail git remote-http http::/example.com/repo.git
|
|
+ test_must_fail git remote-http http::/example.com/repo.git 2>stderr &&
|
|
+ test_i18ngrep "url has no scheme" stderr
|
|
'
|
|
|
|
test_expect_success 'redirects can be forbidden/allowed' '
|
|
--- a/t/t7416-submodule-dash-url.sh
|
|
+++ b/t/t7416-submodule-dash-url.sh
|
|
@@ -60,6 +60,38 @@ test_expect_success 'trailing backslash
|
|
test_i18ngrep ! "unknown option" err
|
|
'
|
|
|
|
+test_expect_success 'fsck rejects missing URL scheme' '
|
|
+ git checkout --orphan missing-scheme &&
|
|
+ cat >.gitmodules <<-\EOF &&
|
|
+ [submodule "foo"]
|
|
+ url = http::one.example.com/foo.git
|
|
+ EOF
|
|
+ git add .gitmodules &&
|
|
+ test_tick &&
|
|
+ git commit -m "gitmodules with missing URL scheme" &&
|
|
+ test_when_finished "rm -rf dst" &&
|
|
+ git init --bare dst &&
|
|
+ git -C dst config transfer.fsckObjects true &&
|
|
+ test_must_fail git push dst HEAD 2>err &&
|
|
+ grep gitmodulesUrl err
|
|
+'
|
|
+
|
|
+test_expect_success 'fsck rejects relative URL resolving to missing scheme' '
|
|
+ git checkout --orphan relative-missing-scheme &&
|
|
+ cat >.gitmodules <<-\EOF &&
|
|
+ [submodule "foo"]
|
|
+ url = "..\\../.\\../:one.example.com/foo.git"
|
|
+ EOF
|
|
+ git add .gitmodules &&
|
|
+ test_tick &&
|
|
+ git commit -m "gitmodules with relative URL that strips off scheme" &&
|
|
+ test_when_finished "rm -rf dst" &&
|
|
+ git init --bare dst &&
|
|
+ git -C dst config transfer.fsckObjects true &&
|
|
+ test_must_fail git push dst HEAD 2>err &&
|
|
+ grep gitmodulesUrl err
|
|
+'
|
|
+
|
|
test_expect_success 'fsck permits embedded newline with unrecognized scheme' '
|
|
git checkout --orphan newscheme &&
|
|
cat >.gitmodules <<-\EOF &&
|
|
--
|
|
1.8.3.1
|
|
|