标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-2699] 作者: ecawen 发表于: [2020-06-17]
本文共 [528] 位读者顶过
This vulnerability applied to a 5 year old end of life version of CobaltStrike and is being published in the spirit of archaeological interest in the vulnerability. tl;drThis blog looks at some of the communication and encryption internals of Cobalt Strike between Beacons and the Team Server in the 3.5 family. We then explore the subsequent exploitation of a vulnerability in Cobalt Strike 3.5 from 2016 to achieve remote unauthenticated code execution on the Team Server. We hope that this post will help Blue Teams with detection engineering and provide a good understanding of the encryption fundamentals that underpin Cobalt Strike. For the Red Team, we provide an example of why it is important to harden your Command and Control infrastructure. Back StoryIn Cobalt Strike there was a vulnerability fixed that existed in a number of versions:
The vulnerability was disclosed by the team at Cobalt Strike in 2016 as being actively exploited in September. A patch was promptly released in the guise of 3.5.1. Beacon Staging Primer
Beacon staging is the process of downloading a beacon (DLL) shellcode blob, which will be executed via a smaller shellcode stager – typically as a result of an exploit or dropper document. The aim here being to work around size-constrained vulnerability exploitation, for example where you only have a certain amount of space to hold your shellcode as the result of a buffer overflow or similar. That said, from a Red Team Operational perspective, fully staged (a.k.a Stageless) payloads are always preferred where possible.[出自:jiwo.org]
Retrieving stager via Checksum8
Since Cobalt Strike 3.5.1, you can now also disable staging entirely using the “host_stage = false” setting. This was added as a feature following the official fix for the vulnerability discussed in this post. After the DLL is extracted from the stager blob, the beacon settings can be extracted, along with the public key, using a fixed XOR key of 0x69. This was recently published by the SentinelOne team, who released the CobaltStrikeParser tool. The Internals of Cobalt Strike Beacon CommsOnce decoded and executed, the beacon then needs to communicate with the Team Server. This involves various Cobalt Strike communications and encryption internals we need to understand prior to being able to build an exploit payload. Beacon Checking In – 1..2..3 – Over: Keys Keys Keys..
Whenever beacon checks in, it sends an encrypted metadata blob. This is encrypted using the RSA public key, extracted from the stager. To aid in debugging you may also wish to dump the RSA private key from the Team Server. When run, the output will look like this:
Dumping Keys
It should be noted that this is strictly only to aid in debugging whilst writing an exploit. In a real-world scenario it is not possible to decrypt existing Beacon communications as the keys are negotiated securely over RSA, with the beacon only having the public key. However, if you are in possession of the public key (which can be retrieved via the checksum8 staging URL), then it is possible to encrypt and decrypt taskings via a fake session. Beacon Communication Encryption and MetadataEncryption, Decryption and Structure
Metadata from the beacon is sent according to the settings in the malleable C2 profile. This allows the operator to customise various properties of the traffic, such as where the metadata blob is sent (.e.g in a header, or a cookie), and how it is encoded. The following is from the Cobalt Strike blog example. Malleable C2 Config http-get { set uri "/foobar"; client { metadata { base64; prepend "user="; header "Cookie"; } } The following HTTP request capture shows a metadata blob being sent Base64 encoded in the Cookie header, which is the default setting: Beacon Metadata RequestBeacon metadata encryption uses RSA with PKCS1 padding, the following is an example in Python of encrypting beacon metadata using the stager public key: When decrypted (using the private key we extracted from our test Team Server) the metadata looks like the below:
Decrypted Metadata Blob
All decrypted metadata blobs are prepended with 8 bytes, which must always be present. These 8 bytes are the magic number 48879 (0xBEEF), followed by the data size: Beacon Metadata StructureSo we can now encrypt / decrypt the metadata. Now onto the parsing.. Beacon Metadata ParsingThe following Python code shows how the metadata from a Cobalt Strike beacon is parsed. On Cobalt Strike < 4.0, the metadata fields (aside from the first 16-bytes) are made up of a tab-delimited string. This results in the IP address being treated as a (non sanity-checked) string, which in version 3.5 leads to the directory traversal issue. However, on later versions the IP address field is validated to ensure it is indeed a valid IP address using a regex. Note that this changed in Cobalt Strike 4.0, which added a number of new fields. The code below covers both 3.5 and 4.0 versions. When the parser is run on our decrypted metadata blob, it will result in the following output:
Metadata Parsing
We now have enough information to generate and encrypt our own metadata. Symmetric EncryptionCobalt Strike uses AES-256 in CBC mode with HMAC-SHA-256 for task encryption. For the version of Cobalt Strike that the vulnerability existed in, this was included in the trial version, however from version 3.6 this is no longer enabled in non-licensed versions of Cobalt Strike. This means that for some cracked or trial versions of Cobalt Strike used by adversaries, network communications will be sent in cleartext. However, as we are looking at a version prior to 3.6, task encryption is always enabled.
Once the metadata is parsed, the Team Server will do a check to see whether this beacon is a new beacon by checking whether the AES keys specified in the metadata are already registered for the beacon ID value (also parsed from the metadata). The following Python script shows how the AES encryption/decryption works. Beacon Tasking So far we have covered staging, metadata, checkins, asymmetric (RSA) and symmetric (AES) encryption. We can now stage fake beacons and decrypt taskings sent from the Team Server to the beacon. Next we will cover how to decrypt/encrypt beacon output back to the Team Server. After the beacon has checked in (by including the encrypted metadata we previously covered, within the request), if the Team Server has a task for the beacon it will send this as an encrypted response. As shown earlier, this is decrypted using the negotiated AES session keys. What does the response to a tasking look like? In short, this response is also encrypted with AES in the same way that a tasking from the server is sent, however the beacon response data is prepended with a length field. The following screenshot shows an example of encrypted data sent by the beacon in response to a “ps” tasking: Encrypted Callback ResponseOnce the data is decrypted, we can see that it is prepended with 12 bytes, which indicate various properties of the output. 00 00 00 02 <- Counter (has to be higher than the previous one) 00 00 0D 1B <- Size of the data 00 00 00 11 <- Type of callback (in this case it's 17, which is OUTPUT_PS) 5B 53 79 73 <- Data of size 0xD1B 74 65 6D 20 The following python code shows how to decrypt and decode beacon output Running this code decrypts the output and shows the results of the “ps” command:
Decrypting Beacon Output
So at this point we can extract the keys we need, encrypt and decrypt communications so on to the vulnerability and exploitation. The VulnerabilityThe vulnerability itself was a directory traversal vulnerability (as the advisory states) in the reported internal IP address of the beacon which was used to build a file path. When processing “download” responses, the Team Server would write these to the file-system by re-creating the target system path on the Team Server filesystem, under the “downloads” folder within the working directory. The following screenshot shows an example of what this normally looks like. As shown, the downloaded file is stored within a folder named after the IP address of the beacon. Within this folder is the re-created filesystem structure of the downloaded file. CS 3.5 Downloads FolderAlthough traversal checks were carried out on the filename itself, the IP address field was not checked, lading to a directory traversal vulnerability in the IP address field, which as we demonstrated earlier, is set in the Beacon Metadata and controlled by the attacker. So instead of reporting the beacons IP address as of 10.133.37.10 we report it as our target folder, e.g. ../../../../etc/. Note: The vulnerable code uses the IP address value to build file paths, in various other places, including writing log files. Although log file poisoning is definitely an exploitable angle, we chose to use the same method as the in-the-wild exploit – download callbacks. ExploitationHaving a file system write primitive against typically against a Linux based server gives us various options for exploitation. We replicated the same technique employed by the in-the-wild exploitation, that is:
*Probably not the official term, but we will use these terms to refer to the task response types here. Whereby, a DOWNLOAD_START is the initial response from a “download” tasking (this causes the file to be created on the file-system), and DOWNLOAD_WRITE, is a response containing data to be written for the download task. Before we can do this however, we need to understand the structure of both the DOWNLOAD_START and DOWNLOAD_WRITE callbacks. As previously explained, we know that these are AES encrypted, prepended with an encrypted length, and also a counter and length once decrypted. But what is the structure of the decrypted data? This is explained below. The DOWNLOAD_START callback structure. This callback type for the task is 2. The (decrypted) callback structure is as follows: The DOWNLOAD_WRITE callback structure This callback type for the task is 8. The (decrypted) callback structure is as follows: To actually achieve code execution we write a cronjob as the in-the-wild attacks did. Typically this would involve sending the following values within the Metadata blob and task callback(s): Assuming we have written our functions to build the metadata blob (with the IP address traversal string), and our chosen AES keys. We can stage a fake beacon and check in the DOWNLOAD_START and DOWNLOAD_WRITE callbacks with our crafted values. The following example code demonstrates what this would look like: The following video shows the exploit in action: The Fix(es)As described in the follow-up post by Cobalt Strike, the following fixes were added in 3.5.1
In summary, the fixes applied in the 3.5.1 update are robust and address the vulnerability from multiple angles. As stated at the top of the post, this vulnerability existed in a legacy version of Cobalt Strike and the vulnerability does not exist in the latest versions. Nevertheless, we hope that this post provided some insight into Cobalt Strike internals, and provides opportunities for both Blue and Red teams to improve in their fight against real adversaries. |