dissect.target 3.14.dev29__py3-none-any.whl → 3.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. dissect/target/containers/ewf.py +1 -1
  2. dissect/target/containers/vhd.py +5 -2
  3. dissect/target/filesystem.py +36 -18
  4. dissect/target/filesystems/dir.py +10 -4
  5. dissect/target/filesystems/jffs.py +122 -0
  6. dissect/target/helpers/compat/path_310.py +506 -0
  7. dissect/target/helpers/compat/path_311.py +539 -0
  8. dissect/target/helpers/compat/path_312.py +443 -0
  9. dissect/target/helpers/compat/path_39.py +545 -0
  10. dissect/target/helpers/compat/path_common.py +223 -0
  11. dissect/target/helpers/cyber.py +512 -0
  12. dissect/target/helpers/fsutil.py +128 -666
  13. dissect/target/helpers/hashutil.py +17 -57
  14. dissect/target/helpers/keychain.py +9 -3
  15. dissect/target/helpers/loaderutil.py +1 -1
  16. dissect/target/helpers/mount.py +47 -4
  17. dissect/target/helpers/polypath.py +73 -0
  18. dissect/target/helpers/record_modifier.py +100 -0
  19. dissect/target/loader.py +2 -1
  20. dissect/target/loaders/asdf.py +2 -0
  21. dissect/target/loaders/cyber.py +37 -0
  22. dissect/target/loaders/log.py +14 -3
  23. dissect/target/loaders/raw.py +2 -0
  24. dissect/target/loaders/remote.py +12 -0
  25. dissect/target/loaders/tar.py +13 -0
  26. dissect/target/loaders/targetd.py +2 -0
  27. dissect/target/loaders/velociraptor.py +12 -3
  28. dissect/target/loaders/vmwarevm.py +2 -0
  29. dissect/target/plugin.py +272 -143
  30. dissect/target/plugins/apps/ssh/openssh.py +11 -54
  31. dissect/target/plugins/apps/ssh/opensshd.py +4 -3
  32. dissect/target/plugins/apps/ssh/putty.py +236 -0
  33. dissect/target/plugins/apps/ssh/ssh.py +58 -0
  34. dissect/target/plugins/apps/vpn/openvpn.py +6 -0
  35. dissect/target/plugins/apps/webserver/apache.py +309 -95
  36. dissect/target/plugins/apps/webserver/caddy.py +5 -2
  37. dissect/target/plugins/apps/webserver/citrix.py +82 -0
  38. dissect/target/plugins/apps/webserver/iis.py +9 -12
  39. dissect/target/plugins/apps/webserver/nginx.py +5 -2
  40. dissect/target/plugins/apps/webserver/webserver.py +25 -41
  41. dissect/target/plugins/child/wsl.py +1 -1
  42. dissect/target/plugins/filesystem/ntfs/mft.py +10 -0
  43. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +10 -0
  44. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +10 -0
  45. dissect/target/plugins/filesystem/ntfs/utils.py +28 -5
  46. dissect/target/plugins/filesystem/resolver.py +6 -4
  47. dissect/target/plugins/general/default.py +0 -2
  48. dissect/target/plugins/general/example.py +0 -1
  49. dissect/target/plugins/general/loaders.py +3 -5
  50. dissect/target/plugins/os/unix/_os.py +3 -3
  51. dissect/target/plugins/os/unix/bsd/citrix/_os.py +68 -28
  52. dissect/target/plugins/os/unix/bsd/citrix/history.py +130 -0
  53. dissect/target/plugins/os/unix/generic.py +17 -12
  54. dissect/target/plugins/os/unix/linux/fortios/__init__.py +0 -0
  55. dissect/target/plugins/os/unix/linux/fortios/_os.py +534 -0
  56. dissect/target/plugins/os/unix/linux/fortios/generic.py +30 -0
  57. dissect/target/plugins/os/unix/linux/fortios/locale.py +109 -0
  58. dissect/target/plugins/os/windows/log/evt.py +1 -1
  59. dissect/target/plugins/os/windows/log/schedlgu.py +155 -0
  60. dissect/target/plugins/os/windows/regf/firewall.py +1 -1
  61. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  62. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  63. dissect/target/plugins/os/windows/registry.py +1 -1
  64. dissect/target/plugins/os/windows/sam.py +3 -0
  65. dissect/target/plugins/os/windows/sru.py +41 -28
  66. dissect/target/plugins/os/windows/tasks.py +5 -2
  67. dissect/target/target.py +7 -3
  68. dissect/target/tools/dd.py +7 -1
  69. dissect/target/tools/fs.py +8 -1
  70. dissect/target/tools/info.py +22 -16
  71. dissect/target/tools/mount.py +28 -3
  72. dissect/target/tools/query.py +146 -117
  73. dissect/target/tools/reg.py +21 -16
  74. dissect/target/tools/shell.py +30 -6
  75. dissect/target/tools/utils.py +28 -0
  76. dissect/target/volumes/bde.py +14 -10
  77. dissect/target/volumes/luks.py +18 -10
  78. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/METADATA +4 -3
  79. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/RECORD +85 -67
  80. dissect/target/plugins/os/unix/linux/fortigate/_os.py +0 -175
  81. /dissect/target/{plugins/os/unix/linux/fortigate → helpers/compat}/__init__.py +0 -0
  82. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/COPYRIGHT +0 -0
  83. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/LICENSE +0 -0
  84. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/WHEEL +0 -0
  85. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/entry_points.txt +0 -0
  86. {dissect.target-3.14.dev29.dist-info → dissect.target-3.15.dist-info}/top_level.txt +0 -0
