t5563: verify that NTLM authentication works

Although NTLM authentication is considered weak (extending even to
NTLMv2, which purportedly allows brute-forcing reasonably complex
8-character passwords in a matter of days, given ample compute
resources), it _is_ one of the authentication methods supported by
libcurl.

Note: The added test case *cannot* reuse the existing `custom_auth`
facility. The reason is that that facility is backed by an NPH script
("No Parse Headers"), which does not allow handling the 3-phase NTLM
authentication correctly (in my hands, the NPH script would not even be
called upon the Type 3 message, a "200 OK" would be returned, but no
headers, let alone the `git http-backend` output as payload). Having a
separate NTLM authentication script makes the exact workings clearer and
more readable, anyway.

Co-authored-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Johannes Schindelin
2025-11-26 17:51:41 +01:00
committed by Git for Windows Build Agent
parent 76fc0d19de
commit 11ed8a2004
4 changed files with 53 additions and 65 deletions

View File

@@ -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"

View File

@@ -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
View 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

View File

@@ -764,78 +764,19 @@ test_expect_success 'access using three-legged auth' '
EOF
'
test_lazy_prereq SPNEGO 'curl --version | grep -qi "SPNEGO\|GSS-API\|Kerberos\|negotiate"'
test_lazy_prereq NTLM 'curl --version | grep -q NTLM'
test_expect_success SPNEGO 'http.emptyAuth=auto attempts Negotiate before credential_fill' '
test_expect_success NTLM 'access using NTLM auth' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Negotiate
id=default response=WWW-Authenticate: Basic realm="example.com"
username=user
password=pwd
EOF
test_config_global credential.helper test-helper &&
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-auto" \
git -c http.emptyAuth=auto \
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
# In auto mode with a Negotiate+Basic server, there should be
# three 401 responses: (1) initial no-auth request, (2) empty-auth
# retry where Negotiate fails (no Kerberos ticket), (3) libcurl
# internal Negotiate retry. The fourth attempt uses Basic
# credentials from credential_fill and succeeds.
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-auto" >actual_401s &&
test_line_count = 3 actual_401s &&
expect_credential_query get <<-EOF
capability[]=authtype
capability[]=state
protocol=http
host=$HTTPD_DEST
wwwauth[]=Negotiate
wwwauth[]=Basic realm="example.com"
EOF
'
test_expect_success SPNEGO 'http.emptyAuth=false skips Negotiate' '
test_when_finished "per_test_cleanup" &&
set_credential_reply get <<-EOF &&
username=alice
password=secret-passwd
EOF
# Basic base64(alice:secret-passwd)
cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF &&
id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA==
EOF
cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF &&
id=1 status=200
id=default response=WWW-Authenticate: Negotiate
id=default response=WWW-Authenticate: Basic realm="example.com"
EOF
test_config_global credential.helper test-helper &&
GIT_TRACE_CURL="$TRASH_DIRECTORY/trace-false" \
git -c http.emptyAuth=false \
ls-remote "$HTTPD_URL/custom_auth/repo.git" &&
# With emptyAuth=false, Negotiate is stripped immediately and
# credential_fill is called right away. Only one 401 response.
grep "HTTP/[0-9.]* 401" "$TRASH_DIRECTORY/trace-false" >actual_401s &&
test_line_count = 1 actual_401s
GIT_TRACE_CURL=1 \
git ls-remote "$HTTPD_URL/ntlm_auth/repo.git"
'
test_done