Now Reading
mTLS: When certificates authentication is finished incorrect

mTLS: When certificates authentication is finished incorrect

2023-11-02 01:13:21

Though X.509 certificates have been right here for some time, they’ve turn into extra widespread for consumer authentication in zero-trust networks lately. Mutual TLS, or authentication based mostly on X.509 certificates usually, brings benefits in comparison with passwords or tokens, however you get elevated complexity in return.

On this put up, I’ll deep dive into some fascinating assaults on mTLS authentication. We gained’t trouble you with heavy crypto stuff, however as a substitute we’ll take a look at implementation vulnerabilities and the way builders could make their mTLS techniques weak to consumer impersonation, privilege escalation, and data leakages.

We are going to current some CVEs we present in widespread open-source id servers and methods to use them. Lastly, we’ll clarify how these vulnerabilities might be noticed in supply code and easy methods to repair them.

This weblog put up is predicated on work that I just lately introduced at Black Hat USA and DEF CON.

Introduction: What’s mutual TLS?

Website certificates are a very widely recognized technology, even to people who don’t work in the tech industry, thanks to the padlock icon used by web browsers. Whenever we connect to Gmail or GitHub, our browser checks the certificate provided by the server to make sure it’s truly the service we want to talk to. Fewer people know that the same technology can be used to authenticate clients: the TLS protocol is also designed to be able to verify the client using public and private key cryptography.

It happens on the handshake level, even before any application data is transmitted:

Excerpt from RFC 5246:

If configured to do so, a server can ask a client to provide a security certificate in the X.509 format. This certificate is just a blob of binary data that contain information about the client, such as its name, public key, issuer, and other fields:

$ openssl x509 -text -in client.crt
        Version: 1 (0x0)
        Serial Number:
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=localhost            //used to locate issuers certificate
            Not Before: Jun 13 14:34:28 2023 GMT
            Not After : Jul 13 14:34:28 2023 GMT
        Subject: CN=client          //aka "user name"
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)

The server checks that this certificate is signed by one of the trusted authorities. This is a bit similar to checking the signature of a JWT token. Next, the client sends a “Certificate verify” message encrypted with the private key, so that the server can verify that the client actually has the private key.

How certificates are validated

“Certificate validation” commonly refers to the PKIX certificate validation process defined in RFC 5280.

Briefly, with the intention to validate the certificates, the server constructs a certification path (often known as a certificates chain) from the goal certificates to a belief anchor. The belief anchor is a self-signed root certificates that’s inherently trusted by the validator. The tip entity certificates is commonly signed by an intermediate CA, which can also be signed by one other intermediate certificates or instantly by a belief anchor.

Diagram of certificate chain with three links: Client certificate, Intermediate CA, Root Certificate Authority

Then, for every certificates within the chain, the validator checks the signature, validity interval, allowed algorithms and key lengths, key utilization, and different properties. There are additionally various optionally available certificates extensions: if they’re included within the certificates, they are often checked as properly. This course of is kind of sophisticated, so each language or library implements it otherwise.

Be aware: in my analysis I principally checked out how mTLS is applied in functions written in Java, however it’s doubtless that the concepts and assaults beneath apply to different languages as properly.

mTLS in a Java net software, an instance

Let’s see how to use mTLS in a Java web application. The bare minimum configuration is to enable it in the application settings and specify the location of all trusted root certificates, like this:

$ cat


From the client, such as curl, you need to specify which certificate is sent to the server. The rest of the application code, such as request mappings, is exactly the same as for a normal web application.

$ curl -k -v –cert client.pem http://localhost/hello

This setup works for very simple mTLS configurations, when there is only a single root certificate, and all client certificates are signed by it. You can find this example in various articles on the web and it’s quite secure due to its simplicity. Let’s quickly break down its pros and cons.


  • Speed: Authorization happens only during TLS handshake, all subsequent “keep-alive” HTTP requests are considered authenticated, saving CPU time.
  • Storage: Similar to JWT, the server does not store all client certificates, only the root certificate.


  • No granular control: if mTLS is enabled, all requests have to be authenticated, even to /static/style.css.
  • Any certificate signed by a trusted CA can be used to access this HTTP service. Even if the certificate is issued for another purpose, it still can potentially be used for TLS authentication.
  • No host verification by default: client certificates can be accepted from any IP.
  • Certificate issuance process needs to be implemented separately.
  • Certificates expire, so need to be rotated frequently.

