❌

Normal view

There are new articles available, click to refresh the page.
Before yesterdayVulnerabily Research

RC4 Is Still Considered Harmful

By: Unknown
27 October 2022 at 19:48

By James Forshaw, Project Zero


I've been spending a lot of time researching Windows authentication implementations, specifically Kerberos. In June 2022 I found an interesting issue number 2310 with the handling of RC4 encryption that allowed you to authenticate as another user if you could either interpose on the Kerberos network traffic to and from the KDC or directly if the user was configured to disable typical pre-authentication requirements.


This blog post goes into more detail on how this vulnerability works and how I was able to exploit it with only a bare minimum of brute forcing required. Note, I'm not going to spend time fully explaining how Kerberos authentication works, there's plenty of resources online. For example this blog post by Steve Syfuhs who works at Microsoft is a good first start.

Background

Kerberos is a very old authentication protocol. The current version (v5) was described in RFC1510 back in 1993, although it was updated in RFC4120 in 2005. As Kerberos' core security concept is using encryption to prove knowledge of a user's credentials the design allows for negotiating the encryption and checksum algorithms that the client and server will use.Β 


For example when sending the initial authentication service request (AS-REQ) to the Key Distribution Center (KDC) a client can specify a list supported encryption algorithms, as predefined integer identifiers, as shown below in the snippet of the ASN.1 definition from RFC4120.


KDC-REQ-BODYΒ  Β  ::= SEQUENCE {

...

Β Β Β Β etypeΒ  Β  [8] SEQUENCE OF Int32 -- EncryptionType

Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β -- in preference order --,

...

}


When the server receives the request it checks its list of supported encryption types and the ones the user's account supports (which is based on what keys the user has configured) and then will typically choose the one the client most preferred. The selected algorithm is then used for anything requiring encryption, such as generating session keys or the EncryptedData structure as shown below:


EncryptedData Β  ::= SEQUENCE {

Β Β Β Β Β Β Β Β etype Β  [0] Int32 -- EncryptionType --,

Β Β Β Β Β Β Β Β kvnoΒ  Β  [1] UInt32 OPTIONAL,

Β Β Β Β Β Β Β Β cipherΒ  [2] OCTET STRING -- ciphertext

}


The KDC will send back an authentication service reply (AS-REP) structure containing the user's Ticket Granting Ticket (TGT) and an EncryptedData structure which contains the session key necessary to use the TGT to request service tickets. The user can then use their known key which corresponds to the requested encryption algorithm to decrypt the session key and complete the authentication process.


Alt Text: Diagram showing the based Kerberos authentication and requesting an AES key type.

This flexibility in selecting an encryption algorithm is both a blessing and a curse. In the original implementations of Kerberos only DES encryption was supported, which by modern standards is far too weak. Because of the flexibility developers were able to add support for AES through RFC3962 which is supported by all modern versions of Windows. This can then be negotiated between client and server to use the best algorithm both support. However, unless weak algorithms are explicitly disabled there's nothing stopping a malicious client or server from downgrading the encryption algorithm in use and trying to break Kerberos using cryptographic attacks.


Modern versions of Windows have started to disable DES as a supported encryption algorithm, preferring AES. However, there's another encryption algorithm which Windows supports which is still enabled by default, RC4. This algorithm was used in Kerberos by Microsoft for Windows 2000, although its documentation was in draft form until RFC4757 was released in 2006.Β 


The RC4 stream cipher has many substantial weaknesses, but when it was introduced it was still considered a better option than DES which has been shown to be sufficiently vulnerable to hardware cracking such as the EFF's "Deep Crack". Using RC4 also had the advantage that it was relatively easy to operate in a reduced key size mode to satisfy US export requirements of cryptographic systems.Β 


If you read the RFC for the implementation of RC4 in Kerberos, you'll notice it doesn't use the stream cipher as is. Instead it puts in place various protections to guard against common cryptographic attacks:

  • The encrypted data is protected by a keyed MD5 HMAC hash to prevent tampering which is trivial with a simple stream cipher such as RC4. The hashed data includes a randomly generated 8-byte "confounder" so that the hash is randomized even for the same plain text.

  • The key used for the encryption is derived from the hash and a base key. This, combined with the confounder makes it almost certain the same key is never reused for the encryption.

  • The base key is not the user's key, but instead is derived from a MD5 HMAC keyed with the user's key over a 4 byte message type value. For example the message type is different for the AS-REQ and the AS-REP structures. This prevents an attacker using Kerberos as an encryption oracle and reusing existing encrypted data in unrelated parts of the protocol.


Many of the known weaknesses of RC4 are related to gathering a significant quantity of ciphertext encrypted with a known key. Due to the design of the RC4-HMAC algorithm and the general functional principles of Kerberos this is not really a significant concern. However, the biggest weakness of RC4 as defined by Microsoft for Kerberos is not so much the algorithm, but the generation of the user's key from their password.Β 


As already mentioned Kerberos was introduced in Windows 2000 to replace the existing NTLM authentication process used from NT 3.1. However, there was a problem of migrating existing users to the new authentication protocol. In general the KDC doesn't store a user's password, instead it stores a hashed form of that password. For NTLM this hash was generated from the Unicode password using a single pass of the MD4 algorithm. Therefore to make an easy upgrade path Microsoft specified that the RC4-HMAC Kerberos key was this same hash value.


As the MD4 output is 16 bytes in size it wouldn't be practical to brute force the entire key. However, the hash algorithm has no protections against brute-force attacks for example no salting or multiple iterations. If an attacker has access to ciphertext encrypted using the RC4-HMAC key they can attempt to brute force the key through guessing the password. As user's will tend to choose weak or trivial passwords this increases the chance that a brute force attack would work to recover the key. And with the key the attacker can then authenticate as that user to any service they like.Β 


To get appropriate cipher text the attacker can make requests to the KDC and specify the encryption type they need. The most well known attack technique is called Kerberoasting. This technique requests a service ticket for the targeted user and specifies the RC4-HMAC encryption type as their preferred algorithm. If the user has an RC4 key configured then the ticket returned can be encrypted using the RC4-HMAC algorithm. As significant parts of the plain-text is known for the ticket data the attacker can try to brute force the key from that.Β 


