Nosql Injection in Buffalo with Hmac Signatures
Nosql Injection in Buffalo with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Buffalo is a web framework for Go that encourages rapid development and often uses MongoDB or other NoSQL stores. When developers combine Buffalo applications with custom HMAC-based request signing to authenticate webhooks or API calls, they can inadvertently introduce a Nosql Injection risk if signature validation is incomplete and user-controlled data is used to build queries.
HMAC signatures provide integrity and origin verification: a client computes a hash over selected parts of a request (method, path, body, timestamp) using a shared secret and sends the signature in a header. The server recomputes the signature and, if it matches, treats the request as trusted. The vulnerability arises when the server uses data extracted from the authenticated request—such as an X-User-ID header or a JSON body field—to construct a NoSQL query without proper validation or escaping. Because the signature is valid, the developer may assume the data is safe and skip normalization, type checks, or allowlist checks, enabling an attacker to inject NoSQL operators.
For example, an attacker can send a POST with a valid HMAC that includes {"selector": {"email": {"$ne": null}}} in the body. If Buffalo deserializes the body into a bson.M and directly passes it to a MongoDB Find call, the $ne operator executes as intended by the attacker, potentially returning other users’ records. Similarly, query endpoints that append user input to a selector string (e.g., collection.Find(bson.M{"_id": id})) can be abused if id is attacker-controlled and not type-asserted, enabling injection through crafted values like {"$gt": ""} when the field is numeric.
Because HMAC verification runs before application logic, developers may trust headers or payload fields implicitly. In Buffalo, a common pattern is to bind incoming JSON to a struct or map and then pass it to a service that builds queries. If the binding is too permissive (e.g., using json.Unmarshal into interface{} or using query parameters to build selectors), and the service does not enforce strict schema validation, the signed request becomes a vector for NoSQL Injection. The framework does not automatically sanitize or parameterize queries; it is the developer’s responsibility to ensure that even authenticated inputs are validated, type-checked, and constrained.
An additional risk occurs with timestamp or nonce replay when HMACs include timestamps without server-side freshness checks. An attacker can capture a valid request and replay it with modified query parameters that the server trusts due to the valid signature. If the server uses these parameters in NoSQL filters without normalization (e.g., directly using a string category in a selector), injection becomes feasible. This is especially relevant for endpoints that expose filters over collections and use HMAC to prevent tampering but do not validate the content of filtered fields.
Hmac Signatures-Specific Remediation in Buffalo — concrete code fixes
Remediation focuses on strict input validation, type enforcement, and avoiding direct use of untrusted data in query construction, even when an HMAC is present. Treat authenticated data as untrusted for query building; validate, cast, and use allowlists.
- Validate and bind to typed structs instead of raw maps or interfaces. Define explicit request structs so only expected fields are accepted and types are enforced.
- Use allowlists for known-safe values (e.g., status, sort direction) and reject any unexpected operators such as
$ne,$gt,$inin user-controlled selector fields. - Normalize and sanitize string inputs: trim, limit length, and escape special characters before using them in query selectors.
- Do not directly concatenate user input into query strings or BSON documents; use parameterized queries or builder methods that enforce schema constraints.
- Verify HMAC after deserialization, and ensure signature covers all data used in query construction, including timestamps and nonces, and enforce short time windows and one-time nonces to prevent replay.
Example 1: Unsafe usage with HMAC verification in Buffalo
In this example, a Buffalo action accepts a JSON body and an HMAC header, verifies the signature, and then directly uses the body to query MongoDB. This pattern is vulnerable to NoSQL Injection because the body is bound to interface{} and passed as-is to the query.
func (v ItemsController) Show(c buffalo.Context) error {
sig := c.Request().Header.Get("X-Signature")
body, _ := ioutil.ReadAll(c.Request().Body)
if !verifyHMAC(body, sig, secret) {
return c.Error(401, errors.New("invalid signature"))
}
var filter interface{}
json.Unmarshal(body, &filter)
var results []models.Item
c.DB().Find(&results, filter) // Unsafe: filter from untrusted data
return c.Render(200, r.JSON(results))
}
Example 2: Safe remediation with typed struct and allowlist
This version defines a typed request struct, validates the HMAC, checks allowed fields and values, and builds a clean BSON selector. It prevents injection by never passing raw user data into the query.
type ItemFilter struct {
Status string `json:"status" validate="oneof=active archived"`
Name string `json:"name"`
Limit int `json:"limit" validate="min=1,max=100"`
SortDir string `json:"sort" validate="oneof=asc desc"`
}
func verifyHMAC(body []byte, sig string, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(sig))
}
func (v ItemsController) SafeShow(c buffalo.Context) error {
sig := c.Request().Header.Get("X-Signature")
body, _ := ioutil.ReadAll(c.Request().Body)
if !verifyHMAC(body, sig, secret) {
return c.Error(401, errors.New("invalid signature"))
}
var f ItemFilter
if err := bindAndValidate(c, &f); err != nil {
return c.Error(400, err)
}
selector := bson.M{}
if f.Name != "" {
selector["name"] = f.Name
}
if f.Status != "" {
selector["status"] = f.Status
}
opts := []options.FindOptions{}
if f.SortDir == "desc" {
opts = append(opts, options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}))
}
var results []models.Item
cursor, err := c.DB().Find(context.Background(), selector, opts...)
if err != nil {
return c.Error(500, err)
}
cursor.All(context.Background(), &results)
return c.Render(200, r.JSON(results))
}
Example 3: HMAC replay protection with timestamp
Include a timestamp in the signed payload and reject requests with timestamps outside an allowed window. This prevents replay attacks that could reuse a valid HMAC to inject malicious query fragments.
type SignedRequest struct {
Timestamp int64 `json:"ts"`
Payload string `json:"payload"`
Signature string `json:"signature"`
}
func (v WebhooksController) Handle(c buffalo.Context) error {
var req SignedRequest
if bindErr := c.Bind(&req); bindErr != nil {
return c.Error(400, bindErr)
}
if !verifyHMAC([]byte(req.Payload), req.Signature, secret) {
return c.Error(401, errors.New("invalid signature"))
}
if math.Abs(float64(time.Now().Unix()-req.Timestamp)) > 30 {
return c.Error(400, errors.New("stale timestamp"))
}
var payload map[string]interface{}
json.Unmarshal([]byte(req.Payload), &payload)
// Build selector safely, avoiding raw injection
selector := buildSafeSelector(payload)
var items []models.Item
c.DB().Find(&items, selector)
return c.Render(200, r.JSON(items))
}