[PATCH] Fix SPNEGO interoperability with servers implementing RFC2478

David Woodhouse dwmw2 at infradead.org
Mon Aug 4 10:10:16 EDT 2014

On Fri, 2014-07-25 at 18:57 -0400, Greg Hudson wrote:
> On 07/25/2014 03:45 PM, David Woodhouse wrote:
> > If that isn't the intention, the I wonder why the request-mic state was
> > added at all.
> I'm not sure.  It wouldn't be the first time that a protocol used
> explicit state where implicit state would serve.

No, it's much more than that.

You are quite right that it's not unheard-of for protocols to use
explicit state where implicit state would serve. (And sometimes that
makes sense, for forward compatibility. You might explicitly know the
state *today* but in future new possibilities might be added.)

But this additional state was *added* to an existing protocol.

If the intention was that new servers and clients must *always* require
MIC processing without regard to backward compatibility, there was
absolutely no need for that change. Just send and check/require the MIC
unconditionally (when the underlying mechanism supports it), and that's
all there is to it.

The only way that the addition of the REQUEST-MIC state makes sense, is
for clients to be able to tell the difference between RFC4178-compliant
and older servers.

Furthermore, in your interpretation it's not just redundant state on the
wire. There's also verbiage in the RFC which would be entirely
redundant, where it *explicitly* sets out how a client should behave
when a server doesn't send the REQUEST-MIC state in its reply.

Seriously, the *only* logic that makes sense for the change is that it
allows clients to interoperate with older servers.

FWIW I tested on Windows 7, with a fake server. My client (using SSPI)
sends a Kerberos token, and the server sends back:
     Proxy-Authenticate: Negotiate oRUwE6ADCgEBoQwGCisGAQQBgjcCAgo=
That is, a SPNEGO "no, try GSS-NTLMSSP" with ACCEPT-INCOMPLETE. At which
point Windows happily falls back to GSS-NTLMSSP as requested.

This behaviour matches my reading of the specification, and I'm fairly
certain that it *is* what they intended.

Now, that doesn't mean we *have* to do what they intended, of course.
But it's a fairly strong hint in that direction.

> > A server compliant with RFC4178 will not only send request-mic, but it
> > will also expect us to actually *send* a MIC. If a hypothetical attacker
> > downgrades 'request-mic' in the reply to 'accept-incomplete', the server
> > isn't actually going to accept our authentication, surely?
> I think the server will wind up returning accept-incomplete because it
> hasn't yet received a mic from the client.  But the client won't
> necessarily see that; the attacker could alter the final message from
> the server to have accept-complete state.

If it's the client that sends the last token, as is the case in NTLMSSP,
there's no downgrade attack here. Either we send our final packet
without a MIC, or perhaps we'd send it with an unsolicited MIC of the
tampered packets, and either way the server is doing to reject that. 

If it's the server that sends the last token, then still we're never
going to be able to authenticate to that server because it'll be waiting
for a MIC. But you are correct that the attacker could alter the final
message to have ACCEPT-COMPLETE state, and fool us into thinking that we
succeeded. Although we'd be left with a GSSAPI context which isn't
actually *useful* for anything, because the server will hate us.

Maybe I'm missing a reason why we'd really care about that, given that
an attacker could have bypassed the whole exchange and just made it look
like the server didn't require authentication in the first place.

But I'm not going to lose sleep over that. Given the limited
circumstances in which the fallback is necessary, it would be perfectly
acceptable to accept fallback without REQUEST-MIC *only* if the
mechanism being requested is one where the client sends the final token.
Or indeed, *only* if the mechanism is NTLMSSP.

Like this, for example...

From ec9988362314819963e025aee5b0deca28a98eb6 Mon Sep 17 00:00:00 2001
From: David Woodhouse <David.Woodhouse at intel.com>
Date: Fri, 25 Jul 2014 11:10:01 +0100
Subject: [PATCH] Fix SPNEGO interoperability with servers implementing RFC2478
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Servers running Windows 2003 and earlier do not send a negState of
request-mic in their initial reply when the first mechanism is
declined. That's because they implement (roughly) RFC2478, which
predates the addition of the request-mic state.

