def rulesset_list(request, type_modsec): """ Page dedicated to show mod_security rules set list """ try: if (type_modsec == 'virtualpatching'): rulesset_list = ModSecRulesSet.objects(type_rule=type_modsec) else: rulesset_list = [] for modsecrules_set in ModSecRulesSet.objects(): if modsecrules_set.type_rule == 'wlbl': if len(ModSecRules.objects.filter(rs=modsecrules_set)): rulesset_list.append(modsecrules_set) else: rulesset_list.append(modsecrules_set) except: rulesset_list = None cluster = Cluster.objects.get() system_settings = cluster.system_settings global_conf = system_settings.global_settings if global_conf.trustwave_url and global_conf.trustwave_user: trustwave_enable = 1 else: trustwave_enable = None return render_to_response('modsec_rulesset.html', { 'rulesset_list': rulesset_list, 'trustwave_enable': trustwave_enable, 'type_modsec': type_modsec }, context_instance=RequestContext(request))
def create_wl(self, rs_name, date_rule, rule_name, rule_tpl): try: ruleset = ModSecRulesSet.objects.get(name=rs_name) except ModSecRulesSet.DoesNotExist as e: ruleset = ModSecRulesSet(name=rs_name, type_rule='vulture') ruleset.save() rule = ModSecRules.objects.filter(name=rule_name, rs=ruleset).first() if not rule: rule = ModSecRules(name=rule_name, rs=ruleset, is_enabled=True, date_rule=date_rule) env = Environment(loader=FileSystemLoader(settings.SVC_TEMPLATES_DIR + 'def_wl/')) if settings.OS == 'FreeBSD': conf = FREEBSD_TEMPLATES_CONF tpl_conf = conf.get(rule_tpl) tpl = env.get_template(tpl_conf['tpl_name']) rule.rule_content = tpl.render() rule.save() ruleset.conf = ruleset.get_conf() ruleset.save() print("Defender whitelist '" + str(rs_name) + "' successfully imported")
def import_crs(request, trustwave=None): """ View dedicated to mod_security OWASP Core Rule Set or Trustwave importation :param request: Django request object :param trustwave: If Set, import trustwave rules instead of CRS rules """ cluster = Cluster.objects.get() system_settings = cluster.system_settings global_conf = system_settings.global_settings if trustwave: if not global_conf.trustwave_url or not global_conf.trustwave_user: import_msg = '#0 - Please configure Trustwave URL & Authentication token first' return render_to_response('modsec_rulesset.html', {'import_msg': import_msg}, context_instance=RequestContext(request)) modsec_url = global_conf.trustwave_url else: modsec_url = global_conf.owasp_crs_url vulture_modsec_tmp_dir = "/tmp" file_name = vulture_modsec_tmp_dir + '/' + modsec_url.split('/')[-1] # Create Mod Security dir if it does not exist if not os.path.exists(vulture_modsec_tmp_dir): try: os.makedirs(vulture_modsec_tmp_dir) except OSError as e: import_msg = '#0 - Unable to create ModSecurity Directory. <br>Error is: ' + str( e.strerror) logger.error(import_msg + str(vulture_modsec_tmp_dir)) return render_to_response('modsec_rulesset.html', {'import_msg': import_msg}, context_instance=RequestContext(request)) # Clean previous download, if any if os.path.isfile(file_name): try: os.remove(file_name) except OSError as e: import_msg = '#0 - Unable to remove previous rules File. <br>Error is: ' + str( e.strerror) logger.error(import_msg + str(file_name)) return render_to_response('modsec_rulesset.html', {'import_msg': import_msg}, context_instance=RequestContext(request)) # Download the archive try: if trustwave: thread = TrustwaveDownload(cluster, global_conf.trustwave_user, global_conf.trustwave_url) logger.info("Starting download of trustwave rules.") thread.start() """ Try to retrieve the status of the thread """ thread.join(15.0) if thread.isAlive(): return HttpResponseRedirect( '/firewall/modsec_rules/#downloading') else: if not thread.result.get('status'): return render_to_response( 'modsec_rulesset.html', {'import_msg': thread.result.get('message')}, context_instance=RequestContext(request)) return HttpResponseRedirect('/firewall/modsec_rules/') else: r = requests.get(modsec_url, proxies=UpdateUtils.get_proxy_conf()) except requests.exceptions.RequestException as e: import_msg = '#1 - Unable to download rules file. <br>Error is: ' + str( e.message) logger.error(import_msg + str(modsec_url)) return render_to_response('modsec_rulesset.html', {'import_msg': import_msg}, context_instance=RequestContext(request)) try: f = open(file_name, 'wb') except IOError as e: import_msg = '#2 - Unable to store rules file. <br>Error is: ' + str( e.strerror) logger.error(import_msg + str(file_name)) return render_to_response('modsec_rulesset.html', {'import_msg': import_msg}, context_instance=RequestContext(request)) for chunk in r: f.write(chunk) f.close() # If we are here, we assume that the download was OK # Next: unzip the file try: zfile = zipfile.ZipFile(file_name) except zipfile.BadZipfile: import_msg = '#3 - Unable to unzip rules file' logger.error(import_msg + str(file_name)) return render_to_response('modsec_rulesset.html', {'import_msg': import_msg}, context_instance=RequestContext(request)) # Be sure to use a unique directory for extraction extract_dir = tempfile.mkdtemp(dir=vulture_modsec_tmp_dir) zfile.extractall(extract_dir) # Everything is ok, create a new rule set with a default name (can be changed after) rs = ModSecRulesSet() now = datetime.datetime.now() ts = now.strftime("%Y-%m-%d-%H%M") rs.name = "[" + str(ts) + "] OWASP_CRS" rs.type_rule = 'crs' rs.save() # Ignore useless files in archives reg = re.compile('.*\.(conf|lua|data|txt)$') reg_ignore = re.compile( ".*regression.*|.*\/util\/.*|.*activated_rules.*|.*README.*|.*INSTALL.*|.*CHANGES.*|.*gitignore.*|.*LICENSE.*|.*\.pdf$|.*\.c$|.*\.h$" ) path_match = re.match( 'https://github\.com/SpiderLabs/owasp-modsecurity-crs/archive/v([0-9|\.]+)/master\.zip', modsec_url) if os.path.exists(extract_dir + '/' + 'owasp-modsecurity-crs-' + path_match.group(1)): p = 'owasp-modsecurity-crs-' + path_match.group(1) elif os.path.exists(extract_dir + '/' + 'owasp-modsecurity-crs-' + path_match.group(1) + '-master'): p = 'owasp-modsecurity-crs-' + path_match.group(1) + '-master' else: logger.error("Could not find path for owasp rules : {}/{}".format( vulture_modsec_tmp_dir, modsec_url)) for root, dirs, files in os.walk(extract_dir + '/' + p): for file in files: if not reg.match(file) or reg_ignore.match( root) or reg_ignore.match(file): continue path_file = "%s/%s" % (root, file) # Create an entry for the rules try: with open(path_file, 'r') as content_file: content = content_file.read() # This can happen if there is an antivirus on the host except IOError: logger.debug("Unable to read file: " + str(path_file)) pass r = ModSecRules() r.rs = rs r.name = path_file.replace(extract_dir + '/' + p, '') r.rule_content = content.replace("SecComponentSignature", "#SecComponentSignature") r.is_enabled = True r.save() conf = rs.get_conf() rs.conf = conf rs.save() return HttpResponseRedirect('/firewall/modsec_rules/')
def import_scan(request, object_id=None): """ View dedicated to ModSecurity Rules Creation based on Scan results :param request: Django request object """ form = ModSecScanForm(request.POST or None) log_info = None if request.method == 'POST': f = None parser_type = None buffer = "" try: f = request.FILES['file'] parser_type = request.POST['type'] except Exception as e: pass if f and parser_type in ["zap", "qualys", "acunetix_9"]: for chunk in f.chunks(): buffer = buffer + chunk.decode('utf8') # We have results => Parse them and create ModSecurityRules results = scan_to_modsec(parser_type, buffer) if results: log_info = "" now = datetime.datetime.now() ts = now.strftime("%Y-%m-%d-%H%M") for app_name, data in results.items(): log_info = log_info + "<h2>Virtual Patching Status for application: '" + escape( app_name) + "'</h2>" + '<br/><br/>' + data[1] # Create a new rules set rs = ModSecRulesSet() rs.type_rule = "virtualpatching" rs.name = "[" + str(ts) + "] Virtual patching - " + escape( app_name) rs.save() # Create the related rule r = ModSecRules() r.name = "custom.conf" r.is_enabled = True r.rs = rs r.rule_content = data[0] r.save() conf = rs.get_conf() rs.conf = conf rs.save() else: log_info = "Sorry, something when wrong when parsing the XML report." logger.debug( "Something when wrong when parsing the XML report") # No file given, display the submit form return render_to_response('modsec_scan.html', { 'form': form, 'log_info': log_info }, context_instance=RequestContext(request))
def run(self): try: modsec_id = self.get_modsec_id() modsec_headers = { 'ModSec-unique-id': modsec_id, 'ModSec-status': "{},{}".format(self.get_modsec_status(), modsec_id), 'ModSec-key': self.trustwave_user } logger.debug("Downloading {}".format(self.trustwave_url)) response = requests.get(self.trustwave_url, proxies=UpdateUtils.get_proxy_conf(), headers=modsec_headers) logger.info("Trustwave rules {} downloaded.".format( self.trustwave_url)) content = response.content # Everything is ok, create a new rule set with a default name (can be changed after) rs = ModSecRulesSet() now = datetime.datetime.now() ts = now.strftime("%Y-%m-%d-%H%M") rs.name = "[" + str(ts) + "] TRUSTWAVE_SPIDERLABS" rs.type_rule = 'trustwave' """ We have to save the object, to reference-it in ModSecRules """ rs.save() for match in re.findall( "(https://dashboard.modsecurity.org/rules/resources/download/([^\"]+))\"", content): r = ModSecRules() r.rs = rs # Change the ruleset as data type, to not include it in apache conf r.name = '.'.join(match[1].split('.')[:-1] + ["data"]) logger.debug( "Downloading additionnal trustwave rules {} : {}".format( match[1], match[0])) r.rule_content = requests.get( match[0], proxies=UpdateUtils.get_proxy_conf(), headers=modsec_headers).content logger.info( "Additionnal trustwave rules {} downloaded.".format( match[0])) # Enable the rule so it's written on disk r.is_enabled = True r.save() content = content.replace(match[0], r.filename) r = ModSecRules() r.rs = rs r.name = "Downloaded base rules" r.rule_content = content r.is_enabled = True r.save() rs.conf = rs.get_conf() rs.save() logger.info("[TRUSTWAVE] Ruleset '{}' successfully added.".format( rs.name)) self.result[ 'message'] = "[TRUSTWAVE] Ruleset '{}' successfully added.".format( rs.name) except Exception as e: self.result['status'] = False self.result['message'] = str(e)
def clone(request, object_id=None): """ View dedicated to application cloning :param object_id: MongoDB object_id of application :param request: Django request object """ # Retrieve application configuration application = Application.objects.with_id(ObjectId(object_id)) incoming_headers = application.headers_in outgoing_headers = application.headers_out content_rules = application.content_rules listeners = application.listeners # Clone incoming headers incoming_headers_list = list() for h in incoming_headers: h.pk = None h.save() incoming_headers_list.append(h) # Clone outgoing headers outgoing_headers_list = list() for h in outgoing_headers: h.pk = None h.save() outgoing_headers_list.append(h) # Clone content rules content_rules_list = list() for r in content_rules: r.pk = None r.save() content_rules_list.append(r) # Clone listeners listeners_list = list() for l in listeners: l.pk = None l.is_up2date = False l.save() listeners_list.append(l) #Clone application application.pk = None application.name = 'Copy Of ' + str( application.name) + '#' + get_random_string(4) application.headers_in = incoming_headers_list application.headers_out = outgoing_headers_list application.content_rules = content_rules_list application.listeners = listeners_list modsec_wl_bl = ModSecRulesSet(name="{} whitelist/blacklist".format( application.name), type_rule="wlbl") modsec_wl_bl.save() modsec_wl_bl.conf = modsec_wl_bl.get_conf() modsec_wl_bl.save() application.wl_bl_rules = modsec_wl_bl application.save() return HttpResponseRedirect('/application/')
def edit(request, object_id=None): """ View dedicated to application management :param object_id: MongoDB object_id of application :param request: Django request object """ # Retrieving application configuration application = Application.objects.with_id(ObjectId(object_id)) specific_rules_set = [] incoming_headers = [] outgoing_headers = [] content_rules = [] listeners = [] listeners_ips = [] svms = [] activated_svms = [] # Application doesn"'t exist ==> We want to create a new one # Fix some default values if not application and request.method != 'POST': application = Application(name="My App", type="http", public_name="www.example.com", public_alias="www.ex_1.fr", public_dir="/", private_uri="https://192.168.1.1/owa/") # Fix default security rules incoming_headers.append( HeaderOut(True, 'unset', '^X-Forwarded-', '', '', 'always', '')) # outgoing_headers.append(HeaderOut ('set', 'Content-Security-Policy', 'default-src \'self\'', '', 'always', '')) outgoing_headers.append( HeaderOut(True, 'set', 'X-Frame-Options', 'SAMEORIGIN', '', 'always', '')) outgoing_headers.append( HeaderOut(True, 'set', 'X-Content-Type-Options', 'nosniff', '', 'always', '')) outgoing_headers.append( HeaderOut(True, 'set', 'X-XSS-Protection', '1; mode=block', '', 'always', '')) application.headers_in = incoming_headers application.headers_out = outgoing_headers # Check if request are valid - and populate arrays with form values if request.method == 'POST': dataPosted = request.POST dataPostedRaw = str(request.body).split("&") has_listener = False dataPosted_length = len(dataPostedRaw) if application and not "enabled=on" in dataPostedRaw: application.delete_listeners() for data in dataPostedRaw: if data.startswith("address_"): # Listener management for cpt in range(dataPosted_length): if data.startswith("address_" + str(cpt) + "="): inet = None port = None redirect_port = None try: listener_id = dataPosted['address_' + str(cpt)] inet = Listener.objects.with_id( ObjectId(listener_id)) except Exception as e: pass try: port = dataPosted['port_' + str(cpt)] except Exception as e: pass try: redirect_port = dataPosted['redirect_port_' + str(cpt)] except Exception as e: pass try: ssl_profile = ModSSL.objects.with_id( ObjectId(dataPosted['ssl_profile_' + str(cpt)])) except Exception as e: ssl_profile = None pass if (inet and port): address = ListenAddress( address=inet, port=port, ssl_profile=ssl_profile, redirect_port=redirect_port) """ Add '1' because we don't want to save yet the listener """ address.related_node = address.get_related_node(1) listeners.append(address) listeners_ips.append( (address.related_node, inet.ip, port)) has_listener = True dataPosted_length -= 4 if data.startswith("SpecificRS_url_"): for cpt in range(dataPosted_length): if data.startswith("SpecificRS_url_" + str(cpt) + "="): # Force default values to prevent injection or any problem specific_rs_url = dataPosted.get( 'SpecificRS_url_' + str(cpt), '/login/') try: specific_rs_rs = ModSecRulesSet.objects.with_id( ObjectId( dataPosted.get( 'modsec_specific_rs_select' + str(cpt), None))) except: specific_rs_rs = ModSecRulesSet.objects.first() specific_rs = ModSecSpecificRulesSet( url=specific_rs_url, rs=specific_rs_rs) specific_rules_set.append(specific_rs) cpt -= 2 # Incoming header management if data.startswith("header_action_"): for cpt in range(dataPosted_length): if data.startswith("header_action_" + str(cpt) + "="): # Force harmless default values to prevent any injection or jQuery problem header_enable = (dataPosted.get( 'header_enable_' + str(cpt), 'off') == 'on') header_action = dataPosted.get( 'header_action_' + str(cpt), 'add') header_name = dataPosted.get('header_name_' + str(cpt), 'Vulture') header_value = dataPosted.get( 'header_value_' + str(cpt), '') header_replacement = dataPosted.get( 'header_replacement_' + str(cpt), '') header_condition = dataPosted.get( 'header_condition_' + str(cpt), 'always') header_condition_v = dataPosted.get( 'header_condition_v_' + str(cpt), '') # FIXME: Coherence control header = HeaderIn(header_enable, header_action, header_name, header_value, header_replacement, header_condition, header_condition_v) incoming_headers.append(header) dataPosted_length -= 7 # Outgoing header management if data.startswith("header_out_action_"): for cpt in range(dataPosted_length): if data.startswith("header_out_action_" + str(cpt) + "="): # Force harmless default values to prevent any injection or jQuery problem header_enable = (dataPosted.get( 'header_out_enable_' + str(cpt), 'off') == 'on') header_action = dataPosted.get( 'header_out_action_' + str(cpt), 'add') header_name = dataPosted.get( 'header_out_name_' + str(cpt), 'Vulture') header_value = dataPosted.get( 'header_out_value_' + str(cpt), '') header_replacement = dataPosted.get( 'header_out_replacement_' + str(cpt), '') header_condition = dataPosted.get( 'header_out_condition_' + str(cpt), 'always') header_condition_v = dataPosted.get( 'header_out_condition_v_' + str(cpt), '') # FIXME: Coherence control header = HeaderOut(header_enable, header_action, header_name, header_value, header_replacement, header_condition, header_condition_v) outgoing_headers.append(header) dataPosted_length -= 7 # # Content rules management if data.startswith("content_types_"): for cpt in range(dataPosted_length): if data.startswith("content_types_" + str(cpt) + "="): # Force harmless default values to prevent any injection # or jQuery problem content_enable = True if dataPosted.get( 'content_enable_' + str(cpt), 'off') == 'on' else False content_types = dataPosted.get( 'content_types_' + str(cpt), 'text/html') content_condition = dataPosted.get( 'condition_' + str(cpt), '') content_deflate = True if dataPosted.get( 'content_deflate_' + str(cpt), 'off') == 'on' else False content_inflate = True if dataPosted.get( 'content_inflate_' + str(cpt), 'off') == 'on' else False content_pattern = dataPosted.get( 'content_pattern_' + str(cpt), '') content_replacement = dataPosted.get( 'content_replacement_' + str(cpt), '') content_replacement_flags = dataPosted.get( 'content_replacement_flags_' + str(cpt), '') # FIXME: Coherence control rule = ContentRule(content_enable, content_types, content_condition, content_deflate, content_inflate, content_pattern, content_replacement, content_replacement_flags) content_rules.append(rule) dataPosted_length -= 8 # SVMs management if data.startswith("checkbox_chart_uri_analysis_"): m = re.match('checkbox_chart_uri_analysis_([0-9|a-f]+)', data) if m: dataset_id_ = m.group(1) # Force harmless default values to prevent any injection or jQuery problem svm_enable = False try: svm_enable = True if dataPosted.get( 'checkbox_chart_uri_analysis_' + dataset_id_) == 'on' else False if svm_enable: svm = SVM.objects(dataset_used=dataset_id_, algo_used="Levenstein")[0] activated_svms.append(str(svm.id)) logger.debug( "Activated SVM with id '{}' for application saved" .format(str(svm.id))) except KeyError: pass except Exception as e: logger.error( "Unable to retrieve activated SVM - Dataset_id:{}, algo_used:Levenstein - Exception: {}" .format(dataset_id_, str(e))) if data.startswith("checkbox_chart_uri_analysis_2_"): m = re.match('checkbox_chart_uri_analysis_2_([0-9|a-f]+)', data) if m: dataset_id_ = m.group(1) # Force harmless default values to prevent any injection or jQuery problem svm_enable = False try: svm_enable = True if dataPosted.get( 'checkbox_chart_uri_analysis_2_' + dataset_id_) == 'on' else False if svm_enable: svm = SVM.objects(dataset_used=dataset_id_, algo_used="Levenstein2")[0] activated_svms.append(str(svm.id)) logger.debug( "Activated SVM with id '{}' for application saved" .format(str(svm.id))) except KeyError: pass except Exception as e: logger.error( "Unable to retrieve activated SVM - Dataset_id:{}, algo_used:Levenstein2 - Exception: {}" .format(dataset_id_, str(e))) if data.startswith("checkbox_chart_bytes_received_"): m = re.match('checkbox_chart_bytes_received_([0-9|a-f]+)', data) if m: dataset_id_ = m.group(1) # Force harmless default values to prevent any injection or jQuery problem svm_enable = False try: svm_enable = True if dataPosted.get( 'checkbox_chart_bytes_received_' + dataset_id_) == 'on' else False if svm_enable: svm = SVM.objects( dataset_used=dataset_id_, algo_used="HTTPcode_bytes_received")[0] activated_svms.append(str(svm.id)) logger.debug( "Activated SVM with id '{}' for application saved" .format(str(svm.id))) except KeyError: pass except Exception as e: logger.error( "Unable to retrieve activated SVM - Dataset_id:{}, algo_used:HTTPcode_bytes_received - Exception: {}" .format(dataset_id_, str(e))) if data.startswith("checkbox_chart_ratio_"): m = re.match('checkbox_chart_ratio_([0-9|a-f]+)', data) if m: dataset_id_ = m.group(1) # Force harmless default values to prevent any injection or jQuery problem svm_enable = False try: svm_enable = True if dataPosted.get( 'checkbox_chart_ratio_' + dataset_id_) == 'on' else False if svm_enable: svm = SVM.objects(dataset_used=dataset_id_, algo_used="Ratio")[0] activated_svms.append(str(svm.id)) logger.debug( "Activated SVM with id '{}' for application saved" .format(str(svm.id))) except KeyError: pass except Exception as e: logger.error( "Unable to retrieve activated SVM - Dataset_id:{}, algo_used:Ratio - Exception: {}" .format(dataset_id_, str(e))) form = ApplicationForm(request.POST, instance=application, listeners=listeners) else: if not object_id: try: rulesset_vulture = [ ModSecRulesSet.objects.get(name="Vulture RS").id ] except ModSecRulesSet.DoesNotExist: rulesset_vulture = [] form = ApplicationForm(initial={ "rules_set": rulesset_vulture, "modsec_policy": ModSec.objects.get(name="Default Policy") }, instance=application) else: form = ApplicationForm( initial={'rules_set': [x.id for x in application.rules_set]}, instance=application) incoming_headers = application.headers_in outgoing_headers = application.headers_out content_rules = application.content_rules specific_rules_set = list() for i in application.specific_rules_set: specific_rules_set.append( ModSecSpecificRulesSet(url=re.sub('^' + application.public_dir, '', i.url), rs=i.rs)) # We replace '\' by '\\' in strings because they're interpreted by templates for tmp_rule in content_rules: tmp_rule.pattern = tmp_rule.pattern.replace("\\", "\\\\") tmp_rule.replacement = tmp_rule.replacement.replace("\\", "\\\\") listeners = application.listeners svms = [ SVM.objects(id=ObjectId(svm_id)).no_dereference().only( 'dataset_used', 'algo_used', 'id').first() for svm_id in application.activated_svms ] # Saving information into database and redirect to application list if request.method == 'POST' and form.is_valid(): # Listener is mandatory if has_listener: old_app = Application.objects.with_id(ObjectId(object_id)) # 1) Remove old listeners, headers and content rules if old_app and old_app.listeners: for listener in old_app.listeners: ip = listener.address.ip port = listener.port n = listener.related_node """ Stop the listener if there is only this app running on it """ if (n, ip, port) not in listeners_ips: logger.info("Stopping listener {}:{}".format(ip, port)) listener.stop() listener.delete() if old_app and old_app.headers_in: for header in old_app.headers_in: header.delete() if old_app and old_app.headers_out: for header in old_app.headers_out: header.delete() if old_app and old_app.content_rules: for rule in old_app.content_rules: rule.delete() if old_app and old_app.specific_rules_set: for ruleset in old_app.specific_rules_set: ruleset.delete() old_cookie_encryption = False old_cookie_cipher = None if old_app: application.wl_bl_rules = old_app.wl_bl_rules old_cookie_encryption = old_app.cookie_encryption old_cookie_cipher = old_app.cookie_cipher old_cookie_cipher_key = old_app.cookie_cipher_key old_cookie_cipher_iv = old_app.cookie_cipher_iv # 2) Create new listeners, headers and content rules for listener in listeners: listener.save() for header in incoming_headers: header.save() for header in outgoing_headers: header.save() for rule in content_rules: rule.save() # 3) Assign listeners, headers and content rules auth_backend = form.cleaned_data.get('auth_backend') auth_backend_fallbacks = form.cleaned_data.get( 'auth_backend_fallbacks') application = form.save(commit=False) if auth_backend: application.auth_backend = auth_backend else: application.auth_backend = None if auth_backend_fallbacks: application.auth_backend_fallbacks = auth_backend_fallbacks else: application.auth_backend_fallbacks = None application.listeners = listeners application.headers_in = incoming_headers application.headers_out = outgoing_headers application.specific_rules_set = specific_rules_set application.content_rules = content_rules application.activated_svms = activated_svms if application.cookie_encryption: if not old_cookie_encryption or not old_cookie_cipher or application.cookie_cipher != old_cookie_cipher: application.cookie_cipher_key = get_random_string( 32 ) if application.cookie_cipher == 'aes256' else get_random_string( 16) application.cookie_cipher_iv = get_random_string( 32 ) if application.cookie_cipher == 'aes256' else get_random_string( 16) else: application.cookie_cipher_key = old_cookie_cipher_key application.cookie_cipher_iv = old_cookie_cipher_iv # 4) Save application if not application.public_dir.endswith('/'): application.public_dir += '/' if application.auth_portal and not application.auth_portal.endswith( '/'): application.auth_portal += '/' if application.private_uri and not application.private_uri.endswith( '/'): application.private_uri += '/' for ruleset in specific_rules_set: ruleset.url = os.path.normpath( str(application.public_dir) + '/' + str(ruleset.url)) ruleset.save() if not object_id: # Create BlackList/WhiteList ModSecRuleSet modsec_wl_bl = ModSecRulesSet( name="{} whitelist/blacklist".format(application.name), type_rule="wlbl") modsec_wl_bl.save() modsec_wl_bl.conf = modsec_wl_bl.get_conf() modsec_wl_bl.save() application.wl_bl_rules = modsec_wl_bl else: # If the application is modified, modify the name of the RS references application.wl_bl_rules.name = "{} whitelist/blacklist".format( form.cleaned_data.get('name')) application.wl_bl_rules.save() for modsec_ruleset in ModSecRulesSet.objects.filter( name="Learning {} WL"): modsec_ruleset.name = form.cleaned_data.get('name') modsec_ruleset.save() if application.type == "balanced": application.private_uri = "{}://{}".format( application.proxy_balancer.members[0].uri_type, application.proxy_balancer.members[0].uri) # Check if api_call to reload rsyslogd is needed if old_app: if application.log_custom != old_app.log_custom or application.log_level != old_app.log_level or application.learning != old_app.learning: application.save() else: application.save(no_apicall=True) else: application.save() return HttpResponseRedirect('/application/') inets = Listener.objects() address_list = list() vhid_list = list() # List and categorize inet (carp or not) to render them in template for inet in inets: listener = dict() if inet.is_carp: listener['inet'] = inet listener['id'] = getattr(inet, 'id') vhid_list.append(inet.carp_vhid) elif inet.carp_vhid in vhid_list: continue else: listener['inet'] = inet listener['id'] = getattr(inet, 'id') address_list.append(listener) ssl_profile_list = ModSSL.objects() rules_set_list = ModSecRulesSet.objects.filter( type_rule__in=['crs', 'trustwave', 'vulture', 'custom']) return render_to_response('application_edit.html', { 'form': form, 'object_id': object_id, 'headers_in': incoming_headers, 'headers_out': outgoing_headers, 'content_rules': content_rules, 'listeners': listeners, 'address_list': address_list, 'ssl_profile_list': ssl_profile_list, 'application': application, 'svms': svms, 'rules_set_list': rules_set_list, 'specific_rules_set': specific_rules_set }, context_instance=RequestContext(request))
django.setup() from gui.models.modsec_settings import ModSecRulesSet, ModSecRules from gui.models.application_settings import Application if __name__ == '__main__': apps = Application.objects() for app in apps: try: wl_rule = ModSecRules.objects.filter(rs=app.whitelist) bl_rule = ModSecRules.objects.filter(rs=app.blacklist) wl_bl_ruleset = ModSecRulesSet( name="{} whitelist/blacklist".format(app.name), type_rule='wlbl') wl_bl_ruleset.save() for r in bl_rule: r.rs = wl_bl_ruleset r.save() for r in wl_rule: r.rs = wl_bl_ruleset r.save() app.wl_bl_rules = wl_bl_ruleset app.save() ModSecRulesSet.objects.with_id(ObjectId(app.blacklist)).delete()
import os import sys sys.path.append('/home/vlt-gui/vulture') os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'vulture.settings') import django from bson import ObjectId django.setup() from gui.models.modsec_settings import ModSecRulesSet, ModSecRules if __name__ == '__main__': try: vulture_rs = ModSecRulesSet.objects.get(name='Vulture RS') ModSecRules.objects.filter(rs=ObjectId(vulture_rs.id)).delete() except ModSecRulesSet.DoesNotExist as e: pass from gui.initial_data import modsec_rules_set modsec_rules_set.Import().process() print "Vulture RS successfully updated" ## Write modsec conf for m in ModSecRulesSet.objects(): conf = m.get_conf() m.conf = conf m.save()
def __init__(self, *args, **kwargs): try: self.listeners = kwargs.pop('listeners') except KeyError: pass super(ApplicationForm, self).__init__(*args, **kwargs) self = bootstrap_tooltips(self) repo_lst = BaseAbstractRepository.get_auth_repositories() auth_repo_lst = list() for rep in repo_lst: auth_repo_lst.append(( ObjectId(rep.id), rep.repo_name, )) mod_sec_choices = list() for rule in ModSecRulesSet.objects(type_rule__nin=('wlbl', )): mod_sec_choices.append((ObjectId(rule.id), rule.name)) dataset_list = list() dataset_list.append(((None), "------------------------------")) for dataset in Dataset.objects(svm_built=True).only('name', 'id'): dataset_list.append((ObjectId(dataset.id), dataset.name)) client_certificate = [('', '---------')] for cert in SSLCertificate.objects(is_trusted_ca__ne=True).only( 'id', 'cn'): client_certificate.append(("%sSSLProxyCertificateFile-%s.txt" % (settings.CONF_DIR, cert.id), cert.cn)) COOCKIE_CIPHER = ( ('rc4', 'RC4 (128 bits)'), ('aes128', 'AES 128 (128 bits)'), ('aes256', 'AES 256 (256 bits)'), ) IP_REPUTATION = [] loganalyser_rules = Cluster.objects.get( ).system_settings.loganalyser_settings.loganalyser_rules for rule in loganalyser_rules: tags = rule.tags.split(',') for tag in tags: IP_REPUTATION.append((tag, tag.capitalize())) GEOIP = [] for tag in [ "AF", "AX", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "SS", "ES", "LK", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW" ]: GEOIP.append((tag, tag)) self.fields['block_reputation'] = MultipleChoiceField( required=False, choices=set(IP_REPUTATION), widget=SelectMultiple(attrs={'class': 'form-control select2'})) self.fields['block_geoip'] = MultipleChoiceField( required=False, choices=set(GEOIP), widget=SelectMultiple(attrs={'class': 'form-control select2'})) self.fields['allow_geoip'] = MultipleChoiceField( required=False, choices=set(GEOIP), widget=SelectMultiple(attrs={'class': 'form-control select2'})) self.fields['template'].queryset = portalTemplate.objects.filter() self.fields['auth_backend'] = ChoiceField( choices=auth_repo_lst, required=False, widget=Select(attrs={'class': 'form-control'})) self.fields['auth_backend_fallbacks'] = MultipleChoiceField( choices=auth_repo_lst, required=False, widget=SelectMultiple(attrs={'class': 'form-control select2'})) self.fields['redirect_uri'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 80, 'rows': 1, 'class': 'form-control' })) self.fields['sso_capture_content'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 80, 'rows': 2, 'class': 'form-control' })) self.fields['sso_replace_pattern'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 40, 'rows': 2, 'class': 'form-control' })) self.fields['sso_replace_content'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 40, 'rows': 2, 'class': 'form-control' })) self.fields['sso_after_post_request'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 80, 'rows': 2, 'class': 'form-control' })) self.fields['rules_set'] = MultipleChoiceField( choices=mod_sec_choices, required=False, widget=SelectMultiple(attrs={'class': 'form-control'})) self.fields['datasets'] = ChoiceField( choices=dataset_list, required=False, widget=Select(attrs={'class': 'form-control'})) self.fields['ssl_protocol'] = ChoiceField( choices=SSL_PROTOCOLS, required=False, widget=Select(attrs={'class': 'form-control'})) self.fields['ssl_client_certificate'] = ChoiceField( choices=client_certificate, required=False, widget=Select(attrs={'class': 'form-control'})) self.fields['custom_vhost'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 80, 'rows': 15, 'class': 'form-control' })) self.fields['custom_location'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 80, 'rows': 15, 'class': 'form-control' })) self.fields['custom_proxy'] = CharField( required=False, widget=Textarea(attrs={ 'cols': 80, 'rows': 15, 'class': 'form-control' })) self.fields['cookie_cipher'] = ChoiceField( choices=COOCKIE_CIPHER, required=False, widget=Select(attrs={'class': 'form-control'})) if self.initial.get("auth_backend"): repo = BaseAbstractRepository.search_repository( self.initial.get('auth_backend')) if isinstance(repo, LDAPRepository): try: groups = [(x, x) for x in repo.get_backend().enumerate_groups()] except: groups = [] finally: self.fields['group_registration'] = ChoiceField( choices=groups, required=False, widget=Select(attrs={'class': 'form-control'}))
import django django.setup() from gui.models.modsec_settings import ModSecRulesSet, ModSecRules import datetime if __name__ == '__main__': try: modsec_rules_set = ModSecRulesSet.objects.get(name='Vulture RS', type_rule='vulture') except ModSecRulesSet.DoesNotExist as e: """ Warning: Escpa""" modsec_rules_set = ModSecRulesSet(name='Vulture RS', type_rule='vulture') modsec_rules_set.save() try: modsec_rule = ModSecRules.objects.get(name="vulture_000_session.conf", rs=modsec_rules_set) except ModSecRules.DoesNotExist as e: modsec_rule = ModSecRules(name="vulture_000_session.conf", rs=modsec_rules_set, is_enabled=True, date_rule=datetime.datetime.strptime('2017-03-21T08:45:00', "%Y-%m-%dT%H:%M:%S")) modsec_rule.date_rule = datetime.datetime.strptime('2017-03-21T08:45:00', "%Y-%m-%dT%H:%M:%S") modsec_rule.rule_content = """# Capture UA and compute md5 sum SecRule REQUEST_HEADERS:User-Agent "^(.*)$" "id:'999',phase:2,t:none,t:md5,t:hexEncode,setvar:tx.ua_md5=%{matched_var},nolog,pass" # Init collection with key based on IP+UA SecAction "id:'1000', phase:2, t:none,nolog, initcol:col_session=%{remote_addr}_%{tx.ua_md5}"
def add_wl(request, collection_name): try: wls = json.loads(request.POST['wls']) except: logger.error("DATASET::Add_WL: wls POST variable is not JSON type") return JsonResponse({ 'status': False, 'message': "Whitelist format invalid." }) try: log_id = request.POST['id'] except: logger.error("DATASET::Add_WL: Log_id variable missing.") return JsonResponse({'status': False, 'message': "Log_id missing."}) try: app = Application.objects.only('name').with_id( ObjectId(collection_name[9:])) except: logger.error( "DATASET::Add_WL: Application having id {} not found.".format( collection_name[9:])) return JsonResponse({ 'status': False, 'message': "Application {} not found.".format(collection_name[9:]) }) rs_name = "Learning " + app.name + " WL" try: ruleset = ModSecRulesSet.objects.get(name=rs_name) except ModSecRulesSet.DoesNotExist: logger.info( "DATASET::Add_WL: ModSecRulesSet '{}' not found. Creating-it.". format(rs_name)) ruleset = ModSecRulesSet(name=rs_name, type_rule='vulture') ruleset.save() rule_name = "Learning " + app.name + " rule" rule = ModSecRules.objects.filter(name=rule_name, rs=ruleset).first() if not rule: rule = ModSecRules(name=rule_name, rs=ruleset, is_enabled=True, date_rule=datetime.now()) if not rule.rule_content: rule.rule_content = "" content_modified = False for wl in wls: if wl not in rule.rule_content: rule.rule_content += wl + "\n" content_modified = True if content_modified: rule.save() logger.info("ModSecRule {} updated.".format(rule.name)) ruleset.conf = ruleset.get_conf() ruleset.save() logger.info("ModSecRulesSet {} updated.".format(ruleset.name)) try: nb_updated = update_collection(collection_name, {"_id": ObjectId(log_id)}, {"$set": { "whitelisted": "true" }}) logger.info("DATASET::Add_WL: Entries updated : {}".format(nb_updated)) except Exception as e: logger.error( "DATASET::Add_WL: Fail to update entry '{}' in collection '{}'.". format(log_id, collection_name)) logger.exception(e) return JsonResponse({ 'status': False, 'message': "Failed to update log '{}'".format(log_id) }) return JsonResponse({'status': True})
def generate_wl(request, collection_name): col = get_collection(collection_name) data = list(col.find({"whitelisted": {"$eq": "false"}})) app = Application.objects.only('name').with_id( ObjectId(collection_name[9:])) rs_name = "Learning " + app.name + " WL" try: ruleset = ModSecRulesSet.objects.get(name=rs_name) except ModSecRulesSet.DoesNotExist: ruleset = ModSecRulesSet(name=rs_name, type_rule='vulture') ruleset.save() rule_name = "Learning " + app.name + " rule" rule = ModSecRules.objects.filter(name=rule_name, rs=ruleset).first() if not rule: rule = ModSecRules(name=rule_name, rs=ruleset, is_enabled=True, date_rule=datetime.now()) wls = set() if rule.rule_content: wls = set(rule.rule_content.splitlines()) for record in data: url = record['uri'] entries = [] for ridx in range(10): if not 'id' + str(ridx) + '_0' in record: break entry = {'ids': []} for ridx2 in range(10): if not 'id' + str(ridx) + '_' + str(ridx2) in record: break entry['ids'].append( str(record['id' + str(ridx) + '_' + str(ridx2)])) entry['zone'] = record['zone' + str(ridx)] entry['effective_zone'] = entry['zone'] entry['target_name'] = False if entry['zone'].endswith("|NAME"): entry['target_name'] = True entry['effective_zone'] = entry['zone'][0:-5] entry['var_name'] = record.get('var_name' + str(ridx)) entry['content'] = record.get('content' + str(ridx)) entries.append(entry) for ent in entries: if ent['effective_zone'] in ["ARGS", "BODY", "HEADERS"]: r = 'BasicRule wl:' + \ ','.join(ent['ids']) + ' "mz:$URL:' + url + '|$' + \ ent['effective_zone'] + '_VAR:' + ent['var_name'] + \ ("|NAME" if ent["target_name"] else '') + '";' if r not in wls: wls.add(r) else: r = 'BasicRule wl:' + \ ','.join(ent['ids']) + ' "mz:$URL:' + url + '|URL";' if r not in wls: wls.add(r) rule.rule_content = "\n".join(wls) + "\n" rule.save() ruleset.conf = ruleset.get_conf() ruleset.save() nb_updated = update_collection(collection_name, {"whitelisted": "false"}, {"$set": { "whitelisted": "true" }}) logger.info( "DATASET::Generate_WL: {} document(s) updated".format(nb_updated)) return JsonResponse({'status': True})
def process(self): try: modsec_rules_set = ModSecRulesSet.objects.get(name='Vulture RS') except ModSecRulesSet.DoesNotExist as e: modsec_rules_set = ModSecRulesSet(name='Vulture RS', type_rule='vulture') modsec_rules_set.save() date_rule = datetime.datetime.strptime('2017-03-13T08:45:00', "%Y-%m-%dT%H:%M:%S") name = "vulture_000_session.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=True, date_rule=date_rule) modsec_rule.rule_content = """# Capture UA and compute md5 sum SecRule REQUEST_HEADERS:User-Agent "^(.*)$" "id:'999',phase:2,t:none,t:md5,t:hexEncode,setvar:tx.ua_md5=%{matched_var},nolog,pass" # Init collection with key based on IP+UA SecAction "id:'1000', phase:2, t:none,nolog, initcol:col_session=%{remote_addr}_%{tx.ua_md5}" # Session cookie sent. No stored session SecRule &REQUEST_COOKIES:JSESSIONID "@eq 1" "id:'1001',phase:2,chain,block,log,msg:'No previous session found',tag:'session_hijacking'" SecRule &COL_SESSION:SID "@eq 0" "setvar:tx.inbound_anomaly_score=+%{tx.session_hijacking_anomaly_score},setenv:SESSION_FORCE_EXPIRE=1,setenv:SESSION_ID=%{tx.sid},setenv:SESSION_HIJACKING=1" # Session cookie sent. Different stored session SecRule REQUEST_COOKIES:JSESSIONID "!@streq %{col_session.sid}" "id:'1002',block,log,msg:'%{REQUEST_COOKIES.JSESSIONID} does not match %{col_session.sid}',tag:'session_hijacking',setvar:tx.inbound_anomaly_score=+%{tx.session_hijacking_anomaly_score},setenv:SESSION_HIJACKING=1" # More than one session cookie sent SecRule &REQUEST_COOKIES:JSESSIONID "@gt 1" "id:1003,phase:2,block,log,msg:'Too many sessions provided', tag:'session_hijacking',setvar:tx.inbound_anomaly_score=+%{tx.session_hijacking_anomaly_score},setenv:SESSION_FORCE_EXPIRE=1,setenv:SESSION_ID=%{tx.sid},setenv:SESSION_HIJACKING=1" # Expire the cookie if needed Header set Set-Cookie "JSESSIONID=DUMMY; expires=Thu, 01 Jan 1970 00:00:00 GMT" env=SESSION_FORCE_EXPIRE # Initializes the session when we receive a session cookie from the server SecRule RESPONSE_HEADERS:/Set-Cookie?/ "^(?i:JSESSIONID)=(.*?);" "id:1004,phase:3,t:none,pass,nolog,capture,setvar:col_session.sid=%{tx.1},msg:'New sid: %{tx.1}'" """ modsec_rule.save() name = "vulture_001_csrf.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=True, date_rule=date_rule) modsec_rule.rule_content = """# Init collection with key based on IP+UA SecAction "id:'1200', phase:2, t:none,nolog, initcol:col_csrf=%{remote_addr}_%{tx.ua_md5}" # On every POST checking if the CSRF token has been informed SecRule REQUEST_METHOD "@streq POST" "id:1201,phase:2,chain,block,log,msg:'POST request missing the CSRF token.',tag:'csrf_protection'" SecRule &ARGS:CSRF_TOKEN "!@eq 1" "setvar:tx.inbound_anomaly_score=+%{tx.csrf_hijacking_anomaly_score}" # On every POST checking if the CSRF token matches the random generated one SecRule REQUEST_METHOD "@streq POST" "id:1202,phase:2,chain,capture,block,log,msg:'Invalid CSRF token. Expected %{col_csrf.token} but received %{ARGS.csrf_token}',tag:'csrf_protection'" SecRule ARGS:CSRF_TOKEN "!@streq %{col_csrf.token}" "setvar:tx.inbound_anomaly_score=+%{tx.csrf_hijacking_anomaly_score}" # Generate UUID for CSRF token to be injected SecRule UNIQUE_ID "(.*)" "id:1203,phase:2,chain,t:none,pass,nolog,t:md5,t:hexEncode,setvar:tx.uuid=%{matched_var}" # Save UUID as CRSF token in DB for future check SecAction "setvar:col_csrf.token=%{tx.uuid}" # Inject the UUID as CSRF token SecRule STREAM_OUTPUT_BODY '@rsub s/<\/form>/<input type="hidden" name="csrf_token" value="%{tx.uuid}"><\/form>/' "id:1204,phase:4,t:none,pass,nolog" SecRule STREAM_OUTPUT_BODY "@contains </form>" "id:1205,phase:4,t:none,pass,nolog,setenv:CSRF_INJECTED=1" # Disable cache to prevent crsf token caching Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate" env=CSRF_INJECTED """ modsec_rule.save() name = "vulture_002_useragent.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=False, date_rule=date_rule) modsec_rule.rule_content = """# Init collection with key based on IP+UA SecAction "id:'1300', phase:2, t:none,nolog, initcol:col_ua=%{tx.ua_md5}" # Capture UA SecRule REQUEST_HEADERS:User-Agent "^(.*)$" "id:'1301',phase:2,t:none,setvar:tx.ua=%{matched_var},nolog,pass" # If Unknown UA SecRule &COL_UA:UA "@eq 0" "id:'1302',phase:2,block,log,msg:'Unknown UA:%{tx.ua}, hash:%{tx.ua_hash}, rep:%{ENV.REPUTATION}',tag:'UA_suspicious',setvar:tx.inbound_anomaly_score=+%{tx.ua_unknown_anomaly_score},setenv:UA_UNKNOWN=1" # If UA Anonymous SecRule COL_UA:UA "@contains ua-anonymous" "id:'1303',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_anonymous_anomaly_score},setenv:UA=ANONYMOUS" # If UA Bot SecRule COL_UA:UA "@contains ua-bot" "id:'1304',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_bot_anomaly_score},setenv:UA=BOT" # If UA Browser SecRule COL_UA:UA "@contains ua-browser" "id:'1305',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_browser_anomaly_score},setenv:UA=BROWSER" # If UA Cloud SecRule COL_UA:UA "@contains ua-cloud" "id:'1306',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_cloud_anomaly_score},setenv:UA=CLOUD" # If UA Console SecRule COL_UA:UA "@contains ua-console" "id:'1307',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_console_anomaly_score},setenv:UA=CONSOLE" # If UA Crawler SecRule COL_UA:UA "@contains ua-crawler" "id:'1308',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_crawler_anomaly_score},setenv:UA=CRAWLER" # If UA Emailclient SecRule COL_UA:UA "@contains ua-emailclient" "id:'1309',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_emailclient_anomaly_score},setenv:UA=EMAILCLIENT" # If UA Emailharvester SecRule COL_UA:UA "@contains ua-emailharvester" "id:'1310',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_emailharvester_anomaly_score},setenv:UA=EMAILHARVESTER" # If UA Mobile SecRule COL_UA:UA "@contains ua-mobile" "id:'1311',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_mobile_anomaly_score},setenv:UA=MOBILE" # IF UA Script SecRule COL_UA:UA "@contains ua-script" "id:'1312',phase:2,block,log,msg:'Detected UA: %{col_ua.ua}',setvar:tx.inbound_anomaly_score=+%{tx.ua_script_anomaly_score},setenv:UA=SCRIPT" """ modsec_rule.save() name = "vulture_003_contenttype.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=True, date_rule=date_rule) modsec_rule.rule_content = """# Restrict which content-types we accept. SecRule REQUEST_METHOD "!^(?:GET|HEAD|PROPFIND|OPTIONS)$" \ "phase:request,\ chain,\ t:none,\ block,\ msg:'Request content type is not allowed by policy',\ rev:'2',\ ver:'OWASP_CRS/3.0.0',\ maturity:'9',\ accuracy:'9',\ id:1400,\ severity:'CRITICAL',\ logdata:'%{matched_var}',\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-protocol',\ tag:'OWASP_CRS/POLICY/ENCODING_NOT_ALLOWED',\ tag:'WASCTC/WASC-20',\ tag:'OWASP_TOP_10/A1',\ tag:'OWASP_AppSensor/EE2',\ tag:'PCI/12.1'" SecRule REQUEST_HEADERS:Content-Type "^([^;\s]+)" \ "chain,\ capture" SecRule TX:0 "!^%{tx.allowed_request_content_type}$" \ "t:none,\ ctl:forceRequestBodyVariable=On,\ setvar:'tx.msg=%{rule.msg}',\ setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},\ setvar:tx.%{rule.id}-OWASP_CRS/POLICY/CONTENT_TYPE_NOT_ALLOWED-%{matched_var_name}=%{matched_var}" """ modsec_rule.save() name = "vulture_004_protocol.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=True, date_rule=date_rule) modsec_rule.rule_content = """# Restrict protocol versions. SecRule REQUEST_PROTOCOL "!@within %{tx.allowed_http_versions}" \ "phase:request,\ t:none,\ block,\ msg:'HTTP protocol version is not allowed by policy',\ severity:'CRITICAL',\ rev:'2',\ ver:'OWASP_CRS/3.0.0',\ maturity:'9',\ accuracy:'9',\ id:1401,\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-protocol',\ tag:'OWASP_CRS/POLICY/PROTOCOL_NOT_ALLOWED',\ tag:'WASCTC/WASC-21',\ tag:'OWASP_TOP_10/A6',\ tag:'PCI/6.5.10',\ logdata:'%{matched_var}',\ setvar:'tx.msg=%{rule.msg}',\ setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},\ """ modsec_rule.save() name = "vulture_005_fileext.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=True, date_rule=date_rule) modsec_rule.rule_content = """# Restrict file extension SecRule REQUEST_BASENAME "\.(.*)$" \ "chain,\ capture,\ phase:request,\ t:none,t:urlDecodeUni,t:lowercase,\ block,\ msg:'URL file extension is restricted by policy',\ severity:'CRITICAL',\ rev:'2',\ ver:'OWASP_CRS/3.0.0',\ maturity:'9',\ accuracy:'9',\ id:1402,\ logdata:'%{TX.0}',\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-protocol',\ tag:'OWASP_CRS/POLICY/EXT_RESTRICTED',\ tag:'WASCTC/WASC-15',\ tag:'OWASP_TOP_10/A7',\ tag:'PCI/6.5.10',logdata:'%{TX.0}',\ setvar:tx.extension=.%{tx.1}/" SecRule TX:EXTENSION "@within %{tx.restricted_extensions}" \ "t:none,\ setvar:'tx.msg=%{rule.msg}',\ setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},\ setvar:tx.%{rule.id}-OWASP_CRS/POLICY/EXT_RESTRICTED-%{matched_var_name}=%{matched_var}" """ modsec_rule.save() name = "vulture_006_headers.conf" modsec_rule = ModSecRules.objects.filter(name=name, rs=modsec_rules_set).first() if not modsec_rule: modsec_rule = ModSecRules(name=name, rs=modsec_rules_set, is_enabled=True, date_rule=date_rule) modsec_rule.rule_content = """# Restricted HTTP headers SecRule REQUEST_HEADERS_NAMES "@rx ^(.*)$" \ "msg:'HTTP header is restricted by policy (%{MATCHED_VAR})',\ severity:'CRITICAL',\ phase:request,\ t:none,\ block,\ rev:'2',\ ver:'OWASP_CRS/3.0.0',\ maturity:'9',\ accuracy:'9',\ id:1403,\ capture,\ logdata:' Restricted header detected: %{matched_var}',\ tag:'application-multi',\ tag:'language-multi',\ tag:'platform-multi',\ tag:'attack-protocol',\ tag:'OWASP_CRS/POLICY/HEADER_RESTRICTED',\ tag:'WASCTC/WASC-21',\ tag:'OWASP_TOP_10/A7',\ tag:'PCI/12.1',\ tag:'WASCTC/WASC-15',\ tag:'OWASP_TOP_10/A7',\ tag:'PCI/12.1',\ t:lowercase,\ setvar:'tx.header_name_%{tx.0}=/%{tx.0}/',\ chain" SecRule TX:/^HEADER_NAME_/ "@within %{tx.restricted_headers}" \ "setvar:'tx.msg=%{rule.msg}',\ setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},\ setvar:'tx.%{rule.id}-OWASP_CRS/POLICY/HEADERS_RESTRICTED-%{matched_var_name}=%{matched_var}'" """ modsec_rule.save() conf = modsec_rules_set.get_conf() modsec_rules_set.conf = conf modsec_rules_set.save() print("Vulture RS successfully imported")