Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AIK-3826 NoSQL bypass with get_json(Force=True) #269

Merged
merged 7 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions aikido_zen/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import contextvars
import json
from json import JSONDecodeError
from urllib.parse import parse_qs

from aikido_zen.helpers.build_route_from_url import build_route_from_url
Expand Down Expand Up @@ -99,6 +100,12 @@ def set_body(self, body):
self.body = body
except (TypeError, OverflowError):
self.body = None
if isinstance(self.body, str) and self.body.startswith("{"):
# Might be JSON:
try:
self.body = json.loads(self.body)
except JSONDecodeError:
pass

def get_route_metadata(self):
"""Returns a route_metadata object"""
Expand Down
26 changes: 26 additions & 0 deletions aikido_zen/context/init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,29 @@ def test_set_bytestring():
def test_set_none():
context = Context(req=basic_wsgi_req, body=None, source="flask")
assert context.body is None


def test_set_valid_nested_json_string():
context = Context(req=basic_wsgi_req, body=None, source="flask")
context.set_body('{"key": {"nested_key": "nested_value"}}')
assert context.body == {"key": {"nested_key": "nested_value"}}


def test_set_invalid_json_with_unmatched_quotes():
context = Context(req=basic_wsgi_req, body=None, source="flask")

context.set_body('{"key": "value\'s}')
assert context.body == '{"key": "value\'s}' # Should remain as string


def test_set_valid_json_with_array():
context = Context(req=basic_wsgi_req, body=None, source="flask")

context.set_body('{"key": [1, 2, 3]}')
assert context.body == {"key": [1, 2, 3]}


def test_set_valid_json_with_special_characters():
context = Context(req=basic_wsgi_req, body=None, source="flask")
context.set_body('{"key": "value with special characters !@#$%^&*()"}')
assert context.body == {"key": "value with special characters !@#$%^&*()"}
4 changes: 1 addition & 3 deletions aikido_zen/sources/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ def extract_and_save_data_from_flask_request(req):
try:
context = get_current_context()
if context:
if req.is_json:
context.set_body(req.get_json())
elif req.form:
if req.form:
context.set_body(req.form)
else:
context.set_body(req.data.decode("utf-8"))
Expand Down
28 changes: 28 additions & 0 deletions end2end/flask_mongo_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import time
import pytest
import requests
Expand Down Expand Up @@ -98,3 +99,30 @@ def test_dangerous_auth_nofw():
assert res.ok
assert res.text == "Dog with name bobby_tables authenticated successfully"
assert res.status_code == 200


def test_dangerous_auth_fw_force():
dog_name = "bobby_tables"
pswd = {"$ne": ""}
json_data = json.dumps({'dog_name': dog_name, "pswd": pswd})
res = requests.post(post_json_url_fw + "_force", data=json_data)

assert not res.ok
assert res.status_code == 500

time.sleep(5) # Wait for attack to be reported
events = fetch_events_from_mock("http://localhost:5000")
attacks = filter_on_event_type(events, "detected_attack")

assert len(attacks) == 2
del attacks[0]["attack"]["stack"]
assert attacks[0]["attack"] == {
"blocked": True,
"kind": "nosql_injection",
'metadata': {'filter': '{"dog_name": "bobby_tables", "pswd": {"$ne": ""}}'},
'operation': "pymongo.collection.Collection.find",
'pathToPayload': ".pswd",
'payload': '{"$ne": ""}',
'source': "body",
'user': None
}
14 changes: 14 additions & 0 deletions sample-apps/flask-mongo/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,17 @@ def post_auth():
return f'Dog with name {dog_name} authenticated successfully'
else:
return f'Auth failed'

@app.route("/auth_force", methods=['POST'])
def post_auth2():
data = request.get_json(force=True)
dog_info = {
'dog_name': data.get('dog_name'),
'pswd': data.get('pswd')
}
dog = mongo.db.dogs.find_one(dog_info)
if dog:
dog_name = dog["dog_name"]
return f'Dog with name {dog_name} authenticated successfully'
else:
return f'Auth failed'