mirror of
https://github.com/git-for-windows/git.git
synced 2026-04-02 00:12:29 -05:00
Merge branch 'disallow-ntlm-auth-by-default'
This topic branch addresses the following vulnerability: - **CVE-2025-66413**: When a user clones a repository from an attacker-controlled server, Git may attempt NTLM authentication and disclose the user's NTLMv2 hash to the remote server. Since NTLM hashing is weak, the captured hash can potentially be brute-forced to recover the user's credentials. This is addressed by disabling NTLM authentication by default. (https://github.com/git-for-windows/git/security/advisories/GHSA-hv9c-4jm9-jh3x) Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
committed by
Git for Windows Build Agent
commit
72f281a349
@@ -231,6 +231,11 @@ http.sslKeyType::
|
||||
See also libcurl `CURLOPT_SSLKEYTYPE`. Can be overridden by the
|
||||
`GIT_SSL_KEY_TYPE` environment variable.
|
||||
|
||||
http.allowNTLMAuth::
|
||||
Whether or not to allow NTLM authentication. While very convenient to set
|
||||
up, and therefore still used in many on-prem scenarios, NTLM is a weak
|
||||
authentication method and therefore deprecated. Defaults to "false".
|
||||
|
||||
http.schannelCheckRevoke::
|
||||
Used to enforce or disable certificate revocation checks in cURL
|
||||
when http.sslBackend is set to "schannel" via "true" and "false",
|
||||
|
||||
@@ -360,6 +360,9 @@ int credential_read(struct credential *c, FILE *fp,
|
||||
credential_set_capability(&c->capa_authtype, op_type);
|
||||
else if (!strcmp(value, "state"))
|
||||
credential_set_capability(&c->capa_state, op_type);
|
||||
} else if (!strcmp(key, "ntlm")) {
|
||||
if (!strcmp(value, "allow"))
|
||||
c->ntlm_allow = 1;
|
||||
} else if (!strcmp(key, "continue")) {
|
||||
c->multistage = !!git_config_bool("continue", value);
|
||||
} else if (!strcmp(key, "password_expiry_utc")) {
|
||||
@@ -420,6 +423,8 @@ void credential_write(const struct credential *c, FILE *fp,
|
||||
if (c->ephemeral)
|
||||
credential_write_item(c, fp, "ephemeral", "1", 0);
|
||||
}
|
||||
if (c->ntlm_suppressed)
|
||||
credential_write_item(c, fp, "ntlm", "suppressed", 0);
|
||||
credential_write_item(c, fp, "protocol", c->protocol, 1);
|
||||
credential_write_item(c, fp, "host", c->host, 1);
|
||||
credential_write_item(c, fp, "path", c->path, 0);
|
||||
|
||||
@@ -177,6 +177,9 @@ struct credential {
|
||||
struct credential_capability capa_authtype;
|
||||
struct credential_capability capa_state;
|
||||
|
||||
unsigned ntlm_suppressed:1,
|
||||
ntlm_allow:1;
|
||||
|
||||
char *username;
|
||||
char *password;
|
||||
char *credential;
|
||||
|
||||
41
http.c
41
http.c
@@ -131,7 +131,8 @@ enum http_follow_config http_follow_config = HTTP_FOLLOW_INITIAL;
|
||||
|
||||
static struct credential cert_auth = CREDENTIAL_INIT;
|
||||
static int ssl_cert_password_required;
|
||||
static unsigned long http_auth_methods = CURLAUTH_ANY;
|
||||
static unsigned long http_auth_any = CURLAUTH_ANY & ~CURLAUTH_NTLM;
|
||||
static unsigned long http_auth_methods;
|
||||
static int http_auth_methods_restricted;
|
||||
/* Modes for which empty_auth cannot actually help us. */
|
||||
static unsigned long empty_auth_useless =
|
||||
@@ -436,6 +437,15 @@ static int http_options(const char *var, const char *value,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp("http.allowntlmauth", var)) {
|
||||
if (git_config_bool(var, value)) {
|
||||
http_auth_any |= CURLAUTH_NTLM;
|
||||
} else {
|
||||
http_auth_any &= ~CURLAUTH_NTLM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp("http.schannelcheckrevoke", var)) {
|
||||
if (value && !strcmp(value, "best-effort")) {
|
||||
http_schannel_check_revoke_mode =
|
||||
@@ -674,6 +684,11 @@ static void init_curl_http_auth(CURL *result)
|
||||
|
||||
credential_fill(the_repository, &http_auth, 1);
|
||||
|
||||
if (http_auth.ntlm_allow && !(http_auth_methods & CURLAUTH_NTLM)) {
|
||||
http_auth_methods |= CURLAUTH_NTLM;
|
||||
curl_easy_setopt(result, CURLOPT_HTTPAUTH, http_auth_methods);
|
||||
}
|
||||
|
||||
if (http_auth.password) {
|
||||
if (always_auth_proactively()) {
|
||||
/*
|
||||
@@ -733,11 +748,11 @@ static void init_curl_proxy_auth(CURL *result)
|
||||
if (i == ARRAY_SIZE(proxy_authmethods)) {
|
||||
warning("unsupported proxy authentication method %s: using anyauth",
|
||||
http_proxy_authmethod);
|
||||
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
|
||||
curl_easy_setopt(result, CURLOPT_PROXYAUTH, http_auth_any);
|
||||
}
|
||||
}
|
||||
else
|
||||
curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
|
||||
curl_easy_setopt(result, CURLOPT_PROXYAUTH, http_auth_any);
|
||||
}
|
||||
|
||||
static int has_cert_password(void)
|
||||
@@ -1084,7 +1099,7 @@ static CURL *get_curl_handle(void)
|
||||
}
|
||||
|
||||
curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
|
||||
curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
curl_easy_setopt(result, CURLOPT_HTTPAUTH, http_auth_any);
|
||||
|
||||
#ifdef CURLGSSAPI_DELEGATION_FLAG
|
||||
if (curl_deleg) {
|
||||
@@ -1483,6 +1498,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
|
||||
set_long_from_env(&http_max_retries, "GIT_HTTP_MAX_RETRIES");
|
||||
set_long_from_env(&http_max_retry_time, "GIT_HTTP_MAX_RETRY_TIME");
|
||||
|
||||
http_auth_methods = http_auth_any;
|
||||
|
||||
curl_default = get_curl_handle();
|
||||
}
|
||||
|
||||
@@ -1914,6 +1931,12 @@ static int handle_curl_result(struct slot_results *results)
|
||||
} else if (missing_target(results))
|
||||
return HTTP_MISSING_TARGET;
|
||||
else if (results->http_code == 401) {
|
||||
http_auth.ntlm_suppressed = (results->auth_avail & CURLAUTH_NTLM) &&
|
||||
!(http_auth_any & CURLAUTH_NTLM);
|
||||
if (http_auth.ntlm_suppressed && http_auth.ntlm_allow) {
|
||||
http_auth_methods |= CURLAUTH_NTLM;
|
||||
return HTTP_REAUTH;
|
||||
}
|
||||
if ((http_auth.username && http_auth.password) ||\
|
||||
(http_auth.authtype && http_auth.credential)) {
|
||||
if (http_auth.multistage) {
|
||||
@@ -1923,6 +1946,16 @@ static int handle_curl_result(struct slot_results *results)
|
||||
credential_reject(the_repository, &http_auth);
|
||||
if (always_auth_proactively())
|
||||
http_proactive_auth = PROACTIVE_AUTH_NONE;
|
||||
if (http_auth.ntlm_suppressed) {
|
||||
warning(_("Due to its cryptographic weaknesses, "
|
||||
"NTLM authentication has been\n"
|
||||
"disabled in Git by default. You can "
|
||||
"re-enable it for trusted servers\n"
|
||||
"by running:\n\n"
|
||||
"git config set "
|
||||
"http.%s://%s.allowNTLMAuth true"),
|
||||
http_auth.protocol, http_auth.host);
|
||||
}
|
||||
return HTTP_NOAUTH;
|
||||
} else {
|
||||
http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE;
|
||||
|
||||
@@ -168,6 +168,7 @@ prepare_httpd() {
|
||||
install_script apply-one-time-script.sh
|
||||
install_script nph-custom-auth.sh
|
||||
install_script http-429.sh
|
||||
install_script ntlm-handshake.sh
|
||||
|
||||
ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
|
||||
|
||||
|
||||
@@ -155,6 +155,13 @@ SetEnv PERL_PATH ${PERL_PATH}
|
||||
CGIPassAuth on
|
||||
</IfDefine>
|
||||
</LocationMatch>
|
||||
<LocationMatch /ntlm_auth/>
|
||||
SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
|
||||
SetEnv GIT_HTTP_EXPORT_ALL
|
||||
<IfDefine USE_CGIPASSAUTH>
|
||||
CGIPassAuth on
|
||||
</IfDefine>
|
||||
</LocationMatch>
|
||||
ScriptAlias /smart/incomplete_length/git-upload-pack incomplete-length-upload-pack-v2-http.sh/
|
||||
ScriptAlias /smart/incomplete_body/git-upload-pack incomplete-body-upload-pack-v2-http.sh/
|
||||
ScriptAlias /smart/no_report/git-receive-pack error-no-report.sh/
|
||||
@@ -166,6 +173,7 @@ ScriptAlias /error/ error.sh/
|
||||
ScriptAliasMatch /one_time_script/(.*) apply-one-time-script.sh/$1
|
||||
ScriptAliasMatch /http_429/(.*) http-429.sh/$1
|
||||
ScriptAliasMatch /custom_auth/(.*) nph-custom-auth.sh/$1
|
||||
ScriptAliasMatch /ntlm_auth/(.*) ntlm-handshake.sh/$1
|
||||
<Directory ${GIT_EXEC_PATH}>
|
||||
Options FollowSymlinks
|
||||
</Directory>
|
||||
|
||||
38
t/lib-httpd/ntlm-handshake.sh
Executable file
38
t/lib-httpd/ntlm-handshake.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
case "$HTTP_AUTHORIZATION" in
|
||||
'')
|
||||
# No Authorization header -> send NTLM challenge
|
||||
echo "Status: 401 Unauthorized"
|
||||
echo "WWW-Authenticate: NTLM"
|
||||
echo
|
||||
;;
|
||||
"NTLM TlRMTVNTUAAB"*)
|
||||
# Type 1 -> respond with Type 2 challenge (hardcoded)
|
||||
echo "Status: 401 Unauthorized"
|
||||
# Base64-encoded version of the Type 2 challenge:
|
||||
# signature: 'NTLMSSP\0'
|
||||
# message_type: 2
|
||||
# target_name: 'NTLM-GIT-SERVER'
|
||||
# flags: 0xa2898205 =
|
||||
# NEGOTIATE_UNICODE, REQUEST_TARGET, NEGOTIATE_NT_ONLY,
|
||||
# TARGET_TYPE_SERVER, TARGET_TYPE_SHARE, REQUEST_NON_NT_SESSION_KEY,
|
||||
# NEGOTIATE_VERSION, NEGOTIATE_128, NEGOTIATE_56
|
||||
# challenge: 0xfa3dec518896295b
|
||||
# context: '0000000000000000'
|
||||
# target_info_present: true
|
||||
# target_info_len: 128
|
||||
# version: '10.0 (build 19041)'
|
||||
echo "WWW-Authenticate: NTLM TlRMTVNTUAACAAAAHgAeADgAAAAFgomi+j3sUYiWKVsAAAAAAAAAAIAAgABWAAAACgBhSgAAAA9OAFQATABNAC0ARwBJAFQALQBTAEUAUgBWAEUAUgACABIAVwBPAFIASwBHAFIATwBVAFAAAQAeAE4AVABMAE0ALQBHAEkAVAAtAFMARQBSAFYARQBSAAQAEgBXAE8AUgBLAEcAUgBPAFUAUAADAB4ATgBUAEwATQAtAEcASQBUAC0AUwBFAFIAVgBFAFIABwAIAACfOcZKYNwBAAAAAA=="
|
||||
echo
|
||||
;;
|
||||
"NTLM TlRMTVNTUAAD"*)
|
||||
# Type 3 -> accept without validation
|
||||
exec "$GIT_EXEC_PATH"/git-http-backend
|
||||
;;
|
||||
*)
|
||||
echo "Status: 500 Unrecognized"
|
||||
echo
|
||||
echo "Unhandled auth: '$HTTP_AUTHORIZATION'"
|
||||
;;
|
||||
esac
|
||||
@@ -719,4 +719,33 @@ test_expect_success 'access using three-legged auth' '
|
||||
EOF
|
||||
'
|
||||
|
||||
test_lazy_prereq NTLM 'curl --version | grep -q NTLM'
|
||||
|
||||
test_expect_success NTLM 'access using NTLM auth' '
|
||||
test_when_finished "per_test_cleanup" &&
|
||||
|
||||
set_credential_reply get <<-EOF &&
|
||||
username=user
|
||||
password=pwd
|
||||
EOF
|
||||
|
||||
test_config_global credential.helper test-helper &&
|
||||
test_must_fail env GIT_TRACE_CURL=1 git \
|
||||
ls-remote "$HTTPD_URL/ntlm_auth/repo.git" 2>err &&
|
||||
test_grep "allowNTLMAuth" err &&
|
||||
|
||||
# Can be enabled via config
|
||||
GIT_TRACE_CURL=1 git -c http.$HTTPD_URL.allowNTLMAuth=true \
|
||||
ls-remote "$HTTPD_URL/ntlm_auth/repo.git" &&
|
||||
|
||||
# Or via credential helper responding with ntlm=allow
|
||||
set_credential_reply get <<-EOF &&
|
||||
username=user
|
||||
password=pwd
|
||||
ntlm=allow
|
||||
EOF
|
||||
|
||||
git ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
Reference in New Issue
Block a user