rusthound_ce/objects/
user.rs

1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3use ldap3::SearchEntry;
4use log::{debug, error, trace};
5use std::collections::HashMap;
6use x509_parser::prelude::*;
7use std::error::Error;
8
9use crate::enums::regex::{OBJECT_SID_RE1, SID_PART1_RE1};
10use crate::objects::common::{LdapObject, AceTemplate, SPNTarget, Link, Member};
11use crate::utils::date::{convert_timestamp, string_to_epoch};
12use crate::utils::crypto::convert_encryption_types;
13use crate::enums::acl::{parse_ntsecuritydescriptor, parse_gmsa};
14use crate::enums::secdesc::LdapSid;
15use crate::enums::sid::sid_maker;
16use crate::enums::spntasks::check_spn;
17use crate::enums::uacflags::get_flag;
18
19/// User structure
20#[derive(Debug, Clone, Deserialize, Serialize, Default)]
21pub struct User {
22    #[serde(rename ="ObjectIdentifier")]
23    object_identifier: String,
24    #[serde(rename ="IsDeleted")]
25    is_deleted: bool,
26    #[serde(rename ="IsACLProtected")]
27    is_acl_protected: bool,
28    #[serde(rename ="Properties")]
29    properties: UserProperties,
30    #[serde(rename ="PrimaryGroupSID")]
31    primary_group_sid: String,
32    #[serde(rename ="SPNTargets")]
33    spn_targets: Vec<SPNTarget>,
34    #[serde(rename ="UnconstrainedDelegation")]
35    unconstrained_delegation: bool,
36    #[serde(rename ="DomainSID")]
37    domain_sid: String,
38    #[serde(rename ="Aces")]
39    aces: Vec<AceTemplate>,
40    #[serde(rename ="AllowedToDelegate")]
41    allowed_to_delegate: Vec<Member>,
42    #[serde(rename ="HasSIDHistory")]
43    has_sid_history: Vec<String>,
44    #[serde(rename ="ContainedBy")]
45    contained_by: Option<Member>,
46}
47
48impl User {
49    // New User
50    pub fn new() -> Self { 
51        Self { ..Default::default()} 
52    }
53
54    // Immutable access.
55    pub fn properties(&self) -> &UserProperties {
56        &self.properties
57    }
58    pub fn aces(&self) -> &Vec<AceTemplate> {
59        &self.aces
60    }
61
62    // Mutable access.
63    pub fn properties_mut(&mut self) -> &mut UserProperties {
64        &mut self.properties
65    }
66    pub fn aces_mut(&mut self) -> &mut Vec<AceTemplate> {
67        &mut self.aces
68    }
69    pub fn object_identifier_mut(&mut self) -> &mut String {
70        &mut self.object_identifier
71    }
72
73    /// Function to parse and replace value for user object.
74    /// <https://bloodhound.readthedocs.io/en/latest/further-reading/json.html#users>
75    pub fn parse(
76        &mut self,
77        result: SearchEntry,
78        domain: &str,
79        dn_sid: &mut HashMap<String, String>,
80        sid_type: &mut HashMap<String, String>,
81        domain_sid: &str
82    ) -> Result<(), Box<dyn Error>> {
83        let result_dn: String = result.dn.to_uppercase();
84        let result_attrs: HashMap<String, Vec<String>> = result.attrs;
85        let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
86
87        // Debug for current object
88        debug!("Parse user: {result_dn}");
89
90        // Trace all result attributes
91        for (key, value) in &result_attrs {
92            trace!("  {key:?}:{value:?}");
93        }
94        // Trace all bin result attributes
95        for (key, value) in &result_bin {
96            trace!("  {key:?}:{value:?}");
97        }
98
99        // Change all values...
100        self.properties.domain = domain.to_uppercase();
101        self.properties.distinguishedname = result_dn;
102        self.properties.enabled = true;
103        self.domain_sid = domain_sid.to_string();
104
105        // With a check
106        let mut group_id: String ="".to_owned();
107        for (key, value) in &result_attrs {
108            match key.as_str() {
109                "sAMAccountName" => {
110                    let name = &value[0];
111                    let email = format!("{}@{}",name.to_owned(),domain);
112                    self.properties.name = email.to_uppercase();
113                    self.properties.samaccountname = name.to_string();
114                }
115                "description" => {
116                    self.properties.description = Some(value[0].to_owned());
117                }
118                "mail" => {
119                    self.properties.email = value[0].to_owned();
120                }
121                "title" => {
122                    self.properties.title = value[0].to_owned();
123                }
124                "userPassword" => {
125                    self.properties.userpassword = value[0].to_owned();
126                }
127                "unixUserPassword" => {
128                    self.properties.unixpassword = value[0].to_owned();
129                }
130                "unicodepwd" => {
131                    self.properties.unicodepassword = value[0].to_owned();
132                }
133                "sfupassword" => {
134                    //self.properties.sfupassword = value[0].to_owned();
135                }
136                "displayName" => {
137                    self.properties.displayname = value[0].to_owned();
138                }
139                "adminCount" => {
140                    let isadmin = &value[0];
141                    let mut admincount = false;
142                    if isadmin =="1" {
143                        admincount = true;
144                    }
145                    self.properties.admincount = admincount;
146                }
147                "homeDirectory" => {
148                    self.properties.homedirectory = value[0].to_owned();
149                }
150                "scriptpath" => {
151                    self.properties.logonscript = value[0].to_owned();
152                }
153                "userAccountControl" => {
154                    let uac = &value[0].parse::<u32>().unwrap_or(0);
155                    self.properties.useraccountcontrol = *uac;
156                    let uac_flags = get_flag(*uac);
157                    //trace!("UAC : {:?}",uac_flags);
158                    for flag in uac_flags {
159                        if flag.contains("AccountDisable") {
160                            self.properties.enabled = false;
161                        };
162                        //if flag.contains("Lockout") { let enabled = true; user_json["Properties"]["enabled"] = enabled;};
163                        if flag.contains("PasswordNotRequired") {
164                            self.properties.passwordnotreqd = true;
165                        };
166                        if flag.contains("DontExpirePassword") {
167                            self.properties.pwdneverexpires = true;
168                        };
169                        if flag.contains("DontReqPreauth") {
170                            self.properties.dontreqpreauth = true;
171                        };
172                        // KUD (Kerberos Unconstrained Delegation)
173                        if flag.contains("TrustedForDelegation") {
174                            self.properties.unconstraineddelegation = true;
175                            self.unconstrained_delegation = true;
176                        };
177                        if flag.contains("NotDelegated") {
178                            self.properties.sensitive = true;
179                        };
180                        //if flag.contains("PasswordExpired") { let password_expired = true; user_json["Properties"]["pwdneverexpires"] = password_expired;};
181                        if flag.contains("TrustedToAuthForDelegation") {
182                            self.properties.trustedtoauth = true;
183                        };
184                    }
185                }
186                "msDS-AllowedToDelegateTo"  => {
187                    // KCD (Kerberos Constrained Delegation)
188                    //trace!(" AllowToDelegateTo: {:?}",&value);
189                    // AllowedToDelegate
190                    let mut vec_members2: Vec<Member> = Vec::new();
191                    for objet in value {
192                        let mut member_allowed_to_delegate = Member::new();
193                        let split = objet.split("/");
194                        let fqdn = split.collect::<Vec<&str>>()[1];
195                        let mut checker = false;
196                        for member in &vec_members2 {
197                          if member.object_identifier().contains(fqdn.to_uppercase().as_str()) {
198                                 checker = true;
199                         }
200                       }
201                        if !checker {
202                          *member_allowed_to_delegate.object_identifier_mut() = fqdn.to_uppercase().to_owned().to_uppercase();
203                          *member_allowed_to_delegate.object_type_mut() ="Computer".to_owned();
204                          vec_members2.push(member_allowed_to_delegate.to_owned()); 
205                       }
206                  }
207                    // *properties.allowedtodelegate = vec_members2.to_owned();
208                    self.allowed_to_delegate = vec_members2;
209                }
210                "lastLogon" => {
211                    let lastlogon = &value[0].parse::<i64>().unwrap_or(0);
212                    if lastlogon.is_positive() {
213                        let epoch = convert_timestamp(*lastlogon);
214                        self.properties.lastlogon = epoch;
215                    }
216                }
217                "lastLogonTimestamp" => {
218                    let lastlogontimestamp = &value[0].parse::<i64>().unwrap_or(0);
219                    if lastlogontimestamp.is_positive() {
220                        let epoch = convert_timestamp(*lastlogontimestamp);
221                        self.properties.lastlogontimestamp = epoch;
222                    }
223                }
224                "pwdLastSet" => {
225                    let pwdlastset = &value[0].parse::<i64>().unwrap_or(0);
226                    if pwdlastset.is_positive() {
227                        let epoch = convert_timestamp(*pwdlastset);
228                        self.properties.pwdlastset = epoch;
229                    }
230                }
231                "whenCreated" => {
232                    let epoch = string_to_epoch(&value[0])?;
233                    if epoch.is_positive() {
234                        self.properties.whencreated = epoch;
235                    }
236                }
237                "servicePrincipalName" => {
238                    // SPNTargets values
239                    let mut targets: Vec<SPNTarget> = Vec::new();
240                    let mut result: Vec<String> = Vec::new();
241                    let mut added: bool = false;
242                    for v in value {
243                        result.push(v.to_owned());
244                        // Checking the spn for service-account (mssql?)
245                        let _target = match check_spn(v).to_owned() {
246                            Some(_target) => {
247                                if !added {
248                                   targets.push(_target.to_owned());
249                                   added = true;
250                                }
251                            },
252                            None => {}
253                        };
254                    }
255                    self.properties.serviceprincipalnames = result;
256                    self.properties.hasspn = true;
257                    self.spn_targets = targets;
258                }
259                "primaryGroupID" => {
260                    group_id = value[0].to_owned();
261                }
262                "IsDeleted" => {
263                    // OID to use: 1.2.840.113556.1.4.417
264                    // https://ldapwiki.com/wiki/IsDeleted
265                    //trace!("isDeleted: {:?}",&value[0]);
266                    self.is_deleted = true;
267                }
268                "msDS-SupportedEncryptionTypes" => {
269                    self.properties.supportedencryptiontypes = convert_encryption_types(value[0].parse::<i32>().unwrap_or(0));
270                }
271                 _ => {}
272            }
273        }
274
275        // For all, bins attributs
276        let mut sid: String = "".to_owned();
277        for (key, value) in &result_bin {
278            match key.as_str() {
279                "objectSid" => {
280                    sid = sid_maker(LdapSid::parse(&value[0]).unwrap().1, domain);
281                    self.object_identifier = sid.to_owned();
282
283                    for domain_sid in OBJECT_SID_RE1.captures_iter(&sid) {
284                        self.properties.domainsid = domain_sid[0].to_owned().to_string();
285                    }
286                }
287                "nTSecurityDescriptor" => {
288                    // nTSecurityDescriptor raw to string
289                    let relations_ace = parse_ntsecuritydescriptor(
290                        self,
291                        &value[0],
292                        "User",
293                        &result_attrs,
294                        &result_bin,
295                        domain,
296                    );
297                    self.aces_mut().extend(relations_ace);
298                }
299                "sIDHistory" => {
300                    // not tested! #tocheck
301                    //debug!("sIDHistory: {:?}",&value[0]);
302                    let mut list_sid_history: Vec<String> = Vec::new();
303                    for bsid in value {
304                        debug!("sIDHistory: {:?}", &bsid);
305                        list_sid_history.push(sid_maker(LdapSid::parse(bsid).unwrap().1, domain));
306                        // Todo function to add the sid history in user_json['HasSIDHistory']
307                    }
308                    self.properties.sidhistory = list_sid_history;
309                }
310                "msDS-GroupMSAMembership" => {
311                    // nTSecurityDescriptor raw to string
312                    let mut relations_ace = parse_ntsecuritydescriptor(
313                        self,
314                        &value[0],
315                        "User",
316                        &result_attrs,
317                        &result_bin,
318                        domain,
319                    );
320                    // Now add the new ACE wich who can read GMSA password
321                    // trace!("User ACES before GMSA: {:?}", self.aces());
322                    parse_gmsa(&mut relations_ace, self);
323                    // trace!("User ACES after GMSA: {:?}", self.aces());
324                }
325                "userCertificate" => {
326                    // <https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adls/d66d1662-0b4f-44ab-a4c8-e788f3ae39cf>
327                    // <https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html>
328                    let res = X509Certificate::from_der(&value[0]);
329                    match res {
330                        Ok((_rem, _cert)) => {},
331                        _ => error!("CA x509 certificate parsing failed: {:?}", res),
332                    }
333                }
334                _ => {}
335            }
336        }
337
338        // primaryGroupID if group_id is set
339        #[allow(irrefutable_let_patterns)]
340        if let id = group_id {
341            if let Some(part1) = SID_PART1_RE1.find(&sid) {
342                self.primary_group_sid = format!("{}{}", part1.as_str(), id);
343            } else {
344                eprintln!("[!] Regex did not match any part of the SID");
345            }
346        }
347
348        // Push DN and SID in HashMap
349        dn_sid.insert(
350            self.properties.distinguishedname.to_owned(),
351            self.object_identifier.to_owned(),
352        );
353        // Push DN and Type
354        sid_type.insert(
355            self.object_identifier.to_owned(),
356            "User".to_string(),
357        );
358
359        // Trace and return User struct
360        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
361        Ok(())
362    }
363}
364
365/// Function to change some values from LdapObject trait for User
366impl LdapObject for User {
367    // To JSON
368    fn to_json(&self) -> Value {
369        serde_json::to_value(self).unwrap()
370    }
371
372    // Get values
373    fn get_object_identifier(&self) -> &String {
374        &self.object_identifier
375    }
376    fn get_is_acl_protected(&self) -> &bool {
377        &self.is_acl_protected
378    }
379    fn get_aces(&self) -> &Vec<AceTemplate> {
380        &self.aces
381    }
382    fn get_spntargets(&self) -> &Vec<SPNTarget> {
383        &self.spn_targets
384    }
385    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
386        &self.allowed_to_delegate
387    }
388    fn get_links(&self) -> &Vec<Link> {
389        panic!("Not used by current object.");
390    }
391    fn get_contained_by(&self) -> &Option<Member> {
392        &self.contained_by
393    }
394    fn get_child_objects(&self) -> &Vec<Member> {
395        panic!("Not used by current object.");
396    }
397    fn get_haslaps(&self) -> &bool {
398        &false
399    }
400
401    // Get mutable values
402    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
403        &mut self.aces
404    }
405    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
406        &mut self.spn_targets
407    }
408    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
409        &mut self.allowed_to_delegate
410    }
411
412    // Edit values
413    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
414        self.is_acl_protected = is_acl_protected;
415        self.properties.isaclprotected = is_acl_protected;
416    }
417    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
418        self.aces = aces;
419    }
420    fn set_spntargets(&mut self, spn_targets: Vec<SPNTarget>) {
421        self.spn_targets = spn_targets;
422    }
423    fn set_allowed_to_delegate(&mut self, allowed_to_delegate: Vec<Member>) {
424        self.allowed_to_delegate = allowed_to_delegate;
425    }
426    fn set_links(&mut self, _links: Vec<Link>) {
427        // Not used by current object.
428    }
429    fn set_contained_by(&mut self, contained_by: Option<Member>) {
430        self.contained_by = contained_by;
431    }
432    fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
433        // Not used by current object.
434    }
435}
436
437/// User properties structure
438#[derive(Debug, Clone, Deserialize, Serialize, Default)]
439pub struct UserProperties {
440    domain: String,
441    name: String,
442    domainsid: String,
443    isaclprotected: bool,
444    distinguishedname: String,
445    highvalue: bool,
446    description: Option<String>,
447    whencreated: i64,
448    sensitive: bool,
449    dontreqpreauth: bool,
450    passwordnotreqd: bool,
451    unconstraineddelegation: bool,
452    pwdneverexpires: bool,
453    enabled: bool,
454    trustedtoauth: bool,
455    lastlogon: i64,
456    lastlogontimestamp: i64,
457    pwdlastset: i64,
458    serviceprincipalnames: Vec<String>,
459    hasspn: bool,
460    displayname: String,
461    email: String,
462    title: String,
463    homedirectory: String,
464    logonscript: String,
465    useraccountcontrol: u32,
466    samaccountname: String,
467    userpassword: String,
468    unixpassword: String,
469    unicodepassword: String,
470    sfupassword: String,
471    admincount: bool,
472    supportedencryptiontypes: Vec<String>,
473    sidhistory: Vec<String>,
474    allowedtodelegate: Vec<String>
475}
476
477impl UserProperties {
478    // Immutable access.
479    pub fn name(&self) -> &String {
480        &self.name
481    }
482    pub fn domainsid(&self) -> &String {
483        &self.domainsid
484    }
485    pub fn isaclprotected(&self) -> &bool {
486        &self.isaclprotected
487    }
488
489    // Mutable access.
490    pub fn name_mut(&mut self) -> &mut String {
491        &mut self.name
492    }
493    pub fn domainsid_mut(&mut self) -> &mut String {
494        &mut self.domainsid
495    }
496    pub fn isaclprotected_mut(&mut self) -> &mut bool {
497        &mut self.isaclprotected
498    }
499}