As you can see, this approach brings some advantages and disadvantages compared to traditional authentication methods, such as password or tokens.

Previous attacks

Before we dive into the attack section, I’ll briefly mention some previous well-known attacks on certificate parsing and validation:

  • Obviously, the security of the authentication system depends on the strength of the signature. If we can somehow forge the content of the certificate, but keep the same signature, we can completely break the authentication process.
  • Since the X.509 format is quite complex, just parsing these data structures can lead to buffer and heap overflows.
  • Lack of basic constraints checking. The end-entity certificates should not be used to sign additional certificates.

My approach

In Java, most of these attacks are already mitigated in APIs provided by the JDK. Weak algorithms are intentionally not allowed. Fuzzing of certificate parsing in Java also did not look productive to me, as the vast majority of PKIX code is implemented in memory-safe Java, instead of using native libraries. I had to take a different approach, so I decided to have a deep look at how mTLS is used from the source code perspective. Since the certificate validation process is quite complex, I suspected that someone might implement it in a weird way. After several weeks, it yielded me some interesting vulnerabilities in popular open source projects.

So, let’s move on to the attack’s section.

In real-life applications, developers often need to access the certificate presented during the TLS handshake. For example, they might need it for authorization purposes, such as checking the current username. In Java, there are two common ways how to access it:

X509Certificate[] certificates = sslSession.getPeerCertificates();  

// another way
X509Certificate[] certificates = request.getAttribute("javax.servlet.request.X509Certificate");

Interestingly, this API returns an array of certificates presented by the client, not a single one. Why? Perhaps because TLS specification defines that clients may send a full chain of certificates, from end-entity to the root CA.

So, I decided to take a look at how different applications use this API. The most common approach I’ve seen is to take only the first certificate from the array and consider it as the client certificate. This is correct, as mTLS RFC explicitly says that the sender’s certificate MUST come first in the list.

//way 1 is good
String user = certificates[0].getSubjectX500Principal().getName();

At the same time, I discovered some rare cases when applications disregard this rule and iterate over the array trying to find a certificate that matches some criteria.

