Skip to content

Commit

Permalink
Add Unix DHCP lease file parser for target.ips (#965)
Browse files Browse the repository at this point in the history
  • Loading branch information
JSCU-CNI authored Dec 11, 2024
1 parent 334e6a4 commit f2f50a5
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 2 deletions.
8 changes: 6 additions & 2 deletions dissect/target/plugins/os/unix/linux/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from dissect.target.plugins.os.unix.bsd.osx._os import MacPlugin
from dissect.target.plugins.os.unix.linux.network_managers import (
LinuxNetworkManager,
parse_unix_dhcp_leases,
parse_unix_dhcp_log_messages,
)
from dissect.target.plugins.os.windows._os import WindowsPlugin
Expand Down Expand Up @@ -39,8 +40,11 @@ def ips(self) -> list[str]:
for ip_set in self.network_manager.get_config_value("ips"):
ips.update(ip_set)

for ip in parse_unix_dhcp_log_messages(self.target, iter_all=False):
ips.add(ip)
if dhcp_lease_ips := parse_unix_dhcp_leases(self.target):
ips.update(dhcp_lease_ips)

elif dhcp_log_ips := parse_unix_dhcp_log_messages(self.target, iter_all=False):
ips.update(dhcp_log_ips)

return list(ips)

Expand Down
35 changes: 35 additions & 0 deletions dissect/target/plugins/os/unix/linux/network_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from defusedxml import ElementTree

from dissect.target.exceptions import PluginError
from dissect.target.helpers import configutil

if TYPE_CHECKING:
from dissect.target.helpers.fsutil import TargetPath
Expand Down Expand Up @@ -601,6 +602,40 @@ def records_enumerate(iterable: Iterable) -> Iterator[tuple[int, JournalRecord |
return ips


def parse_unix_dhcp_leases(target: Target) -> set[str]:
"""Parse NetworkManager and dhclient DHCP ``.lease`` files.
Resources:
- https://linux.die.net/man/5/dhclient.conf
Args:
target: Target to discover and obtain network information from.
Returns:
A set of found DHCP IP addresses.
"""
ips = set()

for lease_file in chain(
target.fs.path("/var/lib/NetworkManager").glob("*.lease*"),
target.fs.path("/var/lib/dhcp").glob("*.lease*"),
target.fs.path("/var/lib/dhclient").glob("*.lease*"),
):
lease_text = lease_file.read_text()

if "lease {" in lease_text:
for line in lease_text.split("\n"):
if "fixed-address" in line:
ips.add(line.split(" ")[-1].strip(";"))

elif "ADDRESS=" in lease_text:
lease = configutil.parse(lease_file, hint="env")
if ip := lease.get("ADDRESS"):
ips.add(ip)

return ips


def should_ignore_ip(ip: str) -> bool:
for i in IGNORED_IPS:
if ip.startswith(i):
Expand Down
55 changes: 55 additions & 0 deletions tests/plugins/os/unix/test_ips.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,58 @@ def test_regression_ips_unique_strings(target_unix: Target, fs_unix: VirtualFile

assert len(target_unix.ips) == 1
assert target_unix.ips == ["1.2.3.4"]


def test_ips_dhcp_lease_files(target_unix: Target, fs_unix: VirtualFilesystem) -> None:
"""Test if we can detect DHCP lease files from NetworkManager and dhclient."""

lease1 = """
# This is private data. Do not parse.
ADDRESS=1.2.3.4
"""

lease2 = """
lease {
interface "eth0";
fixed-address 9.0.1.2;
option dhcp-lease-time 13337;
option routers 0.0.0.0;
option host-name "hostname";
renew 1 2023/12/31 13:37:00;
rebind 2 2023/01/01 01:00:00;
expire 3 2024/01/01 13:37:00;
# real leases contain more key/value pairs
}
lease {
interface "eth0";
fixed-address 5.6.7.8;
option dhcp-lease-time 13337;
option routers 0.0.0.0;
option host-name "hostname";
renew 1 2024/12/31 13:37:00;
rebind 2 2024/01/01 01:00:00;
expire 3 2025/01/01 13:37:00;
# real leases contain more key/value pairs
}
"""

lease3 = """
some-other "value";
lease {
interface "eth1";
fixed-address 3.4.5.6;
}
"""

fs_unix.map_file_fh("/var/lib/NetworkManager/internal-uuid-eth0.lease", BytesIO(textwrap.dedent(lease1).encode()))
fs_unix.map_file_fh("/var/lib/dhcp/dhclient.leases", BytesIO(textwrap.dedent(lease2).encode()))
fs_unix.map_file_fh("/var/lib/dhclient/dhclient.eth0.leases", BytesIO(textwrap.dedent(lease3).encode()))

syslog = "Apr 4 13:37:04 localhost dhclient[4]: bound to 1.3.3.7 -- renewal in 1337 seconds."
fs_unix.map_file_fh("/var/log/syslog", BytesIO(textwrap.dedent(syslog).encode()))

target_unix.add_plugin(LinuxPlugin)

# tests if we did not call :func:`parse_unix_dhcp_log_messages` since :func:`parse_unix_dhcp_leases` has results.
assert len(target_unix.ips) == 4
assert sorted(target_unix.ips) == sorted(["1.2.3.4", "5.6.7.8", "9.0.1.2", "3.4.5.6"])

0 comments on commit f2f50a5

Please sign in to comment.