Skip to content

Commit 680d581

Browse files
authored
curl: disallow NTLM via SPNEGO (#193)
Git for Windows recently offered a new security bug release, which essentially disables NTLM by default. This is considered an important measure to heighten the security stance of Git for Windows. However, there is another path which allows NTLM that was not yet covered in Git for Windows: [the SPNEGO mechanism](https://en.wikipedia.org/wiki/SPNEGO) that allows downgrading Kerberos to NTLM. This PR disables NTLM via SPNEGO altogether, in line with [what the primary cURL maintainer wants](curl/curl#21076 (comment)), too.
2 parents f2a9cc7 + bd3629a commit 680d581

5 files changed

+265
-23
lines changed

mingw-w64-curl/0001-Make-cURL-relocatable.patch

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From 80b680d4796d1ce913185dbaf4c36c6a64669e86 Mon Sep 17 00:00:00 2001
1+
From b0761795838b6645cf1bc7d0c290decc1f57b4ca Mon Sep 17 00:00:00 2001
22
From: Ray Donnelly <mingw.android@gmail.com>
33
Date: Wed, 22 Feb 2017 11:03:04 +0100
4-
Subject: [PATCH 1/2] Make cURL relocatable
4+
Subject: [PATCH 1/4] Make cURL relocatable
55

66
This adds the ability to specify CA_BUNDLE paths that are relative to
77
the MSYS2 pseudo-root directory.
@@ -22,17 +22,17 @@ be copied from `mingw-w64-pathtools` into `lib/`.
2222
Original-patch-by: Ray Donnelly <mingw.android@gmail.com>
2323
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
2424
---
25-
configure.ac | 1 +
25+
configure.ac | 2 ++
2626
lib/Makefile.inc | 2 ++
2727
lib/curl_config.h.in | 3 +++
28-
lib/vtls/vtls.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
28+
lib/vtls/vtls.c | 48 ++++++++++++++++++++++++++++++++++++++++++++
2929
4 files changed, 55 insertions(+)
3030

3131
diff --git a/configure.ac b/configure.ac
32-
index cc16f4120..827d04948 100644
32+
index 6d2cb5583b..cefba6bb1b 100644
3333
--- a/configure.ac
3434
+++ b/configure.ac
35-
@@ -4069,6 +4069,8 @@ dnl default includes
35+
@@ -4035,6 +4035,8 @@ dnl default includes
3636
]
3737
)
3838

@@ -42,18 +42,18 @@ index cc16f4120..827d04948 100644
4242
AC_C_CONST
4343
AC_TYPE_SIZE_T
4444
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
45-
index 8b506febc..841e8a7ea 100644
45+
index 84b4e5425b..8c9010e51a 100644
4646
--- a/lib/Makefile.inc
4747
+++ b/lib/Makefile.inc
48-
@@ -229,6 +229,7 @@ LIB_CFILES = \
48+
@@ -233,6 +233,7 @@ LIB_CFILES = \
4949
noproxy.c \
5050
openldap.c \
5151
parsedate.c \
5252
+ pathtools.c \
5353
pingpong.c \
5454
pop3.c \
5555
progress.c \
56-
@@ -361,6 +362,7 @@ LIB_HFILES = \
56+
@@ -359,6 +360,7 @@ LIB_HFILES = \
5757
netrc.h \
5858
noproxy.h \
5959
parsedate.h \
@@ -62,10 +62,10 @@ index 8b506febc..841e8a7ea 100644
6262
pop3.h \
6363
progress.h \
6464
diff --git a/lib/curl_config.h.in b/lib/curl_config.h.in
65-
index 668be5ea1..9c3139764 100644
65+
index 7108da0790..9dbbfb3666 100644
6666
--- a/lib/curl_config.h.in
6767
+++ b/lib/curl_config.h.in
68-
@@ -14,6 +14,9 @@
68+
@@ -17,6 +17,9 @@
6969
/* If safe CA bundle search is enabled */
7070
#undef CURL_CA_SEARCH_SAFE
7171

@@ -76,7 +76,7 @@ index 668be5ea1..9c3139764 100644
7676
#undef CURL_DEFAULT_SSL_BACKEND
7777

7878
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
79-
index df0449cbe..bb45551b0 100644
79+
index f7201d18d6..b4a0547a8c 100644
8080
--- a/lib/vtls/vtls.c
8181
+++ b/lib/vtls/vtls.c
8282
@@ -77,6 +77,9 @@
@@ -89,7 +89,7 @@ index df0449cbe..bb45551b0 100644
8989

