Bleichenbacher Attack in Gin (Go)
Go-Specific Remediation in Gin — concrete code fixes
The fix is to eliminate padding oracles by ensuring RSA decryption never leaks information about padding validity through errors, timing, or side channels. In Go, this means using crypto/rsa.DecryptPKCS1v15SessionKey when possible (which is constant-time) or implementing constant-time error handling for general decryption.
Below is a vulnerable Gin handler that decrypts RSA-encrypted data and returns different errors based on padding validity — creating a classic Bleichenbacher oracle:
package main
import (
"crypto/rsa"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
var privateKey *rsa.PrivateKey // Assume loaded elsewhere
func decryptHandler(c *gin.Context) {
ciphertextB64 := c.Query("data")
if ciphertextB64 == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "missing data"})
return
}
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextB64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid base64"})
return
}
// VULNERABLE: DecryptPKCS1v15 returns error on invalid padding
plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, ciphertext)
if err != nil {
// This error leaks padding oracle information
c.JSON(http.StatusBadRequest, gin.H{"error": "decryption failed"})
return
}
c.JSON(http.StatusOK, gin.H{"result": string(plaintext)})
}
func main() {
r := gin.Default()
r.GET("/decrypt", decryptHandler)
r.Run(":8080")
}
This is vulnerable because the error from rsa.DecryptPKCS1v15 differs based on whether the padding is correct, allowing an attacker to distinguish valid from invalid padding.
The remediation uses constant-time validation. For session key decryption (common in APIs), use DecryptPKCS1v15SessionKey, which returns error only on decryption failure without leaking padding details:
package main
import (
"crypto/rsa"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
var privateKey *rsa.PrivateKey // Assume loaded elsewhere
func decryptHandler(c *gin.Context) {
ciphertextB64 := c.Query("data")
if ciphertextB64 == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "missing data"})
return
}
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextB64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid base64"})
return
}
// SAFE: Constant-time decryption; error does not leak padding validity
plaintext, err := rsa.DecryptPKCS1v15SessionKey(rand.Reader, privateKey, ciphertext)
if err != nil {
// Generic error — no oracle
c.JSON(http.StatusBadRequest, gin.H{"error": "decryption failed"})
return
}
c.JSON(http.StatusOK, gin.H{"result": string(plaintext)})
}
func main() {
r := gin.Default()
r.GET("/decrypt", decryptHandler)
r.Run(":8080")
}
If you must decrypt arbitrary data (not just session keys), implement constant-time error checking by comparing the result against a dummy decryption and using subtle.ConstantTimeCompare. However, prefer using established protocols like RSA-OAEP (via crypto/rsa.DecryptOAEP) which are not vulnerable to Bleichenbacher when implemented correctly.
Additionally, ensure Gin’s error handling does not leak decryption failures. Avoid using gin.CustomRecovery that returns stack traces, and never log raw decryption errors in production responses. middleBrick’s unauthenticated scan can detect such endpoints by testing for error-based or timing-based oracles, flagging them under Input Validation or Cryptographic Findings with remediation guidance to use constant-time decryption and avoid error leakage.