Sandbox Escape with Mutual Tls
How Sandbox Escape Manifests in Mutual TLS
Mutual TLS (mTLS) is often used to secure service‑to‑service communication in zero‑trust architectures, sidecar proxies, and container‑based platforms. When an attacker can obtain a valid client certificate (for example, by compromising a build pipeline, stealing a key from a container image, or exploiting a weak certificate‑issuance process), they can use that credential to establish a trusted mTLS channel to a privileged service. If the receiving service does not properly validate the client certificate or applies overly permissive authorization logic, the attacker can pivot from the compromised sandbox (e.g., a restricted container) to the host or to another privileged microservice, effectively escaping the sandbox.
Specific code paths where this issue appears include:
- Java SSLSocket/SSLEngine – applications that create an
SSLContextwith aTrustManagerthat callscheckClientTrustedwith an empty implementation or always returnstrue. This disables client‑certificate validation, allowing any presented certificate to be accepted. - Go
tls.Config– settingClientAuth: tls.NoClientCertortls.RequestClientCertwithout providing aVerifyPeerCertificatefunction that checks the certificate chain against a known root. - Node.js
tls.connect– omitting thecaoption or settingrejectUnauthorized: false, which makes the TLS stack accept any certificate signed by any CA in the system store, including attacker‑generated ones. - Envoy/Istio sidecar proxies – misconfigured
client_certificatefields in theSecretresource that point to a wildcard or self‑signed cert, allowing the sidecar to accept connections from any pod that presents a matching cert.
When the service trusts the attacker’s certificate, it may then expose administrative APIs (e.g., Kubernetes /metrics, Istio /healthz, or internal gRPC services) that are intended only for trusted sidecars. By invoking those APIs, the attacker can execute privileged commands, mount host filesystems, or launch new processes, thereby breaking out of the sandbox. Real‑world analogues include CVE‑2021-31207 (Linux eBPF privilege escalation via abused trusted channels) and CVE‑2022-22965 (Spring4Shell), where a trusted communication channel was leveraged to achieve remote code execution.
Mutual TLS-Specific Detection
Detecting insufficient mTLS validation does not require agents or source code; it can be done by probing the unauthenticated attack surface of the endpoint. middleBrick performs the following checks that are relevant to sandbox‑escape risk in mTLS:
- Performs a full TLS handshake without presenting a client certificate to see if the server accepts the connection (indicating missing or optional client‑auth).
- If the server requests a client cert, middleBrick presents a self‑signed certificate** not chain‑to a trusted root** and checks whether the connection succeeds (revealing lax trust‑store validation).
- Validates the certificate chain** and **hostname** verification; failures are reported as findings.
- Checks for the use of weak cipher suites or TLS versions that could facilitate downgrade attacks, which sometimes precede credential theft.
- Cross‑references any provided OpenAPI/Swagger spec with the observed TLS behavior to highlight mismatches (e.g., spec declares
security: [{ mTLS: [] }]but the server accepts connections without client cert).
Example of using the middleBrick CLI to scan an mTLS‑protected endpoint:
# Install the CLI (npm)
npm i -g middlebrick
# Scan the target; the tool returns a JSON report
middlebrick scan https://api.internal.example.com/secure --output json
The resulting JSON includes a section like:
{
"category": "Authentication",
"finding": "Server accepts TLS handshake without client certificate",
"severity": "high",
"remediation": "Enable strict client‑authentication and verify the client certificate chain against a trusted root."
}
If the server also presents a certificate that does not match the expected hostname, middleBrick will flag a "Hostname verification failure" under the "Encryption" category. These findings give developers concrete evidence that the mTLS configuration could be abused for a sandbox escape.
Because middleBrick works purely from the network perspective, it can be integrated into CI/CD pipelines (GitHub Action) or run from an IDE via the MCP Server to catch regressions before deployment.
Mutual TLS-Specific Remediation
Remediation focuses on enforcing strict validation of both sides of the TLS connection and limiting what a trusted client can do once the channel is established. Below are language‑specific examples that use the native TLS libraries to achieve strong mTLS.
Java (using JSSE)
// Load server’s keystore (holds its own private key & cert)
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream ks = new FileInputStream("server.p12")) {
keyStore.load(ks, "changeit".toCharArray());
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "changeit".toCharArray());
// Load truststore that contains only the approved client CA(s)
KeyStore trustStore = KeyStore.getInstance("PKCS12");
try (FileInputStream ts = new FileInputStream("trusted-clients.p12")) {
trustStore.load(ts, "changeit".toCharArray());
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
SSLContext ctx = SSLContext.getInstance("TLSv1.3");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
// Enforce client auth
SSLParameters params = ctx.getDefaultSSLParameters();
params.setNeedClientAuth(true);
params.setEndpointIdentificationAlgorithm("HTTPS");
// Use the context in your server (e.g., Netty, Tomcat, Jetty)
The key points are:
- The truststore contains **only** the CA(s) that are allowed to sign client certificates.
setNeedClientAuth(true)forces the server to abort the handshake if no client cert is presented.- Hostname verification is enabled via
setEndpointIdentificationAlgorithm.
Go (standard library)
// Load server certificate & key
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil { log.Fatal(err) }
// Load the pool of allowed client CAs
clientCAPool := x509.NewCertPool()
ca, err := os.ReadFile("trusted-clients.ca")
if err != nil { log.Fatal(err) }
if ok := clientCAPool.AppendCertsFromPEM(ca); !ok {
log.Fatal("failed to parse client CA")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCAPool,
MinVersion: tls.VersionTLS13,
}
ln, err := tls.Listen("tcp", ":8443", config)
if err != nil { log.Fatal(err) }
for {
conn, err := ln.Accept()
if err != nil { continue }
// handle connection – the TLS layer already validated the client cert
}
Important:
ClientAuth: tls.RequireAndVerifyClientCertmandates a valid client certificate.- The
ClientCAspool restricts acceptance to known CAs. - Setting
MinVersionto TLS 1.3 removes older, weaker protocol versions.
Node.js (using tls module)
const tls = require('tls');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
ca: fs.readFileSync('trusted-clients.ca'), // only these CAs trusted
requestCert: true, // ask for client cert
rejectUnauthorized: true, // abort if client cert not trusted
minVersion: 'TLSv1.3'
};
const server = tls.createServer(options, (cleartextStream) => {
// At this point the client certificate has been validated
cleartextStream.write('welcome\n');
cleartextStream.pipe(cleartextStream);
});
server.listen(8443, () => {
console.log('mTLS server listening on port 8443');
});
After establishing a secure channel, apply the principle of least privilege: the service should expose only the APIs absolutely necessary for the intended client, and each API must enforce its own authorization checks (e.g., JWT scopes, RBAC). This defense‑in‑depth approach ensures that even if an attacker manages to present a valid client certificate, they cannot leverage the channel to escape the sandbox or invoke privileged functions.