dissect.target 3.19.dev57__py3-none-any.whl → 3.20__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/container.py +1 -1
- dissect/target/exceptions.py +6 -5
- dissect/target/filesystem.py +2 -2
- dissect/target/filesystems/btrfs.py +14 -5
- dissect/target/filesystems/config.py +5 -1
- dissect/target/filesystems/extfs.py +5 -4
- dissect/target/filesystems/fat.py +22 -16
- dissect/target/filesystems/ffs.py +11 -4
- dissect/target/filesystems/jffs.py +12 -7
- dissect/target/filesystems/ntfs.py +22 -6
- dissect/target/filesystems/overlay.py +14 -4
- dissect/target/filesystems/smb.py +3 -3
- dissect/target/filesystems/squashfs.py +4 -4
- dissect/target/filesystems/vmfs.py +4 -4
- dissect/target/filesystems/xfs.py +15 -8
- dissect/target/helpers/compat/path_common.py +5 -5
- dissect/target/helpers/configutil.py +128 -32
- dissect/target/helpers/cyber.py +2 -0
- dissect/target/helpers/data/windowsZones.xml +19 -23
- dissect/target/helpers/docs.py +1 -1
- dissect/target/helpers/keychain.py +2 -0
- dissect/target/helpers/mount.py +2 -1
- dissect/target/helpers/record.py +29 -2
- dissect/target/helpers/record_modifier.py +5 -1
- dissect/target/helpers/regutil.py +56 -26
- dissect/target/loader.py +1 -1
- dissect/target/loaders/mqtt.py +104 -9
- dissect/target/loaders/proxmox.py +68 -0
- dissect/target/loaders/vma.py +1 -1
- dissect/target/loaders/xva.py +1 -1
- dissect/target/plugin.py +24 -21
- dissect/target/plugins/apps/av/mcafee.py +2 -0
- dissect/target/plugins/apps/av/sophos.py +2 -0
- dissect/target/plugins/apps/av/trendmicro.py +2 -0
- dissect/target/plugins/apps/browser/chromium.py +27 -6
- dissect/target/plugins/apps/container/docker.py +48 -32
- dissect/target/plugins/apps/editor/__init__.py +0 -0
- dissect/target/plugins/apps/editor/editor.py +23 -0
- dissect/target/plugins/apps/{texteditor → editor}/windowsnotepad.py +40 -31
- dissect/target/plugins/apps/other/__init__.py +0 -0
- dissect/target/plugins/apps/other/env.py +56 -0
- dissect/target/plugins/apps/shell/powershell.py +6 -2
- dissect/target/plugins/apps/shell/wget.py +91 -0
- dissect/target/plugins/apps/ssh/openssh.py +2 -0
- dissect/target/plugins/apps/ssh/opensshd.py +2 -0
- dissect/target/plugins/apps/virtualization/__init__.py +0 -0
- dissect/target/plugins/apps/virtualization/vmware_workstation.py +61 -0
- dissect/target/plugins/apps/vpn/wireguard.py +9 -9
- dissect/target/plugins/apps/webhosting/cpanel.py +2 -0
- dissect/target/plugins/apps/webserver/caddy.py +2 -0
- dissect/target/plugins/apps/webserver/nginx.py +2 -0
- dissect/target/plugins/child/esxi.py +3 -1
- dissect/target/plugins/child/parallels.py +68 -0
- dissect/target/plugins/child/proxmox.py +23 -0
- dissect/target/plugins/child/virtuozzo.py +12 -8
- dissect/target/plugins/child/vmware_workstation.py +23 -8
- dissect/target/plugins/filesystem/acquire_hash.py +2 -1
- dissect/target/plugins/filesystem/icat.py +15 -11
- dissect/target/plugins/filesystem/ntfs/mft.py +10 -6
- dissect/target/plugins/filesystem/ntfs/mft_timeline.py +3 -1
- dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -0
- dissect/target/plugins/filesystem/ntfs/utils.py +3 -1
- dissect/target/plugins/filesystem/unix/suid.py +4 -1
- dissect/target/plugins/filesystem/walkfs.py +2 -0
- dissect/target/plugins/general/example.py +2 -2
- dissect/target/plugins/general/loaders.py +18 -5
- dissect/target/plugins/general/network.py +20 -5
- dissect/target/plugins/general/osinfo.py +1 -0
- dissect/target/plugins/general/plugins.py +53 -10
- dissect/target/plugins/os/unix/_os.py +70 -44
- dissect/target/plugins/os/unix/applications.py +78 -0
- dissect/target/plugins/os/unix/bsd/citrix/history.py +2 -0
- dissect/target/plugins/os/unix/bsd/osx/_os.py +4 -21
- dissect/target/plugins/os/unix/bsd/osx/network.py +92 -0
- dissect/target/plugins/os/unix/bsd/osx/user.py +4 -0
- dissect/target/plugins/os/unix/cronjobs.py +8 -4
- dissect/target/plugins/os/unix/etc/etc.py +4 -0
- dissect/target/plugins/os/unix/generic.py +2 -0
- dissect/target/plugins/os/unix/history.py +27 -25
- dissect/target/plugins/os/unix/linux/_os.py +8 -10
- dissect/target/plugins/os/unix/linux/cmdline.py +2 -0
- dissect/target/plugins/os/unix/linux/debian/apt.py +4 -1
- dissect/target/plugins/os/unix/linux/debian/dpkg.py +3 -3
- dissect/target/plugins/os/unix/linux/debian/proxmox/__init__.py +0 -0
- dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py +141 -0
- dissect/target/plugins/os/unix/linux/debian/proxmox/vm.py +29 -0
- dissect/target/plugins/os/unix/linux/debian/snap.py +79 -0
- dissect/target/plugins/os/unix/linux/environ.py +2 -0
- dissect/target/plugins/os/unix/linux/fortios/_os.py +74 -63
- dissect/target/plugins/os/unix/linux/fortios/generic.py +2 -0
- dissect/target/plugins/os/unix/linux/fortios/locale.py +2 -0
- dissect/target/plugins/os/unix/linux/modules.py +2 -0
- dissect/target/plugins/os/unix/linux/netstat.py +2 -0
- dissect/target/{helpers → plugins/os/unix/linux}/network_managers.py +11 -9
- dissect/target/plugins/os/unix/linux/processes.py +2 -0
- dissect/target/plugins/os/unix/linux/redhat/yum.py +4 -1
- dissect/target/plugins/os/unix/linux/services.py +5 -3
- dissect/target/plugins/os/unix/linux/sockets.py +2 -0
- dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -1
- dissect/target/plugins/os/unix/locale.py +2 -0
- dissect/target/plugins/os/unix/locate/gnulocate.py +4 -2
- dissect/target/plugins/os/unix/locate/mlocate.py +2 -0
- dissect/target/plugins/os/unix/locate/plocate.py +3 -1
- dissect/target/plugins/os/unix/log/atop.py +2 -0
- dissect/target/plugins/os/unix/log/audit.py +3 -1
- dissect/target/plugins/os/unix/log/auth.py +351 -38
- dissect/target/plugins/os/unix/log/journal.py +123 -101
- dissect/target/plugins/os/unix/log/lastlog.py +5 -3
- dissect/target/plugins/os/unix/log/messages.py +51 -27
- dissect/target/plugins/os/unix/log/utmp.py +52 -71
- dissect/target/plugins/os/unix/packagemanager.py +5 -38
- dissect/target/plugins/os/unix/shadow.py +3 -1
- dissect/target/plugins/os/unix/trash.py +132 -0
- dissect/target/plugins/os/windows/_os.py +22 -41
- dissect/target/plugins/os/windows/activitiescache.py +9 -4
- dissect/target/plugins/os/windows/adpolicy.py +2 -1
- dissect/target/plugins/os/windows/amcache.py +16 -13
- dissect/target/plugins/os/windows/defender.py +4 -3
- dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +3 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +3 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +3 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +3 -0
- dissect/target/plugins/os/windows/env.py +1 -2
- dissect/target/plugins/os/windows/exchange/exchange.py +6 -4
- dissect/target/plugins/os/windows/generic.py +68 -19
- dissect/target/plugins/os/windows/lnk.py +2 -0
- dissect/target/plugins/os/windows/locale.py +9 -3
- dissect/target/plugins/os/windows/log/etl.py +5 -4
- dissect/target/plugins/os/windows/log/evt.py +12 -8
- dissect/target/plugins/os/windows/log/evtx.py +9 -7
- dissect/target/plugins/os/windows/log/mssql.py +103 -0
- dissect/target/plugins/os/windows/log/pfro.py +2 -1
- dissect/target/plugins/os/windows/network.py +380 -0
- dissect/target/plugins/os/windows/notifications.py +6 -4
- dissect/target/plugins/os/windows/prefetch.py +7 -2
- dissect/target/plugins/os/windows/regf/7zip.py +9 -1
- dissect/target/plugins/os/windows/regf/applications.py +62 -0
- dissect/target/plugins/os/windows/regf/auditpol.py +2 -1
- dissect/target/plugins/os/windows/regf/bam.py +3 -1
- dissect/target/plugins/os/windows/regf/cit.py +14 -12
- dissect/target/plugins/os/windows/regf/clsid.py +6 -3
- dissect/target/plugins/os/windows/regf/firewall.py +2 -1
- dissect/target/plugins/os/windows/regf/mru.py +9 -8
- dissect/target/plugins/os/windows/regf/nethist.py +6 -3
- dissect/target/plugins/os/windows/regf/recentfilecache.py +3 -1
- dissect/target/plugins/os/windows/regf/regf.py +5 -1
- dissect/target/plugins/os/windows/regf/shellbags.py +351 -345
- dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
- dissect/target/plugins/os/windows/regf/usb.py +2 -1
- dissect/target/plugins/os/windows/regf/userassist.py +2 -1
- dissect/target/plugins/os/windows/registry.py +11 -0
- dissect/target/plugins/os/windows/services.py +3 -2
- dissect/target/plugins/os/windows/startupinfo.py +7 -2
- dissect/target/plugins/os/windows/syscache.py +5 -2
- dissect/target/plugins/os/windows/tasks.py +1 -1
- dissect/target/plugins/os/windows/thumbcache.py +11 -5
- dissect/target/plugins/os/windows/ual.py +12 -9
- dissect/target/plugins/os/windows/wer.py +21 -6
- dissect/target/plugins/os/windows/wua_history.py +0 -1
- dissect/target/target.py +13 -8
- dissect/target/tools/dump/utils.py +4 -0
- dissect/target/tools/fsutils.py +1 -1
- dissect/target/tools/info.py +1 -1
- dissect/target/tools/mount.py +15 -5
- dissect/target/tools/query.py +15 -9
- dissect/target/tools/shell.py +98 -9
- dissect/target/tools/utils.py +7 -7
- dissect/target/volume.py +4 -4
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/METADATA +6 -2
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/RECORD +176 -160
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/WHEEL +1 -1
- dissect/target/helpers/targetd.py +0 -58
- dissect/target/loaders/targetd.py +0 -223
- dissect/target/plugins/apps/texteditor/texteditor.py +0 -13
- dissect/target/plugins/os/unix/etc.py +0 -9
- /dissect/target/plugins/apps/{texteditor → database}/__init__.py +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
import gzip
|
2
2
|
from datetime import datetime
|
3
|
-
from typing import Dict, Generator, List, TextIO
|
3
|
+
from typing import Dict, Generator, Iterator, List, TextIO
|
4
4
|
|
5
5
|
from dissect.target.exceptions import UnsupportedPluginError
|
6
6
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
@@ -59,7 +59,7 @@ class DpkgPlugin(Plugin):
|
|
59
59
|
raise UnsupportedPluginError("No DPKG files found")
|
60
60
|
|
61
61
|
@export(record=DpkgPackageStatusRecord)
|
62
|
-
def status(self):
|
62
|
+
def status(self) -> Iterator[DpkgPackageStatusRecord]:
|
63
63
|
"""Yield records for packages in dpkg's status database"""
|
64
64
|
|
65
65
|
status_file_path = self.target.fs.path(STATUS_FILE_NAME)
|
@@ -82,7 +82,7 @@ class DpkgPlugin(Plugin):
|
|
82
82
|
yield DpkgPackageStatusRecord(_target=self.target, **record_fields)
|
83
83
|
|
84
84
|
@export(record=DpkgPackageLogRecord)
|
85
|
-
def log(self):
|
85
|
+
def log(self) -> Iterator[DpkgPackageLogRecord]:
|
86
86
|
"""Yield records for actions logged in dpkg's logs"""
|
87
87
|
|
88
88
|
for log_file in self.target.fs.glob(LOG_FILES_GLOB):
|
File without changes
|
@@ -0,0 +1,141 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import stat
|
4
|
+
from io import BytesIO
|
5
|
+
from typing import BinaryIO
|
6
|
+
|
7
|
+
from dissect.sql import sqlite3
|
8
|
+
from dissect.util.stream import BufferedStream
|
9
|
+
|
10
|
+
from dissect.target.filesystem import (
|
11
|
+
Filesystem,
|
12
|
+
VirtualDirectory,
|
13
|
+
VirtualFile,
|
14
|
+
VirtualFilesystem,
|
15
|
+
)
|
16
|
+
from dissect.target.helpers import fsutil
|
17
|
+
from dissect.target.plugins.os.unix._os import OperatingSystem, export
|
18
|
+
from dissect.target.plugins.os.unix.linux.debian._os import DebianPlugin
|
19
|
+
from dissect.target.target import Target
|
20
|
+
|
21
|
+
|
22
|
+
class ProxmoxPlugin(DebianPlugin):
|
23
|
+
@classmethod
|
24
|
+
def detect(cls, target: Target) -> Filesystem | None:
|
25
|
+
for fs in target.filesystems:
|
26
|
+
if fs.exists("/etc/pve") or fs.exists("/var/lib/pve"):
|
27
|
+
return fs
|
28
|
+
|
29
|
+
return None
|
30
|
+
|
31
|
+
@classmethod
|
32
|
+
def create(cls, target: Target, sysvol: Filesystem) -> ProxmoxPlugin:
|
33
|
+
obj = super().create(target, sysvol)
|
34
|
+
|
35
|
+
if (config_db := target.fs.path("/var/lib/pve-cluster/config.db")).exists():
|
36
|
+
with config_db.open("rb") as fh:
|
37
|
+
vfs = _create_pmxcfs(fh, obj.hostname)
|
38
|
+
|
39
|
+
target.fs.mount("/etc/pve", vfs)
|
40
|
+
|
41
|
+
return obj
|
42
|
+
|
43
|
+
@export(property=True)
|
44
|
+
def version(self) -> str:
|
45
|
+
"""Returns Proxmox VE version with underlying OS release."""
|
46
|
+
|
47
|
+
for pkg in self.target.dpkg.status():
|
48
|
+
if pkg.name == "proxmox-ve":
|
49
|
+
distro_name = self._os_release.get("PRETTY_NAME", "")
|
50
|
+
return f"{pkg.name} {pkg.version} ({distro_name})"
|
51
|
+
|
52
|
+
@export(property=True)
|
53
|
+
def os(self) -> str:
|
54
|
+
return OperatingSystem.PROXMOX.value
|
55
|
+
|
56
|
+
|
57
|
+
DT_DIR = 4
|
58
|
+
DT_REG = 8
|
59
|
+
|
60
|
+
|
61
|
+
def _create_pmxcfs(fh: BinaryIO, hostname: str | None = None) -> VirtualFilesystem:
|
62
|
+
# https://pve.proxmox.com/wiki/Proxmox_Cluster_File_System_(pmxcfs)
|
63
|
+
db = sqlite3.SQLite3(fh)
|
64
|
+
|
65
|
+
entries = {row.inode: row for row in db.table("tree")}
|
66
|
+
|
67
|
+
vfs = VirtualFilesystem()
|
68
|
+
for entry in entries.values():
|
69
|
+
if entry.type == DT_DIR:
|
70
|
+
cls = ProxmoxConfigDirectoryEntry
|
71
|
+
elif entry.type == DT_REG:
|
72
|
+
cls = ProxmoxConfigFileEntry
|
73
|
+
else:
|
74
|
+
raise ValueError(f"Unknown pmxcfs file type: {entry.type}")
|
75
|
+
|
76
|
+
parts = []
|
77
|
+
current = entry
|
78
|
+
while current.parent != 0:
|
79
|
+
parts.append(current.name)
|
80
|
+
current = entries[current.parent]
|
81
|
+
parts.append(current.name)
|
82
|
+
|
83
|
+
path = "/".join(parts[::-1])
|
84
|
+
vfs.map_file_entry(path, cls(vfs, path, entry))
|
85
|
+
|
86
|
+
if hostname:
|
87
|
+
node_root = vfs.path(f"nodes/{hostname}")
|
88
|
+
vfs.symlink(str(node_root), "local")
|
89
|
+
vfs.symlink(str(node_root / "lxc"), "lxc")
|
90
|
+
vfs.symlink(str(node_root / "openvz"), "openvz")
|
91
|
+
vfs.symlink(str(node_root / "qemu-server"), "qemu-server")
|
92
|
+
|
93
|
+
# TODO: .version, .members, .vmlist, maybe .clusterlog and .rrd?
|
94
|
+
|
95
|
+
return vfs
|
96
|
+
|
97
|
+
|
98
|
+
class ProxmoxConfigFileEntry(VirtualFile):
|
99
|
+
def open(self) -> BinaryIO:
|
100
|
+
return BufferedStream(BytesIO(self.entry.data or b""))
|
101
|
+
|
102
|
+
def lstat(self) -> fsutil.stat_result:
|
103
|
+
# ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
|
104
|
+
return fsutil.stat_result(
|
105
|
+
[
|
106
|
+
stat.S_IFREG | 0o640,
|
107
|
+
self.entry.inode,
|
108
|
+
id(self.fs),
|
109
|
+
1,
|
110
|
+
0,
|
111
|
+
0,
|
112
|
+
len(self.entry.data) if self.entry.data else 0,
|
113
|
+
0,
|
114
|
+
self.entry.mtime,
|
115
|
+
0,
|
116
|
+
]
|
117
|
+
)
|
118
|
+
|
119
|
+
|
120
|
+
class ProxmoxConfigDirectoryEntry(VirtualDirectory):
|
121
|
+
def __init__(self, fs: VirtualFilesystem, path: str, entry: sqlite3.Row):
|
122
|
+
super().__init__(fs, path)
|
123
|
+
self.entry = entry
|
124
|
+
|
125
|
+
def lstat(self) -> fsutil.stat_result:
|
126
|
+
"""Return the stat information of the given path, without resolving links."""
|
127
|
+
# ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
|
128
|
+
return fsutil.stat_result(
|
129
|
+
[
|
130
|
+
stat.S_IFDIR | 0o755,
|
131
|
+
self.entry.inode,
|
132
|
+
id(self.fs),
|
133
|
+
1,
|
134
|
+
0,
|
135
|
+
0,
|
136
|
+
0,
|
137
|
+
0,
|
138
|
+
self.entry.mtime,
|
139
|
+
0,
|
140
|
+
]
|
141
|
+
)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
5
|
+
from dissect.target.plugin import Plugin, export
|
6
|
+
|
7
|
+
VirtualMachineRecord = TargetRecordDescriptor(
|
8
|
+
"proxmox/vm",
|
9
|
+
[
|
10
|
+
("string", "path"),
|
11
|
+
],
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
class VirtualMachinePlugin(Plugin):
|
16
|
+
"""Plugin to list Proxmox virtual machines."""
|
17
|
+
|
18
|
+
def check_compatible(self) -> None:
|
19
|
+
if self.target.os != "proxmox":
|
20
|
+
raise UnsupportedPluginError("Not a Proxmox operating system")
|
21
|
+
|
22
|
+
@export(record=VirtualMachineRecord)
|
23
|
+
def vmlist(self) -> Iterator[VirtualMachineRecord]:
|
24
|
+
"""List Proxmox virtual machines on this node."""
|
25
|
+
for config in self.target.fs.path("/etc/pve/qemu-server").iterdir():
|
26
|
+
yield VirtualMachineRecord(
|
27
|
+
path=config,
|
28
|
+
_target=self.target,
|
29
|
+
)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.filesystems.squashfs import SquashFSFilesystem
|
5
|
+
from dissect.target.helpers import configutil
|
6
|
+
from dissect.target.helpers.fsutil import TargetPath
|
7
|
+
from dissect.target.helpers.record import UnixApplicationRecord
|
8
|
+
from dissect.target.plugin import Plugin, alias, export
|
9
|
+
from dissect.target.target import Target
|
10
|
+
|
11
|
+
|
12
|
+
class SnapPlugin(Plugin):
|
13
|
+
"""Canonical Linux Snapcraft plugin."""
|
14
|
+
|
15
|
+
PATHS = [
|
16
|
+
"/var/lib/snapd/snaps",
|
17
|
+
]
|
18
|
+
|
19
|
+
def __init__(self, target: Target):
|
20
|
+
super().__init__(target)
|
21
|
+
self.installs = list(self._find_installs())
|
22
|
+
|
23
|
+
def check_compatible(self) -> None:
|
24
|
+
if not configutil.HAS_YAML:
|
25
|
+
raise UnsupportedPluginError("Missing required dependency ruamel.yaml")
|
26
|
+
|
27
|
+
if not self.installs:
|
28
|
+
raise UnsupportedPluginError("No snapd install folder(s) found")
|
29
|
+
|
30
|
+
def _find_installs(self) -> Iterator[TargetPath]:
|
31
|
+
for str_path in self.PATHS:
|
32
|
+
if (path := self.target.fs.path(str_path)).exists():
|
33
|
+
yield path
|
34
|
+
|
35
|
+
@export(record=UnixApplicationRecord)
|
36
|
+
@alias("snaps")
|
37
|
+
def snap(self) -> Iterator[UnixApplicationRecord]:
|
38
|
+
"""Yields installed Canonical Linux Snapcraft (snaps) applications on the target system.
|
39
|
+
|
40
|
+
Reads information from installed SquashFS ``*.snap`` files found in ``/var/lib/snapd/snaps``.
|
41
|
+
Logs of the ``snapd`` daemon can be parsed using the ``journal`` or ``syslog`` plugins.
|
42
|
+
|
43
|
+
Resources:
|
44
|
+
- https://github.com/canonical/snapcraft
|
45
|
+
- https://en.wikipedia.org/wiki/Snap_(software)
|
46
|
+
|
47
|
+
Yields ``UnixApplicationRecord`` records with the following fields:
|
48
|
+
|
49
|
+
.. code-block:: text
|
50
|
+
|
51
|
+
ts_modified (datetime): timestamp when the installation was modified
|
52
|
+
name (string): name of the application
|
53
|
+
version (string): version of the application
|
54
|
+
path (string): path to the application snap file
|
55
|
+
"""
|
56
|
+
|
57
|
+
for install_path in self.installs:
|
58
|
+
for snap in install_path.glob("*.snap"):
|
59
|
+
try:
|
60
|
+
squashfs = SquashFSFilesystem(snap.open())
|
61
|
+
|
62
|
+
except (ValueError, NotImplementedError) as e:
|
63
|
+
self.target.log.warning("Unable to open snap file %s", snap)
|
64
|
+
self.target.log.debug("", exc_info=e)
|
65
|
+
continue
|
66
|
+
|
67
|
+
if not (meta := squashfs.path("meta/snap.yaml")).exists():
|
68
|
+
self.target.log.warning("Snap %s has no meta/snap.yaml file")
|
69
|
+
continue
|
70
|
+
|
71
|
+
meta_data = configutil.parse(meta, hint="yaml")
|
72
|
+
|
73
|
+
yield UnixApplicationRecord(
|
74
|
+
ts_modified=meta.lstat().st_mtime,
|
75
|
+
name=meta_data.get("name"),
|
76
|
+
version=meta_data.get("version"),
|
77
|
+
path=snap,
|
78
|
+
_target=self.target,
|
79
|
+
)
|
@@ -6,7 +6,7 @@ from base64 import b64decode
|
|
6
6
|
from datetime import datetime
|
7
7
|
from io import BytesIO
|
8
8
|
from tarfile import ReadError
|
9
|
-
from typing import BinaryIO, Iterator,
|
9
|
+
from typing import BinaryIO, Iterator, TextIO
|
10
10
|
|
11
11
|
from dissect.util import cpio
|
12
12
|
from dissect.util.compression import xz
|
@@ -73,10 +73,11 @@ class FortiOSPlugin(LinuxPlugin):
|
|
73
73
|
return config
|
74
74
|
|
75
75
|
@classmethod
|
76
|
-
def detect(cls, target: Target) ->
|
76
|
+
def detect(cls, target: Target) -> Filesystem | None:
|
77
77
|
for fs in target.filesystems:
|
78
|
-
# Tested on FortiGate
|
79
|
-
|
78
|
+
# Tested on FortiGate, FortiAnalyzer and FortiManager.
|
79
|
+
# Other Fortinet devices may look different.
|
80
|
+
if fs.exists("/rootfs.gz") and (any(map(fs.exists, (".fgtsum", ".fmg_sign", "flatkc", "system.conf")))):
|
80
81
|
return fs
|
81
82
|
|
82
83
|
@classmethod
|
@@ -212,7 +213,7 @@ class FortiOSPlugin(LinuxPlugin):
|
|
212
213
|
return "FortiOS Unknown"
|
213
214
|
|
214
215
|
@export(record=FortiOSUserRecord)
|
215
|
-
def users(self) -> Iterator[
|
216
|
+
def users(self) -> Iterator[FortiOSUserRecord | UnixUserRecord]:
|
216
217
|
"""Return local users of the FortiOS system."""
|
217
218
|
|
218
219
|
# Possible unix-like users
|
@@ -224,7 +225,7 @@ class FortiOSPlugin(LinuxPlugin):
|
|
224
225
|
yield FortiOSUserRecord(
|
225
226
|
name=username,
|
226
227
|
password=":".join(entry.get("password", [])),
|
227
|
-
groups=
|
228
|
+
groups=list(entry.get("accprofile", [])),
|
228
229
|
home="/root",
|
229
230
|
_target=self.target,
|
230
231
|
)
|
@@ -233,69 +234,79 @@ class FortiOSPlugin(LinuxPlugin):
|
|
233
234
|
self.target.log.debug("", exc_info=e)
|
234
235
|
|
235
236
|
# FortiManager administrative users
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
yield FortiOSUserRecord(
|
259
|
-
name=username,
|
260
|
-
password=password,
|
261
|
-
groups=local_groups.get(username, []),
|
262
|
-
home=None,
|
263
|
-
_target=self.target,
|
264
|
-
)
|
265
|
-
except KeyError as e:
|
266
|
-
self.target.log.warning("Exception while parsing FortiOS local users")
|
267
|
-
self.target.log.debug("", exc_info=e)
|
268
|
-
|
269
|
-
# Temporary guest users
|
270
|
-
try:
|
271
|
-
for _, entry in self._config["root-config"]["user"]["group"].get("guestgroup", {}).get("guest", {}).items():
|
272
|
-
try:
|
273
|
-
password = decrypt_password(entry.get("password")[-1])
|
274
|
-
except (ValueError, RuntimeError):
|
275
|
-
password = ":".join(entry.get("password"))
|
237
|
+
if self._config.get("global-config", {}).get("system", {}).get("admin", {}).get("user"):
|
238
|
+
try:
|
239
|
+
for username, entry in self._config["global-config"]["system"]["admin"]["user"].items():
|
240
|
+
yield FortiOSUserRecord(
|
241
|
+
name=username,
|
242
|
+
password=":".join(entry.get("password", [])),
|
243
|
+
groups=list(entry.get("profileid", [])),
|
244
|
+
home="/root",
|
245
|
+
_target=self.target,
|
246
|
+
)
|
247
|
+
except KeyError as e:
|
248
|
+
self.target.log.warning("Exception while parsing FortiManager admin users")
|
249
|
+
self.target.log.debug("", exc_info=e)
|
250
|
+
|
251
|
+
if self._config.get("root-config", {}).get("user", {}).get("local"):
|
252
|
+
# Local users
|
253
|
+
try:
|
254
|
+
local_groups = local_groups_to_users(self._config["root-config"]["user"]["group"])
|
255
|
+
except KeyError as e:
|
256
|
+
self.target.log.warning("Unable to get local user groups in root config")
|
257
|
+
self.target.log.debug("", exc_info=e)
|
258
|
+
local_groups = {}
|
276
259
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
260
|
+
try:
|
261
|
+
for username, entry in self._config["root-config"]["user"].get("local", {}).items():
|
262
|
+
try:
|
263
|
+
password = decrypt_password(entry["passwd"][-1])
|
264
|
+
except (ValueError, RuntimeError):
|
265
|
+
password = ":".join(entry.get("passwd", []))
|
266
|
+
|
267
|
+
yield FortiOSUserRecord(
|
268
|
+
name=username,
|
269
|
+
password=password,
|
270
|
+
groups=local_groups.get(username, []),
|
271
|
+
home=None,
|
272
|
+
_target=self.target,
|
273
|
+
)
|
274
|
+
except KeyError as e:
|
275
|
+
self.target.log.warning("Exception while parsing FortiOS local users")
|
276
|
+
self.target.log.debug("", exc_info=e)
|
277
|
+
|
278
|
+
if self._config.get("root-config", {}).get("user", {}).get("group", {}).get("guestgroup"):
|
279
|
+
# Temporary guest users
|
280
|
+
try:
|
281
|
+
for _, entry in (
|
282
|
+
self._config["root-config"]["user"]["group"].get("guestgroup", {}).get("guest", {}).items()
|
283
|
+
):
|
284
|
+
try:
|
285
|
+
password = decrypt_password(entry.get("password")[-1])
|
286
|
+
except (ValueError, RuntimeError):
|
287
|
+
password = ":".join(entry.get("password"))
|
288
|
+
|
289
|
+
yield FortiOSUserRecord(
|
290
|
+
name=entry["user-id"][0],
|
291
|
+
password=password,
|
292
|
+
groups=["guestgroup"],
|
293
|
+
home=None,
|
294
|
+
_target=self.target,
|
295
|
+
)
|
296
|
+
except KeyError as e:
|
297
|
+
self.target.log.warning("Exception while parsing FortiOS temporary guest users")
|
298
|
+
self.target.log.debug("", exc_info=e)
|
287
299
|
|
288
300
|
@export(property=True)
|
289
301
|
def os(self) -> str:
|
290
302
|
return OperatingSystem.FORTIOS.value
|
291
303
|
|
292
304
|
@export(property=True)
|
293
|
-
def architecture(self) ->
|
305
|
+
def architecture(self) -> str | None:
|
294
306
|
"""Return architecture FortiOS runs on."""
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
return self._get_architecture(path=path)
|
307
|
+
for path in ["/lib/libav.so", "/bin/ctr", "/bin/grep"]:
|
308
|
+
if (bin := self.target.fs.path(path)).exists():
|
309
|
+
return self._get_architecture(path=bin)
|
299
310
|
|
300
311
|
|
301
312
|
class ConfigNode(dict):
|
@@ -528,7 +539,7 @@ def decrypt_rootfs(fh: BinaryIO, key: bytes, iv: bytes) -> BinaryIO:
|
|
528
539
|
return BytesIO(result)
|
529
540
|
|
530
541
|
|
531
|
-
def _kdf_7_4_x(key_data:
|
542
|
+
def _kdf_7_4_x(key_data: str | bytes) -> tuple[bytes, bytes]:
|
532
543
|
"""Derive 32 byte key and 16 byte IV from 32 byte seed.
|
533
544
|
|
534
545
|
As the IV needs to be 16 bytes, we return the first 16 bytes of the sha256 hash.
|
@@ -542,7 +553,7 @@ def _kdf_7_4_x(key_data: Union[str, bytes]) -> tuple[bytes, bytes]:
|
|
542
553
|
return key, iv
|
543
554
|
|
544
555
|
|
545
|
-
def get_kernel_hash(sysvol: Filesystem) ->
|
556
|
+
def get_kernel_hash(sysvol: Filesystem) -> str | None:
|
546
557
|
"""Return the SHA256 hash of the (compressed) kernel."""
|
547
558
|
kernel_files = ["flatkc", "vmlinuz", "vmlinux"]
|
548
559
|
for k in kernel_files:
|
@@ -9,6 +9,8 @@ from dissect.target.plugins.os.unix.generic import calculate_last_activity
|
|
9
9
|
|
10
10
|
|
11
11
|
class GenericPlugin(Plugin):
|
12
|
+
"""Generic FortiOS plugin."""
|
13
|
+
|
12
14
|
def check_compatible(self) -> None:
|
13
15
|
if self.target.os != "fortios":
|
14
16
|
raise UnsupportedPluginError("FortiOS specific plugin loaded on non-FortiOS target")
|
@@ -5,6 +5,8 @@ from dissect.target.plugin import Plugin, export
|
|
5
5
|
|
6
6
|
|
7
7
|
class LocalePlugin(Plugin):
|
8
|
+
"""FortiOS locale plugin."""
|
9
|
+
|
8
10
|
def check_compatible(self) -> None:
|
9
11
|
if self.target.os != "fortios":
|
10
12
|
raise UnsupportedPluginError("FortiOS specific plugin loaded on non-FortiOS target")
|
@@ -7,15 +7,17 @@ from configparser import ConfigParser, MissingSectionHeaderError
|
|
7
7
|
from io import StringIO
|
8
8
|
from itertools import chain
|
9
9
|
from re import compile, sub
|
10
|
-
from typing import Any, Callable, Iterable, Iterator, Match
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Match
|
11
11
|
|
12
12
|
from defusedxml import ElementTree
|
13
13
|
|
14
14
|
from dissect.target.exceptions import PluginError
|
15
|
-
|
16
|
-
|
17
|
-
from dissect.target.
|
18
|
-
from dissect.target.
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from dissect.target.helpers.fsutil import TargetPath
|
18
|
+
from dissect.target.plugins.os.unix.log.journal import JournalRecord
|
19
|
+
from dissect.target.plugins.os.unix.log.messages import MessagesRecord
|
20
|
+
from dissect.target.target import Target
|
19
21
|
|
20
22
|
log = logging.getLogger(__name__)
|
21
23
|
|
@@ -57,7 +59,7 @@ class Template:
|
|
57
59
|
"""Sets the name of the the used parsing template to the name of the discovered network manager."""
|
58
60
|
self.name = name
|
59
61
|
|
60
|
-
def create_config(self, path: TargetPath) ->
|
62
|
+
def create_config(self, path: TargetPath) -> dict | None:
|
61
63
|
"""Create a network config dictionary based on the configured template and supplied path.
|
62
64
|
|
63
65
|
Args:
|
@@ -81,7 +83,7 @@ class Template:
|
|
81
83
|
config = self._parse_configparser_config(path)
|
82
84
|
return config
|
83
85
|
|
84
|
-
def _parse_netplan_config(self, path: TargetPath) ->
|
86
|
+
def _parse_netplan_config(self, path: TargetPath) -> dict | None:
|
85
87
|
"""Internal function to parse a netplan YAML based configuration file into a dict.
|
86
88
|
|
87
89
|
Args:
|
@@ -286,7 +288,7 @@ class Parser:
|
|
286
288
|
if option in translation_values and value:
|
287
289
|
return translation_key
|
288
290
|
|
289
|
-
def _get_option(self, config: dict, option: str, section:
|
291
|
+
def _get_option(self, config: dict, option: str, section: str | None = None) -> str | Callable | None:
|
290
292
|
"""Internal function to get arbitrary options values from a parsed (non-translated) dictionary.
|
291
293
|
|
292
294
|
Args:
|
@@ -334,7 +336,7 @@ class NetworkManager:
|
|
334
336
|
self.config_globs = (config_globs,) if isinstance(config_globs, str) else config_globs
|
335
337
|
self.detection_globs = (detection_globs,) if isinstance(detection_globs, str) else detection_globs
|
336
338
|
|
337
|
-
def detect(self, target:
|
339
|
+
def detect(self, target: Target | None = None) -> bool:
|
338
340
|
"""Detects if the network manager is active on the target
|
339
341
|
|
340
342
|
Returns:
|
@@ -7,13 +7,16 @@ from dissect.target.helpers.utils import year_rollover_helper
|
|
7
7
|
from dissect.target.plugins.os.unix.packagemanager import (
|
8
8
|
OperationTypes,
|
9
9
|
PackageManagerLogRecord,
|
10
|
+
PackageManagerPlugin,
|
10
11
|
)
|
11
12
|
|
12
13
|
YUM_LOG_KEYWORDS = ["Installed", "Updated", "Erased", "Obsoleted"]
|
13
14
|
RE_TS = re.compile(r"(\w+\s{1,2}\d+\s\d{2}:\d{2}:\d{2})")
|
14
15
|
|
15
16
|
|
16
|
-
class YumPlugin(
|
17
|
+
class YumPlugin(PackageManagerPlugin):
|
18
|
+
"""Yum package manager plugin."""
|
19
|
+
|
17
20
|
__namespace__ = "yum"
|
18
21
|
|
19
22
|
LOG_DIR_PATH = "/var/log"
|
@@ -18,6 +18,8 @@ LinuxServiceRecord = TargetRecordDescriptor(RECORD_NAME, DEFAULT_ELEMENTS)
|
|
18
18
|
|
19
19
|
|
20
20
|
class ServicesPlugin(Plugin):
|
21
|
+
"""Linux services plugin."""
|
22
|
+
|
21
23
|
SYSTEMD_PATHS = [
|
22
24
|
"/etc/systemd/system",
|
23
25
|
"/lib/systemd/system",
|
@@ -35,9 +37,9 @@ class ServicesPlugin(Plugin):
|
|
35
37
|
"""Return information about all installed systemd and init.d services.
|
36
38
|
|
37
39
|
References:
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
- https://geeksforgeeks.org/what-is-init-d-in-linux-service-management
|
41
|
+
- http://0pointer.de/blog/projects/systemd-for-admins-3.html
|
42
|
+
- https://www.freedesktop.org/software/systemd/man/latest/systemd.syntax.html
|
41
43
|
"""
|
42
44
|
|
43
45
|
return chain(self.systemd(), self.initd())
|
@@ -7,10 +7,13 @@ from dissect.target.helpers.fsutil import open_decompress
|
|
7
7
|
from dissect.target.plugins.os.unix.packagemanager import (
|
8
8
|
OperationTypes,
|
9
9
|
PackageManagerLogRecord,
|
10
|
+
PackageManagerPlugin,
|
10
11
|
)
|
11
12
|
|
12
13
|
|
13
|
-
class ZypperPlugin(
|
14
|
+
class ZypperPlugin(PackageManagerPlugin):
|
15
|
+
"""Zypper package manager plugin."""
|
16
|
+
|
14
17
|
__namespace__ = "zypper"
|
15
18
|
|
16
19
|
LOG_DIR_PATH = "/var/log/zypp"
|