Skip to content

Commit 5299040

Browse files
committed
Refactor security descriptors
1 parent d9425fb commit 5299040

File tree

6 files changed

+472
-458
lines changed

6 files changed

+472
-458
lines changed

dissect/database/ese/ntds/c_sd.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from __future__ import annotations
2+
3+
from dissect.cstruct import cstruct
4+
5+
# Largely copied from dissect.ntfs
6+
sd_def = """
7+
flag SECURITY_DESCRIPTOR_CONTROL : WORD {
8+
SE_OWNER_DEFAULTED = 0x0001,
9+
SE_GROUP_DEFAULTED = 0x0002,
10+
SE_DACL_PRESENT = 0x0004,
11+
SE_DACL_DEFAULTED = 0x0008,
12+
SE_SACL_PRESENT = 0x0010,
13+
SE_SACL_DEFAULTED = 0x0020,
14+
SE_DACL_AUTO_INHERIT_REQ = 0x0100,
15+
SE_SACL_AUTO_INHERIT_REQ = 0x0200,
16+
SE_DACL_AUTO_INHERITED = 0x0400,
17+
SE_SACL_AUTO_INHERITED = 0x0800,
18+
SE_DACL_PROTECTED = 0x1000,
19+
SE_SACL_PROTECTED = 0x2000,
20+
SE_RM_CONTROL_VALID = 0x4000,
21+
SE_SELF_RELATIVE = 0x8000,
22+
};
23+
24+
flag ACCESS_MASK : DWORD {
25+
ADS_RIGHT_DS_CREATE_CHILD = 0x00000001,
26+
ADS_RIGHT_DS_DELETE_CHILD = 0x00000002,
27+
ADS_RIGHT_DS_LIST_CONTENTS = 0x00000004, // Undocumented?
28+
ADS_RIGHT_DS_SELF = 0x00000008,
29+
ADS_RIGHT_DS_READ_PROP = 0x00000010,
30+
ADS_RIGHT_DS_WRITE_PROP = 0x00000020,
31+
ADS_RIGHT_DS_CONTROL_ACCESS = 0x00000100,
32+
33+
DELETE = 0x00010000,
34+
READ_CONTROL = 0x00020000,
35+
WRITE_DACL = 0x00040000,
36+
WRITE_OWNER = 0x00080000,
37+
SYNCHRONIZE = 0x00100000,
38+
ACCESS_SYSTEM_SECURITY = 0x01000000,
39+
MAXIMUM_ALLOWED = 0x02000000,
40+
GENERIC_ALL = 0x10000000,
41+
GENERIC_EXECUTE = 0x20000000,
42+
GENERIC_WRITE = 0x40000000,
43+
GENERIC_READ = 0x80000000,
44+
};
45+
46+
enum ACE_TYPE : BYTE {
47+
ACCESS_ALLOWED = 0x00,
48+
ACCESS_DENIED = 0x01,
49+
SYSTEM_AUDIT = 0x02,
50+
SYSTEM_ALARM = 0x03,
51+
ACCESS_ALLOWED_COMPOUND = 0x04,
52+
ACCESS_ALLOWED_OBJECT = 0x05,
53+
ACCESS_DENIED_OBJECT = 0x06,
54+
SYSTEM_AUDIT_OBJECT = 0x07,
55+
SYSTEM_ALARM_OBJECT = 0x08,
56+
ACCESS_ALLOWED_CALLBACK = 0x09,
57+
ACCESS_DENIED_CALLBACK = 0x0A,
58+
ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B,
59+
ACCESS_DENIED_CALLBACK_OBJECT = 0x0C,
60+
SYSTEM_AUDIT_CALLBACK = 0x0D,
61+
SYSTEM_ALARM_CALLBACK = 0x0E,
62+
SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F,
63+
SYSTEM_ALARM_CALLBACK_OBJECT = 0x10,
64+
SYSTEM_MANDATORY_LABEL = 0x11,
65+
SYSTEM_RESOURCE_ATTRIBUTE = 0x12,
66+
SYSTEM_SCOPED_POLICY_ID = 0x13,
67+
SYSTEM_PROCESS_TRUST_LABEL = 0x14,
68+
SYSTEM_ACCESS_FILTER = 0x15,
69+
};
70+
71+
flag ACE_FLAGS : BYTE {
72+
OBJECT_INHERIT_ACE = 0x01,
73+
CONTAINER_INHERIT_ACE = 0x02,
74+
NO_PROPAGATE_INHERIT_ACE = 0x04,
75+
INHERIT_ONLY_ACE = 0x08,
76+
INHERITED_ACE = 0x10,
77+
SUCCESSFUL_ACCESS_ACE_FLAG = 0x40,
78+
FAILED_ACCESS_ACE_FLAG = 0x80,
79+
};
80+
81+
flag ACE_OBJECT_FLAGS : DWORD {
82+
ACE_OBJECT_TYPE_PRESENT = 0x01,
83+
ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x02,
84+
};
85+
86+
enum COMPOUND_ACE_TYPE : USHORT {
87+
COMPOUND_ACE_IMPERSONATION = 0x01,
88+
};
89+
90+
typedef struct _ACL {
91+
BYTE AclRevision;
92+
BYTE Sbz1;
93+
WORD AclSize;
94+
WORD AceCount;
95+
WORD Sbz2;
96+
} ACL;
97+
98+
typedef struct _ACE_HEADER {
99+
ACE_TYPE AceType;
100+
ACE_FLAGS AceFlags;
101+
WORD AceSize;
102+
} ACE_HEADER;
103+
104+
typedef struct _SECURITY_DESCRIPTOR_HEADER {
105+
ULONG HashId;
106+
ULONG SecurityId;
107+
ULONG64 Offset;
108+
ULONG Length;
109+
} SECURITY_DESCRIPTOR_HEADER;
110+
111+
typedef struct _SECURITY_DESCRIPTOR_RELATIVE {
112+
BYTE Revision;
113+
BYTE Sbz1;
114+
SECURITY_DESCRIPTOR_CONTROL Control;
115+
ULONG Owner;
116+
ULONG Group;
117+
ULONG Sacl;
118+
ULONG Dacl;
119+
} SECURITY_DESCRIPTOR_RELATIVE;
120+
"""
121+
c_sd = cstruct(sd_def)

