diff --git a/.gitignore b/.gitignore index bf7103a4d..131863727 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,4 @@ settings.json # VisualStudio .vs/ +.vscode/launch.json diff --git a/Makefile b/Makefile index 1ad713518..18a3dbb74 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,11 @@ test-sigmac: $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t elastalert -c tools/config/winlogbeat.yml -O alert_methods=http_post,email -O emails=test@test.invalid -O http_post_url=http://test.invalid rules/ > /dev/null $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t elastalert-dsl -c tools/config/winlogbeat.yml -O alert_methods=http_post,email -O emails=test@test.invalid -O http_post_url=http://test.invalid rules/ > /dev/null $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t ee-outliers -c tools/config/winlogbeat.yml rules/ > /dev/null + $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t es-qs -c tools/config/ecs-cloudtrail.yml rules/ > /dev/null + $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t es-rule -c tools/config/ecs-cloudtrail.yml rules/ > /dev/null + $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t kibana -c tools/config/ecs-cloudtrail.yml rules/ > /dev/null + $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t xpack-watcher -c tools/config/ecs-cloudtrail.yml rules/ > /dev/null + $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t elastalert -c tools/config/ecs-cloudtrail.yml rules/ > /dev/null ! $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk rules/ > /dev/null $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunk -c tools/config/splunk-windows-index.yml rules/ > /dev/null $(COVERAGE) run -a --include=$(COVSCOPE) tools/sigmac -rvdI -t splunkxml -c tools/config/splunk-windows.yml rules/ > /dev/null diff --git a/rules/cloud/aws_ec2_vm_export_failure.yml b/rules/cloud/aws_ec2_vm_export_failure.yml new file mode 100644 index 000000000..a6db628c5 --- /dev/null +++ b/rules/cloud/aws_ec2_vm_export_failure.yml @@ -0,0 +1,28 @@ +title: AWS EC2 VM Export Failure +id: 54b9a76a-3c71-4673-b4b3-2edb4566ea7b +status: experimental +description: An attempt to export an AWS EC2 instance has been detected. A VM Export might indicate an attempt to extract information from an instance. +references: + - https://docs.aws.amazon.com/vm-import/latest/userguide/vmexport.html#export-instance +author: Diogo Braz +date: 2020/04/16 +tags: + - attack.collection + - attack.t1005 + - attack.exfiltration + - attack.t1537 +level: low +logsource: + service: cloudtrail +detection: + selection: + eventName: 'CreateInstanceExportTask' + eventSource: 'ec2.amazonaws.com' + filter1: + errorMessage: '*' + filter2: + errorCode: '*' + filter3: + eventName: 'ConsoleLogin' + responseElements: '*Failure*' + condition: selection and (filter1 or filter2 or filter3) diff --git a/tools/config/ecs-cloudtrail.yml b/tools/config/ecs-cloudtrail.yml new file mode 100644 index 000000000..fe9419bd4 --- /dev/null +++ b/tools/config/ecs-cloudtrail.yml @@ -0,0 +1,60 @@ +title: Elastic Common Schema And Elastic Exported Fields Mapping For AWS CloudTrail Logs +order: 20 +backends: + - es-qs + - es-dsl + - es-rule + - kibana + - xpack-watcher + - elastalert + - elastalert-dsl +fieldmappings: + additionalEventdata: aws.cloudtrail.additional_eventdata + apiVersion: aws.cloudtrail.api_version + awsRegion: cloud.region + errorCode: aws.cloudtrail.error_code + errorMessage: aws.cloudtrail.error_message + eventID: event.id + eventName: event.action + eventSource: event.provider + eventTime: '@timestamp' + eventType: aws.cloudtrail.event_type + eventVersion: aws.cloudtrail.event_version + managementEvent: aws.cloudtrail.management_event + readOnly: aws.cloudtrail.read_only + requestID: aws.cloudtrail.request_id + requestParameters: aws.cloudtrail.request_parameters + resources.accountId: aws.cloudtrail.resources.account_id + resources.ARN: aws.cloudtrail.resources.arn + resources.type: aws.cloudtrail.resources.type + responseElements: aws.cloudtrail.response_elements + serviceEventDetails: aws.cloudtrail.service_event_details + sharedEventId: aws.cloudtrail.shared_event_id + sourceIPAddress: source.address + userAgent: user_agent + userIdentity.accessKeyId: aws.cloudtrail.user_identity.access_key_id + userIdentity.accountId: cloud.account.id + userIdentity.arn: aws.cloudtrail.user_identity.arn + userIdentity.invokedBy: aws.cloudtrail.user_identity.invoked_by + userIdentity.principalId: user.id + userIdentity.sessionContext.attributes.creationDate: aws.cloudtrail.user_identity.session_context.creation_date + userIdentity.sessionContext.attributes.mfaAuthenticated: aws.cloudtrail.user_identity.session_context.mfa_authenticated + userIdentity.type: aws.cloudtrail.user_identity.type + userIdentity.userName: user.name + vpcEndpointId: aws.cloudtrail.vpc_endpoint_id +overrides: + - field: event.outcome + value: failure + regexes: + - (\(\(aws.cloudtrail.error_message.keyword:.* event.action:\"ConsoleLogin\"\)\)) + - (\(\(aws.cloudtrail.error_code.keyword:.* event.action:\"ConsoleLogin\"\)\)) + - (\(\(aws.cloudtrail.error_message.keyword:.* aws.cloudtrail.response_elements.keyword:\*Failure\*\)\)) + - (\(\(aws.cloudtrail.error_code.keyword:.* aws.cloudtrail.response_elements.keyword:\*Failure\*\)\)) + - (\(\(event.action:\"ConsoleLogin\".* aws.cloudtrail.error_message.keyword:\*\)\)) + - (\(\(event.action:\"ConsoleLogin\".* aws.cloudtrail.error_code.keyword:\*\)\)) + - (\(\(aws.cloudtrail.response_elements.keyword:\*Failure\*.* aws.cloudtrail.error_message.keyword:\*\)\)) + - (\(\(aws.cloudtrail.response_elements.keyword:\*Failure\*.* aws.cloudtrail.error_code.keyword:\*\)\)) + - field: event.outcome + value: success + literals: + - 'NOT (event.outcome:failure)' \ No newline at end of file diff --git a/tools/sigma/backends/base.py b/tools/sigma/backends/base.py index 4675b0197..d4c7ad55f 100644 --- a/tools/sigma/backends/base.py +++ b/tools/sigma/backends/base.py @@ -18,6 +18,7 @@ import sys import sigma import yaml +import re from .mixins import RulenameCommentMixin, QuoteCharMixin from sigma.parser.modifiers.base import SigmaTypeModifier @@ -90,6 +91,7 @@ class BaseBackend: options = tuple() # a list of tuples with following elements: option name, default value, help text, target attribute name (option name if None) config_required = True default_config = None + mapExpression = "" def __init__(self, sigmaconfig, backend_options=dict()): """ @@ -130,29 +132,48 @@ class BaseBackend: result = self.generateNode(parsed.parsedSearch) if parsed.parsedAgg: result += self.generateAggregation(parsed.parsedAgg) + #result = self.applyOverrides(result) return result + def applyOverrides(self, query): + try: + if 'overrides' in self.sigmaconfig.config and isinstance(query, str): + for expression in self.sigmaconfig.config['overrides']: + if 'regexes' in expression: + for x in expression['regexes']: + sub = expression['field'] + value = expression['value'] + query = re.sub(x, self.mapExpression % (sub, value), query) + if 'literals' in expression: + for x in expression['literals']: + sub = expression['field'] + value = expression['value'] + query = query.replace(x, self.mapExpression % (sub, value)) + except Exception: + pass + return query + def generateNode(self, node): if type(node) == sigma.parser.condition.ConditionAND: - return self.generateANDNode(node) + return self.applyOverrides(self.generateANDNode(node)) elif type(node) == sigma.parser.condition.ConditionOR: - return self.generateORNode(node) + return self.applyOverrides(self.generateORNode(node)) elif type(node) == sigma.parser.condition.ConditionNOT: - return self.generateNOTNode(node) + return self.applyOverrides(self.generateNOTNode(node)) elif type(node) == sigma.parser.condition.ConditionNULLValue: - return self.generateNULLValueNode(node) + return self.applyOverrides(self.generateNULLValueNode(node)) elif type(node) == sigma.parser.condition.ConditionNotNULLValue: - return self.generateNotNULLValueNode(node) + return self.applyOverrides(self.generateNotNULLValueNode(node)) elif type(node) == sigma.parser.condition.NodeSubexpression: - return self.generateSubexpressionNode(node) + return self.applyOverrides(self.generateSubexpressionNode(node)) elif type(node) == tuple: - return self.generateMapItemNode(node) + return self.applyOverrides(self.generateMapItemNode(node)) elif type(node) in (str, int): - return self.generateValueNode(node) + return self.applyOverrides(self.generateValueNode(node)) elif type(node) == list: - return self.generateListNode(node) + return self.applyOverrides(self.generateListNode(node)) elif isinstance(node, SigmaTypeModifier): - return self.generateTypedValueNode(node) + return self.applyOverrides(self.generateTypedValueNode(node)) else: raise TypeError("Node type %s was not expected in Sigma parse tree" % (str(type(node))))