Why does SPNEGO exchange a mechListMIC, and what is an acceptor requesting with negState request-mic?
answer
- the first token has nothing to sign with
- an edit, not a broken cipher
- checked afterwards, not prevented
- each side hashes the list it saw
- request-mic makes the comparison mandatory
basics
~20 sBecause the advertised mechTypes list travels before any key exists, an active attacker could delete the strong mechanism from it. The mechListMIC is a checksum over that list, computed once a context exists; request-mic(3) demands it.
solid answer
~40 sSPNEGO's `mechTypes` list is sent in the very first token, when no context and therefore no key exists, so it cannot be protected at the time. An active attacker in the path can delete the strongest entry and leave a weaker one both ends accept — and each end sees a perfectly consistent exchange. The `mechListMIC` closes this retrospectively: once the selected mechanism's context is established, each side computes a message integrity code with `GSS_GetMIC` over the initiator's original list and compares it with the list it actually saw. A mismatch means the advertisement was altered and the context is refused. `negState` `request-mic(3)` is the acceptor saying that this checksum exchange is required before the negotiation may complete.
code
pseudocode · 12 lines# initiator, after the selected mechanism's context is established
sent_list = encoding of mechTypes as the initiator sent them
my_mic = GSS_GetMIC(context, sent_list)
send NegTokenResp(mechListMIC = my_mic)
# acceptor
seen_list = encoding of mechTypes as the acceptor received them
if GSS_VerifyMIC(context, seen_list, received_mechListMIC) fails
negState = reject # the advertisement was altered in transit
discard the context
else
negState = accept-completedgo deeper
Note that the list of mechanisms is sent before anything is secured, and that a later checksum exists to confirm nobody edited it.
Explain why the checksum has to come after the context exists, and what the two sides are comparing when they exchange it.
Recognise a downgrade for what it looks like in practice — a working context over a weaker mechanism with no error anywhere — and know which state demands the check.
Decide what your clients advertise and what your acceptors will settle for: the negotiation is only as strong as the weakest entry both ends will still accept.
## The window the first token leaves open A negotiation has an unavoidable ordering problem. The initiator must say what it supports **before** anything has been agreed, so the first `NegTokenInit` goes out with `mechTypes` in the clear and unprotected. There is no key yet, no mechanism yet, and therefore nothing to sign with. An attacker positioned in the path can exploit exactly that. Edit the list — remove the strongest mechanism, leave a weaker one both ends happen to support — and the exchange proceeds. The acceptor selects the weaker mechanism honestly, because as far as it can tell that is all the initiator offered. The initiator accepts the choice honestly, because acceptors are allowed to pick any entry. Neither side sees an anomaly. This is a **downgrade**, and the striking thing about it is that it needs no cryptography to be broken: it needs only an edit to a list nothing was protecting. ## The retrospective fix The `mechListMIC` is the answer, and its trick is that it is computed **late**: 1. The selected mechanism's context is established, which means a key now exists. 2. Each side computes a message integrity code with `GSS_GetMIC` over the initiator's `mechTypes` list **as that side knows it** — the initiator over what it sent, the acceptor over what it received. 3. The codes are exchanged in the `mechListMIC` field of the negotiation tokens and verified. 4. If they do not agree, the two ends were not looking at the same advertisement, and the context is refused. The check cannot prevent the edit, because the edit happens first. It detects it before the context is put to use, which is the most a negotiation can do about its own opening move. ## What `request-mic(3)` is for The four `negState` values include `request-mic(3)`, and it means something narrower than a general demand for security: *the checksum exchange over the mechanism list is required before this negotiation completes*. An acceptor sends it when the case for checking is strongest — most obviously when it did **not** select the initiator's first-listed mechanism, which is exactly the outcome a downgrade would also produce. | Situation | Why the checksum matters | |---|---| | Acceptor chose the first-listed mechanism | the outcome is what an unedited list would give anyway | | Acceptor chose a later entry | indistinguishable from an attacker having deleted the earlier ones | | Either side sent `request-mic(3)` | the exchange is committed to checking before completing | ## What the checksum does not cover Being precise about the scope avoids a false sense of coverage: - It covers the **initiator's advertised mechanism list**, not the application data that follows. - It does not protect the selected mechanism's own tokens — those carry their own protection. - It says nothing about the transport the tokens travelled over. - It cannot help if one side simply does not perform the comparison, which is the quiet failure mode: the downgrade then completes and looks like a legitimate mechanism choice for the rest of the context's life. ## Reading a downgrade in the field The symptom of a successful downgrade is not an error. It is a context established over a weaker mechanism, indistinguishable from a client that never offered the stronger one — the same symptom as a service whose name is unregistered, or a client that was never configured for the stronger mechanism at all. That is why the negotiation state and the advertised list are worth reading from a capture rather than inferred from the outcome: only the list tells you what the client was prepared to do, and only the checksum's result tells you whether the acceptor saw the same list.
- Why can the mechanism list not simply be signed in the first token?Because nothing has been established when it is sent. There is no agreed mechanism and no key, so the opening advertisement is necessarily unauthenticated. The checksum is deliberately retrospective: it proves after the fact that what the initiator advertised is what the acceptor received.
- What is the failure mode if one side skips the comparison?The downgrade completes silently. Both ends hold a working context over the weaker mechanism, and neither can distinguish an attacker's edit from a client that genuinely offered nothing better. Nothing later in the exchange revisits the question.
- Does the checksum protect the tokens of the selected mechanism?No. Its scope is the initiator's advertised mechanism list. The selected mechanism's own tokens carry their own protection — for Kerberos, a ticket sealed under the service principal's key and an `Authenticator` sealed under the session key.
Two people shout across a noisy room to agree which language to speak. Once they are close enough to talk privately, each repeats what they thought they heard shouted, and a difference means someone else was shouting too.
saying these in an interview costs you the question
- Thinks the first NegTokenInit is integrity-protected
- Says the checksum protects the application's data
- Confuses request-mic with a rejected negotiation
- Believes a downgrade requires breaking a cipher
- Assumes the check prevents rather than detects the edit