9090
#define CLONE_STRING(var) \
9191
do { \
92-
@@ -295,6 +299,15 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
92+
@@ -297,6 +300,15 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
9393
#if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE)
9494
struct UserDefined *set = &data->set;
9595
CURLcode result;
@@ -105,7 +105,7 @@ index df0449cbe..bb45551b0 100644
105105
#endif
106106

107107
if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) {
108-
@@ -302,15 +317,41 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
108+
@@ -305,15 +317,41 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
109109
sslc->native_ca_store = TRUE;
110110
#endif
111111
#ifdef CURL_CA_PATH
@@ -147,7 +147,7 @@ index df0449cbe..bb45551b0 100644
147147
if(result)
148148
return result;
149149
}
150-
@@ -353,8 +392,13 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
150+
@@ -352,16 +390,26 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
151151
#endif
152152
#ifdef CURL_CA_PATH
153153
if(!sslc->custom_capath && !set->str[STRING_SSL_CAPATH_PROXY]) {
@@ -161,7 +161,6 @@ index df0449cbe..bb45551b0 100644
161161
if(result)
162162
return result;
163163
}
164-
@@ -362,8 +406,13 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
165164
#endif
166165
#ifdef CURL_CA_BUNDLE
167166
if(!sslc->custom_cafile && !set->str[STRING_SSL_CAFILE_PROXY]) {

mingw-w64-curl/0002-Hack-make-relocation-work-inside-libexec-git-core-an.patch

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
From 5022c4e5a2bd1483c70f2e8177eb51edd36139eb Mon Sep 17 00:00:00 2001
1+
From 0d1cc065356bcb23642563c3046b0b0e860fa9bb Mon Sep 17 00:00:00 2001
22
From: Johannes Schindelin <johannes.schindelin@gmx.de>
33
Date: Wed, 31 Oct 2018 10:52:59 +0100
4-
Subject: [PATCH 2/2] Hack: make relocation work inside libexec/git-core/ and
4+
Subject: [PATCH 2/4] Hack: make relocation work inside libexec/git-core/ and
55
bin/
66

77
In Git for Windows, there is a copy of libcurl-4.dll in
@@ -30,10 +30,10 @@ Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
3030
1 file changed, 20 insertions(+), 1 deletion(-)
3131

3232
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
33-
index bb45551b0..a864fb758 100644
33+
index b4a0547a8c..ce4f4af5e7 100644
3434
--- a/lib/vtls/vtls.c
3535
+++ b/lib/vtls/vtls.c
36-
@@ -343,10 +343,29 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
36+
@@ -341,10 +341,29 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
3737
get_dll_path(relocated_bundle, path_max);
3838
strip_n_suffix_folders(relocated_bundle, 1);
3939
strncat(relocated_bundle, "/", path_max - 1);
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
From 0aa2cd8539608511e34a58473f2974ae0ae50444 Mon Sep 17 00:00:00 2001
2+
From: Matthew John Cheetham <mjcheetham@outlook.com>
3+
Date: Tue, 24 Mar 2026 11:53:02 +0000
4+
Subject: [PATCH 3/4] auth: upgrade SSPI identity to SEC_WINNT_AUTH_IDENTITY_EX
5+
6+
Replace SEC_WINNT_AUTH_IDENTITY with SEC_WINNT_AUTH_IDENTITY_EX across all
7+
SSPI authentication code. The extended structure adds Version, Length, and
8+
PackageList fields while remaining backwards compatible with all SSPI
9+
functions. Available since Windows XP.
10+
11+
Curl_create_sspi_identity now sets the Version and Length fields when
12+
initializing the structure.
13+
14+
Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
15+
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
16+
---
17+
lib/curl_sspi.c | 6 ++++--
18+
lib/curl_sspi.h | 6 +++---
19+
lib/ldap.c | 2 +-
20+
lib/vauth/digest_sspi.c | 10 +++++-----
21+
lib/vauth/vauth.h | 12 ++++++------
22+
5 files changed, 19 insertions(+), 17 deletions(-)
23+
24+
diff --git a/lib/curl_sspi.c b/lib/curl_sspi.c
25+
index 1d4cf925d6..018bfff28e 100644
26+
--- a/lib/curl_sspi.c
27+
+++ b/lib/curl_sspi.c
28+
@@ -93,7 +93,7 @@ void Curl_sspi_global_cleanup(void)
29+
* Returns CURLE_OK on success.
30+
*/
31+
CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
32+
- SEC_WINNT_AUTH_IDENTITY *identity)
33+
+ SEC_WINNT_AUTH_IDENTITY_EX *identity)
34+
{
35+
xcharp_u useranddomain;
36+
xcharp_u user, dup_user;
37+
@@ -105,6 +105,8 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
38+
39+
/* Initialize the identity */
40+
memset(identity, 0, sizeof(*identity));
41+
+ identity->Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
42+
+ identity->Length = sizeof(*identity);
43+
44+
useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar(userp);
45+
if(!useranddomain.tchar_ptr)
46+
@@ -195,7 +197,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
47+
*
48+
* identity [in/out] - The identity structure.
49+
*/
50+
-void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity)
51+
+void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity)
52+
{
53+
if(identity) {
54+
Curl_safefree(identity->User);
55+
diff --git a/lib/curl_sspi.h b/lib/curl_sspi.h
56+
index 3779d51753..ea405f53a7 100644
57+
--- a/lib/curl_sspi.h
58+
+++ b/lib/curl_sspi.h
59+
@@ -34,14 +34,14 @@ void Curl_sspi_global_cleanup(void);
60+
61+
/* This is used to populate the domain in an SSPI identity structure */
62+
CURLcode Curl_override_sspi_http_realm(const char *chlg,
63+
- SEC_WINNT_AUTH_IDENTITY *identity);
64+
+ SEC_WINNT_AUTH_IDENTITY_EX *identity);
65+
66+
/* This is used to generate an SSPI identity structure */
67+
CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp,
68+
- SEC_WINNT_AUTH_IDENTITY *identity);
69+
+ SEC_WINNT_AUTH_IDENTITY_EX *identity);
70+
71+
/* This is used to free an SSPI identity structure */
72+
-void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity);
73+
+void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY_EX *identity);
74+
75+
/* Forward-declaration of global variables defined in curl_sspi.c */
76+
extern PSecurityFunctionTable Curl_pSecFn;
77+
diff --git a/lib/ldap.c b/lib/ldap.c
78+
index e223078b03..59369a556d 100644
79+
--- a/lib/ldap.c
80+
+++ b/lib/ldap.c
81+
@@ -157,7 +157,7 @@ static ULONG ldap_win_bind_auth(LDAP *server, const char *user,
82+
const char *passwd, unsigned long authflags)
83+
{
84+
ULONG method = 0;
85+
- SEC_WINNT_AUTH_IDENTITY cred;
86+
+ SEC_WINNT_AUTH_IDENTITY_EX cred;
87+
ULONG rc = LDAP_AUTH_METHOD_NOT_SUPPORTED;
88+
89+
memset(&cred, 0, sizeof(cred));
90+
diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c
91+
index f29e569cd1..5f4b5e2735 100644
92+
--- a/lib/vauth/digest_sspi.c
93+
+++ b/lib/vauth/digest_sspi.c
94+
@@ -95,8 +95,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
95+
CredHandle credentials;
96+
CtxtHandle context;
97+
PSecPkgInfo SecurityPackage;
98+
- SEC_WINNT_AUTH_IDENTITY identity;
99+
- SEC_WINNT_AUTH_IDENTITY *p_identity;
100+
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
101+
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
102+
SecBuffer chlg_buf;
103+
SecBuffer resp_buf;
104+
SecBufferDesc chlg_desc;
105+
@@ -240,7 +240,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
106+
* Returns CURLE_OK on success.
107+
*/
108+
CURLcode Curl_override_sspi_http_realm(const char *chlg,
109+
- SEC_WINNT_AUTH_IDENTITY *identity)
110+
+ SEC_WINNT_AUTH_IDENTITY_EX *identity)
111+
{
112+
xcharp_u domain, dup_domain;
113+
114+
@@ -466,8 +466,8 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
115+
116+
if(!digest->http_context) {
117+
CredHandle credentials;
118+
- SEC_WINNT_AUTH_IDENTITY identity;
119+
- SEC_WINNT_AUTH_IDENTITY *p_identity;
120+
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
121+
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
122+
SecBuffer resp_buf;
123+
SecBufferDesc resp_desc;
124+
unsigned long attrs;
125+
diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h
126+
index 3e66c89cb5..10a02321e3 100644
127+
--- a/lib/vauth/vauth.h
128+
+++ b/lib/vauth/vauth.h
129+
@@ -170,8 +170,8 @@ struct ntlmdata {
130+
#endif
131+
CredHandle *credentials;
132+
CtxtHandle *context;
133+
- SEC_WINNT_AUTH_IDENTITY identity;
134+
- SEC_WINNT_AUTH_IDENTITY *p_identity;
135+
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
136+
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
137+
size_t token_max;
138+
BYTE *output_token;
139+
BYTE *input_token;
140+
@@ -241,8 +241,8 @@ struct kerberos5data {
141+
CredHandle *credentials;
142+
CtxtHandle *context;
143+
TCHAR *spn;
144+
- SEC_WINNT_AUTH_IDENTITY identity;
145+
- SEC_WINNT_AUTH_IDENTITY *p_identity;
146+
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
147+
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
148+
size_t token_max;
149+
BYTE *output_token;
150+
#else
151+
@@ -309,8 +309,8 @@ struct negotiatedata {
152+
SECURITY_STATUS status;
153+
CredHandle *credentials;
154+
CtxtHandle *context;
155+
- SEC_WINNT_AUTH_IDENTITY identity;
156+
- SEC_WINNT_AUTH_IDENTITY *p_identity;
157+
+ SEC_WINNT_AUTH_IDENTITY_EX identity;
158+
+ SEC_WINNT_AUTH_IDENTITY_EX *p_identity;
159+
TCHAR *spn;
160+
size_t token_max;
161+
BYTE *output_token;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
From cab1cced8a4c48054183192a80d09112d788e868 Mon Sep 17 00:00:00 2001
2+
From: Matthew John Cheetham <mjcheetham@outlook.com>
3+
Date: Mon, 23 Mar 2026 13:34:27 +0000
4+
Subject: [PATCH 4/4] spnego(windows): disallow NTLM via SPNEGO
5+
6+
SPNEGO (Negotiate) authentication can silently fall back to NTLM when
7+
Kerberos is unavailable. This is undesirable because:
8+
9+
1. Kerberos is supposed to be a strong authentication method, and
10+
allowing to downgrade it to NTLM weakens that.
11+
12+
2. If the caller wants to allow or disallow NTLM authentication, they
13+
can do so explicitly; But with NTLM via SPNEGO, there is no such
14+
option.
15+
16+
3. The cURL project deprecated NTLM support and plans on removing it in
17+
September 2026 altogether. If NTLM via SPNEGO was still supported,
18+
that would seriously sabotage the security implications of this plan.
19+
20+
With this patch, libcurl checks the sub-mechanism selected by SPNEGO
21+
after the security context is initialized but before any tokens are
22+
sent on the wire. If NTLM was chosen, the authentication fails with
23+
CURLE_AUTH_ERROR.
24+
25+
Bare NTLM auth (CURLAUTH_NTLM) is not affected.
26+
27+
Note that this patch only changes the Windows (SSPI) side by restricting
28+
SSPI from using NTLM by specifying the special string "!ntlm" in the
29+
PackageList field of the SEC_WINNT_AUTH_IDENTITY_EX structure. A more
30+
comprehensive solution that includes GSS-API is being developed in
31+
https://github.com/curl/curl/pull/21076.
32+
33+
Signed-off-by: Matthew John Cheetham <mjcheetham@outlook.com>
34+
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
35+
---
36+
lib/vauth/spnego_sspi.c | 27 +++++++++++++++++++++++++++
37+
1 file changed, 27 insertions(+)
38+
39+
diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c
40+
index 1f73123a0d..57d7599361 100644
41+
--- a/lib/vauth/spnego_sspi.c
42+
+++ b/lib/vauth/spnego_sspi.c
43+
@@ -146,6 +146,33 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data,
44+
/* Use the current Windows user */
45+
nego->p_identity = NULL;
46+
47+
+ /* Disallow NTLM via SPNEGO */ {
48+
+ /* Exclude NTLM from SPNEGO negotiation via the PackageList field */
49+
+ if(!nego->p_identity) {
50+
+ memset(&nego->identity, 0, sizeof(nego->identity));
51+
+ nego->identity.Version = SEC_WINNT_AUTH_IDENTITY_VERSION;
52+
+ nego->identity.Length = sizeof(nego->identity);
53+
+ nego->identity.Flags =
54+
+#ifdef UNICODE
55+
+ SEC_WINNT_AUTH_IDENTITY_UNICODE;
56+
+#else
57+
+ SEC_WINNT_AUTH_IDENTITY_ANSI;
58+
+#endif
59+
+ nego->p_identity = &nego->identity;
60+
+ }
61+
+
62+
+ /* Use the special name "!ntlm" to prevent NTLM from being used:
63+
+ * https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_winnt_auth_identity_exa
64+
+ */
65+
+ nego->identity.PackageList =
66+
+#ifdef UNICODE
67+
+ (unsigned short *)CURL_UNCONST(TEXT("!ntlm"));
68+
+#else
69+
+ (unsigned char *)CURL_UNCONST(TEXT("!ntlm"));
70+
+#endif
71+
+ nego->identity.PackageListLength = 5;
72+
+ }
73+
+
74+
/* Allocate our credentials handle */
75+
nego->credentials = curlx_calloc(1, sizeof(CredHandle));
76+
if(!nego->credentials)

0 commit comments

Comments
 (0)