1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3use colored::Colorize;
4use ldap3::SearchEntry;
5use log::{info, debug, trace};
6use std::collections::HashMap;
7use std::error::Error;
8
9use crate::enums::{OBJECT_SID_RE1, SID_PART1_RE1, decode_guid_le};
10use crate::objects::common::{LdapObject, Session, AceTemplate, Member, SPNTarget, LocalGroup, Link, DCRegistryData, WebClientRunning};
11use crate::utils::date::{convert_timestamp,string_to_epoch};
12use crate::utils::crypto::convert_encryption_types;
13use crate::enums::acl::{
14 parse_embedded_security_descriptor, parse_ntsecuritydescriptor,
15};
16use crate::enums::secdesc::LdapSid;
17use crate::enums::sid::sid_maker;
18use crate::enums::uacflags::get_flag;
19
20use super::common::UserRight;
21
22#[derive(Debug, Clone, Deserialize, Serialize, Default)]
24pub struct Computer {
25 #[serde(rename = "Properties")]
26 properties: ComputerProperties,
27 #[serde(rename = "Aces")]
28 aces: Vec<AceTemplate>,
29 #[serde(rename = "ObjectIdentifier")]
30 object_identifier: String,
31 #[serde(rename = "IsDeleted")]
32 is_deleted: bool,
33 #[serde(rename = "IsACLProtected")]
34 is_acl_protected: bool,
35 #[serde(rename = "ContainedBy")]
36 contained_by: Option<Member>,
37
38 #[serde(rename = "PrimaryGroupSID")]
39 primary_group_sid: String,
40 #[serde(rename = "AllowedToDelegate")]
41 allowed_to_delegate: Vec<Member>,
42 #[serde(rename = "AllowedToAct")]
43 allowed_to_act: Vec<Member>,
44 #[serde(rename = "HasSIDHistory")]
45 has_sid_history: Vec<String>,
46 #[serde(rename = "DumpSMSAPassword")]
47 dump_smsa_password: Vec<Member>,
48
49 #[serde(rename = "Sessions")]
50 sessions: Session,
51 #[serde(rename = "PrivilegedSessions")]
52 privileged_sessions: Session,
53 #[serde(rename = "RegistrySessions")]
54 registry_sessions: Session,
55 #[serde(rename = "LocalGroups")]
56 local_groups: Vec<LocalGroup>,
57 #[serde(rename = "UserRights")]
58 users_rights: Vec<UserRight>,
59 #[serde(rename = "DCRegistryData")]
60 dcregistry_data: DCRegistryData,
61 #[serde(rename = "IsWebClientRunning")]
62 is_web_client_running: WebClientRunning,
63
64 #[serde(rename = "IsDC")]
65 is_dc: bool,
66 #[serde(rename = "UnconstrainedDelegation")]
67 unconstrained_delegation: bool,
68 #[serde(rename = "DomainSID")]
69 domain_sid: String,
70
71 #[serde(rename = "Status")]
72 status: Option<String>,
73}
74
75impl Computer {
76 pub fn new() -> Self {
78 Self { ..Default::default() }
79 }
80
81 pub fn properties(&self) -> &ComputerProperties {
83 &self.properties
84 }
85 pub fn object_identifier(&self) -> &String {
86 &self.object_identifier
87 }
88 pub fn allowed_to_act(&self) -> &Vec<Member> {
89 &self.allowed_to_act
90 }
91
92 pub fn is_active(&self, expiry_days: i64) -> bool {
95 if !*self.properties().enabled() {
96 return false;
97 }
98 let pls = self.properties().pwdlastset();
99 if pls <= 0 {
100 return false;
101 }
102 let now = chrono::Utc::now().timestamp();
103 now - pls < expiry_days * 86_400
104 }
105
106 pub fn allowed_to_act_mut(&mut self) -> &mut Vec<Member> {
108 &mut self.allowed_to_act
109 }
110 pub fn sessions_mut(&mut self) -> &mut Session {
111 &mut self.sessions
112 }
113 pub fn privileged_sessions_mut(&mut self) -> &mut Session {
114 &mut self.privileged_sessions
115 }
116 pub fn registry_sessions_mut(&mut self) -> &mut Session {
117 &mut self.registry_sessions
118 }
119 pub fn set_is_web_client_running(&mut self, value: WebClientRunning) {
120 self.is_web_client_running = value;
121 }
122 pub fn users_rights_mut(&mut self) -> &mut Vec<UserRight> {
123 &mut self.users_rights
124 }
125 pub fn local_groups_mut(&mut self) -> &mut Vec<LocalGroup> {
126 &mut self.local_groups
127 }
128
129 pub fn parse(
132 &mut self,
133 result: SearchEntry,
134 domain: &str,
135 dn_sid: &mut HashMap<String, String>,
136 sid_type: &mut HashMap<String, String>,
137 fqdn_sid: &mut HashMap<String, String>,
138 fqdn_ip: &mut HashMap<String, String>,
139 domain_sid: &str,
140 schema_guid_map: &HashMap<String, String>,
141 ) -> Result<(), Box<dyn Error>> {
142 let result_dn: String = result.dn.to_uppercase();
143 let result_attrs: HashMap<String, Vec<String>> = result.attrs;
144 let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
145
146 debug!("Parse computer: {result_dn}");
148
149 for (key, value) in &result_attrs {
151 trace!(" {key:?}:{value:?}");
152 }
153 for (key, value) in &result_bin {
155 trace!(" {key:?}:{value:?}");
156 }
157
158 self.properties.domain = domain.to_uppercase();
160 self.properties.distinguishedname = result_dn;
161 self.properties.enabled = true;
162 self.domain_sid = domain_sid.to_string();
163
164 let mut sid: String = "".to_owned();
165 let mut group_id: String = "".to_owned();
166 for (key, value) in &result_attrs {
168 match key.as_str() {
169 "name" => {
170 if !result_attrs.contains_key("dNSHostName") {
176 let name = &value[0];
177 let email = format!("{}.{}",name.to_owned(),domain);
178 self.properties.name = email.to_uppercase();
179 }
180 }
181 "sAMAccountName" => {
182 self.properties.samaccountname = value[0].to_owned();
183 }
184 "dNSHostName" => {
185 self.properties.name = value[0].to_uppercase();
186 }
187 "description" => {
188 self.properties.description = Some(value[0].to_owned());
189 }
190 "adminCount" => {
191 let admin_count = value[0].parse::<i32>().unwrap_or(0) != 0;
194 self.properties.admincount = admin_count;
195 self.properties.adminsdholderprotected = admin_count;
196 }
197 "mail" => {
198 self.properties.email = value[0].to_owned();
199 }
200 "operatingSystem" => {
201 self.properties.operatingsystem = value[0].to_owned();
202 }
203 "lastLogon" => {
221 let lastlogon = &value[0].parse::<i64>().unwrap_or(0);
222 if lastlogon.is_positive() {
223 let epoch = convert_timestamp(*lastlogon);
224 self.properties.lastlogon = epoch;
225 }
226 }
227 "lastLogonTimestamp" => {
228 let lastlogontimestamp = &value[0].parse::<i64>().unwrap_or(0);
229 if lastlogontimestamp.is_positive() {
230 let epoch = convert_timestamp(*lastlogontimestamp);
231 self.properties.lastlogontimestamp = epoch;
232 }
233 }
234 "pwdLastSet" => {
235 let pwdlastset = &value[0].parse::<i64>().unwrap_or(0);
236 if pwdlastset.is_positive() {
237 let epoch = convert_timestamp(*pwdlastset);
238 self.properties.pwdlastset = epoch;
239 }
240 }
241 "whenCreated" => {
242 let epoch = string_to_epoch(&value[0])?;
243 if epoch.is_positive() {
244 self.properties.whencreated = epoch;
245 }
246 }
247 "servicePrincipalName" => {
248 let mut result: Vec<String> = Vec::new();
250 for value in &result_attrs["servicePrincipalName"] {
251 result.push(value.to_owned());
252 }
253 self.properties.serviceprincipalnames = result;
254 }
255 "userAccountControl" => {
256 let uac = value[0].parse::<u32>().unwrap_or(0);
258 self.properties.useraccountcontrol = uac;
259
260 let uac_flags = get_flag(uac);
261 for flag in uac_flags {
263 if flag.contains("AccountDisable") {
264 self.properties.enabled = false;
265 };
266 if flag.contains("Lockout") {
267 self.properties.lockedout = true;
268 };
269 if flag.contains("Script") {
270 self.properties.logonscriptenabled = true;
271 };
272 if flag.contains("EncryptedTextPwdAllowed") {
273 self.properties.encryptedtextpwdallowed = true;
274 };
275 if flag.contains("UseDesKeyOnly") {
276 self.properties.usedeskeyonly = true;
277 };
278 if flag.contains("PasswordExpired") {
279 self.properties.passwordexpired = true;
280 };
281 if flag.contains("PartialSecretsAccount") {
282 self.properties.isreadonlydc = true;
283 };
284 if flag.contains("TrustedForDelegation") {
286 self.properties.unconstraineddelegation = true;
287 self.unconstrained_delegation = true;
288 };
289 if flag.contains("TrustedToAuthForDelegation") {
290 self.properties.trustedtoauth = true;
291 };
292 if flag.contains("PasswordNotRequired") {
293 self.properties.passwordnotreqd = true;
294 };
295 if flag.contains("DontExpirePassword") {
296 self.properties.pwdneverexpires = true;
297 };
298 if flag.contains("ServerTrustAccount") {
299 self.properties.isdc = true;
300 self.properties.is_dc = true;
301 self.is_dc = true;
302 }
303 }
304 }
305 "msDS-AllowedToDelegateTo" => {
306 let mut vec_members2: Vec<Member> = Vec::new();
310 for objet in value {
311 let mut member_allowed_to_delegate = Member::new();
312 let split = objet.split("/");
313 let fqdn = split.collect::<Vec<&str>>()[1];
314 let mut checker = false;
315 for member in &vec_members2 {
316 if member.object_identifier().contains(fqdn.to_uppercase().as_str()) {
317 checker = true;
318 }
319 }
320 if !checker {
321 *member_allowed_to_delegate.object_identifier_mut() = fqdn.to_uppercase().to_owned().to_uppercase();
322 *member_allowed_to_delegate.object_type_mut() = "Computer".to_owned();
323 vec_members2.push(member_allowed_to_delegate.to_owned());
324 }
325 }
326 self.allowed_to_delegate = vec_members2;
328 }
329 "ms-Mcs-AdmPwd" => {
331 info!(
334 "Your user can read LAPS password on {}: {}",
335 &result_attrs["name"][0].yellow().bold(),
336 &result_attrs["ms-Mcs-AdmPwd"][0].yellow().bold()
337 );
338 self.properties.haslaps = true;
339 }
340 "ms-Mcs-AdmPwdExpirationTime" => {
341 self.properties.haslaps = true;
344 }
345 "msLAPS-Password" => {
347 info!(
348 "Your user can read LAPS password on {}: {:?}",
349 &result_attrs["name"][0].yellow().bold(),
350 &value[0].yellow().bold()
351 );
352 self.properties.haslaps = true;
353 }
354 "msLAPS-EncryptedPassword" => {
355 info!(
356 "Your user can read uncrypted LAPS password on {} please check manually to decrypt it!",
357 &result_attrs["name"][0].yellow().bold()
358 );
359 self.properties.haslaps = true;
360 }
361 "msLAPS-PasswordExpirationTime" => {
362 self.properties.haslaps = true;
364 }
365 "primaryGroupID" => {
366 group_id = value[0].to_owned();
367 }
368 "isDeleted" => {
369 self.is_deleted = true;
370 }
371 "msDS-SupportedEncryptionTypes" => {
372 self.properties.supportedencryptiontypes = convert_encryption_types(value[0].parse::<i32>().unwrap_or(0));
373 }
374 _ => {}
375 }
376 }
377
378 for (key, value) in &result_bin {
380 match key.as_str() {
381 "objectGUID" => {
382 let guid = decode_guid_le(&value[0]);
384 self.properties.objectguid = guid;
385 }
386 "objectSid" => {
387 sid = sid_maker(LdapSid::parse(&value[0]).unwrap().1, domain);
389 self.object_identifier = sid.to_owned();
390
391 for domain_sid in OBJECT_SID_RE1.captures_iter(&sid) {
392 self.properties.domainsid = domain_sid[0].to_owned().to_string();
393 }
394 }
395 "nTSecurityDescriptor" => {
396 let relations_ace = parse_ntsecuritydescriptor(
399 self,
400 &value[0],
401 "Computer",
402 &result_attrs,
403 &result_bin,
404 domain,
405 schema_guid_map,
406 );
407 self.aces = relations_ace;
408 }
409 "msDS-AllowedToActOnBehalfOfOtherIdentity" => {
410 let relations_ace = parse_embedded_security_descriptor(
413 self,
414 &value[0],
415 "Computer",
416 &result_attrs,
417 &result_bin,
418 domain,
419 schema_guid_map,
420 );
421 let mut vec_members_allowtoact: Vec<Member> = Vec::new();
422 let mut allowed_to_act = Member::new();
423 for delegated in relations_ace {
424 if *delegated.right_name() == "GenericAll" {
427 *allowed_to_act.object_identifier_mut() = delegated.principal_sid().to_string();
428 vec_members_allowtoact.push(allowed_to_act.to_owned());
429 continue
430 }
431 }
432 self.allowed_to_act = vec_members_allowtoact;
433 }
434 "sIDHistory" => {
435 let mut list_sid_history: Vec<String> = Vec::new();
437 for bsid in value {
438 debug!("sIDHistory: {:?}", &bsid);
439 list_sid_history.push(sid_maker(LdapSid::parse(bsid).unwrap().1, domain));
440 }
441 self.properties.sidhistory = list_sid_history.clone();
442 self.has_sid_history = list_sid_history;
443 }
444 _ => {}
445 }
446 }
447
448 #[allow(irrefutable_let_patterns)]
450 if let id = group_id {
451 if let Some(part1) = SID_PART1_RE1.find(&sid) {
452 self.primary_group_sid = format!("{}{}", part1.as_str(), id);
453 } else {
454 eprintln!("[!] Regex did not match any part of the SID");
455 }
456 }
457
458 dn_sid.insert(
460 self.properties.distinguishedname.to_string(),
461 self.object_identifier.to_string(),
462
463 );
464 sid_type.insert(
466 self.object_identifier.to_string(),
467 "Computer".to_string(),
468 );
469
470 fqdn_sid.insert(
471 self.properties.name.to_string(),
472 self.object_identifier.to_string(),
473 );
474
475 fqdn_ip.insert(
476 self.properties.name.to_string(),
477 String::from(""),
478 );
479
480 Ok(())
483 }
484}
485
486impl LdapObject for Computer {
487 fn to_json(&self) -> Value {
489 serde_json::to_value(self).unwrap()
490 }
491
492 fn get_object_identifier(&self) -> &String {
494 &self.object_identifier
495 }
496 fn get_is_acl_protected(&self) -> &bool {
497 &self.is_acl_protected
498 }
499 fn get_aces(&self) -> &Vec<AceTemplate> {
500 &self.aces
501 }
502 fn get_spntargets(&self) -> &Vec<SPNTarget> {
503 panic!("Not used by current object.");
504 }
505 fn get_allowed_to_delegate(&self) -> &Vec<Member> {
506 &self.allowed_to_delegate
507 }
508 fn get_links(&self) -> &Vec<Link> {
509 panic!("Not used by current object.");
510 }
511 fn get_contained_by(&self) -> &Option<Member> {
512 &self.contained_by
513 }
514 fn get_child_objects(&self) -> &Vec<Member> {
515 panic!("Not used by current object.");
516 }
517 fn get_haslaps(&self) -> &bool {
518 &self.properties.haslaps
519 }
520
521 fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
523 &mut self.aces
524 }
525 fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
526 panic!("Not used by current object.");
527 }
528 fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
529 &mut self.allowed_to_delegate
530 }
531
532 fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
534 self.is_acl_protected = is_acl_protected;
535 self.properties.isaclprotected = is_acl_protected;
536 }
537 fn set_aces(&mut self, aces: Vec<AceTemplate>) {
538 self.aces = aces;
539 }
540 fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
541 }
543 fn set_allowed_to_delegate(&mut self, allowed_to_delegate: Vec<Member>) {
544 self.allowed_to_delegate = allowed_to_delegate;
545 }
546 fn set_links(&mut self, _links: Vec<Link>) {
547 }
549 fn set_contained_by(&mut self, contained_by: Option<Member>) {
550 self.contained_by = contained_by;
551 }
552 fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
553 }
555 fn set_owner_rights_flags(&mut self, any: bool, any_inherited: bool) {
556 self.properties.doesanyacegrantownerrights = any;
557 self.properties.doesanyinheritedacegrantownerrights = any_inherited;
558 }
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize, Default)]
563pub struct ComputerProperties {
564 domain: String,
565 name: String,
566 distinguishedname: String,
567 domainsid: String,
568 objectguid: String,
569 doesanyacegrantownerrights: bool,
570 doesanyinheritedacegrantownerrights: bool,
571 isaclprotected: bool,
572 highvalue: bool,
573 samaccountname: String,
574 haslaps: bool,
575 description: Option<String>,
576 whencreated: i64,
577 enabled: bool,
578 unconstraineddelegation: bool,
579 trustedtoauth: bool,
580 lastlogon: i64,
581 lastlogontimestamp: i64,
582 pwdlastset: i64,
583 passwordnotreqd: bool,
584 pwdneverexpires: bool,
585 serviceprincipalnames: Vec<String>,
586 operatingsystem: String,
587 sidhistory: Vec<String>,
588 supportedencryptiontypes: Vec<String>,
589 useraccountcontrol: u32,
590 isdc: bool,
591 isreadonlydc: bool,
592 admincount: bool,
593 adminsdholderprotected: bool,
594 lockedout: bool,
595 passwordexpired: bool,
596 usedeskeyonly: bool,
597 encryptedtextpwdallowed: bool,
598 logonscriptenabled: bool,
599 email: String,
600 #[serde(skip_serializing)]
601 is_dc: bool
602}
603
604impl ComputerProperties {
605 pub fn name(&self) -> &String {
607 &self.name
608 }
609 pub fn unconstraineddelegation(&self) -> &bool {
610 &self.unconstraineddelegation
611 }
612 pub fn enabled(&self) -> &bool {
613 &self.enabled
614 }
615 pub fn get_is_dc(&self) -> &bool {
616 &self.is_dc
617 }
618 pub fn pwdlastset(&self) -> i64 {
619 self.pwdlastset
620 }
621 pub fn distinguishedname(&self) -> &String {
622 &self.distinguishedname
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn parse_populates_has_sid_history() {
632 let mut computer = Computer::new();
633 let result = SearchEntry {
634 dn: "CN=TESTPC,OU=Computers,DC=example,DC=local".to_string(),
635 attrs: HashMap::new(),
636 bin_attrs: HashMap::from([(
637 "sIDHistory".to_string(),
638 vec![vec![1, 2, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 0x15, 0xCD, 0x5B, 0x07]],
639 )]),
640 };
641 let mut dn_sid = HashMap::new();
642 let mut sid_type = HashMap::new();
643 let mut fqdn_sid = HashMap::new();
644 let mut fqdn_ip = HashMap::new();
645 let schema_guid_map = HashMap::new();
646
647 computer
648 .parse(
649 result,
650 "example.local",
651 &mut dn_sid,
652 &mut sid_type,
653 &mut fqdn_sid,
654 &mut fqdn_ip,
655 "S-1-5-21-1-2-3",
656 &schema_guid_map,
657 )
658 .unwrap();
659
660 assert_eq!(computer.has_sid_history, vec!["S-1-5-21-123456789".to_string()]);
662 }
663}