Skip to main content

NoSQL injection

Need

Prevention of NoSQL injection attacks

Context

  • Usage of Go 1.14 for building scalable and efficient applications
  • Usage of Gin framework for building web applications in Go
  • Usage of the MongoDB Go driver for BSON encoding and decoding
  • Usage of the MongoDB driver for Go to interact with the MongoDB database
  • Usage of MongoDB options from the go.mongodb.org/mongo-driver/mongo package for configuring MongoDB operations
  • Usage of log for logging and debugging purposes
  • Usage of net/http for creating a web server in a Node.js application

Description

Non compliant code

package main

import (
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"net/http"
)

func main() {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}

router := gin.Default()

router.POST("/users", func(c *gin.Context) {
var query bson.M
if err := c.ShouldBindJSON(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

collection := client.Database("test").Collection("users")
result, err := collection.Find(c, query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, result)
})

router.Run()
}

The above code is a simple API endpoint in Go using the Gin framework and MongoDB as a NoSQL database. It's vulnerable to NoSQL injection because it takes user input directly without any form of validation or sanitization.

The vulnerability lies in this block of code:

var query bson.M
if err := c.ShouldBindJSON(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

Here, the API endpoint is expecting a JSON input from the user, which it directly binds to a bson.M map that represents a MongoDB query. This map is then used directly in a Find operation on the users collection:

collection := client.Database("test").Collection("users")
result, err := collection.Find(c, query)

Because there's no validation or sanitization of the user's input before it's used in the query, an attacker could craft a malicious JSON input to manipulate the query, leading to a NoSQL injection attack. This could allow the attacker to view, modify, or delete data they shouldn't have access to.

Steps

  • Validate and sanitize user inputs before using them in NoSQL queries.
  • Use parameterized queries or prepared statements to prevent NoSQL injection.
  • Implement proper access control to limit the privileges of database operations.
  • Regularly update and patch the MongoDB driver to ensure the latest security fixes.
  • Implement logging and monitoring to detect any suspicious or malicious activities.
  • Educate developers about the risks of NoSQL injection and best practices for secure coding.

Compliant code

package main

import (
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"net/http"
)

type User struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}

func main() {
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}

router := gin.Default()

router.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

collection := client.Database("test").Collection("users")
result, err := collection.Find(c, bson.M{"username": user.Username, "password": user.Password})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, result)
})

router.Run()
}

The original code was vulnerable to NoSQL injection because it directly used user input to construct a MongoDB query. This could allow an attacker to manipulate the query, leading to unauthorized data access or other security issues.

The fixed code mitigates this vulnerability by:

  1. Defining a User struct that represents the expected structure of user input. This includes Username and Password fields, both of which are required. The binding:"required" tag tells Gin to automatically validate that these fields are present in the incoming JSON.

  2. Using c.ShouldBindJSON(&user) to parse the incoming JSON into a User struct. If the JSON does not match the expected structure (for example, if a required field is missing), this will return an error.

  3. Constructing the MongoDB query using the User struct. This ensures that only the expected fields are included in the query, and that they cannot be manipulated to change the structure of the query.

By validating and sanitizing user input in this way, the fixed code prevents NoSQL injection attacks. It also provides better error handling for invalid user input.

References