forked from p15670423/monkey
Merge pull request #2133 from guardicore/2004-scan-target-configuration
2004 scan target configuration
This commit is contained in:
commit
fd1c10cd73
|
@ -1,6 +1,4 @@
|
|||
import re
|
||||
|
||||
from marshmallow import Schema, ValidationError, fields, post_load, validate, validates
|
||||
from marshmallow import Schema, fields, post_load, validate
|
||||
|
||||
from .agent_sub_configurations import (
|
||||
CustomPBAConfiguration,
|
||||
|
@ -14,49 +12,19 @@ from .agent_sub_configurations import (
|
|||
TCPScanConfiguration,
|
||||
)
|
||||
from .utils import freeze_lists
|
||||
|
||||
valid_windows_custom_pba_filename_regex = re.compile(r"^[^<>:\"\\\/|?*]*[^<>:\"\\\/|?* \.]+$|^$")
|
||||
valid_linux_custom_pba_filename_regex = re.compile(r"^[^\0/]*$")
|
||||
from .validators import (
|
||||
validate_ip,
|
||||
validate_linux_filename,
|
||||
validate_subnet_range,
|
||||
validate_windows_filename,
|
||||
)
|
||||
|
||||
|
||||
class CustomPBAConfigurationSchema(Schema):
|
||||
linux_command = fields.Str()
|
||||
linux_filename = fields.Str(
|
||||
validate=validate.Regexp(regex=valid_linux_custom_pba_filename_regex)
|
||||
)
|
||||
linux_filename = fields.Str(validate=validate_linux_filename)
|
||||
windows_command = fields.Str()
|
||||
windows_filename = fields.Str(
|
||||
validate=validate.Regexp(regex=valid_windows_custom_pba_filename_regex)
|
||||
)
|
||||
|
||||
@validates("windows_filename")
|
||||
def validate_windows_filename_not_reserved(self, windows_filename):
|
||||
# filename shouldn't start with any of these and be followed by a period
|
||||
if windows_filename.split(".")[0].upper() in [
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9",
|
||||
]:
|
||||
raise ValidationError("Invalid Windows filename: reserved name used")
|
||||
windows_filename = fields.Str(validate=validate_windows_filename)
|
||||
|
||||
@post_load
|
||||
def _make_custom_pba_configuration(self, data, **kwargs):
|
||||
|
@ -73,10 +41,10 @@ class PluginConfigurationSchema(Schema):
|
|||
|
||||
|
||||
class ScanTargetConfigurationSchema(Schema):
|
||||
blocked_ips = fields.List(fields.Str())
|
||||
inaccessible_subnets = fields.List(fields.Str())
|
||||
blocked_ips = fields.List(fields.Str(validate=validate_ip))
|
||||
inaccessible_subnets = fields.List(fields.Str(validate=validate_subnet_range))
|
||||
local_network_scan = fields.Bool()
|
||||
subnets = fields.List(fields.Str())
|
||||
subnets = fields.List(fields.Str(validate=validate_subnet_range))
|
||||
|
||||
@post_load
|
||||
@freeze_lists
|
||||
|
|
|
@ -54,6 +54,20 @@ class PluginConfiguration:
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class ScanTargetConfiguration:
|
||||
"""
|
||||
Configuration of network targets to scan and exploit
|
||||
|
||||
Attributes:
|
||||
:param blocked_ips: IP's that won't be scanned
|
||||
Example: ("1.1.1.1", "2.2.2.2")
|
||||
:param inaccessible_subnets: Subnet ranges that shouldn't be accessible for the agent
|
||||
Example: ("1.1.1.1", "2.2.2.2/24", "myserver")
|
||||
:param local_network_scan: Whether or not the agent should scan the local network
|
||||
:param subnets: Subnet ranges to scan
|
||||
Example: ("192.168.1.1-192.168.2.255", "3.3.3.3", "2.2.2.2/24",
|
||||
"myHostname")
|
||||
"""
|
||||
|
||||
blocked_ips: Tuple[str, ...]
|
||||
inaccessible_subnets: Tuple[str, ...]
|
||||
local_network_scan: bool
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
from .filenames import validate_linux_filename, validate_windows_filename
|
||||
from .ip_ranges import (
|
||||
validate_ip,
|
||||
validate_hostname,
|
||||
validate_ip_range,
|
||||
validate_subnet_range,
|
||||
validate_ip_network,
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
import re
|
||||
from pathlib import PureWindowsPath
|
||||
|
||||
from marshmallow import ValidationError
|
||||
|
||||
_valid_windows_filename_regex = re.compile(r"^[^<>:\"\\\/|?*]*[^<>:\"\\\/|?* \.]+$|^$")
|
||||
_valid_linux_filename_regex = re.compile(r"^[^\0/]*$")
|
||||
|
||||
|
||||
def validate_linux_filename(linux_filename: str):
|
||||
if not re.match(_valid_linux_filename_regex, linux_filename):
|
||||
raise ValidationError(f"Invalid Unix filename {linux_filename}: illegal characters")
|
||||
|
||||
|
||||
def validate_windows_filename(windows_filename: str):
|
||||
_validate_windows_filename_not_reserved(windows_filename)
|
||||
if not re.match(_valid_windows_filename_regex, windows_filename):
|
||||
raise ValidationError(f"Invalid Windows filename {windows_filename}: illegal characters")
|
||||
|
||||
|
||||
def _validate_windows_filename_not_reserved(windows_filename: str):
|
||||
# filename shouldn't start with any of these and be followed by a period
|
||||
if PureWindowsPath(windows_filename).is_reserved():
|
||||
raise ValidationError(f"Invalid Windows filename {windows_filename}: reserved name used")
|
|
@ -0,0 +1,67 @@
|
|||
import re
|
||||
from ipaddress import AddressValueError, IPv4Address, IPv4Network, NetmaskValueError
|
||||
|
||||
from marshmallow import ValidationError
|
||||
|
||||
|
||||
def validate_subnet_range(subnet_range: str):
|
||||
try:
|
||||
return validate_ip(subnet_range)
|
||||
except ValidationError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return validate_ip_range(subnet_range)
|
||||
except ValidationError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return validate_ip_network(subnet_range)
|
||||
except ValidationError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return validate_hostname(subnet_range)
|
||||
except ValidationError:
|
||||
raise ValidationError(f"Invalid subnet range {subnet_range}")
|
||||
|
||||
|
||||
def validate_hostname(hostname: str):
|
||||
# Based on hostname syntax: https://www.rfc-editor.org/rfc/rfc1123#page-13
|
||||
hostname_segments = hostname.split(".")
|
||||
if any((part.endswith("-") or part.startswith("-") for part in hostname_segments)):
|
||||
raise ValidationError(f"Hostname segment can't start or end with a hyphen: {hostname}")
|
||||
if not any((char.isalpha() for char in hostname_segments[-1])):
|
||||
raise ValidationError(f"Last segment of a hostname must contain a letter: {hostname}")
|
||||
|
||||
valid_characters_pattern = r"^[A-Za-z0-9\-]+$"
|
||||
valid_characters_regex = re.compile(valid_characters_pattern)
|
||||
matches = (
|
||||
re.match(valid_characters_regex, hostname_segment) for hostname_segment in hostname_segments
|
||||
)
|
||||
|
||||
if not all(matches):
|
||||
raise ValidationError(f"Hostname contains invalid characters: {hostname}")
|
||||
|
||||
|
||||
def validate_ip_network(ip_network: str):
|
||||
try:
|
||||
IPv4Network(ip_network, strict=False)
|
||||
except (NetmaskValueError, AddressValueError):
|
||||
raise ValidationError(f"Invalid IPv4 network {ip_network}")
|
||||
|
||||
|
||||
def validate_ip_range(ip_range: str):
|
||||
ip_range = ip_range.replace(" ", "")
|
||||
ips = ip_range.split("-")
|
||||
if len(ips) != 2:
|
||||
raise ValidationError(f"Invalid IP range {ip_range}")
|
||||
validate_ip(ips[0])
|
||||
validate_ip(ips[1])
|
||||
|
||||
|
||||
def validate_ip(ip: str):
|
||||
try:
|
||||
IPv4Address(ip)
|
||||
except AddressValueError:
|
||||
raise ValidationError(f"Invalid IP address {ip}")
|
|
@ -0,0 +1,70 @@
|
|||
import pytest
|
||||
from marshmallow import ValidationError
|
||||
|
||||
from common.agent_configuration.validators.ip_ranges import validate_ip, validate_subnet_range
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ip", ["192.168.56.1", "0.0.0.0"])
|
||||
def test_validate_ip_valid(ip):
|
||||
validate_ip(ip)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ip", ["1.1.1", "257.256.255.255", "1.1.1.1.1"])
|
||||
def test_validate_ip_invalid(ip):
|
||||
with pytest.raises(ValidationError):
|
||||
validate_ip(ip)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ip", ["192.168.56.1", "0.0.0.0"])
|
||||
def test_validate_subnet_range__ip_valid(ip):
|
||||
validate_subnet_range(ip)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ip", ["1.1.1", "257.256.255.255", "1.1.1.1.1"])
|
||||
def test_validate_subnet_range__ip_invalid(ip):
|
||||
with pytest.raises(ValidationError):
|
||||
validate_subnet_range(ip)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ip_range", ["1.1.1.1 - 2.2.2.2", "1.1.1.255-1.1.1.1"])
|
||||
def test_validate_subnet_range__ip_range_valid(ip_range):
|
||||
validate_subnet_range(ip_range)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ip_range",
|
||||
[
|
||||
"1.1.1-2.2.2.2",
|
||||
"0-.1.1.1-2.2.2.2",
|
||||
"a..1.1.1-2.2.2.2",
|
||||
"257.1.1.1-2.2.2.2",
|
||||
"1.1.1.1-2.2.2.2-3.3.3.3",
|
||||
],
|
||||
)
|
||||
def test_validate_subnet_range__ip_range_invalid(ip_range):
|
||||
with pytest.raises(ValidationError):
|
||||
validate_subnet_range(ip_range)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("hostname", ["infection.monkey", "1nfection-Monkey", "1.1.1.1a"])
|
||||
def test_validate_subnet_range__hostname_valid(hostname):
|
||||
validate_subnet_range(hostname)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hostname", ["hy&!he.host", "čili-peppers.are-hot", "one.two-", "one-.two", "one@two", ""]
|
||||
)
|
||||
def test_validate_subnet_range__hostname_invalid(hostname):
|
||||
with pytest.raises(ValidationError):
|
||||
validate_subnet_range(hostname)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cidr_range", ["1.1.1.1/24", "1.1.1.1/0"])
|
||||
def test_validate_subnet_range__cidr_valid(cidr_range):
|
||||
validate_subnet_range(cidr_range)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cidr_range", ["1.1.1/24", "1.1.1.1/-1", "1.1.1.1/33", "1.1.1.1/222"])
|
||||
def test_validate_subnet_range__cidr_invalid(cidr_range):
|
||||
with pytest.raises(ValidationError):
|
||||
validate_subnet_range(cidr_range)
|
Loading…
Reference in New Issue