added object_paths
added more tests for pattern expressions added "set" comparison expressions implemented make_constant fixed type name for EmailAddressstix2.1
parent
c1b07ef505
commit
6fa009e509
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
from .bundle import Bundle
|
from .bundle import Bundle
|
||||||
from .constants import (FloatConstant, HashConstant, IntegerConstant,
|
from .constants import (FloatConstant, HashConstant, HexConstant,
|
||||||
StringConstant)
|
IntegerConstant, StringConstant)
|
||||||
|
from .object_path import ObjectPath, ObjectPathComponent
|
||||||
from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
||||||
AutonomousSystem, CustomObservable, Directory,
|
AutonomousSystem, CustomObservable, Directory,
|
||||||
DomainName, EmailAddress, EmailMessage,
|
DomainName, EmailAddress, EmailMessage,
|
||||||
|
@ -29,6 +30,8 @@ from .pattern_expressions import (AndBooleanExpression,
|
||||||
FollowedByObservableExpression,
|
FollowedByObservableExpression,
|
||||||
GreaterThanComparisonExpression,
|
GreaterThanComparisonExpression,
|
||||||
GreaterThanEqualComparisonExpression,
|
GreaterThanEqualComparisonExpression,
|
||||||
|
IsSubsetComparisonExpression,
|
||||||
|
IsSupersetComparisonExpression,
|
||||||
LessThanComparisonExpression,
|
LessThanComparisonExpression,
|
||||||
LessThanEqualComparisonExpression,
|
LessThanEqualComparisonExpression,
|
||||||
LikeComparisonExpression,
|
LikeComparisonExpression,
|
||||||
|
|
|
@ -121,5 +121,15 @@ class ListConstant(Constant):
|
||||||
|
|
||||||
|
|
||||||
def make_constant(value):
|
def make_constant(value):
|
||||||
# TODO: Stub
|
if isinstance(value, str):
|
||||||
pass
|
return StringConstant(value)
|
||||||
|
elif isinstance(value, int):
|
||||||
|
return IntegerConstant(value)
|
||||||
|
elif isinstance(value, float):
|
||||||
|
return FloatConstant(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
return ListConstant(value)
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
return BooleanConstant(value)
|
||||||
|
else:
|
||||||
|
raise ValueError("Unable to create a constant from %s" % value)
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
|
||||||
|
class ObjectPathComponent(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BasicObjectPathComponent(ObjectPathComponent):
|
||||||
|
def __init__(self, property_name, is_key=False):
|
||||||
|
self.property_name = property_name
|
||||||
|
# TODO: set is_key to True if this component is a dictionary key
|
||||||
|
# self.is_key = is_key
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.property_name
|
||||||
|
|
||||||
|
|
||||||
|
class ListObjectPathComponent(ObjectPathComponent):
|
||||||
|
def __init__(self, property_name, index):
|
||||||
|
self.property_name = property_name
|
||||||
|
self.index = index
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s[%s]" % (self.property_name, self.index)
|
||||||
|
|
||||||
|
|
||||||
|
class ReferenceObjectPathComponent(ObjectPathComponent):
|
||||||
|
def __init__(self, reference_property_name):
|
||||||
|
self.property_name = reference_property_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.property_name
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectPath(object):
|
||||||
|
def __init__(self, object_type_name, property_path):
|
||||||
|
self.object_type_name = object_type_name
|
||||||
|
self.property_path = [x if isinstance(x, ObjectPathComponent) else ObjectPath.create_ObjectPathComponent(x)
|
||||||
|
for x in property_path]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s:%s" % (self.object_type_name, ".".join(["%s" % x for x in self.property_path]))
|
||||||
|
|
||||||
|
def merge(self, other):
|
||||||
|
self.property_path.extend(other.property_path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_object_path(lhs):
|
||||||
|
path_as_parts = lhs.split(":")
|
||||||
|
return ObjectPath(path_as_parts[0], path_as_parts[1].split("."))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_ObjectPathComponent(component_name):
|
||||||
|
if component_name.endswith("_ref"):
|
||||||
|
return ReferenceObjectPathComponent(component_name)
|
||||||
|
elif component_name.find("[") != -1:
|
||||||
|
parse1 = component_name.split("[")
|
||||||
|
return ListObjectPathComponent(parse1[0], parse1[1][:-1])
|
||||||
|
else:
|
||||||
|
return BasicObjectPathComponent(component_name)
|
|
@ -123,7 +123,7 @@ class DomainName(_Observable):
|
||||||
|
|
||||||
|
|
||||||
class EmailAddress(_Observable):
|
class EmailAddress(_Observable):
|
||||||
_type = 'email-address'
|
_type = 'email-addr'
|
||||||
_properties = {
|
_properties = {
|
||||||
'type': TypeProperty(_type),
|
'type': TypeProperty(_type),
|
||||||
'value': StringProperty(required=True),
|
'value': StringProperty(required=True),
|
||||||
|
@ -651,7 +651,7 @@ OBJ_MAP_OBSERVABLE = {
|
||||||
'autonomous-system': AutonomousSystem,
|
'autonomous-system': AutonomousSystem,
|
||||||
'directory': Directory,
|
'directory': Directory,
|
||||||
'domain-name': DomainName,
|
'domain-name': DomainName,
|
||||||
'email-address': EmailAddress,
|
'email-addr': EmailAddress,
|
||||||
'email-message': EmailMessage,
|
'email-message': EmailMessage,
|
||||||
'file': File,
|
'file': File,
|
||||||
'ipv4-addr': IPv4Address,
|
'ipv4-addr': IPv4Address,
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
from .constants import ListConstant, make_constant
|
from .constants import Constant, IntegerConstant, ListConstant, make_constant
|
||||||
|
from .object_path import ObjectPath
|
||||||
|
|
||||||
|
|
||||||
class PatternExpression(object):
|
class PatternExpression(object):
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_root_from_object_path(lhs):
|
|
||||||
path_as_parts = lhs.split(":")
|
|
||||||
return path_as_parts[0]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def escape_quotes_and_backslashes(s):
|
def escape_quotes_and_backslashes(s):
|
||||||
return s.replace(u'\\', u'\\\\').replace(u"'", u"\\'")
|
return s.replace(u'\\', u'\\\\').replace(u"'", u"\\'")
|
||||||
|
@ -19,13 +15,16 @@ class ComparisonExpression(PatternExpression):
|
||||||
self.operator = "IN"
|
self.operator = "IN"
|
||||||
else:
|
else:
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
self.lhs = lhs
|
if isinstance(lhs, ObjectPath):
|
||||||
if isinstance(rhs, str):
|
self.lhs = lhs
|
||||||
self.rhs = make_constant(rhs)
|
|
||||||
else:
|
else:
|
||||||
|
self.lhs = ObjectPath.make_object_path(lhs)
|
||||||
|
if isinstance(rhs, Constant):
|
||||||
self.rhs = rhs
|
self.rhs = rhs
|
||||||
|
else:
|
||||||
|
self.rhs = make_constant(rhs)
|
||||||
self.negated = negated
|
self.negated = negated
|
||||||
self.root_type = self.get_root_from_object_path(lhs)
|
self.root_type = self.lhs.object_type_name
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# if isinstance(self.rhs, list):
|
# if isinstance(self.rhs, list):
|
||||||
|
@ -81,7 +80,14 @@ class MatchesComparisonExpression(ComparisonExpression):
|
||||||
super(MatchesComparisonExpression, self).__init__("MATCHES", lhs, rhs, negated)
|
super(MatchesComparisonExpression, self).__init__("MATCHES", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
# TODO: ISASUBSET, ISSUPERSET
|
class IsSubsetComparisonExpression(ComparisonExpression):
|
||||||
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
|
super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
|
class IsSupersetComparisonExpression(ComparisonExpression):
|
||||||
|
def __init__(self, lhs, rhs, negated=False):
|
||||||
|
super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated)
|
||||||
|
|
||||||
|
|
||||||
class BooleanExpression(PatternExpression):
|
class BooleanExpression(PatternExpression):
|
||||||
|
@ -165,7 +171,12 @@ class ExpressionQualifier(PatternExpression):
|
||||||
|
|
||||||
class RepeatQualifier(ExpressionQualifier):
|
class RepeatQualifier(ExpressionQualifier):
|
||||||
def __init__(self, times_to_repeat):
|
def __init__(self, times_to_repeat):
|
||||||
self.times_to_repeat = times_to_repeat
|
if isinstance(times_to_repeat, IntegerConstant):
|
||||||
|
self.times_to_repeat = times_to_repeat
|
||||||
|
elif isinstance(times_to_repeat, int):
|
||||||
|
self.times_to_repeat = IntegerConstant(times_to_repeat)
|
||||||
|
else:
|
||||||
|
raise ValueError("%s is not a valid argument for a Within Qualifier" % times_to_repeat)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "REPEATS %s TIMES" % self.times_to_repeat
|
return "REPEATS %s TIMES" % self.times_to_repeat
|
||||||
|
@ -173,7 +184,12 @@ class RepeatQualifier(ExpressionQualifier):
|
||||||
|
|
||||||
class WithinQualifier(ExpressionQualifier):
|
class WithinQualifier(ExpressionQualifier):
|
||||||
def __init__(self, number_of_seconds):
|
def __init__(self, number_of_seconds):
|
||||||
self.number_of_seconds = number_of_seconds
|
if isinstance(number_of_seconds, IntegerConstant):
|
||||||
|
self.number_of_seconds = number_of_seconds
|
||||||
|
elif isinstance(number_of_seconds, int):
|
||||||
|
self.number_of_seconds = IntegerConstant(number_of_seconds)
|
||||||
|
else:
|
||||||
|
raise ValueError("%s is not a valid argument for a Within Qualifier" % number_of_seconds)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "WITHIN %s SECONDS" % self.number_of_seconds
|
return "WITHIN %s SECONDS" % self.number_of_seconds
|
||||||
|
@ -181,8 +197,18 @@ class WithinQualifier(ExpressionQualifier):
|
||||||
|
|
||||||
class StartStopQualifier(ExpressionQualifier):
|
class StartStopQualifier(ExpressionQualifier):
|
||||||
def __init__(self, start_time, stop_time):
|
def __init__(self, start_time, stop_time):
|
||||||
self.start_time = start_time
|
if isinstance(start_time, IntegerConstant):
|
||||||
self.stop_time = stop_time
|
self.start_time = start_time
|
||||||
|
elif isinstance(start_time, int):
|
||||||
|
self.start_time = IntegerConstant(start_time)
|
||||||
|
else:
|
||||||
|
raise ValueError("%s is not a valid argument for a Within Qualifier" % start_time)
|
||||||
|
if isinstance(stop_time, IntegerConstant):
|
||||||
|
self.stop_time = stop_time
|
||||||
|
elif isinstance(stop_time, int):
|
||||||
|
self.stop_time = IntegerConstant(stop_time)
|
||||||
|
else:
|
||||||
|
raise ValueError("%s is not a valid argument for a Within Qualifier" % stop_time)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "START %s STOP %s" % (self.start_time, self.stop_time)
|
return "START %s STOP %s" % (self.start_time, self.stop_time)
|
||||||
|
|
|
@ -225,7 +225,7 @@ def test_parse_autonomous_system_valid(data):
|
||||||
|
|
||||||
@pytest.mark.parametrize("data", [
|
@pytest.mark.parametrize("data", [
|
||||||
"""{
|
"""{
|
||||||
"type": "email-address",
|
"type": "email-addr",
|
||||||
"value": "john@example.com",
|
"value": "john@example.com",
|
||||||
"display_name": "John Doe",
|
"display_name": "John Doe",
|
||||||
"belongs_to_ref": "0"
|
"belongs_to_ref": "0"
|
||||||
|
@ -233,7 +233,7 @@ def test_parse_autonomous_system_valid(data):
|
||||||
])
|
])
|
||||||
def test_parse_email_address(data):
|
def test_parse_email_address(data):
|
||||||
odata = stix2.parse_observable(data, {"0": "user-account"})
|
odata = stix2.parse_observable(data, {"0": "user-account"})
|
||||||
assert odata.type == "email-address"
|
assert odata.type == "email-addr"
|
||||||
|
|
||||||
odata_str = re.compile('"belongs_to_ref": "0"', re.DOTALL).sub('"belongs_to_ref": "3"', data)
|
odata_str = re.compile('"belongs_to_ref": "0"', re.DOTALL).sub('"belongs_to_ref": "3"', data)
|
||||||
with pytest.raises(stix2.exceptions.InvalidObjRefError):
|
with pytest.raises(stix2.exceptions.InvalidObjRefError):
|
||||||
|
|
|
@ -26,18 +26,32 @@ def test_boolean_expression_with_parentheses():
|
||||||
assert str(exp) == "(email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$')" # noqa
|
assert str(exp) == "(email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$')" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_hash_followed_by_registryKey_expression_python_constant():
|
||||||
|
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||||
|
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
||||||
|
o_exp1 = stix2.ObservableExpression(hash_exp)
|
||||||
|
reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]),
|
||||||
|
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
||||||
|
o_exp2 = stix2.ObservableExpression(reg_exp)
|
||||||
|
fb_exp = stix2.FollowedByObservableExpression([o_exp1, o_exp2])
|
||||||
|
para_exp = stix2.ParentheticalExpression(fb_exp)
|
||||||
|
qual_exp = stix2.WithinQualifier(300)
|
||||||
|
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
||||||
|
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_hash_followed_by_registryKey_expression():
|
def test_hash_followed_by_registryKey_expression():
|
||||||
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
hash_exp = stix2.EqualityComparisonExpression("file:hashes.MD5",
|
||||||
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
stix2.HashConstant("79054025255fb1a26e4bc422aef54eb4", "MD5"))
|
||||||
o_exp1 = stix2.ObservableExpression(hash_exp)
|
o_exp1 = stix2.ObservableExpression(hash_exp)
|
||||||
reg_exp = stix2.EqualityComparisonExpression("win-registry-key:key",
|
reg_exp = stix2.EqualityComparisonExpression(stix2.ObjectPath("windows-registry-key", ["key"]),
|
||||||
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
stix2.StringConstant("HKEY_LOCAL_MACHINE\\foo\\bar"))
|
||||||
o_exp2 = stix2.ObservableExpression(reg_exp)
|
o_exp2 = stix2.ObservableExpression(reg_exp)
|
||||||
fb_exp = stix2.FollowedByObservableExpression([o_exp1, o_exp2])
|
fb_exp = stix2.FollowedByObservableExpression([o_exp1, o_exp2])
|
||||||
para_exp = stix2.ParentheticalExpression(fb_exp)
|
para_exp = stix2.ParentheticalExpression(fb_exp)
|
||||||
qual_exp = stix2.WithinQualifier(stix2.IntegerConstant(300))
|
qual_exp = stix2.WithinQualifier(stix2.IntegerConstant(300))
|
||||||
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
exp = stix2.QualifiedObservationExpression(para_exp, qual_exp)
|
||||||
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
assert str(exp) == "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [windows-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS" # noqa
|
||||||
|
|
||||||
|
|
||||||
def test_file_observable_expression():
|
def test_file_observable_expression():
|
||||||
|
@ -82,7 +96,7 @@ def test_root_types():
|
||||||
|
|
||||||
def test_artifact_payload():
|
def test_artifact_payload():
|
||||||
exp1 = stix2.EqualityComparisonExpression("artifact:mime_type",
|
exp1 = stix2.EqualityComparisonExpression("artifact:mime_type",
|
||||||
stix2.StringConstant("application/vnd.tcpdump.pcap"))
|
"application/vnd.tcpdump.pcap")
|
||||||
exp2 = stix2.MatchesComparisonExpression("artifact:payload_bin",
|
exp2 = stix2.MatchesComparisonExpression("artifact:payload_bin",
|
||||||
stix2.StringConstant("\\xd4\\xc3\\xb2\\xa1\\x02\\x00\\x04\\x00"))
|
stix2.StringConstant("\\xd4\\xc3\\xb2\\xa1\\x02\\x00\\x04\\x00"))
|
||||||
and_exp = stix2.AndBooleanExpression([exp1, exp2])
|
and_exp = stix2.AndBooleanExpression([exp1, exp2])
|
||||||
|
@ -90,8 +104,67 @@ def test_artifact_payload():
|
||||||
assert str(exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']" # noqa
|
assert str(exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_greater_than_python_constant():
|
||||||
|
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
|
||||||
|
7.0)
|
||||||
|
exp = stix2.ObservableExpression(exp1)
|
||||||
|
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
||||||
|
|
||||||
|
|
||||||
def test_greater_than():
|
def test_greater_than():
|
||||||
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
|
exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
|
||||||
stix2.FloatConstant(7.0))
|
stix2.FloatConstant(7.0))
|
||||||
exp = stix2.ObservableExpression(exp1)
|
exp = stix2.ObservableExpression(exp1)
|
||||||
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
|
||||||
|
|
||||||
|
|
||||||
|
def test_and_observable_expression():
|
||||||
|
exp1 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||||
|
"unix"),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||||
|
stix2.StringConstant("1007")),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||||
|
"Peter")])
|
||||||
|
exp2 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||||
|
"unix"),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||||
|
stix2.StringConstant("1008")),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||||
|
"Paul")])
|
||||||
|
exp3 = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("user-account:account_type",
|
||||||
|
"unix"),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:user_id",
|
||||||
|
stix2.StringConstant("1009")),
|
||||||
|
stix2.EqualityComparisonExpression("user-account:account_login",
|
||||||
|
"Mary")])
|
||||||
|
exp = stix2.AndObservableExpression([stix2.ObservableExpression(exp1),
|
||||||
|
stix2.ObservableExpression(exp2),
|
||||||
|
stix2.ObservableExpression(exp3)])
|
||||||
|
assert str(exp) == "[user-account:account_type = 'unix' AND user-account:user_id = '1007' AND user-account:account_login = 'Peter'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1008' AND user-account:account_login = 'Paul'] AND [user-account:account_type = 'unix' AND user-account:user_id = '1009' AND user-account:account_login = 'Mary']" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_hex():
|
||||||
|
exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("file:mime_type",
|
||||||
|
"image/bmp"),
|
||||||
|
stix2.EqualityComparisonExpression("file:magic_number_hex",
|
||||||
|
stix2.HexConstant("ffd8"))])
|
||||||
|
exp = stix2.ObservableExpression(exp_and)
|
||||||
|
assert str(exp) == "[file:mime_type = 'image/bmp' AND file:magic_number_hex = h'ffd8']"
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_qualifiers():
|
||||||
|
exp_and = stix2.AndBooleanExpression([stix2.EqualityComparisonExpression("network-traffic:dst_ref.type",
|
||||||
|
"domain-name"),
|
||||||
|
stix2.EqualityComparisonExpression("network-traffic:dst_ref.value",
|
||||||
|
"example.com")])
|
||||||
|
exp_ob = stix2.ObservableExpression(exp_and)
|
||||||
|
qual_rep = stix2.RepeatQualifier(5)
|
||||||
|
qual_within = stix2.WithinQualifier(stix2.IntegerConstant(1800))
|
||||||
|
exp = stix2.QualifiedObservationExpression(stix2.QualifiedObservationExpression(exp_ob, qual_rep), qual_within)
|
||||||
|
assert str(exp) == "[network-traffic:dst_ref.type = 'domain-name' AND network-traffic:dst_ref.value = 'example.com'] REPEATS 5 TIMES WITHIN 1800 SECONDS" # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_op():
|
||||||
|
exp = stix2.ObservableExpression(stix2.IsSubsetComparisonExpression("network-traffic:dst_ref.value",
|
||||||
|
"2001:0db8:dead:beef:0000:0000:0000:0000/64"))
|
||||||
|
assert str(exp) == "[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']"
|
||||||
|
|
Loading…
Reference in New Issue