This technique does require the attacker to have an account on the KDC to make the service ticket request. It also requires that the user account has a configured Service Principal Name (SPN) so that a ticket can be requested. Also modern versions of Windows Server will try to block this attack by forcing the use of AES keys which are derived from the service user's password over RC4 even if the attacker only requested RC4 support.


An alternative form is called AS-REP Roasting. Instead of requesting a service ticket this relies on the initial authentication requests to return encrypted data. When a user sends an AS-REQ structure, the KDC can look up the user, generate the TGT and its associated session key then return that information encrypted using the user's RC4-HMAC key. At this point the KDC hasn't verified the client knows the user's key before returning the encrypted data, which allows the attacker to brute force the key without needing to have an account themselves on the KDC.


Fortunately this attack is more rare because Windows's Kerberos implementation requires pre-authentication. For a password based logon the user uses their encryption key to encrypt a timestamp value which is sent to the KDC as part of the AS-REQ. The KDC can decrypt the timestamp, check it's within a small time window and only then return the user's TGT and encrypted session key. This would prevent an attacker getting encrypted data for the brute force attack.Β 


However, Windows does support a user account flag, "Do not require Kerberos preauthentication". If this flag is enabled on a user the authentication request does not require the encrypted timestamp to be sent and the AS-REP roasting process can continue. This should be an uncommon configuration.


The success of the brute-force attack entirely depends on the password complexity. Typically service user accounts have a long, at least 25 character, randomly generated password which is all but impossible to brute force. Normal users would typically have weaker passwords, but they are less likely to have a configured SPN which would make them targets for Kerberoasting. The system administrator can also mitigate the attack by disabling RC4 entirely across the network, though this is not commonly done for compatibility reasons. A more limited alternative is to add sensitive users to the Protected Users Group, which disables RC4 for them without having to disable it across the entire network.

Windows Kerberos Encryption Implementation

While working on researching Windows Defender Credential Guard (CG) I wanted to understand how Windows actually implements the various Kerberos encryption schemes. The primary goal of CG at least for Kerberos is to protect the user's keys, specifically the ones derived from their password and session keys for the TGT. If I could find a way of using one of the keys with a weak encryption algorithm I hoped to be able to extract the original key removing CG's protection.


The encryption algorithms are all implemented inside the CRYPTDLL.DLL library which is separate from the core Kerberos library in KERBEROS.DLL on the client and KDCSVC.DLL on the server. This interface is undocumented but it's fairly easy to work out how to call the exported functions. For example, to get a "crypto system" from the encryption type integer you can use the following exported function:


NTSTATUS CDLocateCSystem(int etype, KERB_ECRYPT** engine);


The KERB_ECRYPT structure contains configuration information for the engine such as the size of the key and function pointers to convert a password to a key, generate new session keys, and perform encryption or decryption. The structure also contains a textual name so that you can get a quick idea of what algorithm is supposed to be, which lead to the following supported systems:


NameΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Encryption Type

----Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  ---------------

RSADSI RC4-HMAC Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  24

RSADSI RC4-HMAC Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  23

Kerberos AES256-CTS-HMAC-SHA1-96Β  Β  Β  Β  18

Kerberos AES128-CTS-HMAC-SHA1-96Β  Β  Β  Β  17

Kerberos DES-CBC-MD5Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  3

Kerberos DES-CBC-CRCΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  1

RSADSI RC4-MD4Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -128

Kerberos DES-PlainΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -132

RSADSI RC4-HMAC Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -133

RSADSI RC4Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -134

RSADSI RC4-HMAC Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -135

RSADSI RC4-EXPΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -136

RSADSI RC4Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -140

RSADSI RC4-EXPΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  -141

Kerberos AES128-CTS-HMAC-SHA1-96-PLAINΒ  -148

Kerberos AES256-CTS-HMAC-SHA1-96-PLAINΒ  -149


Encryption types with positive values are well-known encryption types defined in the RFCs, whereas negative values are private types. Therefore I decided to spend my time on these private types. Most of the private types were just subtle variations on the existing well-known types, or clones with legacy numbers.Β 


However, one stood out as being different from the rest, "RSADSI RC4-MD4" with type value -128. This was different because the implementation was incredibly insecure, specifically it had the following properties:


  • Keys are 16 bytes in size, but only the first 8 of the key bytes are used by the encryption algorithm.

  • The key is used as-is, there's no blinding so the key stream is always the same for the same user key.

  • The message type is ignored, which means that the key stream is the same for different parts of the Kerberos protocol when using the same key.

  • The encrypted data does not contain any cryptographic hash to protect from tampering with the ciphertext which for RC4 is basically catastrophic. Even though the name contains MD4 this is only used for deriving the key from the password, not for any message integrity.

  • Generated session keys are 16 bytes in size but only contain 40 bits (5 bytes) of randomness. The remaining 11 bytes are populated with the fixed value of 0xAB.


To say this is bad from a cryptographic standpoint, is an understatement. Fortunately it would be safe to assume that while this crypto system is implemented in CRYPTDLL, it wouldn't be used by Kerberos? Unfortunately not β€” it is totally accepted as a valid encryption type when sent in the AS-REQ to the KDC. The question then becomes how to exploit this behavior?

Exploitation on the Wire (CVE-2022-33647)

My first thoughts were to attack the session key generation. If we could get the server to return the AS-REP with a RC4-MD4 session key for the TGT then any subsequent usage of that key could be captured and used to brute force the 40 bit key. At that point we could take the user's TGT which is sent in the clear and the session key and make requests as that authenticated user.


The most obvious approach to forcing the preferred encryption type to be RC4-MD4 would be to interpose the connection between a client and the KDC. The etype field of the AS-REQ is not protected for password based authentication. Therefore a proxy can modify the field to only include RC4-MD4 which is then sent to the KDC. Once that's completed the proxy would need to also capture a service ticket request to get encrypted data to brute force.