@@ -5,59 +5,15 @@ from typing import Iterator
5
5
 
6
6
  from dissect.target import Target
7
7
  from dissect.target.exceptions import UnsupportedPluginError
8
- from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
9
8
  from dissect.target.helpers.fsutil import TargetPath
10
- from dissect.target.helpers.record import create_extended_descriptor
11
9
  from dissect.target.helpers.ssh import SSHPrivateKey
12
- from dissect.target.plugin import Plugin, export
13
-
14
- OpenSSHUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
15
-
16
- COMMON_ELLEMENTS = [
17
- ("string", "key_type"),
18
- ("string", "comment"),
19
- ("path", "path"),
20
- ]
21
-
22
- AuthorizedKeysRecord = OpenSSHUserRecordDescriptor(
23
- "application/openssh/authorized_keys",
24
- [
25
- *COMMON_ELLEMENTS,
26
- ("string", "public_key"),
27
- ("string", "options"),
28
- ],
29
- )
30
-
31
-
32
- KnownHostRecord = OpenSSHUserRecordDescriptor(
33
- "application/openssh/known_host",
34
- [
35
- *COMMON_ELLEMENTS,
36
- ("string", "hostname_pattern"),
37
- ("string", "public_key"),
38
- ("string", "marker"),
39
- ],
40
- )
41
-
42
-
43
- PrivateKeyRecord = OpenSSHUserRecordDescriptor(
44
- "application/openssh/private_key",
45
- [
46
- *COMMON_ELLEMENTS,
47
- ("datetime", "mtime_ts"),
48
- ("string", "key_format"),
49
- ("string", "public_key"),
50
- ("boolean", "encrypted"),
51
- ],
52
- )
53
-
54
- PublicKeyRecord = OpenSSHUserRecordDescriptor(
55
- "application/openssh/public_key",
56
- [
57
- *COMMON_ELLEMENTS,
58
- ("datetime", "mtime_ts"),
59
- ("string", "public_key"),
60
- ],
10
+ from dissect.target.plugin import export
11
+ from dissect.target.plugins.apps.ssh.ssh import (
12
+ AuthorizedKeysRecord,
13
+ KnownHostRecord,
14
+ PrivateKeyRecord,
15
+ PublicKeyRecord,
16
+ SSHPlugin,
61
17
  )
62
18
 
63
19
 
@@ -72,8 +28,8 @@ def find_sshd_directory(target: Target) -> TargetPath:
72
28
  return target.fs.path("/etc/ssh/")
73
29
 
74
30
 
75
- class OpenSSHPlugin(Plugin):
76
- __namespace__ = "ssh"
31
+ class OpenSSHPlugin(SSHPlugin):
32
+ __namespace__ = "openssh"
77
33
 
78
34
  SSHD_DIRECTORIES = ["/sysvol/ProgramData/ssh", "/etc/ssh"]
79
35
 
@@ -136,7 +92,8 @@ class OpenSSHPlugin(Plugin):
136
92
 
137
93
  for hostname in hostnames:
138
94
  yield KnownHostRecord(
139
- hostname_pattern=hostname,
95
+ host=hostname,
96
+ port=None,
140
97
  key_type=keytype,
141
98
  public_key=public_key,
142
99
  comment=comment,
@@ -3,8 +3,9 @@ from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Union
3
3
  from dissect.target import Target
4
4
  from dissect.target.exceptions import UnsupportedPluginError
5
5
  from dissect.target.helpers.record import DynamicDescriptor, TargetRecordDescriptor
6
- from dissect.target.plugin import Plugin, export
6
+ from dissect.target.plugin import export
7
7
  from dissect.target.plugins.apps.ssh.openssh import find_sshd_directory
8
+ from dissect.target.plugins.apps.ssh.ssh import SSHPlugin
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from dissect.target.plugins.general.config import ConfigurationTreePlugin
@@ -72,8 +73,8 @@ SSHD_MULTIPLE_DEFINITIONS_ALLOWED_FIELDS = (
72
73
  )
73
74
 
74
75
 
75
- class SSHServerPlugin(Plugin):
76
- __namespace__ = "sshd"
76
+ class SSHServerPlugin(SSHPlugin):
77
+ __namespace__ = "opensshd"
77
78
 
78
79
  def __init__(self, target: Target):
79
80
  super().__init__(target)
@@ -0,0 +1,236 @@
1
+ import logging
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from typing import Iterator, Optional, Union
5
+
6
+ from Crypto.PublicKey import ECC, RSA
7
+ from flow.record.fieldtypes import posix_path, windows_path
8
+
9
+ from dissect.target.exceptions import RegistryKeyNotFoundError, UnsupportedPluginError
10
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
11
+ from dissect.target.helpers.fsutil import TargetPath, open_decompress
12
+ from dissect.target.helpers.record import create_extended_descriptor
13
+ from dissect.target.helpers.regutil import RegistryKey
14
+ from dissect.target.plugin import export
15
+ from dissect.target.plugins.apps.ssh.ssh import KnownHostRecord, SSHPlugin
16
+ from dissect.target.plugins.general.users import UserDetails
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+ PuTTYUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
21
+ PuTTYSessionRecord = PuTTYUserRecordDescriptor(
22
+ "application/putty/saved_session",
23
+ [
24
+ ("datetime", "ts"),
25
+ ("string", "session_name"),
26
+ ("string", "protocol"),
27
+ ("string", "host"),
28
+ ("string", "user"),
29
+ ("varint", "port"),
30
+ ("string", "remote_command"),
31
+ ("string", "port_forward"),
32
+ ("string", "manual_ssh_host_keys"),
33
+ ("path", "path"),
34
+ ],
35
+ )
36
+
37
+
38
+ class PuTTYPlugin(SSHPlugin):
39
+ """Extract artifacts from the PuTTY client.
40
+
41
+ NOTE:
42
+ - Does not parse ``$HOME/.putty/randomseed`` (GNU/Linux)
43
+ and ``HKCU\\Software\\SimonTatham\\PuTTY\\RandSeedFile`` (Windows)
44
+
45
+ Resources:
46
+ - http://www.chiark.greenend.org.uk/~sgtatham/putty/0.78/puttydoc.txt
47
+ - http://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html#faq-settings
48
+ """
49
+
50
+ __namespace__ = "putty"
51
+
52
+ def __init__(self, target):
53
+ super().__init__(target)
54
+
55
+ self.regf_installs, self.path_installs = self._detect_putty()
56
+
57
+ def _detect_putty(
58
+ self,
59
+ ) -> tuple[list[set[RegistryKey, Optional[UserDetails]]], list[set[TargetPath, Optional[UserDetails]]]]:
60
+ regf_installs, path_installs = [], []
61
+
62
+ if self.target.has_function("registry"):
63
+ for key in self.target.registry.keys("HKCU\\Software\\SimonTatham\\PuTTY"):
64
+ user_details = self.target.registry.get_user_details(key)
65
+ regf_installs.append((key, user_details))
66
+
67
+ for user_details in self.target.user_details.all_with_home():
68
+ if (putty_path := user_details.home_path.joinpath(".putty")).exists():
69
+ path_installs.append((putty_path, user_details))
70
+
71
+ return regf_installs, path_installs
72
+
73
+ def check_compatible(self) -> None:
74
+ if not any(self.regf_installs + self.path_installs):
75
+ raise UnsupportedPluginError("No PuTTY installations found")
76
+
77
+ @export(record=KnownHostRecord)
78
+ def known_hosts(self) -> Iterator[KnownHostRecord]:
79
+ """Parse PuTTY saved SshHostKeys."""
80
+
81
+ for putty_key, user_details in self.regf_installs:
82
+ yield from self._regf_known_hosts(putty_key, user_details)
83
+
84
+ for putty_path, user_details in self.path_installs:
85
+ yield from self._path_known_hosts(putty_path, user_details)
86
+
87
+ def _regf_known_hosts(self, putty_key: RegistryKey, user_details: UserDetails) -> Iterator[KnownHostRecord]:
88
+ """Parse PuTTY traces in Windows registry."""
89
+
90
+ try:
91
+ ssh_host_keys = putty_key.subkey("SshHostKeys")
92
+ except RegistryKeyNotFoundError:
93
+ return
94
+
95
+ for entry in ssh_host_keys.values():
96
+ key_type, host = entry.name.split("@")
97
+ port, host = host.split(":")
98
+
99
+ yield KnownHostRecord(
100
+ mtime_ts=ssh_host_keys.ts,
101
+ host=host,
102
+ port=port,
103
+ key_type=key_type,
104
+ public_key=construct_public_key(key_type, entry.value),
105
+ comment="",
106
+ marker=None,
107
+ path=windows_path(ssh_host_keys.path),
108
+ _target=self.target,
109
+ _user=user_details.user if user_details else None,
110
+ )
111
+
112
+ def _path_known_hosts(self, putty_path: TargetPath, user_details: UserDetails) -> Iterator[KnownHostRecord]:
113
+ """Parse PuTTY traces in ``.putty`` folders"""
114
+ ssh_host_keys_path = putty_path.joinpath("sshhostkeys")
115
+
116
+ if ssh_host_keys_path.exists():
117
+ ts = ssh_host_keys_path.stat().st_mtime
118
+
119
+ for line in open_decompress(ssh_host_keys_path, "rt"):
120
+ parts = line.split()
121
+ key_type, host = parts[0].split("@")
122
+ port, host = host.split(":")
123
+
124
+ yield KnownHostRecord(
125
+ mtime_ts=ts,
126
+ host=host,
127
+ port=port,
128
+ key_type=key_type,
129
+ public_key=construct_public_key(key_type, parts[1]),
130
+ comment="",
131
+ marker=None,
132
+ path=posix_path(ssh_host_keys_path),
133
+ _target=self.target,
134
+ _user=user_details.user if user_details else None,
135
+ )
136
+
137
+ @export(record=PuTTYSessionRecord)
138
+ def sessions(self) -> Iterator[PuTTYSessionRecord]:
139
+ """Parse PuTTY saved session configuration files."""
140
+
141
+ for putty_key, user_details in self.regf_installs:
142
+ yield from self._regf_sessions(putty_key, user_details)
143
+
144
+ for putty_path, user_details in self.path_installs:
145
+ yield from self._path_sessions(putty_path, user_details)
146
+
147
+ def _regf_sessions(self, putty_key: RegistryKey, user_details: UserDetails) -> Iterator[PuTTYSessionRecord]:
148
+ try:
149
+ sessions = putty_key.subkey("Sessions")
150
+ except RegistryKeyNotFoundError:
151
+ return
152
+
153
+ for session in sessions.subkeys():
154
+ cfg = {s.name: s.value for s in session.values()}
155
+ yield from self._build_session_record(
156
+ session.ts, session.name, windows_path(session.path), cfg, user_details
157
+ )
158
+
159
+ def _path_sessions(self, putty_path: TargetPath, user_details: UserDetails) -> Iterator[PuTTYSessionRecord]:
160
+ sessions_dir = putty_path.joinpath("sessions")
161
+ if sessions_dir.exists():
162
+ for session in sessions_dir.glob("*"):
163
+ if session.is_file():
164
+ cfg = dict(map(str.strip, line.split("=", maxsplit=1)) for line in session.open("rt").readlines())
165
+ yield from self._build_session_record(
166
+ session.stat().st_mtime, session.name, session, cfg, user_details
167
+ )
168
+
169
+ def _build_session_record(
170
+ self, ts: float, name: Union[float, datetime], source: Path, cfg: dict, user_details: UserDetails
171
+ ) -> PuTTYSessionRecord:
172
+ host, user = parse_host_user(cfg.get("HostName"), cfg.get("UserName"))
173
+
174
+ yield PuTTYSessionRecord(
175
+ ts=ts,
176
+ session_name=name,
177
+ protocol=cfg.get("Protocol"),
178
+ host=host,
179
+ user=user,
180
+ port=cfg.get("PortNumber"),
181
+ remote_command=cfg.get("RemoteCommand"),
182
+ port_forward=cfg.get("PortForwardings"),
183
+ manual_ssh_host_keys=cfg.get("SSHManualHostKeys"),
184
+ path=source,
185
+ _target=self.target,
186
+ _user=user_details.user if user_details else None,
187
+ )
188
+
189
+
190
+ def parse_host_user(host: str, user: str) -> tuple[str, str]:
191
+ """Parse host and user from PuTTY hostname component."""
192
+ if "@" in host:
193
+ parsed_user, parsed_host = host.split("@")
194
+ user = user or parsed_user
195
+ host = parsed_host
196
+
197
+ return host, user
198
+
199
+
200
+ def construct_public_key(key_type: str, iv: str) -> str:
201
+ """Returns OpenSSH format public key calculated from PuTTY SshHostKeys format.
202
+
203
+ PuTTY stores raw public key components instead of OpenSSH-formatted public keys
204
+ or fingerprints. With RSA public keys the exponent and modulus are stored.
205
+ With ECC keys the x and y prime coordinates are stored together with the curve type.
206
+
207
+ Currently supports ``ssh-ed25519``, ``ecdsa-sha2-nistp256`` and ``rsa2`` key types.
208
+
209
+ NOTE:
210
+ - Sha256 fingerprints of the reconstructed public keys are currently not generated.
211
+ - More key types could be supported in the future.
212
+
213
+ Resources:
214
+ - https://github.com/github/putty/blob/master/contrib/kh2reg.py
215
+ - https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html
216
+ - https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html
217
+ - https://github.com/mkorthof/reg2kh
218
+ """
219
+
220
+ if key_type == "ssh-ed25519":
221
+ x, y = iv.split(",")
222
+ key = ECC.construct(curve="ed25519", point_x=int(x, 16), point_y=int(y, 16))
223
+ return key.public_key().export_key(format="OpenSSH").split()[-1]
224
+
225
+ if key_type == "ecdsa-sha2-nistp256":
226
+ _, x, y = iv.split(",")
227
+ key = ECC.construct(curve="NIST P-256", point_x=int(x, 16), point_y=int(y, 16))
228
+ return key.public_key().export_key(format="OpenSSH").split()[-1]
229
+
230
+ if key_type == "rsa2":
231
+ exponent, modulus = iv.split(",")
232
+ key = RSA.construct((int(modulus, 16), int(exponent, 16)))
233
+ return key.public_key().export_key(format="OpenSSH").decode("utf-8").split()[-1]
234
+
235
+ log.warning("Could not reconstruct public key: type %s not implemented.", key_type)
236
+ return iv
@@ -0,0 +1,58 @@
1
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
2
+ from dissect.target.helpers.record import create_extended_descriptor
3
+ from dissect.target.plugin import NamespacePlugin
4
+
5
+ OpenSSHUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
6
+
7
+ COMMON_ELLEMENTS = [
8
+ ("string", "key_type"),
9
+ ("string", "comment"),
10
+ ("path", "path"),
11
+ ]
12
+
13
+ AuthorizedKeysRecord = OpenSSHUserRecordDescriptor(
14
+ "application/openssh/authorized_keys",
15
+ [
16
+ *COMMON_ELLEMENTS,
17
+ ("string", "public_key"),
18
+ ("string", "options"),
19
+ ],
20
+ )
21
+
22
+
23
+ KnownHostRecord = OpenSSHUserRecordDescriptor(
24
+ "application/openssh/known_host",
25
+ [
26
+ ("datetime", "mtime_ts"),
27
+ *COMMON_ELLEMENTS,
28
+ ("string", "host"),
29
+ ("varint", "port"),
30
+ ("string", "public_key"),
31
+ ("string", "marker"),
32
+ ],
33
+ )
34
+
35
+
36
+ PrivateKeyRecord = OpenSSHUserRecordDescriptor(
37
+ "application/openssh/private_key",
38
+ [
39
+ ("datetime", "mtime_ts"),
40
+ *COMMON_ELLEMENTS,
41
+ ("string", "key_format"),
42
+ ("string", "public_key"),
43
+ ("boolean", "encrypted"),
44
+ ],
45
+ )
46
+
47
+ PublicKeyRecord = OpenSSHUserRecordDescriptor(
48
+ "application/openssh/public_key",
49
+ [
50
+ ("datetime", "mtime_ts"),
51
+ *COMMON_ELLEMENTS,
52
+ ("string", "public_key"),
53
+ ],
54
+ )
55
+
56
+
57
+ class SSHPlugin(NamespacePlugin):
58
+ __namespace__ = "ssh"
@@ -166,6 +166,8 @@ def _parse_config(content: str) -> dict[str, Union[str, list[str]]]:
166
166
  """Parses Openvpn config files"""
167
167
  lines = content.splitlines()
168
168
  res = {}
169
+ boolean_fields = OpenVPNServer.getfields("boolean") + OpenVPNClient.getfields("boolean")
170
+ boolean_field_names = set(field.name for field in boolean_fields)
169
171
 
170
172
  for line in lines:
171
173
  # As per man (8) openvpn, lines starting with ; or # are comments
@@ -174,6 +176,10 @@ def _parse_config(content: str) -> dict[str, Union[str, list[str]]]:
174
176
  value = value[0] if value else ""
175
177
  # This removes all text after the first comment
176
178
  value = CONFIG_COMMENT_SPLIT_REGEX.split(value, 1)[0].strip()
179
+
180
+ if key in boolean_field_names and value == "":
181
+ value = True
182
+
177
183
  if old_value := res.get(key):
178
184
  if not isinstance(old_value, list):
179
185
  old_value = [old_value]