dissect/database/ese/ntds/c_sd.pyi

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Generated by cstruct-stubgen
2+
from typing import BinaryIO, TypeAlias, overload
3+
4+
import dissect.cstruct as __cs__
5+
6+
class _c_sd(__cs__.cstruct):
7+
class SECURITY_DESCRIPTOR_CONTROL(__cs__.Flag):
8+
SE_OWNER_DEFAULTED = ...
9+
SE_GROUP_DEFAULTED = ...
10+
SE_DACL_PRESENT = ...
11+
SE_DACL_DEFAULTED = ...
12+
SE_SACL_PRESENT = ...
13+
SE_SACL_DEFAULTED = ...
14+
SE_DACL_AUTO_INHERIT_REQ = ...
15+
SE_SACL_AUTO_INHERIT_REQ = ...
16+
SE_DACL_AUTO_INHERITED = ...
17+
SE_SACL_AUTO_INHERITED = ...
18+
SE_DACL_PROTECTED = ...
19+
SE_SACL_PROTECTED = ...
20+
SE_RM_CONTROL_VALID = ...
21+
SE_SELF_RELATIVE = ...
22+
23+
class ACCESS_MASK(__cs__.Flag):
24+
ADS_RIGHT_DS_CREATE_CHILD = ...
25+
ADS_RIGHT_DS_DELETE_CHILD = ...
26+
ADS_RIGHT_DS_SELF = ...
27+
ADS_RIGHT_DS_READ_PROP = ...
28+
ADS_RIGHT_DS_WRITE_PROP = ...
29+
ADS_RIGHT_DS_CONTROL_ACCESS = ...
30+
DELETE = ...
31+
READ_CONTROL = ...
32+
WRITE_DACL = ...
33+
WRITE_OWNER = ...
34+
SYNCHRONIZE = ...
35+
ACCESS_SYSTEM_SECURITY = ...
36+
MAXIMUM_ALLOWED = ...
37+
GENERIC_ALL = ...
38+
GENERIC_EXECUTE = ...
39+
GENERIC_WRITE = ...
40+
GENERIC_READ = ...
41+
42+
class ACE_TYPE(__cs__.Enum):
43+
ACCESS_ALLOWED = ...
44+
ACCESS_DENIED = ...
45+
SYSTEM_AUDIT = ...
46+
SYSTEM_ALARM = ...
47+
ACCESS_ALLOWED_COMPOUND = ...
48+
ACCESS_ALLOWED_OBJECT = ...
49+
ACCESS_DENIED_OBJECT = ...
50+
SYSTEM_AUDIT_OBJECT = ...
51+
SYSTEM_ALARM_OBJECT = ...
52+
ACCESS_ALLOWED_CALLBACK = ...
53+
ACCESS_DENIED_CALLBACK = ...
54+
ACCESS_ALLOWED_CALLBACK_OBJECT = ...
55+
ACCESS_DENIED_CALLBACK_OBJECT = ...
56+
SYSTEM_AUDIT_CALLBACK = ...
57+
SYSTEM_ALARM_CALLBACK = ...
58+
SYSTEM_AUDIT_CALLBACK_OBJECT = ...
59+
SYSTEM_ALARM_CALLBACK_OBJECT = ...
60+
SYSTEM_MANDATORY_LABEL = ...
61+
SYSTEM_RESOURCE_ATTRIBUTE = ...
62+
SYSTEM_SCOPED_POLICY_ID = ...
63+
SYSTEM_PROCESS_TRUST_LABEL = ...
64+
SYSTEM_ACCESS_FILTER = ...
65+
66+
class ACE_FLAGS(__cs__.Flag):
67+
OBJECT_INHERIT_ACE = ...
68+
CONTAINER_INHERIT_ACE = ...
69+
NO_PROPAGATE_INHERIT_ACE = ...
70+
INHERIT_ONLY_ACE = ...
71+
INHERITED_ACE = ...
72+
SUCCESSFUL_ACCESS_ACE_FLAG = ...
73+
FAILED_ACCESS_ACE_FLAG = ...
74+
75+
class ACE_OBJECT_FLAGS(__cs__.Flag):
76+
ACE_OBJECT_TYPE_PRESENT = ...
77+
ACE_INHERITED_OBJECT_TYPE_PRESENT = ...
78+
79+
class COMPOUND_ACE_TYPE(__cs__.Enum):
80+
COMPOUND_ACE_IMPERSONATION = ...
81+
82+
class _ACL(__cs__.Structure):
83+
AclRevision: _c_sd.uint8
84+
Sbz1: _c_sd.uint8
85+
AclSize: _c_sd.uint16
86+
AceCount: _c_sd.uint16
87+
Sbz2: _c_sd.uint16
88+
@overload
89+
def __init__(
90+
self,
91+
AclRevision: _c_sd.uint8 | None = ...,
92+
Sbz1: _c_sd.uint8 | None = ...,
93+
AclSize: _c_sd.uint16 | None = ...,
94+
AceCount: _c_sd.uint16 | None = ...,
95+
Sbz2: _c_sd.uint16 | None = ...,
96+
): ...
97+
@overload
98+
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
99+
100+
ACL: TypeAlias = _ACL
101+
class _ACE_HEADER(__cs__.Structure):
102+
AceType: _c_sd.ACE_TYPE
103+
AceFlags: _c_sd.ACE_FLAGS
104+
AceSize: _c_sd.uint16
105+
@overload
106+
def __init__(
107+
self,
108+
AceType: _c_sd.ACE_TYPE | None = ...,
109+
AceFlags: _c_sd.ACE_FLAGS | None = ...,
110+
AceSize: _c_sd.uint16 | None = ...,
111+
): ...
112+
@overload
113+
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
114+
115+
ACE_HEADER: TypeAlias = _ACE_HEADER
116+
class _SECURITY_DESCRIPTOR_HEADER(__cs__.Structure):
117+
HashId: _c_sd.uint32
118+
SecurityId: _c_sd.uint32
119+
Offset: _c_sd.uint64
120+
Length: _c_sd.uint32
121+
@overload
122+
def __init__(
123+
self,
124+
HashId: _c_sd.uint32 | None = ...,
125+
SecurityId: _c_sd.uint32 | None = ...,
126+
Offset: _c_sd.uint64 | None = ...,
127+
Length: _c_sd.uint32 | None = ...,
128+
): ...
129+
@overload
130+
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
131+
132+
SECURITY_DESCRIPTOR_HEADER: TypeAlias = _SECURITY_DESCRIPTOR_HEADER
133+
class _SECURITY_DESCRIPTOR_RELATIVE(__cs__.Structure):
134+
Revision: _c_sd.uint8
135+
Sbz1: _c_sd.uint8
136+
Control: _c_sd.SECURITY_DESCRIPTOR_CONTROL
137+
Owner: _c_sd.uint32
138+
Group: _c_sd.uint32
139+
Sacl: _c_sd.uint32
140+
Dacl: _c_sd.uint32
141+
@overload
142+
def __init__(
143+
self,
144+
Revision: _c_sd.uint8 | None = ...,
145+
Sbz1: _c_sd.uint8 | None = ...,
146+
Control: _c_sd.SECURITY_DESCRIPTOR_CONTROL | None = ...,
147+
Owner: _c_sd.uint32 | None = ...,
148+
Group: _c_sd.uint32 | None = ...,
149+
Sacl: _c_sd.uint32 | None = ...,
150+
Dacl: _c_sd.uint32 | None = ...,
151+
): ...
152+
@overload
153+
def __init__(self, fh: bytes | memoryview | bytearray | BinaryIO, /): ...
154+
155+
SECURITY_DESCRIPTOR_RELATIVE: TypeAlias = _SECURITY_DESCRIPTOR_RELATIVE
156+
157+
# Technically `c_sd` is an instance of `_c_sd`, but then we can't use it in type hints
158+
c_sd: TypeAlias = _c_sd

