rusthound_ce/objects/
user.rs

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