Arp Spoofing with Mutual Tls
How ARP Spoofing Manifests in Mutual TLS
ARP spoofing (also called ARP poisoning) is a Layer‑2 attack where an attacker sends falsified ARP replies on a local network, associating the attacker’s MAC address with the IP address of a legitimate host. When the victim tries to establish a mutual TLS (mTLS) connection, the TCP handshake is redirected to the attacker instead of the intended peer.
In a correctly implemented mTLS exchange, both sides present X.509 certificates that are verified against a trusted CA (or a pinned certificate). If the victim’s TLS library is configured to skip client‑certificate validation or to accept any certificate presented by the peer, the attacker can complete the TLS handshake using a self‑signed or otherwise unauthorized certificate. The attacker then sits in the middle, decrypting, inspecting, and potentially modifying application data while both parties believe they are communicating directly.
Typical vulnerable code paths include:
- Node.js
tls.createSecureContext()called withrequestCert: truebutrejectUnauthorized: false(or omitted). - Go
tls.ConfigwhereClientAuthis set totls.NoClientCertortls.VerifyClientCertIfGivenwithout enforcing verification. - Python
ssl.SSLContextinstantiated withverify_mode = ssl.CERT_NONEwhile wrapping a socket for mutual authentication.
These configurations allow the attacker’s certificate to be accepted, making the ARP‑spoofed man‑in‑the‑middle (MITM) attack successful despite the use of mTLS.
Mutual TLS‑Specific Detection
Detecting the combination of ARP spoofing risk and weak mTLS configuration relies on observing whether the service enforces mutual authentication and validates certificates correctly. middleBrick performs unauthenticated, black‑box checks that map directly to these properties:
- Authentication check – verifies that the endpoint requires a valid client certificate and rejects connections without one.
- Certificate validation** – confirms that the server validates the client certificate against a trusted CA (or pinned pin) and that
rejectUnauthorized-equivalent behavior is active. - Cipher suite and version analysis** – ensures that only strong TLS versions (≥ TLS 1.2) and non‑weak ciphers are offered, reducing the chance an attacker could downgrade to a vulnerable suite after ARP redirection.
- Data exposure scan** – looks for accidental leakage of private keys or certificates in responses, which would aid an attacker in forging credentials after a successful ARP spoof.
When you run a scan, middleBrick returns a per‑category breakdown. For example, a failing Authentication check with severity high indicates that the service does not enforce client‑certificate validation, a prerequisite for ARP‑spoofing‑based MITM.
Example CLI usage:
# Install the middleBrick CLI (npm)
npm i -g middlebrick
# Scan an API endpoint for mTLS‑related issues
middlebrick scan https://api.example.com --output json
The JSON output includes fields such as authentication.required (boolean) and certificate.validation.enforced (boolean). If either is false, the service is vulnerable to ARP‑spoofing‑enabled MITM despite using TLS.
Mutual TLS‑Specific Remediation
Remediation focuses on tightening the mutual TLS configuration so that even if an attacker successfully redirects traffic via ARP spoofing, the TLS handshake will fail because the attacker cannot present a valid, trusted certificate.
Node.js
const tls = require('tls');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
ca: [fs.readFileSync('ca.pem')], // Trusted CA for client certs
requestCert: true, // Ask for a client certificate
rejectUnauthorized: true, // Abort if client cert is not trusted
minVersion: 'TLSv1.2', // Disallow old, weak versions
ciphers: [ // Strong cipher suite
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256'
]
};
const server = tls.createServer(options, (socket) => {
console.log('Client authorized:', socket.authorized ? 'Yes' : 'No');
socket.write('welcome\n');
socket.pipe(socket);
});
server.listen(8443, () => console.log('mTLS server listening on 8443'));
Go
package main
import (
"crypto/tls"
"fmt"
"log"
)
func main() {
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatalf("load keys: %v", err)
}
caCertPool := tls.NewCertPool()
ca, err := os.ReadFile("ca.pem")
if err != nil {
log.Fatalf("read ca: %v", err)
}
if ok := caCertPool.AppendCertsFromPEM(ca); !ok {
log.Fatalf("failed to append CA")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert, // Enforce mTLS
ClientCAs: caCertPool,
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
}
ln, err := tls.Listen("tcp", ":8443", config)
if err != nil {
log.Fatalf("listen: %v", err)
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Printf("accept error: %v", err)
continue
}
go handle(conn)
}
}
func handle(conn net.Conn) {
defer conn.Close()
fmt.Fprintln(conn, "mutual TLS established")
// … application logic …
}
Python (ssl)
import ssl
import socket
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile='server.crt', keyfile='server.key')
context.load_verify_locations(cafile='ca.pem') # Trusted CA for clients
context.verify_mode = ssl.CERT_REQUIRED # Enforce client cert
context.check_hostname = False # Not needed for mTLS server
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20')
bindsocket = socket.socket()
bindsocket.bind(('0.0.0.0', 8443))
bindsocket.listen(5)
while True:
newsocket, fromaddr = bindsocket.accept()
try:
ssock = context.wrap_socket(newsocket, server_side=True)
# Perform application protocol over ssock
except ssl.SSLError as e:
print(f"TLS handshake failed: {e}")
newsocket.close()
These snippets enforce three critical controls:
- Request and require a client certificate (
requestCert: true/tls.RequireAndVerifyClientCert/ssl.CERT_REQUIRED). - Reject any certificate that cannot be chained to a trusted CA (
rejectUnauthorized: true/ properClientCAs/load_verify_locations). - Restrict to modern TLS versions and strong cipher suites, preventing downgrade attacks that could follow a successful ARP redirect.
When these settings are in place, an attacker who has poisoned ARP tables cannot complete the mutual TLS handshake, and the connection will be terminated with a clear authentication error—detectable by monitoring tools and by middleBrick’s scans.
Frequently Asked Questions
Does mutual TLS prevent ARP spoofing attacks?
How can I verify that my API enforces mutual TLS before deploying to production?
middlebrick scan https://api.staging.example.com). The scan’s Authentication and Certificate Validation checks will report whether the service requires and validates a client certificate. Failures indicate missing mTLS enforcement and should be resolved with the code fixes shown above before promoting to production.