diff --git a/dissect/target/helpers/record.py b/dissect/target/helpers/record.py index d2821a2b1d..b5b9373267 100644 --- a/dissect/target/helpers/record.py +++ b/dissect/target/helpers/record.py @@ -142,7 +142,7 @@ def DynamicDescriptor(types: Sequence[str]) -> RecordDescriptor: ("varint", "gid"), ("string", "gecos"), ("path", "home"), - ("string", "shell"), + ("path", "shell"), ("string", "source"), ] diff --git a/dissect/target/plugins/os/unix/_os.py b/dissect/target/plugins/os/unix/_os.py index c12ccb7039..54a1f57e3d 100644 --- a/dissect/target/plugins/os/unix/_os.py +++ b/dissect/target/plugins/os/unix/_os.py @@ -112,7 +112,7 @@ def users(self, sessions: bool = False) -> Iterator[UnixUserRecord]: gid=pwent.get(3) or None, gecos=pwent.get(4), home=posix_path(pwent.get(5)), - shell=pwent.get(6), + shell=posix_path(pwent.get(6)), source=passwd_file, _target=self.target, ) @@ -156,7 +156,7 @@ def users(self, sessions: bool = False) -> Iterator[UnixUserRecord]: yield UnixUserRecord( name=user["name"], home=posix_path(user["home"]), - shell=user["shell"], + shell=posix_path(user["shell"]), source="/var/log/syslog", _target=self.target, ) diff --git a/dissect/target/plugins/os/unix/bsd/citrix/_os.py b/dissect/target/plugins/os/unix/bsd/citrix/_os.py index 38d6d99b2f..b2f2729ef0 100644 --- a/dissect/target/plugins/os/unix/bsd/citrix/_os.py +++ b/dissect/target/plugins/os/unix/bsd/citrix/_os.py @@ -5,6 +5,7 @@ from io import BytesIO from typing import TYPE_CHECKING +from flow.record.fieldtypes import posix_path from dissect.util.stream import RangeStream from dissect.target.filesystems.ffs import FfsFilesystem @@ -195,9 +196,17 @@ def version(self) -> str | None: @export(property=True) def ips(self) -> list[str]: return self._ips + + @staticmethod + def _as_hashable_path_or_str(value: object | None) -> str | None: + if value is None: + return None + if hasattr(value, "as_posix"): + return value.as_posix() + return str(value) @export(record=UnixUserRecord) - def users(self) -> Iterator[UnixUserRecord]: + def users(self) -> Iterator: nstmp_users = set() seen = set() nstmp_path = self.target.fs.path("/var/nstmp/") @@ -226,23 +235,28 @@ def users(self) -> Iterator[UnixUserRecord]: # for the root user in /var/nstmp. user_home = self.target.fs.path("/root") - seen.add((username, user_home.as_posix() if user_home else None, None)) - yield UnixUserRecord(name=username, home=user_home) + seen.add((username, self._as_hashable_path_or_str(user_home), None)) + yield UnixUserRecord(name=username, home=posix_path(user_home.as_posix()) if user_home else None) # Yield all users in nstmp that were not observed in the config for username in nstmp_users: # The nsmonitor user has a home directory of /var/nstmp/monitors rather than /var/nstmp/nsmonitor home = nstmp_path.joinpath(username) if username != "nsmonitor" else nstmp_path.joinpath("monitors") - seen.add((username, home.as_posix(), None)) - yield UnixUserRecord(name=username, home=home) + seen.add((username, self._as_hashable_path_or_str(home), None)) + yield UnixUserRecord(name=username, home=posix_path(home.as_posix())) # Yield users from /etc/passwd if we have not seem them in previous loops for user in super().users(): - if (user.name, user.home.as_posix(), user.shell) in seen: + if ( + user.name, + self._as_hashable_path_or_str(user.home), + self._as_hashable_path_or_str(user.shell), + ) in seen: continue + # To prevent bogus command history for all users without a home whenever a history is located at the root # of the filesystem, we set the user home to None if their home is equivalent to '/' - user.home = user.home if user.home != "/" else None + user.home = user.home if user.home and str(user.home) != "/" else None yield user @export(property=True) diff --git a/dissect/target/plugins/os/unix/bsd/darwin/macos/_os.py b/dissect/target/plugins/os/unix/bsd/darwin/macos/_os.py index c8547ad415..040e23668a 100644 --- a/dissect/target/plugins/os/unix/bsd/darwin/macos/_os.py +++ b/dissect/target/plugins/os/unix/bsd/darwin/macos/_os.py @@ -104,7 +104,7 @@ def users(self) -> Iterator[MacOSUserRecord]: gid=user.get("gid", [None])[0], gecos=user.get("realname", [None])[0], home=posix_path(home_dir) if home_dir else None, - shell=user.get("shell", [None])[0], + shell=posix_path(shell) if (shell := user.get("shell", [None])[0]) else None, source=path, ) except FileNotFoundError: diff --git a/dissect/target/plugins/os/unix/linux/fortios/_os.py b/dissect/target/plugins/os/unix/linux/fortios/_os.py index b741e8cbde..2e3545d8b1 100644 --- a/dissect/target/plugins/os/unix/linux/fortios/_os.py +++ b/dissect/target/plugins/os/unix/linux/fortios/_os.py @@ -9,6 +9,7 @@ from tarfile import ReadError from typing import TYPE_CHECKING, BinaryIO, TextIO +from flow.record.fieldtypes import posix_path from dissect.util import cpio from dissect.util.compression import xz @@ -282,7 +283,7 @@ def users(self) -> Iterator[FortiOSUserRecord | UnixUserRecord]: name=username, password=":".join(entry.get("password", [])), groups=list(entry.get("accprofile", [])), - home="/root", + home=posix_path("/root"), _target=self.target, ) except KeyError as e: @@ -297,7 +298,7 @@ def users(self) -> Iterator[FortiOSUserRecord | UnixUserRecord]: name=username, password=":".join(entry.get("password", [])), groups=list(entry.get("profileid", [])), - home="/root", + home=posix_path("/root"), _target=self.target, ) except KeyError as e: diff --git a/dissect/target/plugins/os/windows/_os.py b/dissect/target/plugins/os/windows/_os.py index b9bbb36540..9c875586d8 100644 --- a/dissect/target/plugins/os/windows/_os.py +++ b/dissect/target/plugins/os/windows/_os.py @@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, Any from uuid import UUID +from flow.record.fieldtypes import windows_path + from dissect.target.exceptions import RegistryError, RegistryValueNotFoundError from dissect.target.helpers.record import WindowsUserRecord from dissect.target.plugin import OperatingSystem, OSPlugin, export, internal @@ -335,10 +337,12 @@ def users(self) -> Iterator[WindowsUserRecord]: # Use SAM username if available name = self._sam_by_sid[sid].username if sid in self._sam_by_sid else home.split("\\")[-1] + resolved_home = self.target.resolve(home) if home else None + yield WindowsUserRecord( sid=subkey.name, name=name, - home=self.target.resolve(home), + home=windows_path(str(resolved_home)) if resolved_home else None, _target=self.target, ) diff --git a/tests/plugins/os/unix/bsd/citrix/test__os.py b/tests/plugins/os/unix/bsd/citrix/test__os.py index f74764870a..b381e53700 100644 --- a/tests/plugins/os/unix/bsd/citrix/test__os.py +++ b/tests/plugins/os/unix/bsd/citrix/test__os.py @@ -83,4 +83,4 @@ def test_citrix_os(target_citrix: Target, fs_bsd: VirtualFilesystem) -> None: assert users[7].name == "root" # User entry for /root, from /etc/passwd assert users[7].home == posix_path("/root") - assert users[7].shell == "/usr/bin/bash" + assert users[7].shell == posix_path("/usr/bin/bash") diff --git a/tests/plugins/os/unix/bsd/darwin/macos/test__os.py b/tests/plugins/os/unix/bsd/darwin/macos/test__os.py index f18cf5be8c..4a8ca4b733 100644 --- a/tests/plugins/os/unix/bsd/darwin/macos/test__os.py +++ b/tests/plugins/os/unix/bsd/darwin/macos/test__os.py @@ -4,8 +4,6 @@ from typing import TYPE_CHECKING from unittest.mock import Mock -from flow.record.fieldtypes import posix_path - from dissect.target.filesystem import VirtualFilesystem from dissect.target.plugin import OperatingSystem from dissect.target.plugins.os.unix.bsd.darwin.macos._os import MacOSPlugin @@ -39,7 +37,7 @@ def test_macos_os(target_macos_users: Target, fs_macos: VirtualFilesystem) -> No assert dissect_user._desc.name == "macos/user" assert dissect_user.name == "_dissect" assert dissect_user.passwd == "*" - assert dissect_user.home == posix_path("/Users/dissect") + assert str(dissect_user.home) == "/Users/dissect" assert dissect_user.shell == "/usr/bin/false" assert dissect_user.source == "/var/db/dslocal/nodes/Default/users/_dissect.plist" diff --git a/tests/plugins/os/unix/linux/fortios/test__os.py b/tests/plugins/os/unix/linux/fortios/test__os.py index c584f56924..a64701f9be 100644 --- a/tests/plugins/os/unix/linux/fortios/test__os.py +++ b/tests/plugins/os/unix/linux/fortios/test__os.py @@ -4,6 +4,8 @@ from io import BytesIO from typing import TYPE_CHECKING +from flow.record.fieldtypes import posix_path + from dissect.target.plugins.os.unix.linux.fortios._os import FortiOSPlugin from dissect.target.plugins.os.unix.linux.fortios.generic import GenericPlugin from dissect.target.plugins.os.unix.linux.fortios.locale import FortiOSLocalePlugin @@ -93,7 +95,7 @@ def test_fortigate_os(target_unix: Target, fs_unix: VirtualFilesystem) -> None: assert users[0].name == "admin" assert users[0].groups == ["super_admin"] assert users[0].password == "ENC:SH22zS4+QvU399DXuDApIVHu5fGh3wQCwO1aGeqlbA08G9tB/DvJsqLdG9HA18=" - assert users[0].home == "/root" + assert users[0].home == posix_path("/root") assert users[1].hostname == "FortiGate-VM64" assert users[1].name == "guest" diff --git a/tests/plugins/os/unix/test__os.py b/tests/plugins/os/unix/test__os.py index 18e970683d..97f053ed0a 100644 --- a/tests/plugins/os/unix/test__os.py +++ b/tests/plugins/os/unix/test__os.py @@ -184,13 +184,13 @@ def test_users(target_unix_users: Target) -> None: assert users[0].uid == 0 assert users[0].gid == 0 assert users[0].home == posix_path("/root") - assert users[0].shell == "/bin/bash" + assert users[0].shell == posix_path("/bin/bash") assert users[1].name == "user" assert users[1].uid == 1000 assert users[1].gid == 1000 assert users[1].home == posix_path("/home/user") - assert users[1].shell == "/bin/bash" + assert users[1].shell == posix_path("/bin/bash") assert users[2].name == "+@ngtest" assert users[2].uid is None