Brute forcing the 40 bit key would be technically feasible at least if you built a giant lookup table, however I felt like it's not practical. I realized there's a simpler way, when a client authenticates it typically sends a request to the KDC with no pre-authentication timestamp present. As long as pre-authentication hasn't been disabled the KDC returns a Kerberos error to the client with the KDC_ERR_PREAUTH_REQUIRED error code.Β 


As part of that error response the KDC also sends a list of acceptable encryption types in the PA-ETYPE-INFO2 pre-authentication data structure. This list contains additional information for the password to key derivation such as the salt for AES keys. The client can use this information to correctly generate the encryption key for the user. I noticed that if you sent back only a single entry indicating support for RC4-MD4 then the client would use the insecure algorithm for generating the pre-authentication timestamp. This worked even if the client didn't request RC4-MD4 in the first place.


When the KDC received the timestamp it would validate it using the RC4-MD4 algorithm and return the AS-REP with the TGT's RC4-MD4 session key encrypted using the same key as the timestamp. Due to the already mentioned weaknesses in the RC4-MD4 algorithm the key stream used for the timestamp must be the same as used in the response to encrypt the session key. Therefore we could mount a known-plaintext attack to recover the keystream from the timestamp and use that to decrypt parts of the response.


Diagram showing the attack process. A computer with a kerberos client sends an AS-REQ to the attacker, which returns an error requesting pre-authentication and selecting RC4-MD4 encryption. The client then converts the user's password into an RC4-MD4 key which is used to encrypt the timestamp to send to the KDC. This is used to validate the user authentication and an AS-REP is returned with a TGT session key generated for RC4-MD4.
The timestamp itself has the following ASN.1 structure, which is serialized using the Distinguished Encoding Rules (DER) and then encrypted.


PA-ENC-TS-ENC Β  Β  Β  Β  Β  ::= SEQUENCE {

Β Β Β Β Β patimestamp Β  Β  [0] KerberosTime -- client's time --,

Β Β Β Β Β pausecΒ  Β  Β  Β  Β  [1] Microseconds OPTIONAL

}


The AS-REP encrypted response has the following ASN.1 structure:


EncASRepPartΒ  ::= SEQUENCE {

Β Β Β Β Β key Β  Β  Β  Β  Β  Β  [0] EncryptionKey,

Β Β Β Β Β last-reqΒ  Β  Β  Β  [1] LastReq,

Β Β Β Β Β nonce Β  Β  Β  Β  Β  [2] UInt32,

Β Β Β Β Β key-expirationΒ  [3] KerberosTime OPTIONAL,

Β Β Β Β Β flags Β  Β  Β  Β  Β  [4] TicketFlags,

Β Β Β Β Β authtimeΒ  Β  Β  Β  [5] KerberosTime,

Β Β Β Β Β starttime Β  Β  Β  [6] KerberosTime OPTIONAL,

Β Β Β Β Β endtime Β  Β  Β  Β  [7] KerberosTime,

Β Β Β Β Β renew-tillΒ  Β  Β  [8] KerberosTime OPTIONAL,

Β Β Β Β Β srealmΒ  Β  Β  Β  Β  [9] Realm,

Β Β Β Β Β sname Β  Β  Β  Β  Β  [10] PrincipalName,

Β Β Β Β Β caddr Β  Β  Β  Β  Β  [11] HostAddresses OPTIONAL

}


We can see from the two structures that as luck would have it the session key in the AS-REP is at the start of the encrypted data. This means there should be an overlap between the known parts of the timestamp and the key, allowing us to apply key stream recovery to decrypt the session key without any brute force needed.


Diagram showing ASN.1 DER structures for the timestamp and encrypted AS-REP part. It shows there is an overlap between known bytes in the timestamp with the 40 bit session key in the AS-REP.

The diagram shows the ASN.1 DER structures for the timestamp and the start of the AS-REP. The values with specific hex digits in green are plain-text we know or can calculate as they are part of the ASN.1 structure such as types and lengths. We can see that there's a clear overlap between 4 bytes of known data in the timestamp with the first 4 bytes of the session key. We only need the first 5 bytes of the key due to the padding at the end, but this does mean we need to brute force the final key byte.Β 


We can do this brute force one of two ways. First we can send service ticket requests with the user's TGT and a guess for the session key to the KDC until one succeeds. This would require at most 256 requests to the KDC. Alternatively we can capture a service ticket request from the client which is likely to happen immediately after the authentication. As the service ticket request will be encrypted using the session key we can perform the brute force attack locally without needing to talk to the KDC which will be faster. Regardless of the option chosen this approach would be orders of magnitude faster than brute forcing the entire 40 bit session key.


The simplest approach to performing this exploit would be to interpose the client to server connection and modify traffic. However, as the initial request without pre-authentication just returns an error message it's likely the exploit could be done by injecting a response back to the client while the KDC is processing the real request. This could be done with only the ability to monitor network traffic and inject arbitrary network traffic back into that network. However, I've not verified that approach.

Exploitation without Interception (CVE-2022-33679)

The requirement to have access to the client to server authentication traffic does make this vulnerability seem less impactful. Although there's plenty of scenarios where an attacker could interpose, such as shared wifi networks, or physical attacks which could be used to compromise the computer account authentication which would take place when a domain joined system was booted.


It would be interesting if there was an attack vector to exploit this without needing a real Kerberos user at all. I realized that if a user has pre-authentication disabled then we have everything we need to perform the attack. The important point is that if pre-authentication is disabled we can request a TGT for the user, specifying RC4-MD4 encryption and the KDC will send back the AS-REP encrypted using that algorithm.


The key to the exploit is to reverse the previous attack, instead of using the timestamp to decrypt the AS-REP we'll use the AS-REP to encrypt a timestamp. We can then use the timestamp value when sent to the KDC as an encryption oracle to brute force enough bytes of the key stream to decrypt the TGT's session key. For example, if we remove the optional microseconds component of the timestamp we get the following DER encoded values:


