import io import re import chardet from plyara import Plyara from rest_framework.views import exception_handler from rest_framework.exceptions import APIException def check_lexical_convention(entry): return Plyara.is_valid_rule_name(entry) def generate_kwargs_from_parsed_rule(parsed_rule): # Generate parsed rule kwargs for saving a rule name = parsed_rule['rule_name'] tags = parsed_rule.get('tags', []) scopes = parsed_rule.get('scopes', []) # TODO : Update when Plyara moves to clean Python types metadata = parsed_rule.get('metadata', {}) for key, value in metadata.items(): if value not in ('true', 'false'): try: value = int(value) except ValueError: metadata[key] = '"' + value + '"' strings = parsed_rule.get('strings', []) condition = parsed_rule['condition_terms'] # TODO : Update when Plyara moves to stripping quotes from detect_imports module imports = [imp.strip('"') for imp in Plyara.detect_imports(parsed_rule)] comments = parsed_rule.get('comments', []) dependencies = Plyara.detect_dependencies(parsed_rule) # Calculate hash value of rule strings and condition logic_hash = Plyara.generate_logic_hash(parsed_rule) # TEMP FIX - Use only a single instance of a metakey # until YaraGuardian models and functions can be updated for key, value in metadata.items(): if isinstance(value, list): metadata[key] = value[0] return {'name': name, 'tags': list(set(tags)), 'scopes': list(set(scopes)), 'imports': list(set(imports)), 'comments': list(set(comments)), 'metadata': metadata, 'strings': strings, 'condition': condition, 'dependencies': dependencies, 'logic_hash': logic_hash} def parse_rule_submission(raw_submission): # Instantiate Parser parser = Plyara() # Container for results submission_results = {'parsed_rules': [], 'parser_error': ''} try: # Check if submission needs to be read and decoded if hasattr(raw_submission, 'read'): raw_content = raw_submission.read() # Attempt to automatically detect encoding encoding = chardet.detect(raw_content)['encoding'] yara_content = raw_content.decode(encoding=encoding) else: yara_content = raw_submission except Exception: # Unable to decode or read the submitted content yara_content = None submission_results['parser_error'] = "Unable to read submission content" # Ensure content is not blank before passing to parser if yara_content: try: submission_results['parsed_rules'] = parser.parse_string(yara_content) except Exception as error: submission_results['parser_error'] = str(error) return submission_results def build_yarafile(queryset): rules = queryset.order_by('dependencies') # Temporary rule file container temp_file = io.StringIO() # Build import search patterns import_options = Plyara.IMPORT_OPTIONS import_pattern = 'import \"(?:{})\"\n'.format('|'.join(import_options)) for rule in rules.iterator(): # name, tags, imports, metadata, strings, condition, scopes formatted_rule = rule.format_rule() temp_file.write(formatted_rule) temp_file.write('\n\n') present_imports = set(re.findall(import_pattern, temp_file.getvalue())) importless_file = re.sub(import_pattern, '', temp_file.getvalue()) # Finalized rule file container rule_file = io.StringIO() for import_value in present_imports: rule_file.write(import_value) rule_file.write('\n\n') rule_file.write(importless_file) return rule_file def custom_exception_handler(exc, context): response_content = {} response = exception_handler(exc, context) if response is not None: response_content['status_code'] = response.status_code if 'detail' not in response.data: response_content['errors'] = response.data else: response_content['errors'] = [response.data['detail']] response.data = response_content return response