1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! List of RustHound add-on modules
pub mod adcs;
pub mod gpo;
pub mod resolver;
pub mod session;
pub mod localgroup;
pub mod webclient;
use std::error::Error;
use futures::future;
use crate::api::ADResults;
use crate::args::{CollectionMethod, Options};
use crate::modules::adcs::probe_enterpriseca_esc8;
use crate::modules::gpo::sysvol::collect_sysvol_targets;
/// Function to run all modules requested
pub async fn run_modules(
common_args: &Options,
ad: &mut ADResults
) -> Result<(), Box<dyn Error>> {
let cert_auth = common_args.uses_cert();
if cert_auth {
log::warn!("Certificate authentication in use: skipping SMB-based modules \
(sessions and GPO/SYSVOL) no SMB credentials available.");
}
// [MODULE - RESOLVER] Resolve FQDN to IP address.
if common_args.fqdn_resolver {
resolver::resolv::resolving_all_fqdn(
common_args.dns_tcp,
&common_args.name_server,
&mut ad.mappings.fqdn_ip,
&ad.computers,
)
.await;
}
// [MODULE - SESSIONS] Just does user session collection
// <https://github.com/g0h4n/HasSession-rs>
//
// - SRVSVC / NetrSessionEnum - inbound SMB sessions (client IP + username).
// - WKSSVC / NetrWkstaUserEnum - users with an active logon context on the machine.
// - WINREG / HKEY_USERS - SIDs of loaded profile hives (= logged-on users).
if common_args.collection_method.does_session() && !cert_auth {
session::run(common_args, &ad.users, &mut ad.computers).await?;
}
// [MODULE - ESC8] Web enrollment probe on all enterprise CAs.
// Skipped in DCOnly mode (no direct machine connections allowed).
// Each probe's blocking reqwest client runs via tokio::task::spawn_blocking
// on Tokio's dedicated blocking thread pool. Building/dropping a
// reqwest::blocking::Client (which owns its own nested Tokio runtime)
// panics on drop if done on a thread already inside an async context; the
// previous rayon par_iter_mut could run the closure on the calling Tokio
// worker thread itself (e.g. with a single CA), which was the crash.
if !matches!(common_args.collection_method, CollectionMethod::DCOnly)
&& !matches!(common_args.collection_method, CollectionMethod::LdapOnly)
&& !ad.enterprisecas.is_empty()
{
log::info!(
"Starting ESC8 web enrollment probe on {} CA(s)...",
ad.enterprisecas.len()
);
let targets: Vec<(String, String)> = ad
.enterprisecas
.iter()
.map(|ca| (ca.dns_host().to_string(), ca.caname().to_string()))
.collect();
let probes = future::join_all(targets.into_iter().map(|(host, ca_name)| {
tokio::task::spawn_blocking(move || probe_enterpriseca_esc8(&host, &ca_name))
}))
.await;
for (ca, probe) in ad.enterprisecas.iter_mut().zip(probes) {
match probe {
Ok(esc8) => ca.apply_esc8(esc8.http_enrollment_endpoints),
Err(join_err) => log::warn!(
"[adcs] ESC8 probe task for {} did not complete ({}), skipping",
ca.dns_host(),
join_err
),
}
}
}
// [MODULE - GPO SYSVOL] read GptTmpl.inf / Groups.xml off the DC SYSVOL share.
// <#47 Privileges> and <#56 LocalGroup>. DC-side I/O, so it also runs in DCOnly.
if common_args.collection_method.does_gpo() && !cert_auth {
let computer_scope = gpo::sysvol::ComputerGpoScope::from_gpos(&ad.gpos);
let sysvol = match collect_sysvol_targets(common_args, &computer_scope).await {
Ok(v) => v,
Err(e) => {
log::warn!("[gpo] SYSVOL collection failed: {e}");
Vec::new()
}
};
if !sysvol.is_empty() {
log::info!(
"[gpo] mapping {} GPO(s) to GPOChanges / UserRights",
sysvol.len()
);
gpo::apply_gpo(
&mut ad.ous,
&mut ad.domains,
&ad.users,
&ad.groups,
&mut ad.computers,
&sysvol,
&ad.mappings.dn_sid,
&common_args.domain
);
}
}
// [MODULE - LOCAL GROUPS] BUILTIN alias membership over SAMR (issue #69)
// <https://github.com/g0h4n/LocalGroups-rs>
//
// SAMR / SamrOpenAlias + SamrGetMembersInAlias -> Computer.LocalGroups
// RID 544/555/562/580 -> AdminTo / CanRDP / ExecuteDCOM / CanPSRemote
//
// Auth reuses SmbAuth (password / hash / ticket); complements #56 (GPO).
if common_args.collection_method.does_local_group() && !cert_auth {
localgroup::run(common_args, &ad.users, &mut ad.computers, &ad.mappings.sid_type).await?;
}
// [MODULE - IS WEBCLIENT RUNNING] CHeck if WebDAV web client is running on servers/computers (issue #72)
// <https://github.com/g0h4n/IsWebClientRunning-rs>
if common_args.collection_method.does_web_client() && !cert_auth {
webclient::run(common_args, &mut ad.computers).await?;
}
// Other modules need to be add here...
Ok(())
}