Skip to content

Instantly share code, notes, and snippets.

@JaminB
Last active June 10, 2020 15:47
Show Gist options
  • Save JaminB/4e55a10a7db2a1f93ef1e41c25062d32 to your computer and use it in GitHub Desktop.
Save JaminB/4e55a10a7db2a1f93ef1e41c25062d32 to your computer and use it in GitHub Desktop.
import re
ipv4_address_pattern = re.compile('^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1'
'[0-9]{2}|2[0-4][0-9]|25[0-5])$')
ipv6_address_pattern = re.compile('^(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]'
'|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]'
'{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:'
'[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.)'
'{3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::'
'(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]'
'|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25'
'[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:'
'[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]'
'|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:'
'[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]'
'{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.)'
'{3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:)'
'{,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|'
'(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|'
'1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::'
'(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]'
'|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:'
'[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:)'
'{,6}[0-9A-Fa-f]{1,4})?::)$')
def validate_suricata_address_group_values(s):
"""
Must be like the following:
! 1.1.1.1 (Every IP address but 1.1.1.1)
![1.1.1.1, 1.1.1.2] (Every IP address but 1.1.1.1 and 1.1.1.2)
$HOME_NET (Your setting of HOME_NET in yaml)
[$EXTERNAL_NET, !$HOME_NET] (EXTERNAL_NET and not HOME_NET)
[10.0.0.0/24, !10.0.0.5] (10.0.0.0/24 except for 10.0.0.5)
:param s: Test String
:return: True if meets the suricata_address_group_value conditions
"""
def validate_token(token):
if str(token).startswith('!'):
token = token[1:]
def token_is_cidr(tok):
if '/' in tok:
ip, prefix = tok.split('/')
# Check for invalid prefix
try:
if int(prefix) < 0 or int(prefix) > 128:
return False
except ValueError:
return False
else:
return False
return True
def token_is_ip(tok):
return bool(ipv4_address_pattern.findall(tok) or ipv6_address_pattern.findall(tok))
def token_is_list(tok):
tok = str(tok)
if '[' in tok and ']' in tok:
# Negation is valid against sets as well (E.G ![ $HOME_NET, 192.168.0.0/24])
if tok.startswith('!'):
tok = tok[1:]
return validate_suricata_address_group_values(tok)
return False
return token_is_cidr(token) or token_is_ip(token) or token_is_list(token)
s = str(s).replace(' ', '')
valid_group_value_vars = ['$HOME_NET', '$EXTERNAL_NET', '$HTTP_SERVERS', '$SQL_SERVERS',
'$DNS_SERVERS', '$TELNET_SERVERS', '$AIM_SERVERS', '$DC_SERVERS',
'$MODBUS_SERVER', '$MODBUS_CLIENT', '$ENIP_CLIENT', '$ENIP_SERVER']
valid_neg_group_value_vars = ['!' + g for g in valid_group_value_vars]
valid_var_subs = valid_group_value_vars + valid_neg_group_value_vars
# List Formatting
if '[' in s and ']' in s:
# Negation is valid against sets as well (E.G ![ $HOME_NET, 192.168.0.0/24])
if s.startswith('!'):
s = s.replace(' ', '')[1:]
# split on comma, but exclude values in square brackets
tokenized_list = re.split(r",(?![^(\[]*[\])])", s[1:-1])
for t in tokenized_list:
# Check if token in string is valid variable substitution, IP, or CIDR
if t not in valid_var_subs and not validate_token(t):
return False
# Check if 'any' is in the list (you can't combine any with other values)
elif 'any' == t:
return False
return True
# String formatting
else:
if 'any' == s:
return True
# Check if string is valid variable substitution, IP, or CIDR
return validate_token(s) or s in valid_var_subs
def validate_suricata_port_group_values(s):
"""
Must be like the following:
[80, 81, 82] (port 80, 81 and 82)
[80: 82] (Range from 80 till 82)
[1024: ] (From 1024 till the highest port-number)
!80 (Every port but 80)
[80:100,!99] (Range from 80 till 100 but 99 excluded)
[1:80,![2,4]]
:param s: Test String
:return: True if meets the suricata_port_group_value conditions
"""
def validate_token(token):
if str(token).startswith('!'):
token = token[1:]
def token_is_int(tok):
tok = str(tok)
try:
int(tok)
if int(tok) < 1 or int(tok) > 65535:
return False
except ValueError:
return False
return True
if '.' in str(token):
return False
def token_is_range(tok):
tok = str(tok)
if ':' in str(tok):
port_range = tok.split(':')
if len(port_range) == 1:
tok = port_range[0]
return token_is_int(tok)
elif len(port_range) == 2:
r1, r2 = port_range
return token_is_int(r1) and token_is_int(r2) and int(r1) < int(r2)
else:
return False
def token_is_list(tok):
tok = str(tok)
if '[' in tok and ']' in tok:
# Negation is valid against sets as well (E.G ![ $HOME_NET, 192.168.0.0/24])
if tok.startswith('!'):
tok = tok[1:]
return validate_suricata_port_group_values(tok)
return False
return token_is_range(token) or token_is_int(token) or token_is_list(token)
s = str(s).replace(' ', '')
valid_group_value_vars = ['$HTTP_PORTS', '$SHELLCODE_PORTS', '$ORACLE_PORTS', '$SSH_PORTS',
'$DNP3_PORTS', '$MODBUS_PORTS', '$FILE_DATA_PORTS', '$FTP_PORTS']
valid_neg_group_value_vars = ['!' + g for g in valid_group_value_vars]
valid_var_subs = valid_group_value_vars + valid_neg_group_value_vars
# List Formatting
if '[' in s and ']' in s:
# Negation is valid against sets as well (E.G ![ $HTTP_PORTS, 5000])
if s.startswith('!'):
s = s[1:]
# split on comma, but exclude values in square brackets
tokenized_list = re.split(r",(?![^(\[]*[\])])", s[1:-1])
for t in tokenized_list:
if t not in valid_var_subs and not validate_token(t):
return False
return True
else:
return validate_token(s) or s in valid_var_subs
def test_validate_suricata_address_groups():
valid_test_expressions = [
'any',
'8.8.8.8',
'!8.8.8.8',
'192.168.0.1/32',
'!192.168.0.1/32',
'[1.1.1.1, 1.1.1.2]',
'[1.1.1.1, !1.1.1.2]',
'![1.1.1.1, 1.1.1.2]',
'2001:0002:6c::430',
'!2001:0002:6c::430',
'ff01:0:0:0:0:0:0:2',
'!ff01:0:0:0:0:0:0:2',
'fe80::200:5aee:feaa:20a2',
'2000::/3',
'!2000::/3',
'ff00::/8',
'!ff00::/8',
'2001:0002::/48',
'!2001:0002::/48',
'::/128',
'::1/128',
'$HOME_NET',
'[$EXTERNAL_NET]',
'![$EXTERNAL_NET]',
'[!$EXTERNAL_NET]',
'[$EXTERNAL_NET, !$HOME_NET]',
'![$EXTERNAL_NET, !$HOME_NET]',
'[$EXTERNAL_NET, !$HOME_NET, $MODBUS_CLIENT, $HTTP_SERVERS]',
'[$EXTERNAL_NET, !$HOME_NET, $MODBUS_CLIENT, $HTTP_SERVERS, 192.168.0.1, 8.8.8.8]',
'[$EXTERNAL_NET, !$HOME_NET, $MODBUS_CLIENT, $HTTP_SERVERS, 192.168.0.1, 8.8.8.8, ff01:0:0:0:0:0:0:2, ::/128]',
'[192.168.0.1, 8.8.8.8, ff01:0:0:0:0:0:0:2, ::/128, 192.168.0.0/24, [192.168.0.1, 192.168.0.5, 192.168.0.5/32, $HTTP_SERVERS]]'
]
invalid_test_expressions = [
'badstring',
'!any',
'![any]',
'192.168.1',
'2002:cb0a:3cdd:1:',
'::1/129',
'[$HOME_NET, any]',
''
]
for expr in valid_test_expressions + invalid_test_expressions:
print(expr, validate_suricata_address_group_values(expr))
def test_validate_suricata_port_groups():
valid_test_expressions = [
80,
'80',
'!80',
'[80, 8080, 8888]',
'![80, 8080, 8888]',
'$SHELLCODE_PORTS',
'!$SHELLCODE_PORTS',
'[$DNP3_PORTS, 8080, 8888]',
'[$DNP3_PORTS, 8080, 8888]',
'80:8000',
'[80:440, !88, [80, 443]]'
]
invalid_test_expressions = [
'badstring',
'!any',
'![any]',
'',
0,
5.5,
'65536',
''
]
for expr in valid_test_expressions + invalid_test_expressions:
print(expr, validate_suricata_port_group_values(expr))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment