Skip to content

Add open sink os sinks and os.path sinks #38

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 11 commits into from
Aug 6, 2024
2 changes: 2 additions & 0 deletions aikido_firewall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def protect(module="any", server=True):
import aikido_firewall.sinks.mysqlclient
import aikido_firewall.sinks.pymongo
import aikido_firewall.sinks.psycopg2
import aikido_firewall.sinks.builtins
import aikido_firewall.sinks.os
import aikido_firewall.sinks.http_client
import aikido_firewall.sinks.socket

Expand Down
8 changes: 8 additions & 0 deletions aikido_firewall/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,13 @@ def __init__(self, message="You are rate limited by Aikido firewall."):
self.message = message


class AikidoPathTraversal(AikidoException):
"""Exception because of a path traversal"""

def __init__(self, message="This is a path traversal attack, halted by Aikido."):
super().__init__(self, message)
self.message = message


class AikidoSSRF(AikidoException):
"""Exception because of SSRF"""
47 changes: 47 additions & 0 deletions aikido_firewall/sinks/builtins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
Sink module for `builtins`, python's built-in function
"""

import copy
import importhook
from aikido_firewall.helpers.logging import logger
from aikido_firewall.vulnerabilities.path_traversal.check_context_for_path_traversal import (
check_context_for_path_traversal,
)
from aikido_firewall.context import get_current_context
from aikido_firewall.background_process import get_comms
from aikido_firewall.errors import AikidoPathTraversal


@importhook.on_import("builtins")
def on_builtins_import(builtins):
"""
Hook 'n wrap on `builtins`, python's built-in functions
Our goal is to wrap the open() function, which you use when opening files
Returns : Modified builtins object
"""
modified_builtins = importhook.copy_module(builtins)

former_open = copy.deepcopy(builtins.open)

def aikido_new_open(*args, **kwargs):
logger.debug("`builtins` wrapper, filepath : `%s`;", args[0])
context = get_current_context()
if not context:
return former_open(*args, **kwargs)
result = check_context_for_path_traversal(
filename=args[0], operation="builtins.open", context=context
)
if len(result) != 0:
get_comms().send_data_to_bg_process("ATTACK", (result, context))
should_block_res = get_comms().send_data_to_bg_process(
action="READ_PROPERTY", obj="block", receive=True
)
if should_block_res["success"] and should_block_res["data"]:
raise AikidoPathTraversal()
return former_open(*args, **kwargs)

# pylint: disable=no-member
setattr(builtins, "open", aikido_new_open)
logger.debug("Wrapped `builtins` module")
return modified_builtins
96 changes: 96 additions & 0 deletions aikido_firewall/sinks/os.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Sink module for python's `os`
"""

import copy
import importhook
from aikido_firewall.helpers.logging import logger
from aikido_firewall.vulnerabilities.path_traversal.check_context_for_path_traversal import (
check_context_for_path_traversal,
)
from aikido_firewall.context import get_current_context
from aikido_firewall.background_process import get_comms
from aikido_firewall.errors import AikidoPathTraversal

# File functions :
OS_FILE_FUNCTIONS = [
"access",
"chmod",
"chown",
"lstat",
"mkdir",
"listdir",
"readlink",
"unlink",
"rename",
"rmdir",
"remove",
"symlink",
"stat",
"utime",
"link",
"makedirs",
"walk",
]
OS_PATH_FUNCTIONS = [
"exists",
"realpath",
"getsize",
"getmtime",
"getatime",
"getctime",
]
# os.path.join(path, *paths) is not wrapped


def generate_aikido_function(op, former_func):
"""
Returns a generated aikido function given an operation
and the previous function
"""

def aikido_new_func(*args, op=op, former_func=former_func, **kwargs):
logger.debug("`os` wrapper, filepath : `%s`; OP : `%s`", args[0], op)
context = get_current_context()
if not context:
return former_func(*args, **kwargs)
result = check_context_for_path_traversal(
filename=args[0], operation=f"os.{op}", context=context
)
if len(result) != 0:
get_comms().send_data_to_bg_process("ATTACK", (result, context))
should_block_res = get_comms().send_data_to_bg_process(
action="READ_PROPERTY", obj="block", receive=True
)
if should_block_res["success"] and should_block_res["data"]:
raise AikidoPathTraversal()
return former_func(*args, **kwargs)

return aikido_new_func


@importhook.on_import("os")
def on_os_import(os):
"""
Hook 'n wrap on `os`, python's built-in functions
Our goal is to wrap the open() function, which you use when opening files
Returns : Modified os object
"""
modified_os = importhook.copy_module(os)
for op in OS_FILE_FUNCTIONS:
# Wrap os. functions
former_func = copy.deepcopy(getattr(os, op))
aikido_new_func = generate_aikido_function(op, former_func)
setattr(os, op, aikido_new_func)
setattr(modified_os, op, aikido_new_func)

for op in OS_PATH_FUNCTIONS:
# Wrap os.path functions
former_func = copy.deepcopy(getattr(os.path, op))
aikido_new_func = generate_aikido_function(f"path.{op}", former_func)
setattr(os.path, op, aikido_new_func)
# pylint: disable=no-member
setattr(modified_os.path, op, aikido_new_func)

logger.debug("Wrapped `os` module")
return modified_os
11 changes: 11 additions & 0 deletions sample-apps/flask-mysql/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ def create_dog():
connection.commit()
return f'Dog {dog_name} created successfully'


@app.route("/open_file", methods=['GET'])
def show_open_file_form():
return render_template('open_file.html')

@app.route("/open_file", methods=['POST'])
def open_file():
filepath = request.form['filepath']
file = open(filepath, 'r', encoding='utf-8')
return file.read()

@app.route("/request", methods=['GET'])
def show_request_page():
return render_template('request.html')
Expand Down
17 changes: 17 additions & 0 deletions sample-apps/flask-mysql/templates/open_file.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Open file</title>
</head>
<body>
<h1>Open file</h1>
<form method="post">
<label for="filepath">File path :</label>
<input type="text" id="filepath" name="filepath" required>
<button type="submit">Open file</button>
</form>
</body>
</html>
Loading