Skip to content

Aik 3191 : Also get django context #13

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

Merged
merged 6 commits into from
Jul 19, 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
6 changes: 4 additions & 2 deletions aikido_firewall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
load_dotenv()


def protect():
def protect(module="any"):
"""Start Aikido agent"""
# Import sources
import aikido_firewall.sources.django
import aikido_firewall.sources.flask

if module != "django":
import aikido_firewall.sources.flask

# Import sinks
import aikido_firewall.sinks.pymysql
Expand Down
37 changes: 31 additions & 6 deletions aikido_firewall/context/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import threading

SUPPORTED_SOURCES = ["django", "flask"]
local = threading.local()


Expand All @@ -15,21 +16,45 @@ def get_current_context():
return None


def parse_headers(headers):
"""Parse EnvironHeaders object into a dict"""
if isinstance(headers, dict):
return headers
return dict(zip(headers.keys(), headers.values()))


class Context:
"""
A context object, it stores everything that is important
for vulnerability detection
"""

def __init__(self, req):
def __init__(self, req, source):
if not source in SUPPORTED_SOURCES:
raise ValueError(f"Source {source} not supported")
self.source = source
self.method = req.method
self.headers = parse_headers(req.headers)
if source == "flask":
self.set_flask_attrs(req)
elif source == "django":
self.set_django_attrs(req)

def set_django_attrs(self, req):
"""set properties that are specific to django"""
self.remote_address = req.META.get("REMOTE_ADDR")
self.url = req.build_absolute_uri()
self.body = dict(req.POST)
self.query = dict(req.GET)
self.cookies = req.COOKIES

def set_flask_attrs(self, req):
"""Set properties that are specific to flask"""
self.remote_address = req.remote_addr
self.url = req.url
self.body = req.form
self.headers = req.headers
self.query = req.args
self.cookies = req.cookies
self.source = "flask"
self.body = req.form.to_dict()
self.query = req.args.to_dict()
self.cookies = req.cookies.to_dict()

def __reduce__(self):
return (
Expand Down
68 changes: 48 additions & 20 deletions aikido_firewall/context/init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,64 @@
from aikido_firewall.context import Context, get_current_context


@pytest.fixture
def sample_request():
# Mock a sample request object for testing
class Request:
def __init__(self):
self.method = "GET"
self.remote_addr = "127.0.0.1"
self.url = "/test"
self.form = {}
self.headers = {}
self.args = {}
self.cookies = {}

return Request()


def test_get_current_context_no_context():
# Test get_current_context() when no context is set
assert get_current_context() is None


def test_set_as_current_context(sample_request):
def test_set_as_current_context(mocker):
# Test set_as_current_context() method
context = Context(sample_request)
sample_request = mocker.MagicMock()
context = Context(sample_request, "flask")
context.set_as_current_context()
assert get_current_context() == context


def test_get_current_context_with_context(sample_request):
def test_get_current_context_with_context(mocker):
# Test get_current_context() when a context is set
context = Context(sample_request)
sample_request = mocker.MagicMock()
context = Context(sample_request, "flask")
context.set_as_current_context()
assert get_current_context() == context


def test_context_init_flask(mocker):
req = mocker.MagicMock()
req.method = "GET"
req.remote_addr = "127.0.0.1"
req.url = "http://example.com"
req.form.to_dict.return_value = {"key": "value"}
req.headers = {"Content-Type": "application/json"}
req.args.to_dict.return_value = {"key": "value"}
req.cookies.to_dict.return_value = {"cookie": "value"}

context = Context(req, "flask")
assert context.source == "flask"
assert context.method == "GET"
assert context.remote_address == "127.0.0.1"
assert context.url == "http://example.com"
assert context.body == {"key": "value"}
assert context.headers == {"Content-Type": "application/json"}
assert context.query == {"key": "value"}
assert context.cookies == {"cookie": "value"}


def test_context_init_django(mocker):
req = mocker.MagicMock()
req.method = "POST"
req.META.get.return_value = "127.0.0.1"
req.build_absolute_uri.return_value = "http://example.com"
req.POST = {"key": "value"}
req.headers = {"Content-Type": "application/json"}
req.GET = {"key": "value"}
req.COOKIES = {"cookie": "value"}

context = Context(req, "django")
assert context.source == "django"
assert context.method == "POST"
assert context.remote_address == "127.0.0.1"
assert context.url == "http://example.com"
assert context.body == {"key": "value"}
assert context.headers == {"Content-Type": "application/json"}
assert context.query == {"key": "value"}
assert context.cookies == {"cookie": "value"}
3 changes: 3 additions & 0 deletions aikido_firewall/middleware/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from aikido_firewall.helpers.logging import logger
from aikido_firewall.context import Context


class AikidoMiddleware:
Expand All @@ -16,6 +17,8 @@ def __init__(self, get_response):

def __call__(self, request, *args, **kwargs):
logger.debug("Aikido middleware for `django` was called : __call__")
context = Context(request, "django")
context.set_as_current_context()
return self.get_response(request)

def process_exception(self, request, exception):
Expand Down
2 changes: 1 addition & 1 deletion aikido_firewall/sources/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self):
def dispatch(self, request, call_next):
"""Dispatch function"""
logger.debug("Aikido middleware for `flask` was called")
context = Context(request)
context = Context(request, "flask")
context.set_as_current_context()

response = call_next(request)
Expand Down
2 changes: 2 additions & 0 deletions sample-apps/django-mysql/manage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import aikido_firewall # Aikido module
aikido_firewall.protect("django")

import os
import sys

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<h1>Create a Dog</h1>
<form action="/create" method="post">
<label for="dog_name">Dog Name:</label>
<input type="text" id="dog_name" name="dog_name" required>
<button type="submit">Create Dog</button>
</form>
5 changes: 3 additions & 2 deletions sample-apps/django-mysql/sample_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
urlpatterns = [
path("", views.index, name="index"),
path("dogpage/<int:dog_id>", views.dog_page, name="dog_page"),
path("create/<dog_name>", views.create_dogpage, name="create"),
]
path("create/<dog_name>", views.create_dogpage, name="create_old"),
path("create", views.create, name="create")
]
6 changes: 5 additions & 1 deletion sample-apps/django-mysql/sample_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ def create_dogpage(request, dog_name):
query = 'INSERT INTO sample_app_dogs (dog_name, dog_boss) VALUES ("%s", "N/A")' % dog_name
print("QUERY : ", query)
cursor.execute(query)
return HttpResponse("Dog page created")
return HttpResponse("Dog page created")

def create(request):
template = loader.get_template("app/create_dog.html")
return HttpResponse(template.render({}, request))
Loading