Azure AD Pass The Certificate

Mor
12 min readAug 19, 2020

--

Intro

With the evolvement of Azure and Pass-Through authentication, many organizations are connecting devices to Azure AD, making the authentication and management easier. Azure AD devices can be connected only to Azure AD and no longer to On-Prem AD, raising the question how attackers can get access to those machines.

We have already seen the blog post on Azure AD lateral Movement and Mimikatz “Pass The PRT” attack. Now we only need to find out how to jump between Azure AD joined machines which no longer supports the known authentication protocols.

In this blog post I’ll provide network behavior analysis of the new authentication mechanism between 2 Azure AD joined machines, I’ll also explain what is Azure AD P2P certificate and how it is generated.

After learning the necessary information, we will dive into “Pass The Certificate” attack that works against Azure AD joined machines and let you gain access to remote machines. This attack use the PRT from Azure AD machine to gain P2P certificates and by using the certificate, to gain permanent access to any Azure AD joined machine (if sufficient privileges).

Azure AD machine

There are 3 ways to connect a device to Azure AD:

  • Azure AD registered — devices that are registered in Azure but can be accessed using Microsoft account or local account.
  • Azure AD joined — devices that are joined to Azure and can be accessed only by local user or Azure AD user. Those machines are managed in Azure.
  • Hybrid Azure AD joined — devices that are joined both to Azure AD and to On-Prem AD, and can be accessed by Azure AD accounts, On-Prem accounts and local accounts.

Azure AD joined machines support only 2 mech types for authentication, NegoEx (1.3.6.1.4.1.311.2.2.30) and NtlmSSP (1.3.6.1.4.1.311.2.2.10). We already know how NTLM protocol works, and it also supported by Azure AD joined machines for local user authentication.

MechTypes

let’s look on the authentication process between 2 Azure AD joined machines, where the client in the left, the initiator, try to authenticate and access the client in the right, the acceptor.

Authentication process between 2 Azure AD Machines
  1. This step is simple SMB negotiation. In the end of this step, both sides will agree on SMB dialect and will find out what are the mech types supported by the other client. If both support NegoEx, then we will proceed to step 2, otherwise we will proceed to the known NTLM authentication (or other authentication mechanism if they both support it).
  2. If the initiator already has P2P certificate, then we will skip steps 2–4 and proceed to step 5. Otherwise, the initiator will request Nonce from Azure AD that will be used for P2P certificate request.
  3. Azure AD will generate a Nonce that will be signed by the requestor in the next request, to prove that the second request is not passed from man in the middle attack or reused.
  4. The client will build JWT. They will generate random context and place it in the header. In the JWT message they will provide the CSR (certificate signing request) and the PRT. The JWT will be signed by the derived key generated from the context and the session key.
  5. Azure AD will validate the JWT using the derived key and if the verification succeeded, it would generate X.509 certificate and send it in the response. This certificate is a P2P (peer-to-peer) certificate and only the client who initiated the request should have the private key associated with the public key.
  6. The initiator will send multiple NegoEx messages, where the important message is AP request. AP request message is a Kerberos PKU2U request which is the same as Kerberos PKI AS request (TGT request).
  7. The acceptor will send back multiple NegoEx messages, where the important message is challenge. This message is a Kerberos PKU2U response which is the same as Kerberos PKI AS response (TGT response).
  8. The initiator will send 2 NegoEx messages, ap request and verify. The AP request message will be Kerberos AP request to resource using the TGT from the previous stage. Verify message is a checksum message of all the previous NegoEx messages using HMAC_SHA1_96_AES256. The verify message is required, the acceptor will deny the request without it (or with wrong checksum).
  9. The acceptor will validate the checksum and the AP request messages received from the initiator. If the verification succeeded, they would send 2 NegoEx messages, challenge and verify. The challenge message will be Kerberos AP response from the resource, validating that resource is the one you authenticated to. Verify message is a checksum message like in the previous request (8). In this response, the verify message will also contain the checksum of the messages from the previous stage including the challenge.

PKU2U

Public Key Cryptography Based User-to-User authentication is a Kerberos PKI extension that removes the KDC from the equation and allow peer-to-peer (client to client) authentication. In this protocol, the realm name changed to the constant “WELLKNOWN:PKU2U”. The difference from Kerberos PKI is that the acceptor (the client we try to connect to) is acting as the KDC, the acceptor will issue a Kerberos ticket that later will be used by the initiator (the client want to authenticate to the acceptor) to prove that they can extract the session key associated with the ticket. It’s also important to mention that PKU2U protocol data units (PDUs) can’t be exchanged as usual Kerberos but wrapped as GSS-API context tokens.