dissect/database/ese/ntds/database.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def __init__(self, db: Database):
159159
self.db = db
160160
self.table = self.db.ese.table("sd_table")
161161

162-
def dacl(self, id: int) -> ACL | None:
162+
def sd(self, id: int) -> ACL | None:
163163
"""Get the Discretionary Access Control List (DACL), if available.
164164
165165
Args:
@@ -175,5 +175,4 @@ def dacl(self, id: int) -> ACL | None:
175175
if (value := record.get("sd_value")) is None:
176176
return None
177177

178-
sd = SecurityDescriptor(BytesIO(value))
179-
return sd.dacl
178+
return SecurityDescriptor(BytesIO(value))

dissect/database/ese/ntds/object.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from functools import cached_property
34
from typing import TYPE_CHECKING, Any, ClassVar
45

56
from dissect.database.ese.ntds.schema import FIXED_COLUMN_MAP
@@ -9,7 +10,7 @@
910
from collections.abc import Iterator
1011

1112
from dissect.database.ese.ntds.database import Database
12-
from dissect.database.ese.ntds.sd import ACL
13+
from dissect.database.ese.ntds.sd import ACL, SecurityDescriptor
1314
from dissect.database.ese.record import Record
1415

1516

@@ -32,7 +33,7 @@ def __init_subclass__(cls):
3233
cls.__known_classes__[cls.__object_class__] = cls
3334

3435
def __repr__(self) -> str:
35-
return f"<Object name={self.name} objectCategory={self.objectCategory} objectClass={self.objectClass}>"
36+
return f"<Object name={self.name!r} objectCategory={self.objectCategory} objectClass={self.objectClass}>"
3637

3738
def __getattr__(self, name: str) -> Any:
3839
return self.get(name)
@@ -77,6 +78,11 @@ def as_dict(self) -> dict[str, Any]:
7778

7879
return result
7980

81+
@property
82+
def sid(self) -> str | None:
83+
"""Return the object's Security Identifier (SID)."""
84+
return self.get("objectSid")
85+
8086
@property
8187
def distinguishedName(self) -> str | None:
8288
"""Return the fully qualified Distinguished Name (DN) for this object."""
@@ -86,15 +92,25 @@ def distinguishedName(self) -> str | None:
8692