//way 2 is dangerous
for (X509Certificate cert : certificates) {
   if (isClientCertificate(cert)) {
      user = cert.getSubjectX500Principal().getName();

This is dangerous, as the underlying TLS library in Java only verifies the first certificate in the list. Moreover, it does not require the chain to be sent in a strict order.

Example: CVE-2023-2422 improper certificate validation in KeyCloak

One of these examples was a vulnerability I discovered in Keycloak. Keycloak is a popular authorization server that supports OAuth, SAML, and other authorization methods, as well as mutual TLS.

Keycloak iterates over all certificates in the array, searching for the one that matches the client_id form parameter. As soon as it finds a matching certificate, it implicitly trusts it, assuming that its signature has already been checked during the TLS handshake:

X509Certificate[] certs = null;
ClientModel client = null;
try { 
    certs = provider.getCertificateChain(context.getHttpRequest());
    String client_id = null;
    if (formData != null) {
        client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
    matchedCertificate =
        .map(certificate -> certificate.getSubjectDN().getName())
        .filter(subjectdn -> subjectDNPattern.matcher(subjectdn).matches())

In reality, a client can send as many certificates as they want, and the server only verifies the first one.

A potential attacker can exploit this behavior to authenticate under a different username. It is possible to send a list of certificates, where the first one contains one username and is properly chained to a root CA. But the last certificate in the array might be self signed and belong to a different user. The client does not even need to provide a valid private key for it.

Diagram of a certificate list in which the first client certificate is signed by a CA, but the second is self-signed.

Speaking about the exploitation, there are a number of endpoints in Keycloak that support mTLS authentication, but we need one that does not require any additional factors, such as tokens or secrets. “client-management/register-node” is a good example, as it mutates the user’s data. We can normally use this api with mTLS in the following way:

$ cat client1.crt client1.key > chain1.pem
$ curl --tlsv1.2 --tls-max 1.2 --cert chain1.pem -v -i -s -k "" -d "client_cluster_host="

To demonstrate the vulnerability, we generate a new self signed certificate using openssl and add it to the end of the array.

$ openssl req -newkey rsa:2048 -nodes -x509 -subj /CN=client2 -out client2-fake.crt
$ cat client1.crt client1.key client2-fake.crt client1.key > chain2.pem
$ curl --tlsv1.2 --tls-max 1.2 --cert chain2.pem -v -i -s -k "" -d "client_cluster_host="

When we send the second curl request, Keycloak performs this action on behalf of the user specified in client2-fake.crt, instead of client1.crt. Therefore, we can mutate data on the server for any client that supports mTLS.

How to fix that? Easy: just use the first certificate from the array. That’s exactly how Keycloak patched this vulnerability. This CVE is a good example of how developers provide methods and interfaces that can be misunderstood or used incorrectly.

Another common scenario for mTLS deployments is when the TLS connection is terminated on a reverse proxy. In this case, the reverse proxy often checks the certificate and forwards it to a backend server as an additional header. Here is a typical nginx configuration to enable mTLS:

$ cat nginx.conf

http {
    server {
        listen 443 ssl;
        ssl_client_certificate /etc/nginx/ca.pem;
        ssl_verify_client on;

        location / {
            proxy_pass http://host.internal:80;
            proxy_set_header ssl-client-cert $ssl_client_cert;

I’ve seen a number of systems like that, and in most cases the backend servers behind nginx do not perform additional validation, just trusting the reverse proxy. This behavior is not directly exploitable, but it’s not ideal either. Why? Well, first of all, it means that any server in the local network can make a request with this header, so this network segment needs to be carefully isolated from any traffic coming from outside. Additionally, if the backend or reverse proxy is affected by request smuggling or header injection, its exploitation becomes trivial. Over the past few years, we’ve seen a lot of request and header smuggling vulnerabilities, including the latest CVEs in Netty and Nodejs. Watch out when implementing these eventualities and test the certificates’s signature on all servers if doable.

Chapter 2: “Comply with the chain, the place does it lead you?”

Excerpt from RFC 4158:

In large systems, servers may not store all root and intermediate certificates locally, but use external storage instead. RFC 4387 explains the idea of a certificates retailer: an interface you should utilize to lazily entry certificates throughout chain validation. These shops are applied over completely different protocols, corresponding to HTTP, LDAP, FTP, or SQL queries.

RFC 3280 defines some X.509 certificates extensions that may comprise details about the place to seek out the issuer and CA certificates. For example, the Authority Data Entry (AIA) extension accommodates a URL pointing to the Issuer’s certificates. If this extension is used for validation, there’s a excessive probability that you may exploit it to carry out an SSRF assault. Additionally, Topic, Issuer, Serial, and their different names can be utilized to assemble SQL or LDAP queries, creating alternatives for injection assaults.

Client certificate with an AIA extension, containing a link to

When certificates shops are in use, you need to consider these values as “untrusted consumer enter” or “Insertion factors,” just like these we’ve got in Burp Suite’s Intruder. And what attackers will actually love is that every one of those values can be utilized in queries earlier than the signature is checked.

See Also

Instance: CVE-2023-33201 LDAP injection in Bouncy Citadel

To demonstrate an example of this vulnerability, we’ll use LDAPCertStore from the Bouncy Citadel library. Bouncy Citadel is likely one of the hottest libraries for certificates validation in Java. Right here is an instance of how you should utilize this retailer to construct and validate a certificates chain.

PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(keystore, selector);

//setup further LDAP retailer
X509LDAPCertStoreParameters CertStoreParameters = new X509LDAPCertStoreParameters.Builder("ldap://", "CN=certificates").construct();
CertStore certStore = CertStore.getInstance("LDAP", CertStoreParameters, "BC");

// Construct and confirm the certification chain
attempt {
   CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC");
   PKIXCertPathBuilderResult end result =
           (PKIXCertPathBuilderResult) builder.construct(pkixParams);

Below the hood, Bouncy Citadel makes use of the Topic subject from the certificates to construct an LDAP question. The Topic subject is inserted within the filter, with out—you guessed it—any escaping.

Client certificate containing the text

So, if the Topic accommodates any particular characters, it could actually change the syntax of the question. Generally, this may be exploited as a blind ldap question injection. Subsequently, it could be doable to make use of this vulnerability to extract different fields from the LDAP listing. The exploitability depends upon many components, together with whether or not the appliance exposes any errors or not, and it additionally depends upon the construction of the LDAP listing.

Basically, everytime you incorporate user-supplied knowledge into an LDAP question, particular characters must be correctly filtered. That’s precisely how this CVE has been patched within the Bouncy Citadel code.

Chapter 3: Certificates revocation and its unintended makes use of

Similar to Json web tokens, the beauty of certificate chains is that they can be trusted just based on their signature. But what happens if we need to revoke a certificate, so it can no longer be used?

The PKIX specification (RFC 4387) addresses this drawback by proposing a particular retailer for revoked certificates, accessible through HTTP or LDAP protocols. Many builders consider that revocation checking is totally needed, whereas others urge to keep away from it for efficiency causes or solely use offline revocation lists.

Typically talking, the shop location might be hardcoded into the appliance or taken from the certificates itself. There are two certificates extensions used for that: Authority Data Entry OSCP URL and CRL Distribution factors.

Client certificate containing URLs in its AIA OSCL and CRL Distribution points.

it from the hackers standpoint, I feel it’s unimaginable that the placement of the revocation server might be taken from the certificates. So, if the appliance takes URLs counting on AIA or CRLDP extension to make a revocation test, it may be abused for SSRF assaults.

Sadly for attackers, this usually occurs after the signature checks, however in some circumstances it’s nonetheless exploitable.

Furthermore, LDAP can also be supported, a minimum of in Java. You most likely heard that, in Java, unmarshaling an LDAP lookup response can result in a distant code execution. A number of years again, Moritz Bechler reported this drawback and distant code execution through revocation has since been patched within the JDK. You may take a look at his blog post for extra particulars.

In my analysis, I made a decision to test if the Bouncy Citadel library can also be affected. It seems that Bouncy Citadel might be configured to make use of the CRLDP extension and make calls to an LDAP server. On the similar time, Bouncy Citadel solely fetches a selected attribute from the LDAP response and doesn’t assist references. So, distant code execution is just not doable there. HTTP SSRF remains to be viable although.

personal static Assortment getCrlsFromLDAP(CertificateFactory certFact, URI distributionPoint) throws IOException, CRLException
    Map<String, String> env = new Hashtable<String, String>();

    env.put(Context.INITIAL_CONTEXT_FACTORY, "");
    env.put(Context.PROVIDER_URL, distributionPoint.toString());

    byte[] val = null;
        DirContext ctx = new InitialDirContext((Hashtable)env);
        Attributes avals = ctx.getAttributes("");
        Attribute aval = avals.get("certificateRevocationList;binary");
        val = (byte[])aval.get();

Instance: CVE-2023-28857 credentials leak in Apereo CAS

I also had a quick look at open source projects that support mTLS and perform revocation checking. One of these projects was Apereo CAS. It’s another popular authentication server that is highly configurable. Administrators of Apereo CAS can enable the revocation check using an external LDAP server by specifying its address and password in the settings:


If these settings are applied, Apereo CAS performs the revocation check for the certificate, fetching the address from the certificate’s CRLDP extension.

* Validate the X509Certificate received.
* @param cert the cert
* @throws GeneralSecurityException the general security exception
private void validate(final X509Certificate cert) throws GeneralSecurityException {

   val pathLength = cert.getBasicConstraints();
   if (pathLength < 0) {
       if (!isCertificateAllowed(cert)) {
           val msg = "Certificate subject does not match pattern " + this.regExSubjectDnPattern.pattern();

I was afraid that this could lead to remote code execution, but it turns out that Apereo CAS uses a custom library for LDAP connection, which does not support external codebases or object factories needed for RCE.

When I tested this in Apereo CAS, I noticed one interesting behavior. The server prefers the LDAP URL located inside the certificate, instead of the one that is configured in settings. At the same time, Apereo CAS still sends the password from the settings. I quickly set up a testing environment and sent a self-signed certificate in the header. My self-signed certificate had a CRLDP extension with the LDAP URL pointing to a netcat listener. After sending this request to Apereo CAS, I received a request to my netcat listener with the username and password leaked.

Pair of screenshots: the first contains a POST request to Apereo CAS and the second is a terminal running netcat.

After reporting this vulnerability, the application developers issued a fix within just one day. They patched it by clearing the login and password used for LDAP connection if the URL is taken from the CRLDP. Therefore, the password leak is no longer possible. Nevertheless, I would say that using URLs from the CRLDP extension is still dangerous, as it broadens the attack surface.


If you’re developing an mTLS system or performing a security assessment, I suggest:

  1. Pay attention when extracting usernames from the mTLS chain, as the servers only verify the first certificate in the chain.
  2. Use Certificate Stores with caution, as it can lead to LDAP and SQL injections.
  3. Certificate revocation can lead to SSRF or even to RCE in the worst case. So, do the revocation check only after all other checks and do not rely on URLs taken from the certificate extensions.

Source Link

What's Your Reaction?
In Love
Not Sure
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top