2021-10-19 02:35:45 +00:00
# Output backends for sigmac
# Copyright 2021 HAWK.io
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2021-10-13 14:36:49 +00:00
import re
import sigma
import json
import uuid
2021-11-11 01:01:53 -06:00
import re
2021-10-13 14:36:49 +00:00
from sigma . parser . modifiers . base import SigmaTypeModifier
from sigma . parser . modifiers . type import SigmaRegularExpressionModifier
from . base import SingleTextQueryBackend
from . mixins import MultiRuleOutputMixin
2022-03-16 20:26:49 +00:00
2021-10-13 14:36:49 +00:00
class HAWKBackend ( SingleTextQueryBackend ) :
""" Converts Sigma rule into HAWK search """
identifier = " hawk "
2022-03-16 20:26:49 +00:00
mitre_json_url = " https://portal.hawk.io:8080/API/1.1/analytics/attack "
2021-10-13 14:36:49 +00:00
active = True
config_required = False
default_config = [ " sysmon " , " hawk " ]
reEscape = re . compile ( ' ( " ) ' )
2021-10-19 02:35:45 +00:00
logname = None
2021-10-13 14:36:49 +00:00
reClear = None
andToken = " , "
orToken = " , "
subExpression = " { \" id \" : \" and \" , \" key \" : \" And \" , \" children \" : [ %s ] } "
listExpression = " %s "
listSeparator = " "
valueExpression = " %s "
keyExpression = " %s "
nullExpression = " %s = null "
notNullExpression = " %s != null "
mapExpression = " %s = %s "
mapListsSpecialHandling = True
aql_database = " events "
def cleanKey ( self , key ) :
if key == None :
return " "
2021-12-01 15:51:22 +00:00
return self . snake_case ( self . sigmaparser . config . get_fieldmapping ( key ) . resolve_fieldname ( key , self . sigmaparser ) )
2021-10-13 14:36:49 +00:00
def cleanValue ( self , value ) :
""" Remove quotes in text """
return value
def generateNode ( self , node , notNode = False ) :
#print(type(node))
#print(node)
2021-12-01 13:29:15 +00:00
#print("Not: ", notNode)
2021-10-13 14:36:49 +00:00
if type ( node ) == sigma . parser . condition . ConditionAND :
2021-11-11 01:01:53 -06:00
return self . generateANDNode ( node , notNode )
2021-10-13 14:36:49 +00:00
elif type ( node ) == sigma . parser . condition . ConditionOR :
#print("OR NODE")
#print(node)
2021-11-11 01:01:53 -06:00
return self . generateORNode ( node , notNode )
2021-10-13 14:36:49 +00:00
elif type ( node ) == sigma . parser . condition . ConditionNOT :
#print("NOT NODE")
#print(node)
return self . generateNOTNode ( node )
elif type ( node ) == sigma . parser . condition . ConditionNULLValue :
2021-11-11 01:01:53 -06:00
return self . generateNULLValueNode ( node , notNode )
2021-10-13 14:36:49 +00:00
elif type ( node ) == sigma . parser . condition . ConditionNotNULLValue :
return self . generateNotNULLValueNode ( node )
elif type ( node ) == sigma . parser . condition . NodeSubexpression :
#print(node)
2021-11-11 01:01:53 -06:00
return self . generateSubexpressionNode ( node , notNode )
2021-10-13 14:36:49 +00:00
elif type ( node ) == tuple :
2021-10-19 02:35:45 +00:00
#print("TUPLE: ", node)
2021-10-13 14:36:49 +00:00
return self . generateMapItemNode ( node , notNode )
elif type ( node ) in ( str , int ) :
2021-11-11 01:01:53 -06:00
nodeRet = { " key " : " " , " description " : " " , " class " : " column " , " return " : " str " , " args " : { " comparison " : { " value " : " = " } , " str " : { " value " : " 5 " , " regex " : " true " } } }
2021-10-13 14:36:49 +00:00
#key = next(iter(self.sigmaparser.parsedyaml['detection']))
key = " payload "
#nodeRet['key'] = self.cleanKey(key).lower()
nodeRet [ ' key ' ] = key
#print(node)
#print("KEY: ", key)
# they imply the entire payload
nodeRet [ ' description ' ] = key
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
2021-11-17 23:29:41 -06:00
value = self . generateValueNode ( node , False ) . replace ( " * " , " EEEESTAREEE " )
2021-12-01 13:29:15 +00:00
if value [ - 2 : ] == " \\ \\ " :
value = value [ : - 2 ]
2021-11-17 23:29:41 -06:00
value = re . escape ( value )
value = value . replace ( " EEEESTAREEE " , " .* " )
2021-12-01 16:10:13 +00:00
endsWith = False
startsWith = False
2021-11-17 23:29:41 -06:00
if value [ 0 : 2 ] == " .* " :
value = value [ 2 : ]
2021-12-01 16:10:13 +00:00
endsWith = True
2021-11-17 23:29:41 -06:00
if value [ - 2 : ] == " .* " :
value = value [ : - 2 ]
2021-12-01 16:10:13 +00:00
startsWith = True
2021-12-01 16:39:25 +00:00
if endsWith and not startsWith :
2021-12-01 16:10:13 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value + " $ "
2021-12-01 16:39:25 +00:00
elif startsWith and not endsWith :
2021-12-01 16:10:13 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = " ^ " + value
else :
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value
2022-03-16 20:26:49 +00:00
if notNode :
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
2021-10-13 14:36:49 +00:00
return nodeRet
elif type ( node ) == list :
return self . generateListNode ( node , notNode )
else :
raise TypeError ( " Node type %s was not expected in Sigma parse tree " % ( str ( type ( node ) ) ) )
2021-11-11 01:01:53 -06:00
def generateANDNode ( self , node , notNode = False ) :
2021-10-13 14:36:49 +00:00
ret = { " id " : " and " , " key " : " And " , " children " : [ ] }
2021-11-11 01:01:53 -06:00
generated = [ self . generateNode ( val , notNode ) for val in node ]
2021-10-13 14:36:49 +00:00
filtered = [ g for g in generated if g is not None ]
if filtered :
if self . sort_condition_lists :
filtered = sorted ( filtered )
ret [ ' children ' ] = filtered
return ret
else :
return None
2021-11-11 01:01:53 -06:00
def generateORNode ( self , node , notNode = False ) :
if notNode :
ret = { " id " : " and " , " key " : " And " , " children " : [ ] }
else :
ret = { " id " : " or " , " key " : " Or " , " children " : [ ] }
generated = [ self . generateNode ( val , notNode ) for val in node ]
2021-10-13 14:36:49 +00:00
filtered = [ g for g in generated if g is not None ]
if filtered :
if self . sort_condition_lists :
filtered = sorted ( filtered )
ret [ ' children ' ] = filtered
2021-10-26 15:05:27 +00:00
# retAnd['children'].append( ret )
#return retAnd
2021-10-13 14:36:49 +00:00
return ret
else :
return None
2021-11-11 01:01:53 -06:00
def generateSubexpressionNode ( self , node , notNode = False ) :
generated = self . generateNode ( node . items , notNode )
2021-10-13 14:36:49 +00:00
if ' len ' in dir ( node . items ) : # fix the "TypeError: object of type 'NodeSubexpression' has no len()"
if len ( node . items ) == 1 :
# A sub expression with length 1 is not a proper sub expression, no self.subExpression required
return generated
if generated :
return json . loads ( self . subExpression % json . dumps ( generated ) )
else :
return None
def generateListNode ( self , node , notNode = False ) :
if not set ( [ type ( value ) for value in node ] ) . issubset ( { str , int } ) :
raise TypeError ( " List values must be strings or numbers " )
result = [ self . generateNode ( value , notNode ) for value in node ]
if len ( result ) == 1 :
# A list with length 1 is not a proper list, no self.listExpression required
return result [ 0 ]
#print("LIST EXPRESSION")
#print(result)
return self . listExpression % ( self . listSeparator . join ( result ) )
def generateNOTNode ( self , node ) :
generated = self . generateNode ( node . item , True )
return generated
def generateMapItemNode ( self , node , notNode = False ) :
nodeRet = { " key " : " " , " description " : " " , " class " : " column " , " return " : " str " , " args " : { " comparison " : { " value " : " = " } , " str " : { " value " : 5 } } }
if notNode :
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
key , value = node
if self . mapListsSpecialHandling == False and type ( value ) in ( str , int , list ) or self . mapListsSpecialHandling == True and type ( value ) in ( str , int ) :
nodeRet [ ' key ' ] = self . cleanKey ( key ) . lower ( )
nodeRet [ ' description ' ] = key
2021-10-19 02:35:45 +00:00
if key . lower ( ) in ( " logname " , " source " ) :
self . logname = value
2021-12-01 13:29:15 +00:00
if type ( value ) == str and " * " in value :
2021-11-17 23:29:41 -06:00
value = value . replace ( " * " , " EEEESTAREEE " )
value = re . escape ( value )
value = value . replace ( " EEEESTAREEE " , " .* " )
2021-12-01 16:10:13 +00:00
endsWith = False
startsWith = False
2021-11-17 23:29:41 -06:00
if value [ 0 : 2 ] == " .* " :
value = value [ 2 : ]
2021-12-01 16:10:13 +00:00
endsWith = True
2021-11-17 23:29:41 -06:00
if value [ - 2 : ] == " .* " :
value = value [ : - 2 ]
2021-12-01 16:10:13 +00:00
startsWith = True
2021-10-13 14:36:49 +00:00
if notNode :
2021-11-11 01:01:53 -06:00
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
2021-10-13 14:36:49 +00:00
else :
2021-11-11 01:01:53 -06:00
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-12-01 13:29:15 +00:00
if value [ - 2 : ] == " \\ \\ " :
value = value [ : - 2 ]
2021-12-01 16:10:13 +00:00
2021-12-01 16:39:25 +00:00
if endsWith and not startsWith :
2021-12-01 16:10:13 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value + " $ "
2021-12-01 16:39:25 +00:00
elif startsWith and not endsWith :
2021-12-01 16:10:13 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = " ^ " + value
else :
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value
2021-11-11 01:01:53 -06:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' regex ' ] = " true "
2021-10-13 14:36:49 +00:00
# return "%s regex %s" % (self.cleanKey(key), self.generateValueNode(value, True))
#return json.dumps(nodeRet)
return nodeRet
elif type ( value ) is str :
2021-12-01 13:29:15 +00:00
if notNode :
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
else :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value
# return json.dumps(nodeRet)
return nodeRet
elif type ( value ) is int :
nodeRet [ ' return ' ] = " int "
nodeRet [ ' args ' ] [ ' int ' ] = { " value " : value }
2021-12-01 13:29:15 +00:00
if notNode :
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
else :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
del nodeRet [ ' args ' ] [ ' str ' ]
#return self.mapExpression % (self.cleanKey(key), self.generateValueNode(value, True))
#return json.dumps(nodeRet)
return nodeRet
else :
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value
2021-12-01 13:29:15 +00:00
if notNode :
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
else :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
#return json.dumps(nodeRet)
return nodeRet
elif type ( value ) == list :
return self . generateMapItemListNode ( key , value , notNode )
elif isinstance ( value , SigmaTypeModifier ) :
2021-12-01 13:29:15 +00:00
return self . generateMapItemTypedNode ( key , value , notNode )
2021-10-13 14:36:49 +00:00
elif value is None :
#return self.nullExpression % (key, )
2021-11-11 01:01:53 -06:00
#print("Performing null")
#print(notNode)
#print(key)
nodeRet = { " key " : " empty " , " description " : " Value Does Not Exist (IS NULL) " , " class " : " function " , " inputs " : { " comparison " : { " order " : 0 , " source " : " comparison " , " type " : " comparison " } , " column " : { " order " : 1 , " source " : " columns " , " type " : " str " } } , " args " : { " comparison " : { " value " : " != " } , " column " : { " value " : " " } } , " return " : " boolean " }
nodeRet [ ' args ' ] [ ' column ' ] [ ' value ' ] = self . cleanKey ( key ) . lower ( )
nodeRet [ ' description ' ] + = " %s " % key
if notNode :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " != "
else :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
#return json.dumps(nodeRet)
2021-11-11 01:01:53 -06:00
#print(json.dumps(nodeRet))
2021-10-13 14:36:49 +00:00
return nodeRet
else :
raise TypeError ( " Backend does not support map values of type " + str ( type ( value ) ) )
def generateMapItemListNode ( self , key , value , notNode = False ) :
2021-11-11 01:01:53 -06:00
if notNode :
ret = { " id " : " and " , " key " : " And " , " children " : [ ] }
else :
ret = { " id " : " or " , " key " : " Or " , " children " : [ ] }
2021-10-13 14:36:49 +00:00
for item in value :
nodeRet = { " key " : " " , " description " : " " , " class " : " column " , " return " : " str " , " args " : { " comparison " : { " value " : " = " } , " str " : { " value " : " 5 " } } }
nodeRet [ ' key ' ] = self . cleanKey ( key ) . lower ( )
nodeRet [ ' description ' ] = key
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
2021-12-01 13:29:15 +00:00
if notNode :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " != "
else :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
if item is None :
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = ' null '
ret [ ' children ' ] . append ( nodeRet )
elif type ( item ) == str and " * " in item :
2021-11-17 23:29:41 -06:00
item = item . replace ( " * " , " EEEESTAREEE " )
item = re . escape ( item )
item = item . replace ( " EEEESTAREEE " , " .* " )
2021-12-01 16:10:13 +00:00
endsWith = False
startsWith = False
2021-11-17 23:29:41 -06:00
if item [ : 2 ] == " .* " :
item = item [ 2 : ]
2021-12-01 16:10:13 +00:00
endsWith = True
2021-11-17 23:29:41 -06:00
if item [ - 2 : ] == " .* " :
item = item [ : - 2 ]
2021-12-01 16:10:13 +00:00
startsWith = True
2021-12-01 13:29:15 +00:00
if item [ - 2 : ] == " \\ \\ " :
item = item [ : - 2 ]
2021-12-01 16:10:13 +00:00
2021-12-01 16:39:25 +00:00
if endsWith and not startsWith :
2021-12-01 16:10:13 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = item + " $ "
2021-12-01 16:39:25 +00:00
elif startsWith and not endsWith :
2021-12-01 16:10:13 +00:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = " ^ " + item
else :
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = item
2021-11-11 01:01:53 -06:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' regex ' ] = " true "
2021-12-01 16:10:13 +00:00
2021-10-13 14:36:49 +00:00
if notNode :
2021-11-11 01:01:53 -06:00
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
2021-10-13 14:36:49 +00:00
else :
2021-11-11 01:01:53 -06:00
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-12-01 13:29:15 +00:00
#print(item)
2021-10-13 14:36:49 +00:00
ret [ ' children ' ] . append ( nodeRet )
else :
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = self . generateValueNode ( item , True )
ret [ ' children ' ] . append ( nodeRet )
2021-10-26 15:05:27 +00:00
retAnd = { " id " : " and " , " key " : " And " , " children " : [ ret ] }
return retAnd # '('+" or ".join(itemslist)+')'
2021-10-13 14:36:49 +00:00
# return json.dumps(ret) # '('+" or ".join(itemslist)+')'
def generateMapItemTypedNode ( self , fieldname , value , notNode = False ) :
nodeRet = { " key " : " " , " description " : " " , " class " : " column " , " return " : " str " , " args " : { " comparison " : { " value " : " = " } , " str " : { " value " : " 5 " } } }
nodeRet [ ' key ' ] = self . cleanKey ( fieldname ) . lower ( )
nodeRet [ ' description ' ] = fieldname
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
if type ( value ) == SigmaRegularExpressionModifier :
2021-12-06 20:22:14 +00:00
value = self . generateValueNode ( value , True )
2021-11-17 23:29:41 -06:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' value ' ] = value
2021-11-11 01:01:53 -06:00
nodeRet [ ' args ' ] [ ' str ' ] [ ' regex ' ] = " true "
2021-10-13 14:36:49 +00:00
if notNode :
2021-11-11 01:01:53 -06:00
nodeRet [ " args " ] [ " comparison " ] [ " value " ] = " != "
2021-10-13 14:36:49 +00:00
else :
2021-11-11 01:01:53 -06:00
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
return nodeRet
else :
raise NotImplementedError ( " Type modifier ' {} ' is not supported by backend " . format ( value . identifier ) )
def generateValueNode ( self , node , keypresent ) :
return self . valueExpression % ( self . cleanValue ( str ( node ) ) )
2021-11-11 01:01:53 -06:00
def generateNULLValueNode ( self , node , notNode ) :
2021-10-13 14:36:49 +00:00
# node.item
2021-11-17 23:29:41 -06:00
nodeRet = { " key " : " empty " , " description " : " Value Does Not Exist (IS NULL) " , " class " : " function " , " inputs " : { " comparison " : { " order " : 0 , " source " : " comparison " , " type " : " comparison " } , " column " : { " order " : 1 , " source " : " columns " , " type " : " str " } } , " args " : { " comparison " : { " value " : " != " } , " column " : { " value " : node . item } } , " return " : " boolean " }
nodeRet [ ' args ' ] [ ' column ' ] [ ' value ' ] = self . cleanKey ( node . item ) . lower ( )
nodeRet [ ' description ' ] + = " %s " % key
if notNode :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " != "
else :
nodeRet [ ' args ' ] [ ' comparison ' ] [ ' value ' ] = " = "
2021-10-13 14:36:49 +00:00
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
# return json.dumps(nodeRet)
return nodeRet
def generateNotNULLValueNode ( self , node ) :
# return self.notNullExpression % (node.item)
return node . item
def generateAggregation ( self , agg , timeframe = ' 00 ' ) :
if agg == None :
2021-10-26 15:05:27 +00:00
return None
#print(agg.aggfunc)
#print(type(agg.aggfunc))
#print(agg.aggfunc_notrans)
if not agg . aggfunc_notrans . lower ( ) in ( " count " , " sum " ) :
raise NotImplementedError ( " This aggregation operator ' %s ' has not been implemented " % agg . aggfunc_notrans )
2021-10-13 14:36:49 +00:00
if agg . aggfunc == sigma . parser . condition . SigmaAggregationParser . AGGFUNC_NEAR :
2021-10-26 15:05:27 +00:00
return None
2021-10-13 14:36:49 +00:00
if agg . groupfield == None :
2021-10-26 15:05:27 +00:00
agg . groupfield = " priority "
if agg . groupfield != None and timeframe == ' 00 ' :
self . prefixAgg = " SELECT %s ( %s ) as agg_val from %s where " % ( agg . aggfunc_notrans , self . cleanKey ( agg . aggfield ) , self . aql_database )
self . suffixAgg = " group by %s having agg_val %s %s " % ( self . cleanKey ( agg . groupfield ) , agg . cond_op , agg . condition )
#print("Group field and timeframe is 00")
min_count = 60
nodeRet = { " key " : " atomic_counter " , " description " : self . cleanKey ( agg . groupfield ) + " %s aggregation stream counter " % agg . aggfunc_notrans , " class " : " function " , " return " : " int " ,
" inputs " : {
" columns " : { " order " : " 0 " , " source " : " columns " , " type " : " array " , " objectKey " : " columns " } ,
" comparison " : { " order " : " 1 " , " source " : " comparison " , " type " : " comparison " , " objectKey " : " comparison " } ,
" threshold " : { " order " : " 2 " , " source " : " " , " type " : " int " , " objectKey " : " threshold " } ,
" limit " : { " order " : " 3 " , " source " : " time_offset " , " type " : " int " , " objectKey " : " limit " } ,
} ,
" args " : {
" columns " : [ self . cleanKey ( agg . groupfield ) ] ,
" comparison " : { " value " : " %s " % agg . cond_op } ,
" threshold " : { " value " : int ( agg . condition ) } ,
" limit " : { " value " : min_count }
}
}
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
#print("No time range set")
return nodeRet
2021-10-13 14:36:49 +00:00
elif agg . groupfield != None and timeframe != None :
for key , duration in self . generateTimeframe ( timeframe ) . items ( ) :
2021-10-26 15:05:27 +00:00
min_count = 60
if key . lower ( ) == ' hours ' :
min_count = 24 * int ( duration )
nodeRet = { " key " : " atomic_counter " , " description " : self . cleanKey ( agg . groupfield ) + " %s aggregation stream counter " % agg . aggfunc_notrans , " class " : " function " , " return " : " int " ,
" inputs " : {
" columns " : { " order " : " 0 " , " source " : " columns " , " type " : " array " , " objectKey " : " columns " } ,
" comparison " : { " order " : " 1 " , " source " : " comparison " , " type " : " comparison " , " objectKey " : " comparison " } ,
" threshold " : { " order " : " 2 " , " source " : " " , " type " : " int " , " objectKey " : " threshold " } ,
" limit " : { " order " : " 3 " , " source " : " time_offset " , " type " : " int " , " objectKey " : " limit " } ,
} ,
" args " : {
" columns " : [ self . cleanKey ( agg . groupfield ) ] ,
" comparison " : { " value " : " %s " % agg . cond_op } ,
" threshold " : { " value " : int ( agg . condition ) } ,
" limit " : { " value " : min_count }
}
}
nodeRet [ ' rule_id ' ] = str ( uuid . uuid4 ( ) )
#self.prefixAgg = " SELECT %s(%s) as agg_val from %s where " % (agg.aggfunc_notrans, self.cleanKey(agg.aggfield), self.aql_database)
#self.suffixAgg = " group by %s having agg_val %s %s LAST %s %s" % (self.cleanKey(agg.groupfield), agg.cond_op, agg.condition, duration, key)
#print("Group field and timeframe")
#return self.prefixAgg, self.suffixAgg
return nodeRet
2021-10-13 14:36:49 +00:00
else :
self . prefixAgg = " SELECT %s ( %s ) as agg_val from %s where " % ( agg . aggfunc_notrans , self . cleanKey ( agg . aggfield ) , self . aql_database )
self . suffixAgg = " group by %s having agg_val %s %s " % ( self . cleanKey ( agg . groupfield ) , agg . cond_op , agg . condition )
2021-10-26 15:05:27 +00:00
#print("Last option")
raise NotImplementedError ( " The ' agg ' aggregation operator is not yet implemented for this backend " )
2021-10-13 14:36:49 +00:00
return self . prefixAgg , self . suffixAgg
#print(agg)
raise NotImplementedError ( " The ' agg ' aggregation operator is not yet implemented for this backend " )
def generateTimeframe ( self , timeframe ) :
time_unit = timeframe [ - 1 : ]
duration = timeframe [ : - 1 ]
timeframe_object = { }
if time_unit == " s " :
timeframe_object [ ' seconds ' ] = int ( duration )
elif time_unit == " m " :
timeframe_object [ ' minutes ' ] = int ( duration )
elif time_unit == " h " :
timeframe_object [ ' hours ' ] = int ( duration )
elif time_unit == " d " :
timeframe_object [ ' days ' ] = int ( duration )
else :
timeframe_object [ ' months ' ] = int ( duration )
return timeframe_object
2021-10-19 02:35:45 +00:00
def generateBefore ( self , parsed ) :
if self . logname :
return self . logname
return self . logname
2021-10-13 14:36:49 +00:00
def generate ( self , sigmaparser ) :
""" Method is called for each sigma rule and receives the parsed rule (SigmaParser) """
2021-10-19 02:35:45 +00:00
columns = list ( )
mapped = None
#print(sigmaparser.parsedyaml)
self . logsource = sigmaparser . parsedyaml . get ( " logsource " ) if sigmaparser . parsedyaml . get ( " logsource " ) else sigmaparser . parsedyaml . get ( " logsources " , { } )
fields = " "
try :
#print(sigmaparser.parsedyaml["fields"])
for field in sigmaparser . parsedyaml [ " fields " ] :
mapped = sigmaparser . config . get_fieldmapping ( field ) . resolve_fieldname ( field , sigmaparser )
if type ( mapped ) == str :
columns . append ( mapped )
elif type ( mapped ) == list :
columns . extend ( mapped )
else :
raise TypeError ( " Field mapping must return string or list " )
fields = " , " . join ( str ( x ) for x in columns )
fields = " | table " + fields
except KeyError : # no 'fields' attribute
mapped = None
pass
#print("Mapped: ", mapped)
2021-10-13 14:36:49 +00:00
#print(sigmaparser.parsedyaml)
#print(sigmaparser.condparsed)
2021-10-19 02:35:45 +00:00
#print("Columns: ", columns)
#print("Fields: ", fields)
#print("Logsource: " , self.logsource)
2021-10-13 14:36:49 +00:00
for parsed in sigmaparser . condparsed :
query = self . generateQuery ( parsed , sigmaparser )
before = self . generateBefore ( parsed )
after = self . generateAfter ( parsed )
2021-10-19 02:35:45 +00:00
#print("Before: ", before)
#print("Query: ", query)
2021-10-13 14:36:49 +00:00
result = " "
if before is not None :
result = before
if query is not None :
result + = query
if after is not None :
result + = after
return result
2021-11-11 01:01:53 -06:00
def dedupeAnds ( self , arr , parentAnd = False ) :
# simple dedupe
for i in range ( 0 , len ( arr ) ) :
if ' id ' in arr [ i ] and arr [ i ] [ ' id ' ] . lower ( ) == " and " :
arr [ i ] [ ' children ' ] = self . dedupeAnds ( arr [ i ] [ ' children ' ] )
if len ( arr [ i ] [ ' children ' ] ) == 1 and ' id ' in arr [ i ] [ ' children ' ] [ 0 ] and arr [ i ] [ ' children ' ] [ 0 ] [ ' id ' ] . lower ( ) == " and " :
arr [ i ] = arr [ i ] [ ' children ' ] [ 0 ]
return arr
"""
for i in range(0, len(arr)):
if parentAnd and ' id ' in arr[i] and arr[i][ ' id ' ].lower() == " and " :
isAnd = True
else:
isAnd = False
if ' children ' in arr[i]:
arr[i][ ' children ' ] = self.dedupeAnds(arr[ ' i ' ][ ' children ' ], isAnd)
if parentAnd and ' id ' in arr[i] and arr[i][ ' id ' ].lower() == " and " :
pass
if len(arr) == 1 and ' id ' in arr[0] and arr[0][ ' id ' ].lower() == " and " :
# print( " Returning less! " )
for i in range(0, len(arr) ):
if ' id ' in arr[i] and arr[i][ ' id ' ].lower() == " and " :
arr[i][ ' children ' ] = self.dedupeAnds(arr[i][ ' children ' ])
return arr[0][ ' children ' ]
"""
return arr
"""
def dedupeAnds(self, arr, parentAnd=False):
#if not parentAnd:
# for i in range(0, len(arr) ):
# if ' id ' in arr[i] and arr[i][ ' id ' ].lower() == " and " :
# arr[i][ ' children ' ] = self.dedupeAnds(arr[i][ ' children ' ], False)
if len(arr) == 1 and ' id ' in arr[0] and arr[0][ ' id ' ].lower() == " and " :
# print( " Returning less! " )
for i in range(0, len(arr) ):
if ' id ' in arr[i] and arr[i][ ' id ' ].lower() == " and " :
arr[i][ ' children ' ] = self.dedupeAnds(arr[i][ ' children ' ])
return arr[0][ ' children ' ]
allAndCheck = True
for i in range(0, len(arr) ):
# print(arr[i])
if ' id ' in arr[i] and arr[i][ ' id ' ].lower() == " and " :
arr[i][ ' children ' ] = self.dedupeAnds(arr[i][ ' children ' ])
else:
allAndCheck = False
x = [ ]
if allAndCheck:
for i in range(0, len(arr)):
x = x + arr[i][ ' children ' ]
return x
return arr
"""
2021-10-13 14:36:49 +00:00
def generateQuery ( self , parsed , sigmaparser ) :
self . sigmaparser = sigmaparser
result = self . generateNode ( parsed . parsedSearch )
prefix = " "
ret = ' [ { " id " : " and " , " key " : " And " , " children " : [ '
2021-10-19 02:35:45 +00:00
ret2 = ' ] } ] '
2021-10-13 14:36:49 +00:00
try :
mappedFields = [ ]
for field in sigmaparser . parsedyaml [ " fields " ] :
mapped = sigmaparser . config . get_fieldmapping ( field ) . resolve_fieldname ( field , sigmaparser )
2021-10-19 02:35:45 +00:00
#print(mapped)
2021-10-13 14:36:49 +00:00
mappedFields . append ( mapped )
if " " in mapped and not " ( " in mapped :
prefix + = " , \" " + mapped + " \" "
else :
prefix + = " , " + mapped
except KeyError : # no 'fields' attribute
mapped = None
pass
2021-10-26 15:05:27 +00:00
try :
timeframe = sigmaparser . parsedyaml [ ' detection ' ] [ ' timeframe ' ]
except :
timeframe = None
if parsed . parsedAgg and timeframe == None :
addition = self . generateAggregation ( parsed . parsedAgg )
#print(addition)
#print(result)
if addition :
if not ' children ' in result :
rec = self . subExpression % json . dumps ( result )
result = json . loads ( rec )
#print(result)
result [ ' children ' ] . append ( addition )
elif parsed . parsedAgg :
#print(result)
raise Exception ( " No agg returned, something is off " )
elif parsed . parsedAgg != None and timeframe != None :
addition = self . generateAggregation ( parsed . parsedAgg , timeframe )
#print(addition)
#print(result)
if addition :
#print(result)
if not ' children ' in result :
rec = self . subExpression % json . dumps ( result )
result = json . loads ( rec )
#print(result)
result [ ' children ' ] . append ( addition )
elif parsed . parsedAgg :
#print(result)
raise Exception ( " No agg returned, something is off " )
else :
# result = prefix + result
pass
2021-10-13 14:36:49 +00:00
2021-10-19 02:35:45 +00:00
result = json . dumps ( result )
2021-10-13 14:36:49 +00:00
analytic_txt = ret + result + ret2 # json.dumps(ret)
try :
analytic = json . loads ( analytic_txt ) # json.dumps(ret)
2021-11-11 01:01:53 -06:00
# analytic = self.dedupeAnds(analytic)
analytic [ 0 ] [ ' children ' ] = self . dedupeAnds ( analytic [ 0 ] [ ' children ' ] , True )
2021-10-13 14:36:49 +00:00
except Exception as e :
print ( " Failed to parse json: %s " % analytic_txt )
raise Exception ( " Failed to parse json: %s " % analytic_txt )
2021-10-14 15:05:05 +00:00
cmt = " Sigma Rule: %s \n " % sigmaparser . parsedyaml [ ' id ' ]
cmt + = " Author: %s \n " % sigmaparser . parsedyaml [ ' author ' ]
cmt + = " Level: %s \n " % sigmaparser . parsedyaml [ ' level ' ]
if ' falsepositives ' in sigmaparser . parsedyaml and type ( sigmaparser . parsedyaml [ ' falsepositives ' ] ) is list :
if len ( sigmaparser . parsedyaml [ ' falsepositives ' ] ) > 0 :
cmt + = " False Positives: "
for v in sigmaparser . parsedyaml [ ' falsepositives ' ] :
if v :
cmt + = " %s , " % v
else :
cmt + = " None, "
cmt = cmt [ : - 2 ] + " \n "
elif ' falsepositives ' in sigmaparser . parsedyaml and sigmaparser . parsedyaml [ ' falsepositives ' ] :
raise Exception ( " Unknown type for false positives: " , type ( sigmaparser . parsedyaml [ ' falsepositives ' ] ) )
if ' references ' in sigmaparser . parsedyaml :
2021-10-18 21:34:48 +00:00
ref = " %s \n " % " \n " . join ( sigmaparser . parsedyaml [ ' references ' ] )
else :
ref = ' '
2021-10-13 14:36:49 +00:00
record = {
" rules " : analytic , # analytic_txt.replace('"','""'),
" filter_name " : sigmaparser . parsedyaml [ ' title ' ] ,
2021-11-11 01:01:53 -06:00
" filter_details " : cmt ,
2021-10-13 14:36:49 +00:00
" actions_category_name " : " Add (+) " ,
" correlation_action " : 5.00 ,
" date_added " : sigmaparser . parsedyaml [ ' date ' ] ,
2021-11-11 01:01:53 -06:00
" enabled " : False ,
# "enabled" : True,
2021-10-13 14:36:49 +00:00
" public " : True ,
2021-10-18 21:34:48 +00:00
" references " : ref ,
2021-10-13 14:36:49 +00:00
" group_name " : " . " ,
2021-11-23 16:57:43 +00:00
" tags " : [ " sigma " ] ,
2021-10-18 21:34:48 +00:00
" hawk_id " : sigmaparser . parsedyaml [ ' id ' ]
2021-10-13 14:36:49 +00:00
}
2021-10-14 15:05:05 +00:00
if ' tags ' in sigmaparser . parsedyaml :
2022-03-16 20:26:49 +00:00
mitre_tactics = [ item . replace ( " attack. " , " " ) for item in sigmaparser . parsedyaml [ ' tags ' ] ]
if len ( mitre_tactics ) > 0 :
record [ " tags " ] = record [ ' tags ' ] + mitre_tactics
# set 1st tactic and technique found
mitre_tactics_filtered = [ ]
for item in mitre_tactics :
if re . match ( " ^t[0-9]+ " , item ) : mitre_tactics_filtered . append ( item . upper ( ) )
if len ( mitre_tactics_filtered ) > 0 :
record [ " technique " ] = mitre_tactics_filtered [ 0 ]
2021-10-14 15:05:05 +00:00
2021-10-13 14:36:49 +00:00
if not ' status ' in self . sigmaparser . parsedyaml or ' status ' in self . sigmaparser . parsedyaml and self . sigmaparser . parsedyaml [ ' status ' ] != ' experimental ' :
2022-01-11 15:05:52 +00:00
record [ ' correlation_action ' ] + = 5.0 ;
2021-11-23 16:57:43 +00:00
elif ' status ' in self . sigmaparser . parsedyaml and self . sigmaparser . parsedyaml [ ' status ' ] == ' experimental ' :
record [ " tags " ] . append ( " qa " )
2021-10-13 14:36:49 +00:00
if ' falsepositives ' in self . sigmaparser . parsedyaml and len ( self . sigmaparser . parsedyaml [ ' falsepositives ' ] ) > 1 :
record [ ' correlation_action ' ] - = ( 2.0 * len ( self . sigmaparser . parsedyaml [ ' falsepositives ' ] ) )
if ' level ' in self . sigmaparser . parsedyaml :
if self . sigmaparser . parsedyaml [ ' level ' ] . lower ( ) == ' critical ' :
record [ ' correlation_action ' ] + = 15.0 ;
elif self . sigmaparser . parsedyaml [ ' level ' ] . lower ( ) == ' high ' :
record [ ' correlation_action ' ] + = 10.0 ;
elif self . sigmaparser . parsedyaml [ ' level ' ] . lower ( ) == ' medium ' :
2022-01-28 00:24:20 +00:00
# record['correlation_action'] += 0.0;
pass
2021-10-13 14:36:49 +00:00
elif self . sigmaparser . parsedyaml [ ' level ' ] . lower ( ) == ' low ' :
2022-01-11 15:05:52 +00:00
record [ ' correlation_action ' ] - = 10.0 ;
2021-12-01 17:25:23 +00:00
elif self . sigmaparser . parsedyaml [ ' level ' ] . lower ( ) == ' informational ' :
record [ ' correlation_action ' ] - = 15.0 ;
2022-02-07 14:15:16 +00:00
if record [ ' correlation_action ' ] < 0.0 :
record [ ' correlation_action ' ] = 0.0
2021-10-13 14:36:49 +00:00
return json . dumps ( record )
2021-12-01 15:51:22 +00:00
def snake_case ( self , str ) :
res = [ str [ 0 ] . lower ( ) ]
for c in str [ 1 : ] :
if c in ( ' ABCDEFGHIJKLMNOPQRSTUVWXYZ ' ) :
res . append ( ' _ ' )
res . append ( c . lower ( ) )
else :
res . append ( c )
return ' ' . join ( res )