8793
DN = distinguishedName
8894

95+
@cached_property
96+
def sd(self) -> SecurityDescriptor | None:
97+
"""Return the Security Descriptor for this object."""
98+
if (sd_id := self.get("nTSecurityDescriptor")) is not None:
99+
return self.db.sd.sd(sd_id)
100+
return None
101+
89102
@property
90-
def dacl(self) -> ACL | None:
91-
"""Get the Discretionary Access Control List (DACL) for this object.
103+
def sacl(self) -> ACL | None:
104+
"""Return the System Access Control List (SACL) for this object."""
105+
if (sd := self.sd) is not None:
106+
return sd.sacl
107+
return None
92108

93-
Returns:
94-
The ACL object containing access control entries.
95-
"""
96-
if (sd_id := self.get("nTSecurityDescriptor")) is not None:
97-
return self.db.sd.dacl(sd_id)
109+
@property
110+
def dacl(self) -> ACL | None:
111+
"""Return the Discretionary Access Control List (DACL) for this object."""
112+
if (sd := self.sd) is not None:
113+
return sd.dacl
98114
return None
99115

100116

@@ -104,7 +120,7 @@ class Group(Object):
104120
__object_class__ = "group"
105121

106122
def __repr__(self) -> str:
107-
return f"<Group name={self.sAMAccountName}>"
123+
return f"<Group name={self.sAMAccountName!r}>"
108124

109125
def members(self) -> Iterator[User]:
110126
"""Yield all members of this group."""
@@ -128,7 +144,7 @@ class Server(Object):
128144
__object_class__ = "server"
129145

130146
def __repr__(self) -> str:
131-
return f"<Server name={self.name}>"
147+
return f"<Server name={self.name!r}>"
132148

133149

134150
class User(Object):
@@ -138,7 +154,7 @@ class User(Object):
138154

139155
def __repr__(self) -> str:
140156
return (
141-
f"<User name={self.name} sAMAccountName={self.sAMAccountName} "
157+
f"<User name={self.name!r} sAMAccountName={self.sAMAccountName!r} "
142158
f"is_machine_account={self.is_machine_account()}>"
143159
)
144160

0 commit comments

Comments
 (0)