Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions nmapui/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@
r"^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?"
r"(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$"
)
_IPV4_LIKE_RE = re.compile(r"^[0-9./-]+$")


def _is_valid_ipv4_range(item: str) -> bool:
"""Return whether an item is an Nmap-style IPv4 octet range."""
octets = item.split(".")
if len(octets) != 4 or not any("-" in octet for octet in octets):
return False

for octet in octets:
bounds = octet.split("-")
if len(bounds) > 2 or any(not bound.isdigit() for bound in bounds):
return False
values = [int(bound) for bound in bounds]
if any(value > 255 for value in values):
return False
if len(values) == 2 and values[0] > values[1]:
return False

return True


def _is_valid_item(item: str) -> tuple[bool, str | None]:
Expand All @@ -27,6 +47,14 @@ def _is_valid_item(item: str) -> tuple[bool, str | None]:
except ValueError:
pass

# Nmap supports ranges within IPv4 octets, such as 192.168.1.1-254.
if _is_valid_ipv4_range(item):
return True, None

# Do not let malformed dotted IPs fall through and pass as hostnames.
if _IPV4_LIKE_RE.fullmatch(item):
return False, f"Invalid target: {item}"
Comment on lines +55 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Nmap octet-list targets

When users enter a Nmap-supported octet-list target such as 192.168.3-5,7.1, validate_target first splits on the comma and then this new numeric fallback rejects the first piece (192.168.3-5) as invalid. I checked the official Nmap target specification, which documents comma-separated numbers/ranges within an octet using that example (https://nmap.org/book/man-target-specification.html); this target previously passed validation and can be scanned by Nmap, so the new malformed-IP guard blocks a valid range form before the scan starts.

Useful? React with 👍 / 👎.


# Hostname / FQDN (nmap accepts these natively)
if _HOSTNAME_RE.match(item):
return True, None
Expand Down
35 changes: 35 additions & 0 deletions tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest

from nmapui.validation import validate_target


@pytest.mark.parametrize(
"target",
[
"192.168.1.1",
"192.168.1.0/24",
"192.168.1.1-254",
"10-12.0.0.1-20",
"scanme.nmap.org",
"192.168.1.1, 10.0.0.0/8",
],
)
def test_validate_target_accepts_supported_target_formats(target):
assert validate_target(target) == (True, None)


@pytest.mark.parametrize(
"target",
[
"192.168.1.999",
"192.168.1",
"192.168.1.1/33",
"192.168.1.254-1",
"192.168.1.1-999",
],
)
def test_validate_target_rejects_malformed_ip_like_targets(target):
valid, error = validate_target(target)

assert valid is False
assert error == f"Invalid target: {target}"
Loading