http: treat http-alternates like redirects

The previous commit made HTTP redirects more obvious and tightened up the default behavior. However, there's another way for a server to ask a git client to fetch arbitrary content: by having an http-alternates file (or a regular alternates file, which is used as a backup). Similar to the HTTP redirect case, a malicious server can claim to have refs pointing at object X, return a 404 when the client asks for X, but point to some other URL via http-alternates, which the client will transparently fetch. The end result is that it looks from the user's perspective like the objects came from the malicious server, as the other URL is not mentioned at all. Worse, because we feed the new URL to curl ourselves, the usual protocol restrictions do not kick in (neither curl's default of disallowing file://, nor the protocol whitelisting in f4113cac0 (http: limit redirection to protocol-whitelist, 2015-09-22). Let's apply the same rules here as we do for HTTP redirects. Namely: - unless http.followRedirects is set to "always", we will not follow remote redirects from http-alternates (or alternates) at all - set CURLOPT_PROTOCOLS alongside CURLOPT_REDIR_PROTOCOLS restrict ourselves to a known-safe set and respect any user-provided whitelist. - mention alternate object stores on stderr so that the user is aware another source of objects may be involved The first item may prove to be too restrictive. The most common use of alternates is to point to another path on the same server. While it's possible for a single-server redirect to be an attack, it takes a fairly obscure setup (victim and evil repository on the same host, host speaks dumb http, and evil repository has access to edit its own http-alternates file). So we could make the checks more specific, and only cover cross-server redirects. But that means parsing the URLs ourselves, rather than letting curl handle them. This patch goes for the simpler approach. Given that they are only used with dumb http, http-alternates are probably pretty rare. And there's an escape hatch: the user can allow redirects on a specific server by setting http.<url>.followRedirects to "always". Reported-by: Jann Horn <jannh@google.com> Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Dec 6, 2016 at 13:24 UTC cb4d2d35c4622ec2513c1c352d30ff8f9f9cdb9e
3 files changed +44 -3
http-walker.c
+5 -3
@@ -290,9 +290,8 @@ static void process_alternates_response(void *callback_data)
290 struct strbuf target = STRBUF_INIT;
291 strbuf_add(&target, base, serverlen);
292 strbuf_add(&target, data + i, posn - i - 7);
293 - if (walker->get_verbosely)
294 - fprintf(stderr, "Also look at %s\n",
295 - target.buf);
293 + warning("adding alternate object store: %s",
294 + target.buf);
295 newalt = xmalloc(sizeof(*newalt));
296 newalt->next = NULL;
297 newalt->base = strbuf_detach(&target, NULL);
@@ -318,6 +317,9 @@ static void fetch_alternates(struct walker *walker, const char *base)
317 struct alternates_request alt_req;
318 struct walker_data *cdata = walker->data;
319
320 + if (http_follow_config != HTTP_FOLLOW_ALWAYS)
321 + return;
322 +
323 /*
324 * If another request has already started fetching alternates,
325 * wait for them to arrive and return to processing this request's
http.c
+1
@@ -581,6 +581,7 @@ static CURL *get_curl_handle(void)
581 if (is_transport_allowed("ftps"))
582 allowed_protocols |= CURLPROTO_FTPS;
583 curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
584 + curl_easy_setopt(result, CURLOPT_PROTOCOLS, allowed_protocols);
585 #else
586 if (transport_restrict_protocols())
587 warning("protocol restrictions not applied to curl redirects because\n"
t/t5550-http-fetch-dumb.sh
+38
@@ -322,5 +322,43 @@ test_expect_success 'http.followRedirects defaults to "initial"' '
322 test_must_fail git clone $HTTPD_URL/redir-objects/repo.git default
323 '
324
325 +# The goal is for a clone of the "evil" repository, which has no objects
326 +# itself, to cause the client to fetch objects from the "victim" repository.
327 +test_expect_success 'set up evil alternates scheme' '
328 + victim=$HTTPD_DOCUMENT_ROOT_PATH/victim.git &&
329 + git init --bare "$victim" &&
330 + git -C "$victim" --work-tree=. commit --allow-empty -m secret &&
331 + git -C "$victim" repack -ad &&
332 + git -C "$victim" update-server-info &&
333 + sha1=$(git -C "$victim" rev-parse HEAD) &&
334 +
335 + evil=$HTTPD_DOCUMENT_ROOT_PATH/evil.git &&
336 + git init --bare "$evil" &&
337 + # do this by hand to avoid object existence check
338 + printf "%s\\t%s\\n" $sha1 refs/heads/master >"$evil/info/refs"
339 +'
340 +
341 +# Here we'll just redirect via HTTP. In a real-world attack these would be on
342 +# different servers, but we should reject it either way.
343 +test_expect_success 'http-alternates is a non-initial redirect' '
344 + echo "$HTTPD_URL/dumb/victim.git/objects" \
345 + >"$evil/objects/info/http-alternates" &&
346 + test_must_fail git -c http.followRedirects=initial \
347 + clone $HTTPD_URL/dumb/evil.git evil-initial &&
348 + git -c http.followRedirects=true \
349 + clone $HTTPD_URL/dumb/evil.git evil-initial
350 +'
351 +
352 +# Curl supports a lot of protocols that we'd prefer not to allow
353 +# http-alternates to use, but it's hard to test whether curl has
354 +# accessed, say, the SMTP protocol, because we are not running an SMTP server.
355 +# But we can check that it does not allow access to file://, which would
356 +# otherwise allow this clone to complete.
357 +test_expect_success 'http-alternates cannot point at funny protocols' '
358 + echo "file://$victim/objects" >"$evil/objects/info/http-alternates" &&
359 + test_must_fail git -c http.followRedirects=true \
360 + clone "$HTTPD_URL/dumb/evil.git" evil-file
361 +'
362 +
363 stop_httpd
364 test_done