These are the steps to perform PKU2U authentication:

  1. The initiator generates Kerberos PKI AS request (with the new realm).
  2. The acceptor verifies the authenticator and PADATA in the request.
  3. If the verification succeeded, the acceptor generate ticket that proves the verification of the initiator, and like in Kerberos PKI, encrypt it with the session key (public-key or Diffie-Hellman).
  4. The initiator receives the AS response packet, decrypt the relevant parts, and extract the second session key that will be used for the second request.
  5. The initiator generates Kerberos AP request packet using the ticket and the session key from the previous stage and send it to the acceptor.
  6. The acceptor receives the packet and validates the ticket and the authenticator like in normal AP request.
  7. If the acceptor succeeded to verify the AP request, it will generate AP response and send it back to the initiator.
  8. The initiator will receive the AP response and validate the authenticator.
  9. If the initiator succeeded to verify the AP response, connection is established between the initiator and the acceptor.

SPNEGO

Simple and Protected GSS-API (Generic Security Service Application Program Interface) Negotiation Mechanism which play a key role in the negotiation process for choosing the security mechanism to use, in our case it will be NTLM or NegoEx. It’s a known negotiation protocol so we will not go into more details.

NegoEx

SPNEGO Extended Negotiation security mechanism. When the NEGOEX security mechanism is selected by SPNEGO, it provides a method allowing selection of a common authentication protocol based on factors beyond just the fact that both client and server support a given security mechanism.

There are 7 types of messages that are used in authentication:

  • Initiator Nego — used for sending the authentication schema, like hello message.
  • Acceptor Nego — used for sending authentication schema from the acceptor if needed.
  • Initiator Metadata — used for sending the metadata (GSS-API token) of the initiator if needed.
  • Acceptor Metadata — used for sending the metadata (GSS-API token) of the acceptor if needed.
  • Challenge — used for accepting the initiator AP Request, contains Kerberos response messages.
  • AP Request — used for authenticating to the acceptor, contains Kerberos request messages.
  • Verify — used for verifying the conversation.
  • Alert — used to notify on errors in the authentication.

The flow as it described in MS-NEGOEX:

Let’s quickly go over the message types:

First, let’s understand the structure of the MESSAGE_HEADER since it required in every message. It is used to provide metadata about each message. According to the RFC, the structure will be

struct{

ULONG64 Signature; // contains MESSAGE_SIGNATURE

MESSAGE_TYPE MessageType;

ULONG SequenceNum; // the message sequence number of this,

// conversation, starting with 0 and sequentially

// incremented

ULONG cbHeaderLength; // the header length of this message,

// including the message specific header, excluding the

// payload

ULONG cbMessageLength; // the length of this message

CONVERSATION_ID ConversationId;

} MESSAGE_HEADER;

The fields will be:

  1. The Signature that will be “NEGOEXTS” as constant.
  2. The message type, 1 of the 7 types listed above.
  3. The sequence number of the message which will be required for signing the message in the verify message by the order they sent.
  4. The header struct size.
  5. Length of the message.
  6. Conversation id that should be the same for every message during the same authentication session.

We will not get into NegoEx details since those are typical GSSAPI messages. It’s important to say that Initiator Nego, Acceptor Nego, Initiator Metadata, Acceptor Metadata and alert messages don’t have any “secret” (they can be generated without the private key or any secret). But, those messages required to generate the verify message which requires checksum over all the previous messages by the order they sent.

Nego messages

The Nego messages used to begin and exchange negotiation of security mechanisms. First the initiator will send Nego message with message type 0 (INITIATOR_NEGO) and a list of supported security mechanisms (MechTypes) to begin the negotiation. Afterwards, the acceptor will respond with Nego message with message type 1 (ACCEPTOR_NEGO) and the list of supported security mechanisms.

Initiator Nego

Initiator Nego message

Acceptor Nego

Acceptor Nego message

Exchange messages

Those messages are used to encapsulate context tokens of the negotiated security mechanism.

Initiator Metadata

Acceptor Metadata message

Where the exchange bytes are Asn1 struct and they are the metadata token.

Acceptor Metadata

Where the exchange bytes are Asn1 struct and they are the metadata token.

AP Request

AP request message

The challenge is like Kerberos AS/AP request. The exchange bytes contain the PKU2U PDUs.

Challenge

Challenge message

The challenge is like Kerberos AS/AP response. The exchange bytes contain the PKU2U PDUs.

Verify

Verify message

