Padding Oracle in Chi with Mutual Tls
Padding Oracle in Chi with Mutual Tls — how this specific combination creates or exposes the vulnerability
In Chi, a Padding Oracle attack can manifest when an API endpoint using Mutual TLS (mTLS) processes encrypted requests and returns distinct error messages based on padding validity. mTLS ensures both client and server authenticate with certificates, but it does not inherently prevent an attacker from observing whether a server rejects a malformed ciphertext with a padding error versus another kind of failure. If the server leaks this information—such as returning an HTTP 400 with “invalid padding” versus a generic “bad request”—an attacker can iteratively decrypt or forge plaintext by sending manipulated ciphertexts and observing the responses.
Mutual TLS in Chi typically involves client certificates verified during the TLS handshake. Once the handshake completes, application-layer encryption (e.g., AES in CBC mode) may still be used for additional data protection. The vulnerability arises when the application layer does not use constant-time padding validation and instead relies on early termination or distinguishable exceptions. For example, a Chi endpoint that decrypts a token and checks padding bytes before validating certificate bindings might first reject bad padding, allowing an attacker without a valid certificate to learn about padding correctness through timing or error differences. Since mTLS binds identity to the TLS layer, developers might mistakenly assume the transport is sufficient, leading to inconsistent padding checks at the application layer.
Consider a scenario where a Chi service accepts mTLS and then decrypts an encrypted JSON Web Token (JWT) or custom blob. If the decryption routine uses a non-constant padding check and the API returns a specific error for invalid padding, an attacker who can capture responses (even without a client cert) can perform a padding oracle attack to recover plaintext. middleBrick detects such risks under its Input Validation and Data Exposure checks when scanning endpoints that handle encrypted payloads over mTLS, highlighting the discrepancy between transport-layer authentication and application-layer cryptography hygiene.
Mutual Tls-Specific Remediation in Chi — concrete code fixes
Remediation focuses on ensuring that padding validation does not leak information and that application-layer cryptography does not depend on the presence or absence of mTLS. In Chi, use constant-time padding removal and avoid branching on sensitive data. Below are concrete examples using a common cryptography library approach.
Example 1: Constant-time AES-CBC padding validation in Chi
// Chi application code (pseudo-implementation; adapt to your crypto provider)
import "crypto"
func decryptConstantTime(ciphertext []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
if len(ciphertext)%aes.BlockSize != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
plaintext := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(plaintext, ciphertext)
// Constant-time padding removal to avoid oracle behavior
padLen := int(plaintext[len(plaintext)-1])
if padLen <= 0 || padLen > aes.BlockSize {
return nil, errors.New("invalid padding")
}
// Verify all padding bytes in constant time
valid := subtle.ConstantTimeCompare(plaintext[len(plaintext)-padLen:], bytes.Repeat([]byte{byte(padLen)}, padLen))
if subtle.ConstantTimeSelect(valid, 1, 0) != 1 {
return nil, errors.New("invalid padding")
}
return plaintext[:len(plaintext)-padLen], nil
}
Example 2: Chi handler that enforces mTLS and safe decryption
// Chi router with mTLS and safe payload handling
import (
"net/http"
"crypto/tls"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func secureHandler(decryptFunc func([]byte, []byte) ([]byte, error)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Ensure client cert is present and validated by TLS layer
if r.TLS == nil || r.TLS.VerifiedChains == nil || len(r.TLS.VerifiedChains[0]) == 0 {
http.Error(w, "mTLS required", http.StatusUnauthorized)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
key := getKeyForRequest(r) // application-specific key management
plaintext, err := decryptFunc(body, key)
if err != nil {
// Return generic error to avoid leaking padding details
http.Error(w, "bad request", http.StatusBadRequest)
return
}
// Process plaintext
w.Header().Set("Content-Type", "application/json")
w.Write(plaintext)
}
}
func main() {
r := chi.NewRouter()
r.Use(middleware.RealIP)
r.Get("/secure", secureHandler(myDecrypt))
server := &http.Server{
Addr: ":8443",
Handler: r,
TLSConfig: &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: loadClientCAPool(),
// Cipher suites should prioritize strong, modern suites
CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384},
MinVersion: tls.VersionTLS12,
},
}
server.ListenAndServeTLS("server.crt", "server.key")
}
Operational guidance
- Always return generic error messages for decryption or padding failures to prevent information leakage.
- Use TLS best practices: require client certificates, pin appropriate cipher suites, and disable legacy protocols.
- Validate and enforce application-level permissions even when mTLS is used, since mTLS only authenticates peers, not authorization.
middleBrick can help identify padding oracle risks and mTLS misconfigurations by scanning your endpoints, providing findings mapped to standards such as OWASP API Top 10 and PCI-DSS. With the Pro plan, you can enable continuous monitoring to detect regressions in encryption handling over time.