From e2564734d4e6d1440c20b1f6fba4eaa49902a7fe Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 4 Dec 2024 17:48:26 +0100 Subject: [PATCH 1/3] obtain ip addresses from stored lease files --- dissect/target/plugins/os/unix/linux/_os.py | 8 ++- .../plugins/os/unix/linux/network_managers.py | 36 ++++++++++++ tests/plugins/os/unix/test_ips.py | 55 +++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/dissect/target/plugins/os/unix/linux/_os.py b/dissect/target/plugins/os/unix/linux/_os.py index fb2e6367d..545eb2615 100644 --- a/dissect/target/plugins/os/unix/linux/_os.py +++ b/dissect/target/plugins/os/unix/linux/_os.py @@ -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 @@ -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) diff --git a/dissect/target/plugins/os/unix/linux/network_managers.py b/dissect/target/plugins/os/unix/linux/network_managers.py index 4fb4def76..e54b48717 100644 --- a/dissect/target/plugins/os/unix/linux/network_managers.py +++ b/dissect/target/plugins/os/unix/linux/network_managers.py @@ -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 @@ -601,6 +602,41 @@ def records_enumerate(iterable: Iterable) -> Iterator[tuple[int, JournalRecord | return ips +def parse_unix_dhcp_leases(target: Target) -> set[str]: + """Parse 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("*.leases"), + 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): diff --git a/tests/plugins/os/unix/test_ips.py b/tests/plugins/os/unix/test_ips.py index c86d8505e..ec52a76c5 100644 --- a/tests/plugins/os/unix/test_ips.py +++ b/tests/plugins/os/unix/test_ips.py @@ -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"]) From bfd4a47051ab907e167217b9b49d8061ffa36bce Mon Sep 17 00:00:00 2001 From: Computer Network Investigation <121175071+JSCU-CNI@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:24:01 +0100 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/plugins/os/unix/linux/network_managers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dissect/target/plugins/os/unix/linux/network_managers.py b/dissect/target/plugins/os/unix/linux/network_managers.py index e54b48717..35a498e4c 100644 --- a/dissect/target/plugins/os/unix/linux/network_managers.py +++ b/dissect/target/plugins/os/unix/linux/network_managers.py @@ -603,7 +603,7 @@ def records_enumerate(iterable: Iterable) -> Iterator[tuple[int, JournalRecord | def parse_unix_dhcp_leases(target: Target) -> set[str]: - """Parse DHCP ``*.lease*`` files. + """Parse NetworkManager and dhclient DHCP ``.lease`` files. Resources: - https://linux.die.net/man/5/dhclient.conf @@ -614,7 +614,6 @@ def parse_unix_dhcp_leases(target: Target) -> set[str]: Returns: A set of found DHCP IP addresses. """ - ips = set() for lease_file in chain( From 8e750fbd95ff136510d548800634a52b9191d491 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:27:08 +0100 Subject: [PATCH 3/3] glob for *.lease* --- dissect/target/plugins/os/unix/linux/network_managers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dissect/target/plugins/os/unix/linux/network_managers.py b/dissect/target/plugins/os/unix/linux/network_managers.py index 35a498e4c..75fb1bbe7 100644 --- a/dissect/target/plugins/os/unix/linux/network_managers.py +++ b/dissect/target/plugins/os/unix/linux/network_managers.py @@ -617,9 +617,9 @@ def parse_unix_dhcp_leases(target: Target) -> set[str]: ips = set() for lease_file in chain( - target.fs.path("/var/lib/NetworkManager").glob("*.lease"), - target.fs.path("/var/lib/dhcp").glob("*.leases"), - target.fs.path("/var/lib/dhclient").glob("*.lease"), + 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()