Skip to content

Commit 9eab997

Browse files
committed
v2.4.8, fix false-positive GenericAll on Exchange objects (#33) and groundwork for dynamic schema GUID map (#35)
1 parent b6f809f commit 9eab997

25 files changed

Lines changed: 275 additions & 310 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 2.4.8 - 2026-06-22
4+
5+
Fix issue [#33](https://github.com/g0h4n/RustHound-CE/issues/33) regarding false-positive `GenericAll` edges on Exchange-enabled objects. [acl.rs](https://github.com/g0h4n/RustHound-CE/blob/main/src/enums/acl.rs)
6+
7+
`ACCESS_ALLOWED_OBJECT_ACE` entries scoped to specific attribute GUIDs (e.g. Exchange-related properties) were incorrectly promoted to `GenericAll` on the whole object. The fix adds an `ace_applies()` check on `ACE_OBJECT_TYPE_PRESENT` to ensure object-scoped ACEs are not over-classified.
8+
9+
Groundwork for issue [#35](https://github.com/g0h4n/RustHound-CE/issues/35): added dynamic schema GUID collection by parsing `attributeSchema` objects from the `CN=Schema` naming context (cf [schema.rs](https://github.com/g0h4n/RustHound-CE/blob/main/src/objects/schema.rs)). The collected `name:schemaIDGUID` mappings are stored in a `schema_guid_map` and will replace the static `OBJECTTYPE_GUID_HASHMAP` for ACE resolution in a future release.
10+
311
## 2.4.7 - 2026-01-09
412

513
Add [obfstr](https://docs.rs/obfstr/latest/obfstr/) for string obfuscation support, required by other projects.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ keywords = ["bloodhound", "pentest", "ldap", "tokio", "async"]
66
repository = "https://github.com/g0h4n/RustHound-CE"
77
homepage = "https://github.com/g0h4n/RustHound-CE"
88
documentation = "https://docs.rs/rusthound-ce/"
9-
version = "2.4.7"
9+
version = "2.4.8"
1010
edition = "2021"
1111
license = "MIT"
1212
readme = "README.md"

src/api.rs

Lines changed: 121 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@ use indicatif::ProgressBar;
44
use ldap3::SearchEntry;
55

66
use crate::{
7-
args::Options, banner::progress_bar, enums::{get_type, Type, PARSER_MOD_RE1, PARSER_MOD_RE2}, json::{
8-
checker::check_all_result,
9-
},
7+
args::Options, banner::progress_bar, enums::{PARSER_MOD_RE1, PARSER_MOD_RE2, Type, get_type}, json::checker::check_all_result,
108
objects::{
11-
aiaca::AIACA, certtemplate::CertTemplate, common::parse_unknown, computer::Computer, container::Container, domain::Domain, enterpriseca::EnterpriseCA, fsp::Fsp, gpo::Gpo, group::Group, inssuancepolicie::IssuancePolicie, ntauthstore::NtAuthStore, ou::Ou, rootca::RootCA, trust::Trust, user::User
9+
aiaca::AIACA,
10+
certtemplate::CertTemplate,
11+
common::parse_unknown,
12+
computer::Computer,
13+
container::Container,
14+
domain::Domain,
15+
enterpriseca::EnterpriseCA,
16+
fsp::Fsp,
17+
gpo::Gpo,
18+
group::Group,
19+
inssuancepolicie::IssuancePolicie,
20+
ntauthstore::NtAuthStore,
21+
ou::Ou,
22+
rootca::RootCA,
23+
trust::Trust,
24+
user::User,
25+
schema::Schema,
1226
},
13-
storage::{EntrySource}
27+
storage::EntrySource
1428
};
1529

1630
#[derive(Default)]
@@ -30,7 +44,6 @@ pub struct ADResults {
3044
pub enterprisecas: Vec<EnterpriseCA>,
3145
pub certtemplates: Vec<CertTemplate>,
3246
pub issuancepolicies: Vec<IssuancePolicie>,
33-
3447
pub mappings: DomainMappings,
3548
}
3649

@@ -44,6 +57,8 @@ pub struct DomainMappings {
4457
pub fqdn_sid: HashMap<String, String>,
4558
/// fqdn to an ip address
4659
pub fqdn_ip: HashMap<String, String>,
60+
/// schema guid map
61+
pub schema_guid_map: HashMap<String, String>,
4762
}
4863

4964
impl ADResults {
@@ -117,12 +132,25 @@ pub fn parse_result_type_from_source(
117132
match atype {
118133
Type::User => {
119134
let mut user: User = User::new();
120-
user.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
135+
user.parse(
136+
entry,
137+
domain, dn_sid,
138+
sid_type,
139+
&domain_sid,
140+
&results.mappings.schema_guid_map
141+
)?;
121142
results.users.push(user);
122143
}
123144
Type::Group => {
124145
let mut group = Group::new();
125-
group.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
146+
group.parse(
147+
entry,
148+
domain,
149+
dn_sid,
150+
sid_type,
151+
&domain_sid,
152+
&results.mappings.schema_guid_map
153+
)?;
126154
results.groups.push(group);
127155
}
128156
Type::Computer => {
@@ -135,18 +163,32 @@ pub fn parse_result_type_from_source(
135163
fqdn_sid,
136164
fqdn_ip,
137165
&domain_sid,
166+
&results.mappings.schema_guid_map
138167
)?;
139168
results.computers.push(computer);
140169
}
141170
Type::Ou => {
142171
let mut ou = Ou::new();
143-
ou.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
172+
ou.parse(
173+
entry,
174+
domain,
175+
dn_sid,
176+
sid_type,
177+
&domain_sid,
178+
&results.mappings.schema_guid_map
179+
)?;
144180
results.ous.push(ou);
145181
}
146182
Type::Domain => {
147183
let mut domain_object = Domain::new();
148184
let domain_sid_from_domain =
149-
domain_object.parse(entry, domain, dn_sid, sid_type)?;
185+
domain_object.parse(
186+
entry,
187+
domain,
188+
dn_sid,
189+
sid_type,
190+
&results.mappings.schema_guid_map
191+
)?;
150192
domain_sid = domain_sid_from_domain;
151193
// Only add domains with valid ObjectIdentifier (excludes DomainDnsZones, ForestDnsZones, etc.)
152194
if !domain_object.object_identifier().is_empty() {
@@ -155,7 +197,13 @@ pub fn parse_result_type_from_source(
155197
}
156198
Type::Gpo => {
157199
let mut gpo = Gpo::new();
158-
gpo.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
200+
gpo.parse(
201+
entry,
202+
domain, dn_sid,
203+
sid_type,
204+
&domain_sid,
205+
&results.mappings.schema_guid_map
206+
)?;
159207
results.gpos.push(gpo);
160208
}
161209
Type::ForeignSecurityPrincipal => {
@@ -173,7 +221,14 @@ pub fn parse_result_type_from_source(
173221

174222
//trace!("Container: {}",&entry.dn.to_uppercase());
175223
let mut container = Container::new();
176-
container.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
224+
container.parse(
225+
entry,
226+
domain,
227+
dn_sid,
228+
sid_type,
229+
&domain_sid,
230+
&results.mappings.schema_guid_map
231+
)?;
177232
results.containers.push(container);
178233
}
179234
Type::Trust => {
@@ -183,34 +238,82 @@ pub fn parse_result_type_from_source(
183238
}
184239
Type::NtAutStore => {
185240
let mut nt_auth_store = NtAuthStore::new();
186-
nt_auth_store.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
241+
nt_auth_store.parse(
242+
entry,
243+
domain,
244+
dn_sid,
245+
sid_type,
246+
&domain_sid,
247+
&results.mappings.schema_guid_map
248+
)?;
187249
results.ntauthstores.push(nt_auth_store);
188250
}
189251
Type::AIACA => {
190252
let mut aiaca = AIACA::new();
191-
aiaca.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
253+
aiaca.parse(
254+
entry,
255+
domain, dn_sid,
256+
sid_type,
257+
&domain_sid,
258+
&results.mappings.schema_guid_map
259+
)?;
192260
results.aiacas.push(aiaca);
193261
}
194262
Type::RootCA => {
195263
let mut root_ca = RootCA::new();
196-
root_ca.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
264+
root_ca.parse(
265+
entry,
266+
domain,
267+
dn_sid,
268+
sid_type,
269+
&domain_sid,
270+
&results.mappings.schema_guid_map
271+
)?;
197272
results.rootcas.push(root_ca);
198273
}
199274
Type::EnterpriseCA => {
200275
let mut enterprise_ca = EnterpriseCA::new();
201-
enterprise_ca.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
276+
enterprise_ca.parse(
277+
entry,
278+
domain,
279+
dn_sid,
280+
sid_type,
281+
&domain_sid,
282+
&results.mappings.schema_guid_map
283+
)?;
202284
results.enterprisecas.push(enterprise_ca);
203285
}
204286
Type::CertTemplate => {
205287
let mut cert_template = CertTemplate::new();
206-
cert_template.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
288+
cert_template.parse(
289+
entry,
290+
domain,
291+
dn_sid,
292+
sid_type,
293+
&domain_sid,
294+
&results.mappings.schema_guid_map
295+
)?;
207296
results.certtemplates.push(cert_template);
208297
}
209298
Type::IssuancePolicie => {
210299
let mut issuance_policie = IssuancePolicie::new();
211-
issuance_policie.parse(entry, domain, dn_sid, sid_type, &domain_sid)?;
300+
issuance_policie.parse(
301+
entry,
302+
domain,
303+
dn_sid,
304+
sid_type,
305+
&domain_sid,
306+
&results.mappings.schema_guid_map
307+
)?;
212308
results.issuancepolicies.push(issuance_policie);
213309
}
310+
Type::Schema => {
311+
let schema = Schema::new();
312+
schema.parse(
313+
entry,
314+
&mut results.mappings.schema_guid_map,
315+
)?;
316+
}
214317
Type::Unknown => {
215318
let _unknown = parse_unknown(entry, domain);
216319
}

0 commit comments

Comments
 (0)