TL;DR
Intel’s Data Center Manager Console is a real-time monitoring and management all-in-one console that allows you to manage your entire data centre.
This small series of two blog posts covers an entire vulnerability chain to go from unauthenticated user to full remote code execution against Intel’s Data Center Manager (up to version 4.1.1.45749). All described issues were found purely based on a source code review of the decompiled application.
The chain’s first vulnerability bypasses DCM’s entire authentication process if the application is configured to allow authentication from Active Directory groups with publicly known SIDs. Since Intel’s DCM only relies on the SID and there’s no validation of the given active directory service, it is trivially easy to force the application to communicate with an arbitrary Kerberos/LDAP server. The arbitrary server then answers the authentication requests from Intel’s DCM by simply returning a successful authentication, including a known/matching SID. This ultimately allows authenticating using any user with any password and any Active Directory domain.
Intel has released their security advisory INTEL-SA-00713 about this issue and has assigned CVE-2022-33942 to it.
Thanks rootkid for proofreading!
Vulnerable Configuration
Let’s assume that an administrator configured the group Guests of the domain rce.local to be allowed to access the DCM with the lowest possible rights, which is DCM’s Guest level:
Whereof the Guests group only has no current group members:
Based on this configuration, you could assume that this configuration is secure because:
- You have to be a member of the Guests group within the rce.local domain
- You have to know the password of any member of the Guests group
Let’s prove that you’re wrong.
Authentication Options
When hitting DCM on port 8643 using HTTPS, you are presented with a typical authentication screen of the DcmConsole. Here you can also select AD Account as an authentication type, which will show an additional field called Domain:
So what happens if you try to authenticate using this option: The application issues an HTTP POST request which has its type set to 1:
Source Code Review FTW
I will guide you step-by-step on how I’ve discovered this vulnerability and will utilize an arbitrary Kerberos and LDAP server implementation via Python to exploit this vulnerability. My exploit only assumes that you have control over a custom domain (I’m using hack.local for demo purposes). I have also hardcoded the Active Directory password Password0 into the script, meaning you can choose any user you want, but you must use that password for Kerberos reasons.
Spoofing a Kerberos Authentication Server
Most of the responsible source code can be found in the com.intel.console.server.login.UserMgmtHandler
class.
The first important thing here is the differentiation between the authentication types specified by the type parameter. When selecting type 1 (aka Active Directory), the function loginAD()
is called on line 1030, which hands over all the request values, including the username, password, and the domain:
The method loginAD()
does a couple of preflight things, such as getting the full domain name (line 1190) and getting the pure user name (line 1191). This is because you could also authenticate using the username@domain schema, for example, against DCM’s REST API. The resulting values are then used as java.security.krb5
properties, which is at the same time the first significant piece of the puzzle – the attacker can control the kdc/realm of the authenticating server:
Next comes the authentication process itself, which is based on Kerberos v5 and which will fail if the Kerberos authentication is not successful (lines 1201 to 1203):
So how do we get past this check if we have control over the domain? Correct: We need to build an arbitrary Kerberos server to answer the authentication request and return a successful authentication.
AS_REQ
Let’s quickly dive into the Kerberos v5 authentication process and how DCM uses it. When the login()
call on line 1201 is reached, DCM sends an AS_REQ
request to the given Kerberos server. This request looks like the following:
To answer the AS_REQ
request, extracting a couple of information from the request is essential:
- nonce – the answer must also contain the nonce that the client (DCM) provided. The intention is to prevent replay attacks.
- realm – this is essentially the requested Active Directory domain and is required for the AS_REP
- username – this is the Active Directory user which we need to return a successful authentication for.
- etype – these are the encryption algorithms that the client supports. We don’t need to extract those but just choose one for the response.
AS_REP
In response to the AS_REQ
the arbitrary Kerberos server will answer with an AS_REP
message which looks like the following:
The first etype points to the encryption algorithm, followed by the realm and the username, aka CNameString. The most important part is the enc-part at the end of the message because it is an authentication proof. It is a data blob encrypted by the Kerberos server using the user’s password, which the Kerberos fetched from its database. If the user also supplied the same password during the authentication process, DCM can successfully decrypt and read its contents.
The decrypted enc-part looks like the following:
Some key data points here are:
- nonce – This is the same nonce value that the client presented within the AS_REQ message and reused here for integrity validation purposes
- authtime – This is used to make sure there are no clock skews as well as the request isn’t replayed
- endtime – This is used to specify the validity of the data
- srealm – This matches the realm from the AS_REQ
My final exploit automagically sets all the required values and encrypts the enc-part using the hardcoded password Password0, making the DCM pass the Kerberos authentication successfully.
Returning Arbitrary LDAP Objects
After passing the Kerberos authentication, the application switches over to LDAP by using the com.intel.console.server.login.ADHelper
class (line 1206, 1215-1216). This means that we also need an arbitrary LDAP server to answer any incoming LDAP queries from the DCM.
The init()
method here simply prepares the LDAP connection to the very same host given by the fullDomain
variable, which we do have under control, and finally causes an LDAP bindRequest (line 1216):
The bindRequest
looks like the following:
And will be answered by our arbitrary LDAP server using a bind success message, which looks like the following:
The next few lines are more important:
First, the call to getUserSid()
(line 1218) constructs an LDAP search query (line 73) and performs the actual LDAP searchRequest (line 80) to return the objects SID (lines 82-86) or otherwise null if the queried user isn’t found:
The raw searchRequest
looks like the following:
However, our arbitrary LDAP server will return a SID for any queried user of S-1-5-4294967295-4294967295-4294967295-4294967295-4294967295
(hex: 0x0105000000000005ffffffffffffffffffffffffffffffffffffffff
), which is important to pass another check later on:
The call to getUsersBySidAndDomain()
on line 1219 finally constructs a SQL query validating the previously received sid
against DCM’s database (line 421):
However, since we’re using our own arbitrary Kerberos/LDAP, which returns a random SID here (the SID of real users are impossible to guess), it’ll return an empty userList
on line 462:
Since userList
is empty, the call to the next method createUserPrincipal()
on line 1220 will also return null
resulting in userPrincipal
also becoming null
:
This implementation is aimed at the single Active Directory user authentication process. However, we cannot exploit this route since we don’t know (and cannot guess) the SID of any single user object of an Active Directory.
Confusing DCM with Known SIDs
What happens next is a check whether userPrincipal
is null (line 1222). Since the single-user authentication process failed, the next logical step is verifying whether the user’s group is allowed to authenticate, because this is an actual authentication option in DCM. This happens by using the following sequence to fetch the user’s Active Directory group information:
The call on line 1223 passes our arbitrary user SID of S-1-5-4294967295-4294967295-4294967295-4294967295-4294967295
into getUserGroupSid()
where a search for the given user SID happens on line 122:
This causes two more LDAP queries. The first one for the user object:
which our arbitrary LDAP server always answers regardless of the user SID by simply returning an arbitrary distinguishedName
:
The second LDAP query (line 133) queries for the tokenGroups
(aka the group SIDs) of the given user:
Since our arbitrary LDAP server knows the SID S-1-5-4294967295-4294967295-4294967295-4294967295-4294967295, it will happily answer this request for the user’s group SIDs by responding with S-1-5-32-546 (hex: 0x01020000000000052000000022020000), S-1-5-32-545 (hex: 0x01020000000000052000000020020000) and another random ID. Thereby the first two group SIDs are called “well-known-sids” and represent the Guests group and the Users group:
This means that the groupNames
array (line 1222) is now filled with the returned group SIDs and is passed into the getUserGroupInfo()
call on line 1224:
getUserGroupInfo()
essentially builds a SQL query condition based on the different SIDs (lines 1312-1324):
and finally passes the condition into another getUsers()
call:
getUsers()
in return performs an SQL query which checks whether the given SID exists in the database:
Since the administrator has actually configured the Guests group to be able to authenticate, it returns a couple of things including the group name and it’s SID:
The last call in the authentication sequence goes to createUserPrincipal()
, which includes the data of the previous SQL query and ultimately checks whether the given user is present in DCM’s allowed users list:
Since the group S-1-5-32-546
is authorized to authenticate against DCM, and our arbitrary LDAP server returned a random user object, which has the same group SID set, DCM happily proceeds with authenticating the user by returning a DcmUserPrincipal
object:
This ultimately means that it is possible to authenticate against DCM using any username, and any domain name because the only thing being validated here is the user group’s SID.
Auto-Exploitation
I’ve put together a rather complex script to exploit this vulnerability:
Here’s the exploit in action:
Intel’s Fix
Intel has fixed this issue by enforcing LDAPS and performing an additional certificate check against DCM’s internal SSL keystore, where the Active Directory CA certificate needs to be trusted, starting from version 5.0 of DCM.
About Intel’s CVSS (Mis)Interpretation
I initially reported this bug to Intel’s bug bounty program. It is essential to mention that they state in their program policy that they are using CVSS to estimate the impact of a vulnerability, which means they should follow the official CVSS definition, right?
Well, not so much. Intel downgraded this issue to a 8.8 at CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H and also mentions this in their security advisory INTEL-SA-00713. Are you curious why my official advisory rates this issue at 10.0 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) instead?
AV:A vs AV:N
Intel thinks that “DCM is an enterprise application and was developed for administrative networks”, which is their excuse to downgrade the AV vector. I confronted them with the official CVSS specification document, which defines the Adjacent
value as follows:
The vulnerable component is bound to the network stack, but the attack is limited at the protocol level to a logically adjacent topology. This can mean an attack must be launched from the same shared physical (e.g., Bluetooth or IEEE 802.11) or logical (e.g., local IP subnet) network, or from within a secure or otherwise limited administrative domain (e.g., MPLS, secure VPN to an administrative network zone). One example of an Adjacent attack would be an ARP (IPv4) or neighbor discovery (IPv6) flood leading to a denial of service on the local LAN segment (e.g., CVE‑2013‑6014).
While the vulnerable component is indeed bound to the network stack, it is NOT limited at the protocol level – they don’t even enforce any iptables rules or similar to force it to be adjacent-like. Therefore AV must be set to N.
UI:R vs. UI:N
Intel also thinks that UI:R
applies because an Administrator has to configure DCM in a way that allows authentication for an Active Directory group with a well-known SID.
However, the CVSS specification document explicitly mentions this configuration change condition under the Attack Complexity vector:
If a specific configuration is required for an attack to succeed, the Base metrics should be scored assuming the vulnerable component is in that configuration.
This essentially means, the UI vector should stay untouched at UI:N and the AC vector must be set to the base metric at AC:L.
Consequences
Intel made a one-time exception and rewarded $10,000 for this bug, which is great, but it also was an exhausting fight to get to this point.
It is crucial for hackers and bug bounty programs to have a common baseline for impact measurement and discussion based thereon. I also acknowledge that there is a bit of play space regarding CVSS interpretation, but fundamentally ignoring core definitions of the underlying framework essentially means breaking trust with hackers.