diff --git a/README.md b/README.md index 954d9e5..b0ec794 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![](pyrasp.png)

- version 0.6.2 + version 0.7.0 A project by ParaCyberBellum @@ -20,6 +20,7 @@ One specificity of `pyrasp` relies on the fact that it does not use signatures. # Documentation [Full documentation](https://paracyberbellum.gitbook.io/pyrasp)
[Release Notes](https://github.com/rbidou/pyrasp/blob/main/RELEASE-NOTES.md) +
[Web Site](https://pyrasp.paracyberbellum.io) # Contacts Renaud Bidou - renaud@paracyberbellum.io diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ee8c114..5f23edc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,17 @@ +# v0.7.0 + +## New features +- PyRASP classes API + +## Improvements +- **Improved ML engines for SQL Injection and XSS detection** + - Default SQL Injection detection probabilities raised to 0.85 + - Default XSS detection probabilities raised to 0.70 +- Attack payloads are now base64 encoded in logs + +## Bug fix +- Flask agent was still processing page, even if attack was detected + # v0.6.2 ## New features diff --git a/pyproject.toml b/pyproject.toml index e8d3678..780af4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pyrasp" -version = "0.6.2" +version = "0.7.0dev2" authors = [ { name = "Renaud Bidou", email = "renaud@paracyberbellum.io" } ] @@ -24,8 +24,9 @@ dependencies = [ ] [project.urls] -Homepage = "https://github.com/rbidou/pyrasp" +Homepage = "https://pyrasp.paracyberbellum.io" Documentation = "https://paracyberbellum.gitbook.io/pyrasp" +GitHub = "https://github.com/rbidou/pyrasp" [tool.setuptools.package-data] pyrasp = ["data/*"] diff --git a/pyrasp/data/sqli_model-1.0.0 b/pyrasp/data/sqli_model-1.0.0 deleted file mode 100644 index ed4a27e..0000000 Binary files a/pyrasp/data/sqli_model-1.0.0 and /dev/null differ diff --git a/pyrasp/data/sqli_model-1.1.0 b/pyrasp/data/sqli_model-1.1.0 deleted file mode 100644 index 9f957d9..0000000 Binary files a/pyrasp/data/sqli_model-1.1.0 and /dev/null differ diff --git a/pyrasp/data/sqli_model-2.0.0 b/pyrasp/data/sqli_model-2.0.0 new file mode 100644 index 0000000..bef41c8 Binary files /dev/null and b/pyrasp/data/sqli_model-2.0.0 differ diff --git a/pyrasp/data/xss_model-1.1.0 b/pyrasp/data/xss_model-1.1.0 deleted file mode 100644 index 6eb2068..0000000 Binary files a/pyrasp/data/xss_model-1.1.0 and /dev/null differ diff --git a/pyrasp/data/xss_model-1.2.0 b/pyrasp/data/xss_model-1.2.0 deleted file mode 100644 index ef3edb4..0000000 Binary files a/pyrasp/data/xss_model-1.2.0 and /dev/null differ diff --git a/pyrasp/data/xss_model-2.0.0 b/pyrasp/data/xss_model-2.0.0 new file mode 100644 index 0000000..ea717bc Binary files /dev/null and b/pyrasp/data/xss_model-2.0.0 differ diff --git a/pyrasp/pyrasp.py b/pyrasp/pyrasp.py index 010c856..1c83108 100644 --- a/pyrasp/pyrasp.py +++ b/pyrasp/pyrasp.py @@ -1,4 +1,4 @@ -VERSION = '0.6.2' +VERSION = '0.7.0' from pprint import pprint import time @@ -22,7 +22,7 @@ # Flask try: - from flask import request + from flask import request, redirect, url_for from flask import Response as FlaskResponse from flask.wrappers import Response as FlaskResponseType from werkzeug.utils import import_string @@ -308,6 +308,17 @@ class PyRASP(): 'errors': 0, 'attacks': 0 } + + # API DATA + API_CONFIG = {} + API_BLACKLIST = [] + API_STATUS = { + 'version': '', + 'blacklist': 0, + 'xss_loaded': False, + 'sqli_loaded': False, + 'config': 'Default' + } #################################################### # CONSTRUCTOR & DESTRUCTOR @@ -362,6 +373,8 @@ def __init__(self, app = None, app_name = None, hosts = [], conf = None, key = N if not self.load_cloud_config(): self.print_screen('[!] Could not load configuration from cloud server. Running default configuration.', init=True, new_line_up = True) + else: + self.API_STATUS['config'] = 'Cloud' # Load configuration file if not conf is None: @@ -370,7 +383,8 @@ def __init__(self, app = None, app_name = None, hosts = [], conf = None, key = N self.CONF_FILE = os.environ.get('PYRASP_CONF') if not self.CONF_FILE is None: - self.load_file_config(self.CONF_FILE) + if self.load_file_config(self.CONF_FILE): + self.API_STATUS['config'] = 'Local' # Default config customization from if all([ @@ -462,6 +476,10 @@ def __init__(self, app = None, app_name = None, hosts = [], conf = None, key = N else: self.print_screen('[+] SQLI model loaded', init=True, new_line_up = False) + # Agent status + self.API_STATUS['version'] = VERSION + self.API_STATUS['xss_loaded'] = xss_model_loaded + self.API_STATUS['sqli_loaded'] = sqli_model_loaded # AWS, GCP & Azure if self.PLATFORM in CLOUD_FUNCTIONS: @@ -607,8 +625,7 @@ def send_beacon(self): remove_blacklist_entries = blacklist_update.get('remove') or [] for remove_entry in remove_blacklist_entries: if remove_entry in self.BLACKLIST: - del self.BLACKLIST[remove_entry] - + del self.BLACKLIST[remove_entry] # Set configuration if not error and server_data.get('config'): @@ -750,6 +767,8 @@ def load_cloud_config(self): def load_file_config(self, conf_file): + result = True + self.print_screen(f'[+] Loading configuration from {conf_file}', init = True, new_line_up = False) try: @@ -757,13 +776,17 @@ def load_file_config(self, conf_file): config = json.load(f) except Exception as e: self.print_screen(f'[!] Error reading {conf_file}: {str(e)}', init = True, new_line_up = False) + result = False else: self.load_config(config) + + return result def load_config(self, config): # Load parameters config_params = config.get('config') or config + self.API_CONFIG = config_params for key in config_params: setattr(self, key, config_params[key]) @@ -780,6 +803,7 @@ def load_config(self, config): config_blacklist = config.get('blacklist') if config_blacklist: + self.BLACKLIST = config_blacklist return True @@ -793,28 +817,41 @@ def handle_attack(self, attack, host, request_path, source_ip, timestamp): attack_id = attack['type'] attack_check = ATTACKS_CHECKS[attack_id] attack_details = attack.get('details') or {} + attack_payload = None + if attack_details and attack_details.get('payload'): + attack_payload = attack_details['payload'] + try: + attack_payload_b64 = base64.b64encode(attack_details['payload'].encode()).decode() + attack_details['payload'] = attack_payload_b64 + except: + pass + action = None - # Generic case + # Action + ## Generic case if not attack_id == 0: action = self.SECURITY_CHECKS[attack_check] - # Blacklist + ## Blacklist else: action = 2 attack_details['action'] = action + + # Attack type if ATTACKS_CODES.get(attack_id): attack_details['codes'] = ATTACKS_CODES[attack_id] if not self.BLACKLIST_OVERRIDE and action == 2: self.blacklist_ip(source_ip, timestamp, attack_check) - + # Print screen try: - self.print_screen(f'[!] {ATTACKS[attack_id]}: {attack["details"]["location"]} -> {attack["details"]["payload"]}') + self.print_screen(f'[!] {ATTACKS[attack_id]}: {attack["details"]["location"]} -> {attack_payload}') except: self.print_screen(f'[!] Attack - No details') + # Log if self.LOG_ENABLED: self.log_security_event(attack_check, source_ip, None, attack_details) @@ -1735,6 +1772,26 @@ def check_pattern(self, text, pattern, match_type): return match + #################################################### + # API + #################################################### + + def get_config(self): + + return self.API_CONFIG + + def get_blacklist(self): + + self.API_BLACKLIST = [ i for i in self.BLACKLIST ] + + return self.API_BLACKLIST + + def get_status(self): + + self.API_STATUS['blacklist'] = len(self.BLACKLIST) + + return self.API_STATUS + class FlaskRASP(PyRASP): CURRENT_ATTACKS = {} @@ -1768,8 +1825,9 @@ def before_request_callback(): # Send attack status in status code for handling by @after_request if not attack == None: attack_id = '::'.join([host, request_method, request_path, source_ip]) - self.CURRENT_ATTACKS[attack_id] = attack - + self.CURRENT_ATTACKS[attack_id] = attack + return FlaskResponse() + # Outgoing responses def set_after_security_checks(self, app): @app.after_request diff --git a/pyrasp/pyrasp_data.py b/pyrasp/pyrasp_data.py index 04c66f4..38f992b 100644 --- a/pyrasp/pyrasp_data.py +++ b/pyrasp/pyrasp_data.py @@ -3,8 +3,8 @@ # DATA_VERSION = '1.1.0' -XSS_MODEL_VERSION = '1.2.0' -SQLI_MODEL_VERSION = '1.1.0' +XSS_MODEL_VERSION = '2.0.0' +SQLI_MODEL_VERSION = '2.0.0' # # PLATFORMS @@ -210,10 +210,10 @@ "EXCEPTIONS" : [], - "XSS_PROBA" : 0.60, + "XSS_PROBA" : 0.70, "MIN_XSS_LEN": 16, - "SQLI_PROBA" : 0.725, + "SQLI_PROBA" : 0.85, "MIN_SQLI_LEN": 8, "DLP_PHONE_NUMBERS": False,