Skip to main content

rusthound_ce/modules/
mod.rs

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