Introduction
One thing I’m a fan of as a red/purple teamer is living off the land. This isn’t just LOLBINS but, for example, using client’s enterprise software against them; especially when that software isn’t as well known and potentially not as battle hardened as high-profile targets.
For example, why run the risk of detection running Bloodhound on a target when a client’s enterprise app inventory component provides readily accessible information on Active Directory Users, Groups, Computers, etc and where people have logged on.
During a recent assessment we came across such an application. A product called IXP Data EasyInstall [1] was used to manage their laptop and server estate. It included inventory management and much more – but the thing that originally drew us to it was its Local Admin Password Management Solution (LAPM aka LAPS) capability. Our investigation ultimately resulted in a successful compromise and several CVEs – including an unauthenticated remote secure wipe.
This blog is about the vulnerabilities in EasyInstall, rather than the detection and response assessments.
The issues could be broken down into the following themes in terms of remediation advice:
- Don’t hard code keying material for cryptographic operations in your application,
- Understand how cryptographic operations protect data and how this could be circumvented,
- Understand how permissions work on the target filesystem,
- Authenticate endpoints and encrypt data in transit.
The vulnerabilities we will discuss are:
Vuln# | CVE | Title |
1 | CVE-2023-27791 | Password generation using an insecure PRNG |
2 | CVE-2023-27792 | Insecure local filesystem ACLs |
3 | CVE-2023-27793 | Storing the local Administrator password using base64 encoding |
4 | N/A | Remotely accessible share (containing sensitive information) |
5 | CVE-2023-27795 | Encryption using a static XOR key |
6 | CVE-2023-30131 | Unauthenticated agent API |
7 | CVE-2023-30132 | Insecure key generation |
8 | N/A | Engine credentials XOR encoded in the registry |
Vulnerable versions and fixes:
CVE/Vuln | Fixes |
CVE-2023-27791 | Fixed in version 6.6.14907.0 |
CVE-2023-27792 | Partially fixed in version 6.6.14907.0
Fixed in version 6.7.15012.0 |
CVE-2023-27793 | Fixed in version 6.6.14907.0 |
Vuln 4 | Partially fixed in version 6.6.14907.0
Fixed in version 6.7.15012.0 |
CVE-2023-27795 | Fixed in version 6.7.15012.0 |
CVE-2023-30131 | Fixed in version 6.7.15012.0 |
CVE-2023-30132 | Introduced in version 6.6.14907.0
Fixed in version 6.7.15012.0 |
Vuln 8 | Fixed in version 6.7.15012.0 |
Summary of Vulnerabilities
Before we delve into the detail of the vulnerabilities, let’s summarize them here (TL;DR)
Vulnerability 1 – CVE-2023-27791 – Password generation using an insecure PRNG
The LAPM software relies on seeding an insecure PRNG using the current time as reported from _time64(). As the Administrator “last password change” is visible from multiple tools like “net user”, it is trivial to recompute the password and escalate to administrator once the algorithm is known (one-off reverse engineering the application). The calculation can be done on a separate machine and thus is invisible to any EDR.
The limiting factor is the attacker knowing the character classes in use and password length. This may be a written policy but also may be obtained via technical methods such has those in Vulnerability 2 (CVE-2023-27792).
Vulnerability 2 – CVE-2023-27792 – Insecure local filesystem ACLs
Insecure ACLs on EasyInstall configuration files allows viewing of sensitive security parameters such as the LAPM password generation policy, who’s logged in (e.g. Domain Administrator’s) and the ability to write certain information.
Vulnerability 3 – CVE-2023-27793 – Storing the local Administrator password using base64 encoding
The Administrator password is stored in the C:\IXP\DATA\INVENTORY.IXP file under tag 5155 as a base64 encoded string.
Vulnerability 4 – Remotely accessible share (containing sensitive information)
The files in vulnerability 2 and 3 are also accessible on the EasyInstall server under network path ixp$\INVENTORY\<HOSTNAME>.BOX e.g. the Inventory.ixp containing the base64 encoded password at ixp$\INVENTORY\<HOSTNAME>.BOX\Inventory.ixp
Vulnerability 5 – CVE-2023-27795 – Encryption using a static XOR key
We found that several security sensitive items, such as the agent credentials, are protected by a hard coded XOR key.
Vulnerability 6 – CVE-2023-30131 – Unauthenticated agent API
Calls to the EasyInstall agent API on port 20051/tcp are unauthenticated. We can set server parameters (automatic overwrite), generate popups, etc. More critically we found calls can also perform remote tasks such as terminate processes and erase devices.
Vulnerability 7 – CVE-2023-30132 – Insecure key generation
The fix for vulnerabilities in EasyInstall 6.6.14884.0 introduced in 6.6.14907.0 makes use of AES256 encryption in CBC mode. Unfortunately, this is via the use of a static string within the application executable, which is the only source used to derive the key for the subsequent encryption operation. This is used for protecting sensitive information transmitted over the network, and for data at rest in configuration and report files.
Vulnerability 8 – Engine credentials XOR encoded in the registry
We found that the privileged credentials used by the engine are stored as part of a binary blob in the registry. Everyone (with access to the server) has read access to the registry key, and the credential information is protected by a single byte XOR key.
Vulnerability Details
Vulnerability 1 – CVE-2023-27791 – Password generation using an insecure PRNG
The EasyInstall LAPM solution relies on seeding an insecure PRNG using the current time as reported from _time64(). If we know the seed and algorithm, we can replay the password generation to reveal the password.
First, we note in FUN_14004bc00() the reading of several key configuration attributes. These correspond to the LAPM password policy.
Whilst PasswordLength is self-explanatory, the Settings value is not. Effectively it is a mask corresponding to what character classes are required in a password, such as upper case alphabetic or digits.
Further down we can see the calls to generate the password.
The key high-level function responsible for password generation is FUN_14004b99c(). Let’s look at that.
As we can see, it calls FUN_14004b1c8() to generate the password, before performing some checks and conversions. Let’s investigate that.
Right at the start we can see where things go wrong, in that we seed the random number generator based on a predicable value. Due to compiler optimisations it isn’t a call to an external DLL, but is in effect srand(). To quote the definition at [3]:
“To reinitialize the generator to create the same sequence of results, call the srand function and use the same seed argument again.”
The function then loops round, calling rand() (see [2]) and updating the password when a result is within a valid character class. To quote from [2]:
“The rand function generates a well-known sequence and isn’t appropriate for use as a cryptographic function.”
There is one final important twist. The main function (FUN_14004b99c()), when checking the resultant password, will update it if the password is missing any required character class.
It is noteworthy to say that the random number generator may include a character class (say a digit) in only one of these locations (say index 1). If in this example no lowercase alphabetic characters are generated, then the digit gets overwritten, thus you can still get non-compliant passwords.
The use of “fixed” values in a password like this is discouraged in any case.
The password generator should use a cryptographically secure PRNG.
At this point we can generate our own implementation of the algorithm to re-generate the password for the Administrator account on any box that uses EasyInstall LAPM.
Finally, we need the password policy. If we do not have access to it via another vulnerability, we may find it via a company policy document, another box where we have legitimate administrative access rights, or an educated guess. In the worst case, as the entropy is so low, we can test the policy combinations and their respective calculated passwords slowly to avoid detections.
In our example, we have the policy, so first we get the password change time.
We then use our tool to generate the password.
We can then run cmd.exe as administrator and this password for privilege escalation.
Vulnerability 2 – CVE-2023-27792 – Insecure local filesystem ACLs
This is as the result of a standard privilege assigned to users in Windows and a lack of appropriate ACLs further down the directory tree. Specifically, the privilege SeChangeNotifyPrivilege, which is also known as Bypass traverse checking.
The Microsoft Tech Community website has a good explanation at [4], from which I quote:
“It’s perfectly natural if you’re having trouble fathoming the connection between SeChangeNotifyPrivilege and “Bypass traverse checking”. The two have nothing to do with each other, other than the fact that Windows overloads the same privilege with two unrelated powers. The security system internally names the privilege according to one if its uses, change notification, and LSPE names it by the other, bypass traverse checking.
Put succinctly, bypass traverse checking gives a process the ability to skip security checks on intermediate directories or Registry keys when it opens a file, directory or Registry key.”
If we look at C:\IXP as an admin we see that the permissions are sound:
However, further down this is not the case:
We can also see this with the key files we are interested in for CVE-2023-27791:
By default, our standard user will have the privilege we need which is enabled by default:
As a result, even though we cannot access the main directory, we can still view the sensitive files on the client device as that unprivileged user – for example the LAPM password policy.
We can identify file names via multiple routes, including looking at the binaries or on a “test” system.
Vulnerability 3 – CVE-2023-27793 – Storing the local Administrator password using base64 encoding
We noticed that the Administrator password was stored in plaintext using base64 encoding. This was against a “key” of 5155. Due to vulnerability 2 and 4, this was accessible to unprivileged users.
If we try to log in or Run as administrator using this password, we successfully gain access with full administrative rights.
Looking at the program in Ghidra we can see the code that performs this task.
Function FUN_14001a3b0() performs the translation of the password into base64 before FUN_140070560() writes it to the file:
From [5] we see that the flags for CryptBinaryToStringA() are CRYPT_STRING_BASE64 and CRYPT_STRING_NOCRLF.
Vulnerability 4 – Remotely accessible share (containing sensitive information)
As an unprivileged user, we were able to view a list of all machines registered with EasyInstall and, if they had LAPM enabled, the Administrator password.
First, listing all the potentially vulnerable machines.
Next, viewing one of the LAPM passwords (in base64):
You can download all the local administrator passwords, together with who logged in and when, with a simple powershell one-liner from the remote directory:
ls *\Inventory.ixp | select-string “(UserLoginName|LastLogin|5155=)”
Vulnerability 5 – CVE-2023-27795 – Encryption using a static XOR key
During the engagement we found that the agent and other security sensitive data was protected by a hard coded XOR key. These were typically denoted by a prefix of “h” indicating the key to use. Most notably, the ClientID has the agent credentials encoded using this format.
The offending code can be seen in the IXP administrator IXPADM64A.DLL .Net file, in the IXP.MDM.Shared namespace, where we have the IxpScrambler class. Here we have the routines to scramble and unscramble the data. Let’s look at one to scramble the data:
Here we can see that the data is base64 encoded and then xor encoded according to a multi-byte array. Further down we see the array.
With this information, and the varying conversions it handles (not shown), we can write a straightforward python script to “unencrypt” the data.
We have observed this over the network during a login on the EasyInstall Portal. As the user will typically be something like ADDOMAIN\Username, this also infers we have a known-plaintext attack, since the AD domain will be the one we are joined to in the target environment. By taking the domain name and XORing with the encrypted domain name we can extract the key (or at least a partial if the domain is too short).
Vulnerability 6 – CVE-2023-30131 – Unauthenticated agent API
We had some additional time in the engagement to have a look at the agent API. We found that it supports several administrative management tasks, such as installation or removal of software (from the EasyInstall repo), generate pop-ups, remote wipe devices, and so on.
This API is remotely accessible and unauthenticated.
Using a simple powershell command, we found that we could successfully call this API and perform the actions without any authentication.
A simple example is a popup command (API function 21):
API function 41 allows us to change the admin password. This, of course, gives us the time it changed – required knowledge to replay the admin password generator attack. We can see this confirmed in the log file.
As an update, due to changes IXP Data have made to remedy the issue around the use of an insecure PRNG to generate the passwords, this feature is of less use to an attacker. However, it is worth noting that the password now would be sent back to the EasyInstall server using an AES256 encrypted blob using a static key from the executable.
Next, we tried API function 37 to terminate a running process as SYSTEM. The EasyInstall agent duly complies – you can also see us influencing some of the text.
We can see the process terminating unexpectedly via the event log:
We found that the API calls also automatically update part of the IXPAS.IXP config:
API function 36 is to facilitate secure erase of the target device.
After some time to acquire a 2nd device by the client for use in the test, we were able to proceed with testing unauthenticated secure erase of the device.
This call requires a destruct code that is derived from the hostname of the client, which must be present for it to succeed. We included that in the call, sent it, and were immediately logged off.
We tried to log in and were presented with a message suggesting the call worked.
Later it disappeared off the network.
As the device is remote, we needed to wait for someone to be present the following week to see what the state of the device is. Confirmation on the Monday is that the hard drive was erased.
Finally, we note that the API supports an option to run an arbitrary command (API function 7). However, this is done by a callback to the EasyInstall server over an RPC call. This call will return to the client the command encrypted using the XOR encoding. If we can repoint the client at an attacker-controlled server, we believe full RCE is possible. Unfortunately, we did not have time to investigate this further.
Partial mitigation of the issues in this section may be achieved by ensuring the local device’s firewall and other relevant network firewalls only allow access to EasyInstall agent ports from specific trusted devices such as the EasyInstall server.
We would suggest the vendor protect sensitive operations by mutual authentication and ensure all data is encrypted in transit.
Vulnerability 7 – CVE-2023-30132 – Insecure key generation
IXP Data were pro-active at generating a fix for the original vulnerabilities. Unfortunately, whilst they are now using AES256 in CBC mode, the key is effectively static. Indeed, it uses a similar format to the XOR encoding vulnerability, in that the first character identifies the key to use, though there is an extra step involved.
If we look in the inventory file, we see it all appears encrypted.
One thing stands out. The GPO values all start with the same string, suggesting that the encryption is using the same key and IV.
If we investigate the new application code, specifically the function handling the encryption, we understand why. First, this is looking very promising – the use of the Microsoft Crypto API.
Further down we see a hash object get created.
This is fed a key.
This is used to derivate a cryptographic key for the AES256 operation.
The application sets the mode to CBC.
Then it encrypts the information.
So, how is that initial key fed into the hashing algorithm built? To see that we look at the start of the function.
So, what does this function do? I suspect you can guess.
The application sets up a static value (it is a printable string; but it looks like this as the result of a compiler optimisation). We can take this recipe and generate a python script. The IV is 0x0 and the key is the result of the sha256 hashing of the static value.
Taking a sample of the code we can then decrypt it and base64 decode it to get the result:
Taking it a stage further, we combined the unauthenticated API call to force change the administrator password, and wireshark on the device to capture the response, and escalate to Administrator. As any existing installation of wireshark by default allows unprivileged users to sniff on the network, this represents a useful attack vector.
Here we can see that we need to use key “z” for the initial seeding of the AES key via the algorithm shown above.
As in previous instances, running the encrypted value through our script gave the unencrypted information, allowing us to elevate successfully to a full Administrator prompt.
Vulnerability 8 – Engine credentials XOR encoded in the registry
We noticed some interesting registry keys on the EasyInstall server.
Looking at the EasyInstall engine executable we quickly noticed this is another route to get privileged information.
In RPC_SetEngineAccount() we see a reference to the engine key.
Further down we can see where it encodes this information.
Effectively the blob is a data structure. The first three dwords are integers corresponding to the length of the account’s domain, user and password strings. 0xc, 0x4c and 0x8c correspond to the offsets in the blob for the start of each of those (encrypted) values.
What is the Decrypt() function. It’s an XOR with a static key of 0x15.
What is the protection on that key?
Again, a simple bit of python gets us the credentials.
We would suggest IXP Data consider the use on Windows versions of the in-built DPAPI to protect this data.
References
[1] https://www.ixpdata.com/easyinstall-menu/easyinstall-description/ [2] https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/rand?view=msvc-170 [3] https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/srand?view=msvc-170 [4] https://techcommunity.microsoft.com/t5/windows-blog-archive/the-bypass-traverse-checking-or-is-it-the-change-notify/ba-p/723428 [5] https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptbinarytostringaTimeline
Date | Action |
03-Mar-2023 | BTL notified the vendor of the following vulnerabilities:
CVE-2023-27791, CVE-2023-27792, CVE-2023-27793, Vuln 4, CVE-2023-27795 |
03-Mar-2023 | The vendor acknowledged the vulnerabilities |
24-Mar-2023 | The vendor asserted vulnerabilities fixed in version 6.6.14907.0 |
28-Mar-2023 | BTL notified the vendor of the following vulnerabilities:
CVE-2023-30131, CVE-2023-30132, Vuln 8 BTL notified the vendor of our findings when testing the fixes deployed. |
30-Aug-2023 | The vendor has confirmed they have fixed all these issues and that customers should all now be updated. |