The verify message is sent by both the initiator and acceptor. This packet ensures that both sides participated in the whole conversation. This message protects from replay attacks by generating a checksum of all the previous NegoEx messages sent in the conversation, those message checksum by the session key which both the initiator and the acceptor agree on during the authentication.

Azure AD certificate

For client authentication between 2 Azure AD joined machines, certificate must be issued by Azure AD CA for the required user (as the subject). Certificate will be automatically requested by the Windows machine using the function aadcloudap!GetUserCertificate when the negotiated security mechanism is NegoEx.

As you can see bellow, the output will be certificate that can be used using PKU2U for client to client authentication and will be issued by the CA “MS-Organization-P2P-Access [2020]”.

Azure AD P2P Certificate

The process of requesting certificate is as follow (those are steps 2–5 authentication process between 2 Azure AD machines illustration in the beginning of the post).

The client must request nonce by sending grant type of srv_challenge to “login.microsoftonline.com” using the tenant id of his organization.

Nonce request — Fiddler

After receiving the Nonce, a second request should be made to request for certificate using the following parameters:

  • grant_type of “urn:ietf:params:oauth:grant-type:jwt-bearer”
  • request that will contain the json web token (JWT).

In the request, the client will have to generate the following:

  • Security context.
  • Certificate signing request (CSR).
  • Derived key using the client session key and the security context.

After generating the required information, the client will have to generate json web token (JWT). In the JWT header the client will provide the security context and algorithm (that will be HS256 — HMAC SHA256), and in the message they will provide the following fields:

  • Iss — issuer and signer of the token (it will always be “aad:brokerplugin”).
  • Grant_type — the token requested (it will always be “refresh_token”).
  • Aud — the audience, who the token intended for (it will always be “login.microsoftonline.com”).
  • Request_nonce — the Nonce from the first step.
  • Scope — the requested permissions (it will always be “openid aza ugs”).
  • Refresh_token — the client PRT.
  • client_id — the AAD cloud ap identifier (it will always be “38aa3b87-a06d-4817-b275–7a316988d93b”).
  • cert_token_use — the certificate required (it will always be “user_cert”).
  • csr_type — the certificate signing request type (it will always be “http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10”).
  • csr — the certificate signing request.
JWT — jwt.io

The client will take the header and the payload (message) and sign them using the derived key, add the signature field to the and send the JWT (header + ”.” +payload + ”.” + signature) to Azure AD.

After receiving the request, Azure AD will verify the JWT signature using the client session key and the security context, and will check if the refresh token is valid. If all the checks passed, Azure AD will generate certificate for the user and will send a response with the following fields:

  • cert_token_use — the same field from the request (it will always be “user_cert”).
  • expires_in — the duration of time the certificate is valid for.
  • expires_on — the date when the certificate is no longer valid.
  • ext_expires_in — value to indicate the extended lifetime of an access token.
  • id_token — token with the identity of the user.
  • refresh_token — new PRT for the user (the previous one is still valid).
  • resource — the resource the certificate generated for (it will always be “p2p_cert”).
  • session_key — new session key for the user (the previous one is still valid).
  • x5c — the PEM certificate generated from the CSR.
  • x5c_ca — the CA of the certificate.

When the client receives the response from Azure AD, they can store the refresh token and the session key for future requests and they will validate the certificate subject against the PEM certificate to make sure this is the requested account. The P2P certificate will be stored in the certificate store and can be used for client authentication if it still valid.

Request for certificate — fiddler

PRT -> Cert -> Pass The Certificate

Now that were familiar with the protocols, we can implement them and authenticate to Azure AD machine from any machine.

We already saw in his tweet that Mimikatz is extracting the PRT and session key from Azure AD machine, so we can use Mimikatz function “sekurlsa::cloudap” to get the PRT and the session key. Then, we will use other Mimikatz function, “dpapi::cloudapkd”, to get derived key for security context.

Now we have the following:

  • Username
  • Tenant ID
  • PRT
  • Security context
  • Derived Key

It seems that we have everything we need to request P2P certificate for that user. To request certificate, I wrote python tool using the module requests.

Now we got certificates for all the PRT lifetime. To use the certificate, I wrote another python tool that will authenticate to the remote machine, run PSEXEC and open a CMD on the victim machine. This will allow us to use Mimikatz again to get the PRT of another user.

And if you are a Wireshark person, I also modified Wireshark and parsed PKU2U PDUs. Its not in Wireshark code yet, but you can download my modified Wireshark version.

*right now, the “Pass The Certificate” tool is in python 2.7 and call only PSEXEC, but I’m working to change it to python 3 and add more functionality.

--

--