diff --git a/tools/config/stix-linux.yml b/tools/config/stix-linux.yml new file mode 100644 index 000000000..e374f29b2 --- /dev/null +++ b/tools/config/stix-linux.yml @@ -0,0 +1,36 @@ +title: STIX for Linux Logs +backends: + - stix +order: 40 +logsources: + linux: + product: linux +fieldmappings: + type: + - x-event:action + keywords: + - x-sigma:keywords + a0: + - process:command_line + a1: + - process:command_line + name: + - file:name + a3: + - process:command_line + key: + - x-sigma:keywords + exe: + - file:name + a2: + - process:command_line + SYSCALL: + - x-event:action + pam_message: + - x-event:action + pam_user: + - user-account:user_id + pam_rhost: + - x-host:name + USER: + - user-account:user_id \ No newline at end of file diff --git a/tools/config/stix-windows.yml b/tools/config/stix-windows.yml index b3026badb..6a9de243c 100644 --- a/tools/config/stix-windows.yml +++ b/tools/config/stix-windows.yml @@ -21,8 +21,6 @@ fieldmappings: - user-account:x_security_id CallTrace: - x-windows:calltrace - ChangedAttributes: - - x-windows:changedattributes ClientIP: - ipv4-addr:value - ipv6-addr:value @@ -84,7 +82,7 @@ fieldmappings: Image: - process:image_ref.name ImageLoadedTempPath: - - process:image_ref.x_temp_path + - process:extensions.'windows-service-ext'.service_dll_refs[*].x_temp_path ImageName: - process:image_ref.name ImagePath: @@ -101,9 +99,9 @@ fieldmappings: IntegrityLevel: - x-windows:integritylevel LoadedImage: - - process:image_ref.name + - process:extensions.'windows-service-ext'.service_dll_refs[*].name LoadedImageName: - - process:image_ref.name + - process:extensions.'windows-service-ext'.service_dll_refs[*].name LogonType: - x-windows:logontype MD5Hash: @@ -116,8 +114,6 @@ fieldmappings: - x-windows:objectname ObjectType: - x-windows:objecttype - PSEncodedCommand: - - x-windows:psencodedcommand ParentCommandLine: - process:parent_ref.command_line ParentImage: @@ -152,30 +148,23 @@ fieldmappings: - x-windows:queryresults QueryStatus: - x-windows:querystatus - Realm: - - x-windows:realm - RecordNumber: - - x-windows:recordnumber RegistryKey: - windows-registry-key:key RegistryValueData: - windows-registry-key:values[*].data RegistryValueName: - windows-registry-key:values[*].name - RunLevel: - - x-windows:runlevel SAMAccountName: - - x-windows:samaccountname + - user-account:account_login + - user-account:display_name SHA1Hash: - file:hashes.SHA-1 SHA256Hash: - file:hashes.SHA-256 - Scope: - - x-windows:scope ServiceFileName: - - process:extensions.windows-service-ext.service_dll_refs[*].name + - process:extensions.'windows-service-ext'.service_dll_refs[*].name ServiceName: - - process:extensions.windows-service-ext.service_name + - process:extensions.'windows-service-ext'.service_name ShareName: - x-windows:sharename SharePath: @@ -233,12 +222,6 @@ fieldmappings: - user-account:user_id UserDomain: - user-account:x_domain - UserPrincipalName: - - x-windows:userprincipalname - UserRight: - - x-windows:userright - UserWorkstations: - - x-windows:userworkstations event-id: - x-event:id eventId: @@ -248,9 +231,9 @@ fieldmappings: event_data.Image: - process:image_ref.name event_data.ImageLoaded: - - process:image_ref.name + - process:extensions.'windows-service-ext'.service_dll_refs[*].name ImageLoaded: - - process:image_ref.name + - process:extensions.'windows-service-ext'.service_dll_refs[*].name event_data.ImagePath: - process:image_ref.name event_data.ParentCommandLine: @@ -262,7 +245,7 @@ fieldmappings: event_data.PipeName: - x-windows:pipename event_data.ServiceFileName: - - process:extensions.windows-service-ext.service_dll_refs[*].name + - process:extensions.'windows-service-ext'.service_dll_refs[*].name event_data.ShareName: - x-windows:sharename event_data.Signature: diff --git a/tools/config/stix.yml b/tools/config/stix.yml index c6d13293c..7cdad7e78 100644 --- a/tools/config/stix.yml +++ b/tools/config/stix.yml @@ -91,4 +91,45 @@ fieldmappings: username: - user-account:user_id utf8_payload: - - artifact:payload_bin \ No newline at end of file + - artifact:payload_bin + + # Web mapping + c-uri: + - url:value + keywords: + - x-sigma:keywords + cs-method: + - http-request-ext:request_method + sc-status: + - x-web:status_code + clientip: + - ipv4-addr:value + - ipv6-addr:value + - network-traffic:src_ref.value + + # Cloud mapping + eventSource: + - x-host:name + eventName: + - x-event:action + requestParameters.attribute: + - x-cloud:request_parameters + responseElements.publiclyAccessible: + - x-cloud:publicly_accessible + errorMessage: + - x-error:message + errorCode: + - x-error:code + responseElements: + - x-cloud:response_elements + requestParameters.userData: + - x-cloud:request_parameters + userIdentity.type: + - user-account:account_login + eventType: + - x-event:action + userIdentity.arn: + - user-account:account_login + - user-account:display_name + responseElements.pendingModifiedValues.masterUserPassword: + - user-account:credential diff --git a/tools/sigma/backends/stix.py b/tools/sigma/backends/stix.py index 82e7b3fd5..c48b950e2 100644 --- a/tools/sigma/backends/stix.py +++ b/tools/sigma/backends/stix.py @@ -14,6 +14,7 @@ class STIXBackend(SingleTextQueryBackend): subExpression = "(%s)" valueExpression = "\'%s\'" mapExpression = "%s = %s" + notMapExpression = "%s != %s" mapListsSpecialHandling = True sigmaSTIXObjectName = "x-sigma" @@ -26,15 +27,76 @@ class STIXBackend(SingleTextQueryBackend): def cleanValue(self, value): return value - def generateMapItemListNode(self, key, value): + def generateANDNode(self, node, currently_within_NOT_node=False): + generated = [self.generateNode(val, currently_within_NOT_node) for val in node] + filtered = [g for g in generated if g is not None] + if filtered: + if self.sort_condition_lists: + filtered = sorted(filtered) + return self.andToken.join(filtered) + else: + return None + + def generateORNode(self, node, currently_within_NOT_node=False): + generated = [self.generateNode(val, currently_within_NOT_node) for val in node] + filtered = [g for g in generated if g is not None] + if filtered: + if self.sort_condition_lists: + filtered = sorted(filtered) + return self.orToken.join(filtered) + else: + return None + + def generateNOTNode(self, node, currently_within_NOT_node=False): + currently_within_NOT_node = not(currently_within_NOT_node) + generated = self.generateNode(node.item, currently_within_NOT_node) + if generated is not None: + return generated + else: + return None + + def generateSubexpressionNode(self, node, currently_within_NOT_node=False): + generated = self.generateNode(node.items, currently_within_NOT_node) + if generated: + return self.subExpression % generated + else: + return None + + def generateMapItemNode(self, node, currently_within_NOT_node=False): + fieldname, value = node + + transformed_fieldname = self.fieldNameMapping(fieldname, value) + if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int): + if currently_within_NOT_node: + return self.notMapExpression % (transformed_fieldname, self.generateNode(value)) + return self.mapExpression % (transformed_fieldname, self.generateNode(value)) + elif type(value) == list: + return self.generateMapItemListNode(transformed_fieldname, value, currently_within_NOT_node) + elif isinstance(value, SigmaTypeModifier): + return self.generateMapItemTypedNode(transformed_fieldname, value) + elif value is None: + return self.nullExpression % (transformed_fieldname, ) + else: + raise TypeError("Backend does not support map values of type " + str(type(value))) + + def generateMapItemListNode(self, key, value, currently_within_NOT_node=False): items_list = list() for item in value: if type(item) == str and "*" in item: item = item.replace("*", "%") - items_list.append('%s LIKE %s' % (self.cleanKey(key), self.generateValueNode(item))) + if currently_within_NOT_node: + items_list.append('%s NOT LIKE %s' % (self.cleanKey(key), self.generateValueNode(item))) + else: + items_list.append('%s LIKE %s' % (self.cleanKey(key), self.generateValueNode(item))) else: - items_list.append('%s = %s' % (self.cleanKey(key), self.generateValueNode(item))) - return '('+" OR ".join(items_list)+')' + if currently_within_NOT_node: + items_list.append('%s != %s' % (self.cleanKey(key), self.generateValueNode(item))) + else: + items_list.append('%s = %s' % (self.cleanKey(key), self.generateValueNode(item))) + if currently_within_NOT_node: + return '(' + " AND ".join(items_list) + ')' + else: + return '('+" OR ".join(items_list)+')' def generateMapItemTypedNode(self, key, value): if type(value) == SigmaRegularExpressionModifier: @@ -48,18 +110,22 @@ class STIXBackend(SingleTextQueryBackend): else: raise NotImplementedError("Type modifier '{}' is not supported by backend".format(value.identifier)) - def generateMapItemNode(self, node): + def generateMapItemNode(self, node, currently_within_NOT_node=False): key, value = node if ":" not in key: key = "%s:%s" % (self.sigmaSTIXObjectName, str(key).lower()) if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int): if type(value) == str and "*" in value: value = value.replace("*", "%") + if currently_within_NOT_node: + return "%s NOT LIKE %s" % (self.cleanKey(key), self.generateValueNode(value)) return "%s LIKE %s" % (self.cleanKey(key), self.generateValueNode(value)) elif type(value) in (str, int): + if currently_within_NOT_node: + return self.notMapExpression % (self.cleanKey(key), self.generateValueNode(value)) return self.mapExpression % (self.cleanKey(key), self.generateValueNode(value)) elif type(value) == list: - return self.generateMapItemListNode(key, value) + return self.generateMapItemListNode(key, value, currently_within_NOT_node) elif isinstance(value, SigmaTypeModifier): return self.generateMapItemTypedNode(key, value) else: @@ -68,17 +134,21 @@ class STIXBackend(SingleTextQueryBackend): def generateValueNode(self, node): return self.valueExpression % (self.cleanValue(str(node))) - def generateNode(self, node): + def generateNode(self, node, currently_within_NOT_node=False): if type(node) == sigma.parser.condition.ConditionAND: - return self.generateANDNode(node) + if currently_within_NOT_node: + return self.generateORNode(node, currently_within_NOT_node) + return self.generateANDNode(node, currently_within_NOT_node) elif type(node) == sigma.parser.condition.ConditionOR: - return self.generateORNode(node) + if currently_within_NOT_node: + return self.generateANDNode(node, currently_within_NOT_node) + return self.generateORNode(node, currently_within_NOT_node) elif type(node) == sigma.parser.condition.ConditionNOT: - return self.generateNOTNode(node) + return self.generateNOTNode(node, currently_within_NOT_node) elif type(node) == sigma.parser.condition.NodeSubexpression: - return self.generateSubexpressionNode(node) + return self.generateSubexpressionNode(node, currently_within_NOT_node) elif type(node) == tuple: - return self.generateMapItemNode(node) + return self.generateMapItemNode(node, currently_within_NOT_node) else: raise TypeError("Node type %s was not expected in Sigma parse tree" % (str(type(node))))