Phishing Api Keys with Mutual Tls
How Phishing API Keys Manifests in Mutual TLS
Phishing API keys in Mutual TLS environments exploit the trust established between client and server certificates. While Mutual TLS (mTLS) provides strong authentication through client certificates, attackers can still compromise API keys when they're transmitted alongside the TLS handshake. This manifests in several critical ways.
The most common pattern occurs when applications validate the client certificate but then rely on API keys for authorization. An attacker who obtains a valid client certificate through phishing or compromise can then use stolen API keys without additional authentication. This bypasses the primary security benefit of mTLS - that client identity is cryptographically proven.
Consider this vulnerable pattern in a Node.js Express application using mTLS:
app.post('/api/data', (req, res) => {
// Client cert validated by mTLS
const cert = req.socket.getPeerCertificate();
// BUT then relies on API key for authorization
const apiKey = req.headers['x-api-key'];
if (!isValidApiKey(apiKey)) {
return res.status(401).json({error: 'Invalid API key'});
}
// Process request...
});The vulnerability here is that the API key becomes the primary authorization mechanism, not the client certificate. If an attacker phishes an API key from a legitimate user who already has a valid client certificate, they can impersonate that user entirely.
Another manifestation occurs in microservices architectures where mTLS is used between services, but API keys are used for application-level authorization. An attacker who compromises one service's API key can then make requests to other services, even though mTLS would normally restrict access to specific clients.
Database connections present another attack surface. Applications using mTLS for database connections might still use API keys or passwords for application-level access control. A phishing attack that captures these credentials allows database access even with mTLS in place.
Service mesh environments like Istio or Linkerd often use mTLS for service-to-service communication, but applications may layer API keys on top. This creates a situation where the service mesh provides transport security, but application-level API keys remain vulnerable to phishing attacks.
Mobile applications using mTLS for backend connections frequently store API keys insecurely on the device. An attacker who gains device access through phishing can extract both the client certificate (if stored) and API keys, completely compromising the mTLS connection.
Mutual TLS-Specific Detection
Detecting phishing API key vulnerabilities in mTLS environments requires examining both the TLS handshake and application-layer authentication. middleBrick's mTLS-specific scanning identifies these patterns by analyzing the complete authentication chain.
The scanner first verifies that mTLS is properly configured by checking for client certificate validation during the TLS handshake. It then examines whether API keys are being used as the primary authorization mechanism after TLS authentication completes.
Key detection patterns include:
# middleBrick scan output showing mTLS + API key vulnerability
$ middlebrick scan https://api.example.com --verbose
=== Mutual TLS Analysis ===
✓ Client certificate validation detected
✓ TLS 1.3 with strong cipher suite
⚠️ API key in Authorization header detected
⚠️ API key used for authorization after mTLS
=== Authentication Chain ===
1. TLS handshake (client cert validated)
2. API key validation (primary auth)
3. Application authorization
=== Findings ===
[CRITICAL] Phishing API Keys - Mutual TLS Bypass
Risk: High
Description: Application validates client certificate via mTLS but then relies on API keys for authorization. An attacker with a valid client certificate and stolen API key can fully impersonate legitimate users.
Remediation: Implement certificate-based authorization at the application layer, or remove API keys entirely when mTLS is enforced.The scanner also checks for API key exposure in TLS client hello messages, where keys might be sent before the handshake completes. This creates a window where an attacker could capture keys even if the final connection is rejected.
middleBrick analyzes OpenAPI specifications to identify endpoints that accept both mTLS and API keys, flagging configurations where the API key provides the primary authorization rather than being a secondary factor.
The tool examines certificate validation depth and ensures that client certificates are properly validated against trusted CAs. Weak validation allows attackers to use self-signed certificates with stolen API keys.
Network-level detection includes checking for API key transmission over mTLS connections that might be intercepted at the application layer. The scanner verifies that API keys are never sent in URLs, query parameters, or headers that could be logged or cached.
middleBrick's LLM security module specifically checks for AI/ML APIs that use mTLS but still require API keys for model access, a common pattern in AI service providers that creates phishing vulnerabilities.
Mutual TLS-Specific Remediation
Remediating phishing API key vulnerabilities in mTLS environments requires eliminating the API key dependency and implementing certificate-based authorization. Here are mTLS-specific fixes for common patterns.
For Node.js applications using Express with mTLS:
const mTLS = require('mtls-express');
// Configure mTLS middleware to extract certificate info
const mtlsMiddleware = mTLS({
ca: fs.readFileSync('ca.crt'),
requestCert: true,
rejectUnauthorized: true
});
// Application-level authorization using certificate data
function authorizeByCertificate(req, res, next) {
const cert = req.socket.getPeerCertificate();
// Extract identity from certificate
const clientId = cert.subject.CN;
const clientOrg = cert.issuer.CN;
// Lookup permissions based on certificate attributes
const permissions = getPermissionsFromCert(clientId, clientOrg);
if (!permissions) {
return res.status(403).json({error: 'Certificate not authorized'});
}
// Attach permissions to request
req.user = { id: clientId, permissions };
next();
}
// Secure route without API keys
app.post('/api/data',
mtlsMiddleware,
authorizeByCertificate,
(req, res) => {
// Certificate already validated and authorized
// No API key needed
res.json({data: processRequest(req)});
}
);For Python applications using Flask with mTLS:
from flask import Flask, request
from OpenSSL import SSL
import jwt
app = Flask(__name__)
# mTLS context with client certificate validation
context = SSL.Context(SSL.TLSv1_2_METHOD)
context.load_cert_chain('server.crt', 'server.key')
context.load_verify_locations('ca.crt')
context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cert)
# Certificate-based authorization
def get_permissions_from_cert(cert):
# Extract and validate certificate fields
subject = cert.get_subject()
if not subject.commonName or not subject.organizationalUnit:
return None
# Lookup permissions based on certificate attributes
return permissions_db.get(subject.commonName)
@app.route('/api/data', methods=['POST'])
@requires_mtls
@requires_certificate_auth
def secure_endpoint():
# Certificate already validated and authorized
# Process request without API key
return {'data': process_request(request.data)}
if __name__ == '__main__':
app.run(ssl_context=context)For Go applications using mTLS:
package main
import (
"crypto/tls"
"crypto/x509"
"net/http"
)
type certificateAuth struct {
caPool *x509.CertPool
}
func (ca *certificateAuth) authorize(cert *x509.Certificate) bool {
// Extract identity from certificate
clientId := cert.Subject.CommonName
// Lookup permissions based on certificate
permissions, ok := permissionsDB[clientId]
return ok && permissions.Allowed
}
func (ca *certificateAuth) middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cert := r.TLS.PeerCertificates[0]
if !ca.authorize(cert) {
http.Error(w, "Unauthorized", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
// Load CA and create TLS config
caCert, _ := ioutil.ReadFile("ca.crt")
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
ClientCAs: caPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
server := &http.Server{
Addr: ":443",
TLSConfig: tlsConfig,
Handler: certificateAuth{caPool}.middleware(http.DefaultServeMux),
}
http.HandleFunc("/api/data", secureHandler)
server.ListenAndServeTLS("server.crt", "server.key")
}For Kubernetes services using Istio with mTLS:
apiVersion: authentication.istio.io/v1beta1
kind: Policy
metadata:
name: mtls-strict
spec:
targets:
- name: my-api-service
peers:
- mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: cert-based-auth
spec:
selector:
matchLabels:
app: my-api-service
rules:
- from:
- source:
principals: ["*" matching: "*/allowed-client" "]
to:
- operation:
methods: ["POST"]
paths: ["/api/data"]
when:
- key: request.auth.claims[permissions]
values: ["read", "write"]The key principle across all implementations is that the client certificate itself contains the authorization information, eliminating the need for separate API keys. This removes the phishing attack surface while maintaining the security benefits of mTLS.