Diagram showing ASN.1 DER structures for the timestamp and encrypted AS-REP part. It shows there is an overlap between known bytes in the AS-REP and the timestamp string which can be used to generate a valid encrypted timestamp.

The diagram shows that currently there's no overlap between the timestamp, represented by the T bytes, and the 40 bit session key. However, we know or at least can calculate the entire DER encoded data for the AS-REP to cover the entire timestamp buffer. We can use this to calculate the keystream for the user's RC4-MD4 key without actually knowing the key itself. With the key stream we can encrypt a valid timestamp and send it to the KDC.Β 


If the KDC responds with a valid AS-REP then we know we've correctly calculated the key stream. How can we use this to start decrypting the session key? The KerberosTime value used for the timestamp is an ASCII string of the form YYYYMMDDHHmmssZ. The KDC parses this string to a format suitable for processing by the server. The parser takes the time as a NUL terminated string, so we can add an additional NUL character to the end of the string and it shouldn't affect the parsing. Therefore we can change the timestamp to the following:



Diagram showing ASN.1 DER structures for the timestamp and encrypted AS-REP part. It shows there is an overlap between a final NUL character at the end of the timestamp string which overlaps with the first byte of the 40 bit session key.

We can now guess a value for the encrypted NUL character and send the new timestamp to the KDC. If the KDC returns an error we know that the parsing failed as it didn't decrypt to a NUL character. However, if the authentication succeeds the value we guessed is the next byte in the key stream and we can decrypt the first byte of the session key.


At this point we've got a problem, we can't just add another NUL character as the parser would stop on the first one we sent. Even if the value didn't decrypt to a NUL it wouldn't be possible to detect. This is when a second trick comes into play, instead of extending the string we can abuse the way value lengths are encoded in DER. A length can be in one of two forms, a short form if the length is less than 128, or a long form for everything else.Β 


For the short form the length is encoded in a single byte. For the long form, the first byte has the top bit set to 1, and the lower 7 bits encode the number of trailing bytes of the length value in big-endian format. For example in the above diagram the timestamp's total size is 0x14 bytes which is stored in the short form. We can instead encode the length in an arbitrary sized long form, for example 0x81 0x14, 0x82 0x00 0x14, 0x83 0x00 0x00 0x14 etc. The examples shown below move the NUL character to brute force the next two bytes of the session key:



Diagram showing ASN.1 DER structures for the timestamp and encrypted AS-REP part. It shows extending the first length field so there is an overlap between a final NUL character at the end of the timestamp string which overlaps with the second and third bytes of the 40 bit session key.

Even though technically DER should expect the shortest form necessary to encode the length the Microsoft ASN.1 library doesn't enforce that when parsing so we can just repeat this length encoding trick to cover the remaining 4 unknown bytes of the key. As the exploit brute forces one byte at a time the maximum number of requests that we'd need to send to the KDC is 5 Γ— 28 which is 1280 requests as opposed to 240 requests which would be around 1 trillion.Β 


Even with such a small number of requests it can still take around 30 seconds to brute force the key, but that still makes it a practical attack. Although it would be very noisy on the network and you'd expect any competent EDR system to notice, it might be too late at that point.

The Fixes

The only fix I can find is in the KDC service for the domain controller. Microsoft has added a new flag which by default disables the RC4-MD4 algorithm and an old variant of RC4-HMAC with the encryption type of -133. This behavior can be re-enabled by setting the KDC configuration registry value AllowOldNt4Crypto. The reference to NT4 is a good indication on how long this vulnerability has existed as it presumably pre-dates the introduction of Kerberos in Windows 2000. There are probably some changes to the client as well, but I couldn't immediately find them and it's not really worth my time to reverse engineer it.


It'd be good to mitigate the risk of similar attacks before they're found. Disabling RC4 is definitely recommended, however that can bring its own problems. If this particular vulnerability was being exploited in the wild it should be pretty easy to detect. Also unusual Kerberos encryption types would be an immediate red-flag as well as the repeated login attempts.


Another option is to enforce Kerberos Armoring (FAST) on all clients and KDCs in the environment. This would make it more difficult to inspect and tamper with Kerberos authentication traffic. However it's not a panacea, for example for FAST to work the domain joined computer needs to first authenticate without FAST to get a key they can then use for protecting the communications. If that initial authentication is compromised the entire protection fails.


LethalHTA - A new lateral movement technique using DCOM and HTA

By: Unknown
6 July 2018 at 12:08

The following blog post introduces a new lateral movement technique that combines the power of DCOM and HTA. The research on this technique is partly an outcome of our recent research efforts on COM Marshalling: Marshalling to SYSTEM - An analysis of CVE-2018-0824.

Previous Work

Several lateral movement techniques using DCOM were discovered in the past by Matt Nelson, Ryan Hanson, Philip Tsukerman and @bohops. A good overview of all the known techniques can be found in the blog post by Philip Tsukerman. Most of the existing techniques execute commands via ShellExecute(Ex). Some COM objects provided by Microsoft Office allow you to execute script code (e.g VBScript) which makes detection and forensics even harder.

LethalHTA

