Why is this an issue?
NoSQL injections occur when an application retrieves untrusted data and inserts it into a database query without sanitizing it first.
What is the potential impact?
In the context of a web application that is vulnerable to NoSQL injection:
After discovering the injection point, attackers insert data into
the vulnerable field to execute malicious commands in the affected databases.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.
Identity spoofing and data leakage
In the context of simple query logic breakouts, a malicious database query enables privilege escalation or direct data leakage from one or more
databases.
This threat is the most widespread impact.
Data deletion and denial of service
The malicious query makes it possible for the attacker to delete data in the affected databases.
This threat is particularly insidious if the
attacked organization does not maintain a disaster recovery plan (DRP) as missing data can disrupt the regular operations of an organization.
Chaining NoSQL injections with other vulnerabilities
Attackers who exploit NoSQL injections rely on other vulnerabilities to maximize their profits.
Most of the time, organizations overlook some
defense in depth measures because they assume attackers cannot reach certain points in the infrastructure. This misbehavior can lead to multiple
attacks with great impact:
- When secrets are stored unencrypted in databases: Secrets can be exfiltrated and lead to compromise of other components.
- If server-side OS and/or database permissions are misconfigured, injection can lead to remote code execution (RCE).
How to fix it in Amazon DynamoDB
Code examples
The following code is vulnerable to NoSQL injection because untrusted data is concatenated to the FilterExpression
value. This
expression determines which items within the results should be returned.
A malicious HTTP request containing the following query parameter values username=admin&password=size(password) or
size(password)=size(password)
would allow an attacker to manipulate the returned data and bypass authentication.
Noncompliant code example
@app.route('/login')
def login():
dynamodb = AWS_SESSION.client('dynamodb')
username = request.args["username"]
password = request.args["password"]
dynamodb.scan(
FilterExpression= "username = " + username + " and password = " + password, # Noncompliant
TableName="users",
ProjectionExpression="username"
)
Compliant solution
@app.route('/login')
def login():
dynamodb = AWS_SESSION.client('dynamodb')
username = request.args["username"]
password = request.args["password"]
dynamodb.query(
KeyConditionExpression= "username = :u",
FilterExpression= "password = :p",
ExpressionAttributeValues={
":u": { 'S': username },
":p": { 'S': password }
},
TableName="users",
ProjectionExpression="username"
)
How does this work?
As a rule of thumb, the approach to protect against injection vulnerabilities is to ensure that untrusted data cannot break out of the initially
intended logic.
When using DynamoDB with Boto3, the best way to do so is by using expression attributes as placeholders (:placeholder
). It will end up
replacing the attribute with the value defined in ExpressionAttributeValues
and prevent any alteration of the original query logic. The
compliant code example uses such an approach.
When possible, use the method query
over scan
as it disallows the OR
operator on the
KeyConditionExpression
attribute and therefore reduces the attack surface. It also optimizes speed and costs.
This logic applies both when using the DynamoDB.Client
and the DynamoDB.Table
class, though the syntax differs for the
latter, and the ExpressionAttributeValues
would look like the following:
ExpressionAttributeValues={
":u": username,
":p": password
}
Although injection can occur on all the query or scan Expression
attributes, its most severe impact occurs in the
FilterExpression
.
Resources
Articles & blog posts
Standards