Skip to content

Commit ba6f49b

Browse files
committed
v.0.7.0
1 parent 96c839e commit ba6f49b

File tree

11 files changed

+92
-18
lines changed

11 files changed

+92
-18
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
![](pyrasp.png)
22

33
<p>
4-
<img src="https://img.shields.io/badge/Version-0.6.2-green?style=for-the-badge" alt="version 0.6.2"/>
4+
<img src="https://img.shields.io/badge/Version-0.7.0-green?style=for-the-badge" alt="version 0.7.0"/>
55
<a href="https://www.paracyberbellum.io">
66
<img src="https://img.shields.io/badge/A%20project%20by-ParaCyberBellum-blue?style=for-the-badge" alt="A project by ParaCyberBellum"/>
77
</a>
@@ -20,6 +20,7 @@ One specificity of `pyrasp` relies on the fact that it does not use signatures.
2020
# Documentation
2121
[Full documentation](https://paracyberbellum.gitbook.io/pyrasp)
2222
<br>[Release Notes](https://github.com/rbidou/pyrasp/blob/main/RELEASE-NOTES.md)
23+
<br>[Web Site](https://pyrasp.paracyberbellum.io)
2324

2425
# Contacts
2526
Renaud Bidou - renaud@paracyberbellum.io

RELEASE-NOTES.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# v0.7.0
2+
3+
## New features
4+
- PyRASP classes API
5+
6+
## Improvements
7+
- **Improved ML engines for SQL Injection and XSS detection**
8+
- Default SQL Injection detection probabilities raised to 0.85
9+
- Default XSS detection probabilities raised to 0.70
10+
- Attack payloads are now base64 encoded in logs
11+
12+
## Bug fix
13+
- Flask agent was still processing page, even if attack was detected
14+
115
# v0.6.2
216

317
## New features

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pyrasp"
3-
version = "0.6.2"
3+
version = "0.7.0dev2"
44
authors = [
55
{ name = "Renaud Bidou", email = "renaud@paracyberbellum.io" }
66
]
@@ -24,8 +24,9 @@ dependencies = [
2424
]
2525

2626
[project.urls]
27-
Homepage = "https://github.com/rbidou/pyrasp"
27+
Homepage = "https://pyrasp.paracyberbellum.io"
2828
Documentation = "https://paracyberbellum.gitbook.io/pyrasp"
29+
GitHub = "https://github.com/rbidou/pyrasp"
2930

3031
[tool.setuptools.package-data]
3132
pyrasp = ["data/*"]

pyrasp/data/sqli_model-1.0.0

-393 KB
Binary file not shown.

pyrasp/data/sqli_model-1.1.0

-472 KB
Binary file not shown.

pyrasp/data/sqli_model-2.0.0

1.25 MB
Binary file not shown.

pyrasp/data/xss_model-1.1.0

-1020 KB
Binary file not shown.

pyrasp/data/xss_model-1.2.0

-1.06 MB
Binary file not shown.

pyrasp/data/xss_model-2.0.0

3.76 MB
Binary file not shown.

pyrasp/pyrasp.py

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = '0.6.2'
1+
VERSION = '0.7.0'
22

33
from pprint import pprint
44
import time
@@ -22,7 +22,7 @@
2222

2323
# Flask
2424
try:
25-
from flask import request
25+
from flask import request, redirect, url_for
2626
from flask import Response as FlaskResponse
2727
from flask.wrappers import Response as FlaskResponseType
2828
from werkzeug.utils import import_string
@@ -308,6 +308,17 @@ class PyRASP():
308308
'errors': 0,
309309
'attacks': 0
310310
}
311+
312+
# API DATA
313+
API_CONFIG = {}
314+
API_BLACKLIST = []
315+
API_STATUS = {
316+
'version': '',
317+
'blacklist': 0,
318+
'xss_loaded': False,
319+
'sqli_loaded': False,
320+
'config': 'Default'
321+
}
311322

312323
####################################################
313324
# CONSTRUCTOR & DESTRUCTOR
@@ -362,6 +373,8 @@ def __init__(self, app = None, app_name = None, hosts = [], conf = None, key = N
362373

363374
if not self.load_cloud_config():
364375
self.print_screen('[!] Could not load configuration from cloud server. Running default configuration.', init=True, new_line_up = True)
376+
else:
377+
self.API_STATUS['config'] = 'Cloud'
365378

366379
# Load configuration file
367380
if not conf is None:
@@ -370,7 +383,8 @@ def __init__(self, app = None, app_name = None, hosts = [], conf = None, key = N
370383
self.CONF_FILE = os.environ.get('PYRASP_CONF')
371384

372385
if not self.CONF_FILE is None:
373-
self.load_file_config(self.CONF_FILE)
386+
if self.load_file_config(self.CONF_FILE):
387+
self.API_STATUS['config'] = 'Local'
374388

375389
# Default config customization from
376390
if all([
@@ -462,6 +476,10 @@ def __init__(self, app = None, app_name = None, hosts = [], conf = None, key = N
462476
else:
463477
self.print_screen('[+] SQLI model loaded', init=True, new_line_up = False)
464478

479+
# Agent status
480+
self.API_STATUS['version'] = VERSION
481+
self.API_STATUS['xss_loaded'] = xss_model_loaded
482+
self.API_STATUS['sqli_loaded'] = sqli_model_loaded
465483

466484
# AWS, GCP & Azure
467485
if self.PLATFORM in CLOUD_FUNCTIONS:
@@ -607,8 +625,7 @@ def send_beacon(self):
607625
remove_blacklist_entries = blacklist_update.get('remove') or []
608626
for remove_entry in remove_blacklist_entries:
609627
if remove_entry in self.BLACKLIST:
610-
del self.BLACKLIST[remove_entry]
611-
628+
del self.BLACKLIST[remove_entry]
612629

613630
# Set configuration
614631
if not error and server_data.get('config'):
@@ -750,20 +767,26 @@ def load_cloud_config(self):
750767

751768
def load_file_config(self, conf_file):
752769

770+
result = True
771+
753772
self.print_screen(f'[+] Loading configuration from {conf_file}', init = True, new_line_up = False)
754773

755774
try:
756775
with open(conf_file) as f:
757776
config = json.load(f)
758777
except Exception as e:
759778
self.print_screen(f'[!] Error reading {conf_file}: {str(e)}', init = True, new_line_up = False)
779+
result = False
760780
else:
761781
self.load_config(config)
782+
783+
return result
762784

763785
def load_config(self, config):
764786

765787
# Load parameters
766788
config_params = config.get('config') or config
789+
self.API_CONFIG = config_params
767790

768791
for key in config_params:
769792
setattr(self, key, config_params[key])
@@ -780,6 +803,7 @@ def load_config(self, config):
780803
config_blacklist = config.get('blacklist')
781804

782805
if config_blacklist:
806+
783807
self.BLACKLIST = config_blacklist
784808

785809
return True
@@ -793,28 +817,41 @@ def handle_attack(self, attack, host, request_path, source_ip, timestamp):
793817
attack_id = attack['type']
794818
attack_check = ATTACKS_CHECKS[attack_id]
795819
attack_details = attack.get('details') or {}
820+
attack_payload = None
821+
if attack_details and attack_details.get('payload'):
822+
attack_payload = attack_details['payload']
823+
try:
824+
attack_payload_b64 = base64.b64encode(attack_details['payload'].encode()).decode()
825+
attack_details['payload'] = attack_payload_b64
826+
except:
827+
pass
828+
796829
action = None
797830

798-
# Generic case
831+
# Action
832+
## Generic case
799833
if not attack_id == 0:
800834
action = self.SECURITY_CHECKS[attack_check]
801-
# Blacklist
835+
## Blacklist
802836
else:
803837
action = 2
804838

805839
attack_details['action'] = action
840+
841+
# Attack type
806842
if ATTACKS_CODES.get(attack_id):
807843
attack_details['codes'] = ATTACKS_CODES[attack_id]
808844

809845
if not self.BLACKLIST_OVERRIDE and action == 2:
810846
self.blacklist_ip(source_ip, timestamp, attack_check)
811847

812-
848+
# Print screen
813849
try:
814-
self.print_screen(f'[!] {ATTACKS[attack_id]}: {attack["details"]["location"]} -> {attack["details"]["payload"]}')
850+
self.print_screen(f'[!] {ATTACKS[attack_id]}: {attack["details"]["location"]} -> {attack_payload}')
815851
except:
816852
self.print_screen(f'[!] Attack - No details')
817853

854+
# Log
818855
if self.LOG_ENABLED:
819856
self.log_security_event(attack_check, source_ip, None, attack_details)
820857

@@ -1735,6 +1772,26 @@ def check_pattern(self, text, pattern, match_type):
17351772

17361773
return match
17371774

1775+
####################################################
1776+
# API
1777+
####################################################
1778+
1779+
def get_config(self):
1780+
1781+
return self.API_CONFIG
1782+
1783+
def get_blacklist(self):
1784+
1785+
self.API_BLACKLIST = [ i for i in self.BLACKLIST ]
1786+
1787+
return self.API_BLACKLIST
1788+
1789+
def get_status(self):
1790+
1791+
self.API_STATUS['blacklist'] = len(self.BLACKLIST)
1792+
1793+
return self.API_STATUS
1794+
17381795
class FlaskRASP(PyRASP):
17391796

17401797
CURRENT_ATTACKS = {}
@@ -1768,8 +1825,9 @@ def before_request_callback():
17681825
# Send attack status in status code for handling by @after_request
17691826
if not attack == None:
17701827
attack_id = '::'.join([host, request_method, request_path, source_ip])
1771-
self.CURRENT_ATTACKS[attack_id] = attack
1772-
1828+
self.CURRENT_ATTACKS[attack_id] = attack
1829+
return FlaskResponse()
1830+
17731831
# Outgoing responses
17741832
def set_after_security_checks(self, app):
17751833
@app.after_request

0 commit comments

Comments
 (0)