LethalHTA is based on a very well-known COM object that was used in all the Office Moniker attacks in the past (see FireEye's blog post):

  • ProgID: "htafile"
  • CLSID : "{3050F4D8-98B5-11CF-BB82-00AA00BDCE0B}"
  • AppID : "{40AEEAB6-8FDA-41E3-9A5F-8350D4CFCA91}"
Using James Forshaw's OleViewDotNet we get some details on the COM object. The COM object runs as local server.

It has an App ID and default launch and access permissions. Only COM objects having an App ID can be used for lateral movement.

It also implements various interfaces as we can see from OleViewDotNet.

One of the interfaces is IPersistMoniker. This interface is used to save/restore a COM object's state to/from an IMoniker instance.

Our initial plan was to create the COM object and restore its state by calling the IPersistMoniker->Load() method with a URLMoniker pointing to an HTA file. So we created a small program and run it in VisualStudio.

But calling IPersistMoniker->Load() returned an error code 0x80070057. After some debugging we realized that the error code came from a call to CUrlMon::GetMarshalSizeMax(). That method is called during custom marshalling of a URLMoniker. This makes perfect sense since we called IPersistMoniker->Load() with a URLMoniker as a parameter. Since we do a method call on a remote COM object the parameters need to get (custom) marshalled and sent over RPC to the RPC endpoint of the COM server.

So looking at the implementation of CUrlMon::GetMarshalSizeMax() in IDA Pro we can see a call to CUrlMon::ValidateMarshalParams() at the very beginning.

At the very end of this function we can find the error code set as return value of the function. Microsoft is validating the dwDestContext parameter. If the parameter is MSHCTX_DIFFERENTMACHINE (0x2) then we eventually reach the error code.

As we can see from the references to CUrlMon::ValidateMarshalParams() the method is called from several functions during marshalling.

In order to bypass the validation we can take the same approach as described in our last blog post: Creating a fake object. The fake object needs to implement IMarshal and IMoniker. It forwards all calls to the URLMoniker instance. To bypass the validation the implementation methods for CUrlMon::GetMarshalSizeMax, CUrlMon::GetUnmarshalClass, CUrlMon::MarshalInterface need to modify the dwDestContext parameter to MSHCTX_NOSHAREDMEM(0x1). The implementation for CUrlMon::GetMarshalSizeMax() is shown in the following code snippet.

And that's all we need to bypass the validation. Of course we could also patch the code in urlmon.dll. But that would require us to call VirtualProtect() to make the page writable and modify CUrlMon::ValidateMarshalParams() to always return zero. Calling VirtualProtect() might get caught by EDR or "advanced" AV products so we wouldn't recommend it.

Now we are able to call the IPersistMoniker->Load() on the remote COM object. The COM object implemented in mshta.exe will load the HTA file from the URL and evaluate its content. As you already know the HTA file can contain script code such as JScript or VBScript. You can even combine our technique with James Forshaw's DotNetToJScript to run your payload directly from memory!

It should be noted that the file doesn't necessarily need to have the hta file extension. Extensions such as html, txt, rtf work fine as well as no extension at all.

LethalHTA and LethalHTADotNet

We created implementations of our technique in C++ and C#. You can run them as standalone programms. The C++ version is more a proof-of-concept and might help you creating a reflective DLL from it. The C# version can also be loaded as an Assembly with Assembly.Load(Byte[]) which makes it easy to use it in a Powershell script. You can find both implementations under releases on our GitHub.

CobaltStrike Integration

To be able to easily use this technique in our day-to-day work we created a Cobalt Strike Aggressor Script called LethalHTA.cna that integrates the .NET implementation (LethalHTADotNet) into Cobalt Strike by providing two distinct methods for lateral movement that are integrated into the GUI, named HTA PowerShell Delivery (staged - x86) and HTA .NET In-Memory Delivery (stageless - x86/x64 dynamic)

The HTA PowerShell Delivery method allows to execute a PowerShell based, staged beacon on the target system. Since the PowerShell beacon is staged, the target systems need to be able to reach the HTTP(S) host and TeamServer (which are in most cases on the same system).

The HTA .NET In-Memory Delivery takes the technique a step further by implementing a memory-only solution that provides far more flexibility in terms of payload delivery and stealth. Using the this option it is possible to tunnel the HTA delivery/retrieval process through the beacon and also to specify a proxy server. If the target system is not able to reach the TeamServer or any other Internet-connected system, an SMB listener can be used instead. This allows to reach systems deep inside the network by bootstrapping an SMB beacon on the target and connecting to it via named pipe from one of the internal beacons.

Due to the techniques used, everything is done within the mshta.exe process without creating additional processes.

The combination of two techniques, in addition to the HTA attack vector described above, is used to execute everything in-memory. Utilizing DotNetToJScript, we are able to load a small .NET class (SCLoader) that dynamically determines the processes architecture (x86 or x64) and then executes the included stageless beacon shellcode. This technique can also be re-used in other scenarios where it is not apparent which architecture is used before exploitation.

For a detailed explanation of the steps involved visit our GitHub Project.

Detection

To detect our technique you can watch for files inside the INetCache (%windir%\[System32 or SysWOW64]\config\systemprofile\AppData\Local\Microsoft\Windows\INetCache\IE\) folder containing "ActiveXObject". This is due to mshta.exe caching the payload file. Furthermore it can be detected by an mshta.exe process spawned by svchost.exe.

Marshalling to SYSTEM - An analysis of CVE-2018-0824

By: Unknown
15 June 2018 at 13:19
In May 2018 Microsoft patched an interesting vulnerability (CVE-2018-0824) which was reported by Nicolas Joly of Microsoft's MSRC:
A remote code execution vulnerability exists in "Microsoft COM for Windows" when it fails to properly handle serialized objects. An attacker who successfully exploited the vulnerability could use a specially crafted file or script to perform actions. In an email attack scenario, an attacker could exploit the vulnerability by sending the specially crafted file to the user and convincing the user to open the file. In a web-based attack scenario, an attacker could host a website (or leverage a compromised website that accepts or hosts user-provided content) that contains a specially crafted file that is designed to exploit the vulnerability. However, an attacker would have no way to force the user to visit the website. Instead, an attacker would have to convince the user to click a link, typically by way of an enticement in an email or Instant Messenger message, and then convince the user to open the specially crafted file. The security update addresses the vulnerability by correcting how "Microsoft COM for Windows" handles serialized objects.
The keywords "COM" and "serialized" pretty much jumped into my face when the advisory came out. Since I had already spent several months of research time on Microsoft COM last year I decided to look into it. Although the vulnerability can result in remote code execution, I'm only interested in the privilege escalation aspects.

Before I go into details I want to give you a quick introduction into COM and how deserialization/marshalling works. As I'm far from being an expert on COM, all this information is either based on the great book "Essential COM" by Don Box or the awesome Infiltrate '17 Talk "COM in 60 seconds". I have skipped several details (IDL/MIDL, Apartments, Standard Marshalling, etc.) just to keep the introduction short.

Introduction to COM and Marshalling

COM (Component Object Model) is a Windows middleware having reusable code (=component) as a primary goal. In order to develop reusable C++ code, Microsoft engineers designed COM in an object-oriented manner having the following key aspects in mind:
  • Portability
  • Encapsulation
  • Polymorphism
  • Separation of interfaces from implementation
  • Object extensibility
  • Resource Management
  • Language independence

COM objects are defined by an interface and implementation class. Both interface and implementation class are identified by a GUID. A COM object can implement several interfaces using inheritance.
All COM objects implement the IUnknown interface which looks like the following class definition in C++:

The QueryInterface() method is used to cast a COM object to a different interface implemented by the COM object. The AddRef() and Release() methods are used for reference counting.

Just to keep it short I rather go on with an existing COM object instead of creating an artificial example COM object. A Control Panel COM object is identified by the GUID {06622D85-6856-4460-8DE1-A81921B41C4B}. To find out more about the COM object we could analyze the registry manually or just use the great tool "OleView .NET".

The "Control Panel" COM object implements several interfaces as we can see in the screenshot of OleView .NET: The implementation class of the COM object (COpenControlPanel) can be found in shell32.dll. To open a "Control Panel" programmatically we make use of the COM API:
  • In line 6 we initialize the COM environment
  • In line 7 we create an instance of a "Control Panel" object
  • In line 8 we cast the instance to the IOpenControlPanel interface
  • In line 9 we open the "Control Panel" by calling the "Open" method
Inspecting the COM object in the debugger after running until line 9 shows us the virtual function table (vTable) of the object:

The function pointers in the vTable of the object point to the actual implementation functions in shell32.dll. The reason for that is that the COM object was created as a so called InProc server which means that shell32.dll got loaded into the current process address space. When passing CLSCTX_ALL, CoCreateInstance() tries to create an InProc server first. If it fails, other activation methods are tried (see CLSCTX enumeration).

By changing the CLSCTX_ALL parameter to function CoCreateInstance() to CLSCTX_LOCAL_SERVER and running the program again we can notice some differences: The vTable of the object contains now function pointers from the OneCoreUAPCommonProxyStub.dll. And the 4th function pointer which corresponds to the Open()" method now points to OneCoreUAPCommonProxyStub!ObjectStublessClient3().

The reason for that is that we created the COM object as an out-of-process server. The following diagram tries to give you an architectural overview (shamelessly borrowed from Project Zero):

The function pointers in the COM object point to functions of the proxy class. When we execute the IOpenControlPanel::Open()" method, the method OneCoreUAPCommonProxyStub!ObjectStublessClient3() gets called on the proxy. The proxy class itself eventually calls RPC methods (e.g. RPCRT4!NdrpClientCall3) to send the parameters to the RPC server in the out-of-process server. The parameters need to get serialized/marshalled to send them over RPC. In the out-of-process-server the parameters get deserialized/unmarshalled and the Stub invokes shell32!COpenControlPanel::Open(). For non-complex parameters like strings the serialization/marshalling is trivial as these are sent by value.

How about complex parameters like COM objects? As we can see from the method definition of IOpenControlPanel::Open() the third parameter is a pointer to an IUnknown COM object:
The answer is that a complex object can either get marshalled by reference (standard marshalling) or the serialization/marshalling logic can be customized by implementing the IMarshal interface (custom marshalling).

The IMarshal interface has a few methods as we can see in the following definition:
During serialization/marshalling of a COM object the IMarshal::GetUnmarshalClass() method gets called by the COM which returns the GUID of the class to be used for unmarshalling. Then the method IMarshal::GetMarshalSizeMax() is called to prepare a buffer for marshalling data. Finally the IMarshal::MarshalInterface() method is called which writes the custom marshalling data to the IStream object. The COM runtime sends the GUID of the "Unmarshal class" and the IStream object via RPC to the server.

On the server the COM runtime creates the "Unmarshal class" using the CoCreateInstance() function, casts it to the IMarshal interface using QueryInterface and eventually invokes the IMarshsal::UnmarshalInterface() method on the "Unmarshal class" instance, passing the IStream as a parameter.

And that's also where all the misery starts ...

Diffing the patch

After downloading the patch for Windows 8.1 x64 and extracting the files, I found two patched DLLs related to Microsoft COM:
  • oleaut32.dll
  • comsvcs.dll
Using Hexray's IDA Pro and Joxean Koret's Diaphora I analyzed the changes made by Microsoft.
In oleaut32.dll several functions were changed but nothing special related to deserialisation/marshalling:


In comsvcs.dll only four functions were changed:


Clearly, one method stood out: CMarshalInterceptor::UnmarshalInterface().

The method CMarshalInterceptor::UnmarshalInterface() is the implementation of the UnmarshalInterface() method of the IMarshal interface. As we already know from the introduction this method gets called during unmarshalling.

The bug

Further analysis was done on Windows 10 Redstone 4 (1803) including March patches (ISO from MSDN). In the very beginning of the method CMarshalInterceptor::UnmarshalInterface() 20 bytes are read from the IStream object into a buffer on the stack.

Later the bytes in the buffer are compared against the GUID of the CMarshalInterceptor class (ECABAFCB-7F19-11D2-978E-0000F8757E2A). If the bytes in the stream match we reach the function CMarshalInterceptor::CreateRecorder().


In function CMarshalInterceptor::CreateRecorder() the COM-API function ReadClassStm is called. This function reads a CLSID(GUID) from the IStream and stores it into a buffer on the stack. Then the CLSID gets compared against the GUID of a CompositeMoniker.
As you may have already followed the different Moniker "vulnerabilities" in 2016/17 (URLMoniker, ScriptMoniker, SOAPMoniker), Monikers are definitely something you want to find in code which you might be able to trigger.

The IMoniker interface inherits from IPersistStream which allows a COM object implementing it to load/save itself from/to an IStream object. Monikers identify objects uniquely and can locate, activate and get a reference to the object by calling the BindToObject() method of the IMoniker instance.

If the CLSID doesn't match the GUID of the CompositeMoniker we follow the path to the right. Here, the COM-API functionCoCreateInstance() is called with the CLSID read from the IStream as the first parameter. If COM finds the specific class and is able to cast it to an IMoniker interface we reach the next basic block. Next, the IPersistStream::Load() method is called on the newly created instance which restores the saved Moniker state from the IStream object.

And finally we reach the call to BindToObject() which triggers all evil ...

Exploiting the bug

For exploitation I'm following the same approach as described in the bug tracker issue "DCOM DCE/RPC Local NTLM Reflection Elevation of Privilege" by Project Zero.
I'm creating a fake COM Object class which implements the IStorage and IMarshal interfaces.

All implementation methods for the IStorage interface will be forwarded to a real IStorage instance as we will see later. Since we are implementing custom marshalling, the COM runtime wants to know which class will be used to deserialize/unmarshal our fake object. Therefore the COM runtime calls IMarshal::GetUnmarshalClass(). To trigger the Moniker, we just need to return the GUID of the "QC Marshal Interceptor Class" class (ECABAFCB-7F19-11D2-978E-0000F8757E2A).

The final step is to implement the IMarshal::MarshalInterface() method. As you already know the method gets called by the COM runtime to marshal an object into an IStream.
To trigger the call to IMoniker::BindToObject(), we only need to write the required bytes to the IStream object to satisfy all conditions in CMarshalInterceptor::UnmarshalInterface().

I tried to create a Script Moniker COM object with CLSID {06290BD3-48AA-11D2-8432-006008C3FBFC} using CoCreateInstance(). But hey, I got a "REGDB_E_CLASSNOTREG" error code. Looks like Microsoft introduced some changes. Apparently, the Script Moniker wouldn't work anymore. So I thought of exploiting the bug using the "URLMoniker/hta file". But luckily I remembered that in the method CMarshalInterceptor::CreateRecorder() we had a check for a CompositeMoniker CLSID.

So following the left path, we have a basic block in which 4 bytes are read from the stream into the stack buffer (var_78). Next we have a call to CMarshalInterceptor::LoadAndCompose() with the IStream, a pointer to an IMoniker interface pointer and the value from the stack buffer as parameters.

.
In this method an IMoniker instance is read and created from the IStream using the OleLoadFromStream() COM-API function. Later in the method, CMarshalInterceptor::LoadAndCompose() is called recursively to compose a CompositeMoniker. By invoking IMoniker::ComposeWith() a new IMoniker is created being a composition of two monikers. The pointer to the new CompositeMoniker will be stored in the pointer which was passed to the current function as parameter. As we have seen in one of the previous screenshots the BindToObject() method will be called on the CompositeMoniker later on.

As I remembered from Haifei Li's blog post there was a way to create a Script Moniker by composing a File Moniker and a New Moniker. Armed with that knowledge I implemented the final part of the IMarshal::MarshalInterface() method.

I placed a SCT file in "c:\temp\poc.sct" which runs notepad from an ActiveXObject. Then I tried BITS as a target server first which didn't work.

Using OleView .NET I found out that BITS doesn't support custom marshalling (see EOAC_NO_CUSTOM_MARSHAL). But the SearchIndexer service with CLSID {06622d85-6856-4460-8de1-a81921b41c4b} was running as SYSTEM and allowed custom marshalling.
So I created a PoC which has the following main() function.

The call to CoGetInstanceFromIStorage() will activate the target COM server and trigger the serialization of the FakeObject instance. Since the COM-API function requires an IStorage as a parameter, we had to implement the IStorage interface in our FakeObject class.
After running the POC we finally have a "notepad.exe" running as SYSTEM.


The POC can be found on our github.

The patch

Microsoft is now checking a flag read from the Thread-local storage. The flag is set in a different method not related to marshalling. If the flag isn't set, the function CMarshalInterceptor::UnmarshalInterface() will exit early without reading anything from the IStream.

Takeaways

Serialization/Unmarshalling without validating the input is bad. That's for sure.
Although this blog post only covers the privilege escalation aspect, the vulnerability can also be triggered from Microsoft Office or by an Active X running in the browser. But I will leave this as an exercise to the reader :-)

