When lunch conversations at work take on a mischievous tone, all sorts of strange ideas come forth. This time, as Elad Shamir (@elad_shamir) was present, talk of course turned to his recent work on Kerberos, Wagging the Dog (or as I prefer to call it, “Screwing the Pooch”, its original title, which was eventually vetoed by a person far more sensible than either of us).

My fuzzing targets had gone stale, and I was on the lookout for new openings. Someone mentioned constrained delegation and began describing the flow of Kerberos messages. Well, what about Kerberos messages? How many researchers have in fact explored this attack surface for memory corruption? The lunch gang reasoned that the number could probably be placed in the hundreds if not thousands.

Owing to cockiness, I decided to investigate anyway.

Round 1 - Local Crashes

The plan was to eliminate the obvious bugs first by performing dumb fuzzing against messages received by LSASS, then titrate the fuzzer’s complexity to dig deeper until we felt satisfied we had covered enough ground. A few hours of writing a fuzzing harness and away we went. Unsurprisingly, this approach produced exactly zero crashes.

We reasoned that the functionality being exercised with this approach was almost certainly related to ASN.1 parsing, and so most likely very well covered by researchers before us. In light of this, we dived deeper into Kerberos’ structures, notably structures hidden within encrypted parts…

… Which led us to the PAC (“Privilege Attribute Certificate”). The PAC is signed and encrypted in the key of the target service and also possesses a signature in the key of the KDC (“krbtgt”.)

The surface area available for an attack is attractive but the presence of signatures did not bode well for exploitability. Nevertheless, we tooled up and proceeded to fuzz, and within a few seconds of pulling the trigger, got this:

localdos.gif

So what is going wrong exactly? A lack of bounds checking in kerberos.dll!KerbVerifyPacSignature (what luck!). Specifically, setting a large value for the cbBufferSize field of the KDC checksum PAC_INFO_BUFFER entry will trigger this issue.

Getting a crash here is lucky for the reason that it is among the few pieces of functionality an attacker may influence before the KDC checksum is validated, and because we don’t know the KDC key, cannot forge.

Due to the critical nature of the LSASS process, a crash will result in a full system reboot, following a 60-second timeout interval.

Round 2 - Remote Crashes

Well, local DoS bugs are useful primitives, but there are many of those already (Microsoft do not seem particularly interested in fixing these). My confederate and I began discussing ways to trigger the same vulnerable code path but remotely. This is possible, as it turns out, by using S4U2Proxy. Thank you, Elad! Very clever.

S4U2Proxy can only be invoked by services configured for Constrained Delegation, which can normally only be done by a user with the SeEnableDelegation privilege. However, the introduction of Resource-based Constrained Delegation allows all resources to enable arbitrary accounts to invoke S4U2Proxy. Any domain user can create a computer account with an SPN by default by abusing the MachineAccountQuota, and then configure Resource-based Constrained Delegation from the computer account to itself, as explained in Elad’s paper.

We weaponized Rubeus to launch the PoC and much to our surprise, it was enough to crash the DC. Here’s Rubeus being rambunctious:

remotedos.gif

The stack trace was actually different from the former crash, but in logically identical functionality - suggesting duplicate code (tut tut!).

There are some interesting abuse cases for cycling a DC, as you might imagine, but we will save that for another time.

Author

Danyal Drew (@danyaldrew) from The Missing Link.

Thanks to Elad for the ideas and assistance - without his knowledge of Kerberos and persistent hectoring against my online reclusiveness, the bug would not have been found nor this post written. Lunch club for the conspiratorial atmosphere.

Disclosure Timeline

  • 1st March: Report sent to Microsoft
  • 5th March: Automated response
  • 16th March: Response from the case manager
  • 28th March: Request to delay disclosure until June patch release
  • 12th June: Patch released (CVE-2019-0972)

The moral of the story is to never assume a target has been thoroughly exhausted be plucky, and follow your nose.