A server conforming to RFC4178 MUST send request-mic there, but it
is equally clear that on the client side, we should deal with its
absence. For example, the language in §5 (c) is as follows:
    "if the negState was request-mic in the first reply from the
     target, a mechlistMIC token MUST be included; otherwise..."

In the MIT implementation as it stands, there is no "otherwise".
Because we already overzealously aborted the negotiation.

Other language in RFC4178 which supports this interpretation includes
the whole of Appendix C, which explains the changes since RFC2478.

Looking at that, in fact it seems that the addition of request-mic on
the wire was done specifically to allow interoperability with older
implementations which don't set it. If not for that consideration,
and if we were only ever communicating between RFC4178-compliant
implementations, we could always have just behaved as if request-mic
were set — and there would be no need to *say* so on the wire.

Testing on Windows 7 shows that the Windows client *will* continue in
this situation without using a MIC, as requested by the server.

This change does NOT make us vulnerable to a downgrade attack where an
attacker changes request-mic in a server's response to accept-incomplete.

The server in any such situation is still going to be looking for the
MIC that it asked for. And when we fail to send one (or if we send one
but we've hashed the tampered packets), the server is going to reject
our authentication.

In the case where the final mechanism token is sent from client to
server, the server will reject our authentication attempt outright.
This is, in fact, the case we see in practice because it is NTLMSSP
that the affected servers will be asking us to fall back to.

In the case where the final mechanism token is sent from server to
client, the server will send its final token with accept-incomplete
state indicating that it is waiting for the MIC. A hypothetical
attacker could change that to accept-complete, thus tricking us into
thinking that the authentication succeeded. It's not entirely clear
what practical purpose this could serve in any situation, since
the context *isn't* established and the server *hasn't* authenticated
us. But we don't need to lose sleep over that. We *only* need to allow
NTLMSSP for this fallback anyway, so just disallow it for any other
 src/lib/gssapi/spnego/spnego_mech.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c
index 2aa6810..0abe175 100644
--- a/src/lib/gssapi/spnego/spnego_mech.c
+++ b/src/lib/gssapi/spnego/spnego_mech.c
@@ -806,6 +806,9 @@ init_ctx_nego(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
 	return ret;
+static const gss_OID_desc gss_mech_ntlmssp_oid =
+        { 10, "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" };
  * Handle acceptor's counter-proposal of an alternative mechanism.
@@ -831,17 +834,20 @@ init_ctx_reselect(OM_uint32 *minor_status, spnego_gss_ctx_id_t sc,
 	sc->internal_mech = &sc->mech_set->elements[i];
-	 * Windows 2003 and earlier don't correctly send a
-	 * negState of request-mic when counter-proposing a
-	 * mechanism.  They probably don't handle mechListMICs
-	 * properly either.
+	 * A server conforming to RFC4178 MUST set REQUEST_MIC here
+	 * but Windows 2003 and earlier implement (roughly) RFC2478
+	 * instead, and send ACCEPT_INCOMPLETE. Tolerate that *only*
+	 * if they are asking us to fall back to NTLMSSP.
-	if (acc_negState != REQUEST_MIC)
+	if (acc_negState == ACCEPT_INCOMPLETE) {
+		if (!g_OID_equal(supportedMech, &gss_mech_ntlmssp_oid))
+	} else if (acc_negState != REQUEST_MIC)
 	sc->mech_complete = 0;
-	sc->mic_reqd = 1;
-	*negState = REQUEST_MIC;
+	sc->mic_reqd = (acc_negState == REQUEST_MIC);
+	*negState = acc_negState;
 	*tokflag = CONT_TOKEN_SEND;

-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 5745 bytes
Desc: not available
Url : http://mailman.mit.edu/pipermail/krbdev/attachments/20140804/3cc0173f/attachment.bin

More information about the krbdev mailing list