Log Injection with Mutual Tls
How Log Injection Manifests in Mutual Tls
Log injection in Mutual Tls environments occurs when untrusted data from client certificates or TLS handshake metadata is improperly logged without sanitization. Since Mutual Tls establishes trust through client certificates, attackers can craft certificates with malicious payloads that exploit logging systems.
Consider a Mutual Tls server that logs client certificate details:
func handleRequest(w http.ResponseWriter, r *http.Request) {
cert := r.TLS.PeerCertificates[0]
log.Printf("Client: %s, Serial: %s, Issuer: %s",
cert.Subject.CommonName,
cert.SerialNumber,
cert.Issuer.CommonName)
w.Write([]byte("OK"))
}An attacker can create a certificate with a CommonName containing newline characters and log injection payloads:
CN=victim.com
ERROR: Panic: deliberate crash
[secret-data]When logged, this creates:
Client: victim.com ERROR: Panic: deliberate crash [secret-data], Serial: ..., Issuer: ...
This corrupts log integrity and can trigger alerts or hide malicious activity. The injection works because TLS certificate fields are treated as trusted input without validation.
Another attack vector involves X.509 extension fields. Custom extensions in certificates can carry arbitrary data:
from cryptography.x509.oid import ExtensionOID
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
# Create malicious certificate with log injection in extension
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
cert = x509.CertificateBuilder().subject_name(x509.Name([
x509.NameAttribute(x509.NameOID.COMMON_NAME, 'victim.com\nALERT: Injection Detected\n')
])).issuer_name(x509.NameOID.COMMON_NAME).add_extension(
x509.UnrecognizedExtension(
ExtensionOID.BASIC_CONSTRAINTS.value,
b'\n[hidden-data]\n'
), critical=False).sign(key, hashes.SHA256())
Servers logging these extension values without sanitization create the same injection vulnerabilities. The Mutual Tls trust model means these certificates are accepted and processed without the scrutiny applied to other untrusted inputs.
Mutual Tls-Specific Detection
Detecting log injection in Mutual Tls requires examining how certificate data flows through your logging pipeline. Start by analyzing certificate parsing and logging code paths.
Static analysis should identify:
# Find certificate field logging
grep -r "PeerCertificates\|clientAuth\|mtls" --include="*.go" |
grep -E "(log\.Print|fmt\.Print|logger\.Info)" |
head -10Dynamic testing involves creating test certificates with known injection payloads and observing log output. Use OpenSSL to generate test certificates:
# Generate certificate with newline injection
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=victim.com%0ALogInjectionTest%0A"middleBrick's black-box scanning approach tests Mutual Tls endpoints by establishing connections with crafted certificates and analyzing log responses. The scanner detects:
| Detection Method | Mutual Tls Specific | Detection Signal |
|---|---|---|
| Certificate Field Analysis | ✅ | Newline characters in CN/Serial fields |
| Extension Parsing | ✅ | Malformed or oversized extensions |
| Handshake Metadata | ✅ | Abnormal TLS version/extension data |
| Log Output Analysis | ✅ | Unexpected formatting or injection patterns |
The scanner's LLM security module also checks for AI-specific injection patterns in certificate fields, as some systems use certificate data for AI model inputs or logging.
Runtime monitoring should watch for:
# Prometheus log monitoring rules
- alert: MutualTlsLogInjection
expr: increase(log_lines_total{job="mtls-api"} offset 5m) > 100
for: 2m
labels:
severity: critical
annotations:
summary: "Suspicious log volume from Mutual Tls endpoint"
description: "High log volume may indicate injection attack via certificate fields"Network-based detection can identify abnormal certificate patterns before they reach application layers:
tcpdump -i eth0 -nn -tttt dst port 443 and "(ip[2:2] < 40)" |
while read line; do
if echo "$line" | grep -q "Client Hello"; then
# Check for abnormal certificate characteristics
echo "$line" | grep -E "(\n|0x0a)" >/dev/null &&
echo "Potential injection attempt detected"
fi
doneMutual Tls-Specific Remediation
Remediation requires sanitizing certificate data before logging and validating certificate contents. The core principle: treat all certificate fields as untrusted input regardless of Mutual Tls trust model.
Go implementation with proper sanitization:
import (
"crypto/x509"
"regexp"
"strings"
"log"
)
var logInjectionPattern = regexp.MustCompile(`[\n\r\t\x00-\x1f\x7f-\x9f]`)
func sanitizeCertificateField(input string) string {
return logInjectionPattern.ReplaceAllString(input, " ")
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
http.Error(w, "Mutual Tls required", http.StatusUnauthorized)
return
}
cert := r.TLS.PeerCertificates[0]
// Sanitize all certificate fields before logging
safeCN := sanitizeCertificateField(cert.Subject.CommonName)
safeSerial := sanitizeCertificateField(cert.SerialNumber.String())
safeIssuer := sanitizeCertificateField(cert.Issuer.CommonName)
log.Printf("Client: %s, Serial: %s, Issuer: %s",
safeCN, safeSerial, safeIssuer)
w.Write([]byte("OK"))
}Python with Flask and sanitization:
from flask import Flask, request
import re
import logging
from cryptography import x509
from cryptography.hazmat.primitives import serialization
log_injection_re = re.compile(r'[\n\r\t\x00-\x1f\x7f-\x9f]')
def sanitize_cert_field(value: str) -> str:
return log_injection_re.sub(' ', value)
def sanitize_cert_extensions(cert: x509.Certificate) -> dict:
sanitized = {}
for ext in cert.extensions:
sanitized[ext.oid.dotted_string] =
sanitize_cert_field(str(ext.value))
return sanitized
app = Flask(__name__)
@app.route('/')
def mtls_endpoint():
if not request.is_secure:
return 'Mutual Tls required', 401
cert = request.client_cert
if cert is None:
return 'Client certificate required', 401
# Parse and sanitize certificate
parsed_cert = x509.load_pem_x509_certificate(cert)
safe_cn = sanitize_cert_field(parsed_cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value)
safe_serial = sanitize_cert_field(str(parsed_cert.serial_number))
safe_issuer = sanitize_cert_field(parsed_cert.issuer.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value)
safe_extensions = sanitize_cert_extensions(parsed_cert)
# Log sanitized data
app.logger.info(f"Client: {safe_cn}, Serial: {safe_serial}, Issuer: {safe_issuer}")
return 'OK', 200
if __name__ == '__main__':
app.run(ssl_context='mutual')
Certificate validation should reject suspicious certificates before they reach application logic:
func validateCertificate(cert *x509.Certificate) error {
// Check for injection-prone characters
if strings.ContainsAny(cert.Subject.CommonName, "\n\r\t") {
return errors.New("certificate contains newline characters")
}
// Validate extension lengths
for _, ext := range cert.Extensions {
if len(ext.Value) > 1024 {
return errors.New("extension too large")
}
}
return nil
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
cert := r.TLS.PeerCertificates[0]
if err := validateCertificate(cert); err != nil {
http.Error(w, "Invalid certificate", http.StatusBadRequest)
return
}
// Continue with sanitized logging
}Network-level filtering with Nginx for Mutual Tls:
server {
listen 443 ssl http2;
ssl_certificate /path/to/server.crt;
ssl_certificate_key /path/to/server.key;
ssl_client_certificate /path/to/ca.crt;
ssl_verify_client on;
# Custom filter for suspicious certificates
location /api {
if ($ssl_client_s_dn ~* "(\n|\\r|\\t)") {
return 400 "Invalid certificate";
}
# Sanitize certificate fields in logs
log_format mtls_combined '$remote_addr - $ssl_client_s_dn_san_dns '
'$ssl_client_verify $request_uri';
access_log /var/log/nginx/mtls_access.log mtls_combined;
proxy_pass http://app_server;
}
}