Skip to main content

rusthound_ce/objects/
fsp.rs

1use serde_json::value::Value;
2use serde::{Deserialize, Serialize};
3use ldap3::SearchEntry;
4use log::{debug, trace};
5use std::collections::HashMap;
6use std::error::Error;
7
8use crate::enums::decode_guid_le;
9use crate::enums::regex::OBJECT_SID_RE1;
10use crate::objects::common::{LdapObject, AceTemplate, SPNTarget, Link, Member};
11use crate::utils::date::string_to_epoch;
12use crate::enums::secdesc::LdapSid;
13use crate::enums::sid::{objectsid_to_vec8, sid_maker};
14
15/// FSP (ForeignSecurityPrincipal) structure
16#[derive(Debug, Clone, Deserialize, Serialize, Default)]
17pub struct Fsp {
18    #[serde(rename = "Properties")]
19    properties: FspProperties,
20    #[serde(rename = "Aces")]
21    aces: Vec<AceTemplate>,
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 = "ContainedBy")]
29    contained_by: Option<Member>,
30}
31
32impl Fsp {
33    // New FSP
34    pub fn new() -> Self { 
35        Self { ..Default::default() } 
36    }
37
38    /// Function to parse and replace value in json template for ForeignSecurityPrincipal object.
39    pub fn parse(
40        &mut self,
41        result: SearchEntry,
42        domain: &str,
43        dn_sid: &mut HashMap<String, String>,
44        sid_type: &mut HashMap<String, String>,
45        domain_sid: &str
46    ) -> Result<(), Box<dyn Error>> {
47        let result_dn: String = result.dn.to_uppercase();
48        let result_attrs: HashMap<String, Vec<String>> = result.attrs;
49        let result_bin: HashMap<String, Vec<Vec<u8>>> = result.bin_attrs;
50
51        // Debug for current object
52        debug!("Parse ForeignSecurityPrincipal: {result_dn}");
53
54        // Trace all result attributes
55        for (key, value) in &result_attrs {
56            trace!("  {key:?}:{value:?}");
57        }
58        // Trace all bin result attributes
59        for (key, value) in &result_bin {
60            trace!("  {key:?}:{value:?}");
61        }
62
63        // Change all values...
64        self.properties.domain = domain.to_uppercase();
65        self.properties.distinguishedname = result_dn;    
66        self.properties.domainsid = domain_sid.to_string();
67
68        #[allow(unused_assignments)]
69        let mut sid: String = "".to_owned();
70        let mut ftype: &str = "Base";
71
72        // With a check
73        for (key, value) in &result_attrs {
74            match key.as_str() {
75                "name" => {
76                    let name = format!("{}-{}", domain, &value.first().unwrap_or(&"".to_owned()));
77                    self.properties.name = name.to_uppercase();
78
79                    // Type for group Member maker
80                    // based on https://docs.microsoft.com/fr-fr/troubleshoot/windows-server/identity/security-identifiers-in-windows
81                    let split = value[0].split("-").collect::<Vec<&str>>();
82
83                    // Not currently used:
84                    //let last = split.iter().last().unwrap_or(&"0").parse::<i32>().unwrap_or(0);
85                    if split.len() >= 17 {
86                        ftype = "User";
87                    } else {
88                        ftype = "Group";
89                    }
90                }
91                "whenCreated" => {
92                    let epoch = string_to_epoch(&value[0])?;
93                    if epoch.is_positive() {
94                        self.properties.whencreated = epoch;
95                    }
96                }
97                "objectSid" => {
98                    //objectSid to vec and raw to string
99                    let vec_sid = objectsid_to_vec8(&value[0]);
100                    sid = sid_maker(LdapSid::parse(&vec_sid).unwrap().1, domain);
101                    self.object_identifier = sid.to_owned();
102
103                    for domain_sid in OBJECT_SID_RE1.captures_iter(&sid) {
104                        self.properties.domainsid = domain_sid[0].to_owned().to_string();
105                    }
106                }
107                "isDeleted" => {
108                    self.is_deleted = true;
109                }
110                _ => {}
111            }
112        }
113
114
115        // For all, bins attributs
116        for (key, value) in &result_bin {
117            match key.as_str() {
118                "objectGUID" => {
119                    // objectGUID raw to string; the FSP is identified by its SID
120                    let guid = decode_guid_le(&value[0]);
121                    self.properties.objectguid = guid;
122                }
123                "objectSid" => {
124                    // ldap3 puts objectSid here when it is not valid UTF-8,
125                    // which is the case for almost every domain SID
126                    let sid = sid_maker(LdapSid::parse(&value[0]).unwrap().1, domain);
127                    self.object_identifier = sid.to_owned();
128
129                    for domain_sid in OBJECT_SID_RE1.captures_iter(&sid) {
130                        self.properties.domainsid = domain_sid[0].to_owned().to_string();
131                    }
132                }
133                _ => {}
134            }
135        }
136
137        // Push DN and SID in HashMap
138        if self.object_identifier != "SID" {
139            dn_sid.insert(
140                self.properties.distinguishedname.to_string(),
141                self.object_identifier.to_string()
142            );
143            // Push DN and Type
144            sid_type.insert(self.object_identifier.to_string(), ftype.to_string());
145        }
146
147        // Trace and return Fsp struct
148        // trace!("JSON OUTPUT: {:?}",serde_json::to_string(&self).unwrap());
149        Ok(())
150    }
151}
152
153/// Default FSP properties structure
154#[derive(Debug, Clone, Deserialize, Serialize, Default)]
155pub struct FspProperties {
156    domain: String,
157    name: String,
158    distinguishedname: String,
159    domainsid: String,
160    objectguid: String,
161    doesanyacegrantownerrights: bool,
162    doesanyinheritedacegrantownerrights: bool,
163    isaclprotected: bool,
164    highvalue: bool,
165    description: Option<String>,
166    whencreated: i64,
167}
168
169impl FspProperties {
170   // New default properties.
171   pub fn new(domain: String) -> Self { 
172      Self { 
173         domain,
174         whencreated: -1,
175         ..Default::default() }
176   }
177
178   // Immutable access.
179   pub fn domain(&self) -> &String {
180      &self.domain
181   }
182   pub fn name(&self) -> &String {
183      &self.name
184   }
185   pub fn distinguishedname(&self) -> &String {
186      &self.distinguishedname
187   }
188   pub fn domainsid(&self) -> &String {
189      &self.domainsid
190   }
191   pub fn highvalue(&self) -> &bool {
192      &self.highvalue
193   }
194   pub fn description(&self) -> &Option<String> {
195      &self.description
196   }
197   pub fn whencreated(&self) -> &i64 {
198      &self.whencreated
199   }
200
201   // Mutable access.
202   pub fn domain_mut(&mut self) -> &mut String {
203      &mut self.domain
204   }
205   pub fn name_mut(&mut self) -> &mut String {
206      &mut self.name
207   }
208   pub fn distinguishedname_mut(&mut self) -> &mut String {
209      &mut self.distinguishedname
210   }
211   pub fn domainsid_mut(&mut self) -> &mut String {
212      &mut self.domainsid
213   }
214   pub fn highvalue_mut(&mut self) -> &mut bool {
215      &mut self.highvalue
216   }
217   pub fn description_mut(&mut self) -> &mut Option<String> {
218      &mut self.description
219   }
220   pub fn whencreated_mut(&mut self) -> &mut i64 {
221      &mut self.whencreated
222   }
223}
224
225impl LdapObject for Fsp {
226    // To JSON
227    fn to_json(&self) -> Value {
228        serde_json::to_value(self).unwrap()
229    }
230
231    // Get values
232    fn get_object_identifier(&self) -> &String {
233        &self.object_identifier
234    }
235    fn get_is_acl_protected(&self) -> &bool {
236        &self.is_acl_protected
237    }
238    fn get_aces(&self) -> &Vec<AceTemplate> {
239        &self.aces
240    }
241    fn get_spntargets(&self) -> &Vec<SPNTarget> {
242        panic!("Not used by current object.");
243    }
244    fn get_allowed_to_delegate(&self) -> &Vec<Member> {
245        panic!("Not used by current object.");
246    }
247    fn get_links(&self) -> &Vec<Link> {
248        panic!("Not used by current object.");
249    }
250    fn get_contained_by(&self) -> &Option<Member> {
251        &self.contained_by
252    }
253    fn get_child_objects(&self) -> &Vec<Member> {
254        panic!("Not used by current object.");
255    }
256    fn get_haslaps(&self) -> &bool {
257        &false
258    }
259    
260    // Get mutable values
261    fn get_aces_mut(&mut self) -> &mut Vec<AceTemplate> {
262        &mut self.aces
263    }
264    fn get_spntargets_mut(&mut self) -> &mut Vec<SPNTarget> {
265        panic!("Not used by current object.");
266    }
267    fn get_allowed_to_delegate_mut(&mut self) -> &mut Vec<Member> {
268        panic!("Not used by current object.");
269    }
270    
271    // Edit values
272    fn set_is_acl_protected(&mut self, is_acl_protected: bool) {
273        self.is_acl_protected = is_acl_protected;
274        self.properties.isaclprotected = is_acl_protected;
275    }
276    fn set_aces(&mut self, aces: Vec<AceTemplate>) {
277        self.aces = aces;
278    }
279    fn set_spntargets(&mut self, _spn_targets: Vec<SPNTarget>) {
280        // Not used by current object.
281    }
282    fn set_allowed_to_delegate(&mut self, _allowed_to_delegate: Vec<Member>) {
283        // Not used by current object.
284    }
285    fn set_links(&mut self, _links: Vec<Link>) {
286        // Not used by current object.
287    }
288    fn set_contained_by(&mut self, contained_by: Option<Member>) {
289        self.contained_by = contained_by;
290    }
291    fn set_child_objects(&mut self, _child_objects: Vec<Member>) {
292        // Not used by current object.
293    }
294    fn set_owner_rights_flags(&mut self, any: bool, any_inherited: bool) {
295        self.properties.doesanyacegrantownerrights = any;
296        self.properties.doesanyinheritedacegrantownerrights = any_inherited;
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::Fsp;
303    use crate::json::checker::common::replace_sid_members;
304    use crate::objects::{common::Member, group::Group};
305    use ldap3::SearchEntry;
306    use std::collections::HashMap;
307
308    const GUID: [u8; 16] = [
309        0x58, 0xEC, 0x7B, 0xF7, 0xA7, 0x73, 0x8D, 0x40,
310        0xAF, 0x0D, 0x42, 0xF0, 0xD7, 0x2C, 0x71, 0x14,
311    ];
312    const GUID_STR: &str = "F77BEC58-73A7-408D-AF0D-42F0D72C7114";
313
314    /// A SID in its binary form: revision 1, authority 5 (NT), sub-authorities.
315    fn sid_bytes(sub_authorities: &[u32]) -> Vec<u8> {
316        let mut b = vec![1, sub_authorities.len() as u8, 0, 0, 0, 0, 0, 5];
317        for s in sub_authorities {
318            b.extend_from_slice(&s.to_le_bytes());
319        }
320        b
321    }
322
323    fn parse(entry: SearchEntry) -> (Fsp, HashMap<String, String>) {
324        let mut fsp = Fsp::new();
325        let mut dn_sid = HashMap::new();
326        let mut sid_type = HashMap::new();
327        fsp.parse(entry, "example.local", &mut dn_sid, &mut sid_type, "S-1-5-21-1-2-3")
328            .unwrap();
329        (fsp, dn_sid)
330    }
331
332    /// A principal from a trusted forest: ldap3 delivers its objectSid, which
333    /// is not valid UTF-8, in bin_attrs.
334    fn foreign_principal() -> (String, SearchEntry) {
335        let sid = "S-1-5-21-1111111111-2222222222-3333333333-1108";
336        let bytes = sid_bytes(&[21, 1111111111, 2222222222, 3333333333, 1108]);
337        assert!(std::str::from_utf8(&bytes).is_err(), "delivered in bin_attrs");
338        let dn = format!("CN={sid},CN=ForeignSecurityPrincipals,DC=example,DC=local");
339        let entry = SearchEntry {
340            dn: dn.clone(),
341            attrs: HashMap::from([("name".to_string(), vec![sid.to_string()])]),
342            bin_attrs: HashMap::from([
343                ("objectSid".to_string(), vec![bytes]),
344                ("objectGUID".to_string(), vec![GUID.to_vec()]),
345            ]),
346        };
347        (dn.to_uppercase(), entry)
348    }
349
350    #[test]
351    fn domain_sid_from_bin_attrs_is_the_identifier() {
352        let (dn, entry) = foreign_principal();
353        let (fsp, dn_sid) = parse(entry);
354        let sid = "S-1-5-21-1111111111-2222222222-3333333333-1108";
355        assert_eq!(fsp.object_identifier, sid);
356        assert_eq!(fsp.properties.objectguid, GUID_STR);
357        assert_eq!(fsp.properties.domainsid, "S-1-5-21-1111111111-2222222222-3333333333");
358        assert_eq!(dn_sid.get(&dn).map(String::as_str), Some(sid));
359    }
360
361    #[test]
362    fn well_known_sid_from_attrs_is_not_overwritten_by_the_guid() {
363        // S-1-5-11 (Authenticated Users) is valid UTF-8, so ldap3 delivers it
364        // in attrs.
365        let raw = String::from_utf8(sid_bytes(&[11])).unwrap();
366        let entry = SearchEntry {
367            dn: "CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=example,DC=local".to_string(),
368            attrs: HashMap::from([
369                ("name".to_string(), vec!["S-1-5-11".to_string()]),
370                ("objectSid".to_string(), vec![raw]),
371            ]),
372            bin_attrs: HashMap::from([("objectGUID".to_string(), vec![GUID.to_vec()])]),
373        };
374        let (fsp, _) = parse(entry);
375        assert_eq!(fsp.object_identifier, "EXAMPLE.LOCAL-S-1-5-11");
376        assert_eq!(fsp.properties.objectguid, GUID_STR);
377    }
378
379    #[test]
380    fn group_member_from_a_trusted_forest_resolves_to_its_sid() {
381        let (dn, entry) = foreign_principal();
382        let mut fsp = Fsp::new();
383        let mut dn_sid = HashMap::new();
384        let mut sid_type = HashMap::new();
385        fsp.parse(entry, "example.local", &mut dn_sid, &mut sid_type, "S-1-5-21-1-2-3")
386            .unwrap();
387
388        let mut group = Group::new();
389        let mut member = Member::new();
390        *member.object_identifier_mut() = dn;
391        group.members_mut().push(member);
392        let mut groups = vec![group];
393        replace_sid_members(&mut groups, &dn_sid, &sid_type, &[]).unwrap();
394
395        assert_eq!(
396            groups[0].members()[0].object_identifier(),
397            "S-1-5-21-1111111111-2222222222-3333333333-1108"
398        );
399    }
400}