Padding Oracle in Fiber with Cockroachdb
Padding Oracle in Fiber with Cockroachdb — how this specific combination creates or exposes the vulnerability
A padding oracle attack occurs when an application reveals whether a ciphertext’s padding is valid before failing due to other errors. In a Fiber-based service that uses Cockroachdb as the backend data store, this can happen when encrypted data (for example, authentication tokens or session records) is stored in a Cockroachdb column and later decrypted in Fiber handlers. If the decryption routine produces different responses or timing behavior depending on whether the padding is valid, the service becomes an oracle that an attacker can query to gradually reveal plaintext.
Consider a scenario where user data is encrypted with AES-CBC before being persisted in Cockroachdb. A handler might retrieve the blob from a row, attempt to decrypt it, and then decide what to do next. If decryption errors related to padding are not handled uniformly—such as returning distinct messages or status codes for invalid padding versus other failures—an attacker can observe these differences via crafted requests. Because Cockroachdb reliably stores the ciphertext and the application controls when and how it is fetched, the database becomes part of the oracle path: the ciphertext is supplied to the Fiber endpoint, decrypted, and the server’s observable behavior reveals padding validity.
This combination is notable because the database does not need to be directly exploitable; it simply stores the encrypted payload that the Fiber application later uses. If the Fiber code does not enforce constant-time padding checks and does not mask failures across all decryption outcomes, the unauthenticated attack surface includes any endpoint that processes stored ciphertext. For example, an attacker who can supply arbitrary ciphertexts to a decrypt-and-decide endpoint can iteratively learn about the plaintext byte by byte, even when the database itself enforces strong access controls.
With middleBrick’s 12 security checks running in parallel—including Input Validation, Authentication, and BOLA/IDOR—such misconfigurations are surfaced alongside the relevant findings. The scanner evaluates whether error handling and response patterns could disclose padding validity, and whether encryption practices align with secure storage and retrieval workflows involving Cockroachdb. This is important because the presence of encrypted data in Cockroachdb does not automatically imply confidentiality if the application layer leaks information through its handling.
Cockroachdb-Specific Remediation in Fiber — concrete code fixes
To mitigate padding oracle risks in a Fiber application that uses Cockroachdb, ensure that decryption routines never branch on padding validity and that all failures are handled identically. Use authenticated encryption (such as AES-GCM) where possible, and avoid raw CBC mode unless you implement strict countermeasures. Below are concrete Go examples that demonstrate a vulnerable pattern and a remediated approach.
Vulnerable pattern with raw AES-CBC and Cockroachdb
// WARNING: This code illustrates a vulnerable pattern and should not be used in production.
package main
import (
"crypto/aes"
"crypto/cipher"
"database/sql"
"fmt"
"net/http"
"github.com/gofiber/fiber/v2"
_ "github.com/lib/pq"
)
func decryptHandler(db *sql.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
id := c.Params("id")
var ciphertext []byte
// Retrieve encrypted blob from Cockroachdb
err := db.QueryRow("SELECT data FROM encrypted_records WHERE id = $1", id).Scan(&ciphertext)
if err != nil {
// This error path can leak information via status or message
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
}
block, err := aes.NewCipher([]byte("32-byte-long-key-here-1234567890ab"))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "crypto error"})
}
if len(ciphertext)%aes.BlockSize != 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid ciphertext"})
}
mode := cipher.NewCBCDecrypter(block, []byte("16-byte-iv-here1234"))
plaintext := make([]byte, len(ciphertext))
mode.CryptBlocks(plaintext, ciphertext)
// Vulnerable: non-constant-time padding removal and distinct errors
plaintext, err = pkcs7Unpad(plaintext)
if err != nil {
// Distinguishing between padding errors and other issues can leak information
if err == ErrInvalidPadding {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid padding"})
}
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "decryption failed"})
}
return c.JSON(fiber.Map{"data": string(plaintext)})
}
}
func pkcs7Unpad(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, ErrInvalidPadding
}
padLen := int(data[len(data)-1])
if padLen > len(data) || padLen > aes.BlockSize {
return nil, ErrInvalidPadding
}
// Constant-time check would require more work; this is illustrative
for i := len(data) - padLen; i < len(data); i++ {
if data[i] != byte(padLen) {
return nil, ErrInvalidPadding
}
}
return data[:len(data)-padLen], nil
}
Remediated version with uniform error handling and optional authenticated encryption
// Prefer AES-GCM; if using CBC, ensure constant-time checks and uniform responses.
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"database/sql"
"errors"
"io"
"net/http"
"time"
"github.com/gofiber/fiber/v2"
_ "github.com/lib/pq"
)
var (
ErrDecryption = errors.New("decryption failed")
)
func decryptHandlerSecure(db *sql.DB) fiber.Handler {
return func(c *fiber.Ctx) error {
id := c.Params("id")
var ciphertext []byte
err := db.QueryRow("SELECT data FROM encrypted_records WHERE id = $1", id).Scan(&ciphertext)
if err != nil {
// Use a generic, consistent error path and status
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "request failed"})
}
block, err := aes.NewCipher([]byte("32-byte-long-key-here-1234567890ab"))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "request failed"})
}
if len(ciphertext) < aes.BlockSize {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "request failed"})
}
// Use GCM when possible to provide authenticated encryption
// If you must use CBC, ensure padding checks do not leak information
nonce := ciphertext[:12]
ciphertextBody := ciphertext[12:]
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "request failed"})
}
plaintext, err := aesgcm.Open(nil, nonce, ciphertextBody, nil)
if err != nil {
// Always return the same generic response and status
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "request failed"})
}
// Optional: store or rotate encryption keys via Cockroachdb configuration or Vault
// Example key rotation marker stored alongside record metadata
var rotatedAt time.Time
c.DB().QueryRow("SELECT rotated_at FROM key_metadata WHERE kid = $1", "current").Scan(&rotatedAt)
return c.JSON(fiber.Map{"data": string(plaintext), "rotated_at": rotatedAt})
}
}
Key remediation steps include using authenticated encryption (GCM), ensuring that all error paths return the same HTTP status and generic message, and avoiding branching on padding validity. When using CBC, apply constant-time padding validation and ensure that errors from Cockroachdb queries do not differ based on decryption success. middleBrick’s scans can highlight inconsistencies in error handling and encryption usage to guide these fixes.