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