Exploiting Adobe ColdFusion before CVE-2017-3066

By: Unknown
13 March 2018 at 14:41
In a recent penetration test my teammate Thomas came across several servers running Adobe ColdFusion 11 and 12. Some of them were vulnerable to CVE-2017-3066 but no outgoing TCP connections were possible to exploit the vulnerability. He asked me whether I had an idea how he could still get a SYSTEM shell and the outcome of the short research effort is documented here.

Introduction Adobe ColdFusion & AMF

Before we go into technical details, I will give you a short intro to Adobe ColdFusion (CF). Adobe ColdFusion is an Application Development Platform like ASP.net, however several years older. Adobe ColdFusion allows a developer to build websites, SOAP and REST web services and interact with Adobe Flash using the Action Message Format (AMF).

The AMF protocol is a custom binary serialization protocol. It has two formats, AMF0 and AMF3. An Action Message consists of headers and bodies. Several data types are supported in AMF0 and AMF3. For example the AMF3 format supports the following protocol elements with their type identifier:
Details about the binary message formats of AMF0 and AMF3 can be found on Wikipedia (see https://en.wikipedia.org/wiki/Action_Message_Format).
There are several implementations for AMF in different languages. For Java we have Adobe BlazeDS (now Apache BlazeDS), which is also used in Adobe ColdFusion.
The BlazeDS AMF serializer can serialize complex object graphs. The serializer starts with the root object and serializes its members recursively.
Two general serialization techniques are supported by BlazeDS to serialize complex objects:
  1. Serialization of Bean Properties (AMF0 and AMF3)
  2. Serialization using Java's java.io.Externalizable interface. (AMF3)

Serialization of Bean Properties

This technique requires the object to be serialized to have a public no-arg constructor and for every member public Getter-and Setter-Methods (JavaBeans convention).
In order to collect all member values of an object, the AMF serializer invokes all Getter-methods during serialization. The member names and values are put in the Action message body with the class name of the object.
During deserialization, the classname is taken from the Action Message, a new object is constructed and for every member name the corresponding set method is called with the value as argument. This all happens either in method readScriptObject() of class flex.messaging.io.amf.Amf3Input or readObjectValue() of class flex.messaging.io.amf.Amf0Input.

Serialization using Java's java.io.Externalizable interface

BlazeDS further supports serialization of complex objects of classes implementing the java.io.Externalizable interface which inherits from java.io.Serializable.
Every class implementing this interface needs to provide its own logic to deserialize itself by calling methods on the java.io.ObjectInput-implementation to read serialized primitive types and Strings (e.g. method read(byte[] paramArrayOfByte)).
During deserialization of an object (type 0xa) in AMF3, the method readScriptObject() of class flex.messaging.io.amf.Amf3Input gets called. In line #759 the method readExternalizable is invoked which calls the readExternal() method on the object to be deserialized.

This should be sufficient to serve as an introduction to Adobe ColdFusion and AMF.

Previous work

Chris Gates (@Carnal0wnage) published the paperΒ ColdFusion for PentestersΒ which is an excellent introduction to Adobe ColdFusion.
Wouter Coekaerts (@WouterCoekaerts) already showed in his blog postΒ that deserializing untrusted AMF data is dangerous.
Looking at the history of Adobe ColdFusion vulnerabilities at Flexera/Secunia's database you can find mostly XSS', XXE's and information disclosures.
The most recent ones are:
  • Deserialization of untrusted data over RMI (CVE-2017-11283/4 by @nickstadb)
  • XXE (CVE-2017-11286 by Daniel Lawson of @depthsecurity)
  • XXE (CVE-2016-4264 by @dawid_golunski)

CVE-2017-3066

In 2017 Moritz Bechler of AgNO3 GmbH and my teammate Markus Wulftange discovered independently the vulnerability CVE-2017-3066 in Apache BlazeDS.
The core problem of this vulnerability was that Adobe Coldfusion never did any whitelisting of allowed classes. Thus any class in the classpath of Adobe ColdFusion, which either fulfills the Java Beans Convention or implements java.io.Externalizable could be sent to the server and get deserialized. Both Moritz and Markus found JRE classes (sun.rmi.server.UnicastRef2 sun.rmi.server.UnicastRef) which implemented the java.io.Externalizable interface and triggered an outgoing TCP connection during AMF3 deserialization. After the connection was made to the attacker's server, its response was deserialized using Java's native deserialization using ObjectInputStream.readObject(). Both found a great "bridge" from AMF deserialization to Java's native deserialization which offers well known exploitation primitives using public gadgets. Details about the vulnerability can also be found in Markus' blog post.
Apache introduced validation through the class flex.messaging.validators.ClassDeserializationValidator. It has a default whitelist but can also be configured with a configuration file. For details see the Apache BlazeDS releaseΒ notes.

Finding exploitation primitives before CVE-2017-3066

As already mentioned in the very beginning my teammate Thomas required an exploit which also works without outgoing connection.
I had a quick look into the excellent research paper "Java Unmarshaller Security" of Moritz Bechler where he analysed several "Unmarshallers" including BlazeDS. The exploitation payloads he discovered weren't applicable since the libraries were missing in the classpath.
So I started with my typical approach, fired up my favorite "reverse engineering tool" when it comes to Java, Eclipse. Eclipse together with the powerful decompiler plugin "JD-Eclipse" (https://github.com/java-decompiler/jd-eclipse) is all you need for static and dynamic analysis. As a former Dev I was used to work with IDE's which make your life easier and decompiling and grepping through code is often very inefficient and error prone. So I created a new Java project and added all jar-files of Adobe Coldfusion 12 as external libraries.
The first idea was to look for further calls to Java'sΒ ObjectInputStream.readObject-method. Using Eclipse this is very easy. Just open class ObjectInputStream, right click on the readObject() method and click "Open Call Hierarchy". Thanks to JD-Eclipse and its decompiler, Eclipse is able to construct call graphs based on class information without having any source. The call graph looks big in the very beginning. But with some experience you see very quickly which nodes in the graph are interesting.
After some hours I found two promising call graphs.

Setter-based Exploit

The first one starts with method setState(byte[] new_state) of class org.jgroups.blocks.ReplicatedTree.



Looking at the implementation of this method, we already can imagine what is happening in line #605. A quick look at the call graph confirms that we eventually end up in a call toΒ ObjectInputStream.readObject().

The only thing to mention here is that the byte[] passed to setState() needs to have an additional byte 0x2 at offset 0x0 as we can see from line 364 of class org.jgroups.util.Util.
The exploit can be found in the following image.

The exploit works against Adobe ColdFusion 12 only since JGroups is only available in this specific version.

Externalizable-based Exploit

The second call graph starts in class org.apache.axis2.util.MetaDataEntry with a call to readExternal which is what we are looking for.

In line #297 we have a call to SafeObjectInputStream.install(inObject).
In this function our AMF3Input instance gets wrapped by a org.apache.axis2.context.externalize.SafeObjectInputStream instance.
In line #341 a new instance of class org.apache.axis2.context.externalize.ObjectInputStreamWithCL is created. This class just extends the standard java.io.ObjectInputStream. In line #342 we finally have our call to readObject().
The following image shows the request for the exploit.


The exploit works against Adobe ColdFusion 11 and 12.

ColdFusionPwn

To make your life easier I created the simple tool ColdFusionPwn. It works on the command line and allows you to generate the serialized AMF message. It incorporates Chris Frohoff's ysoserial for gadget generation. It can be found on ourΒ github.

Takeaways

Deserializing untrusted input is bad, that's for sure. From an exploiters perspective exploiting deserialization vulnerabilities is a challenging task since you need to find the "right" objects (gadgets) which trigger functionality you can reuse for exploitation. But it's also more fun :-)

By the way: If you want to make a deep dive into serverside Java Exploitation and all sorts of deserialization vulnerabilities and how to do proper static and dynamic analysis in Java, you might be interested in our upcoming "Advanced Java Exploitation" course.

❌
❌