Closed 1mm0rt41PC closed 10 months ago
Hi @1mm0rt41PC,
I'm going to add the LAPS attributes relating to the encrypted version. This will warn the auditor whether or not if the user used can read the LAPS passwords stored for a computer object. https://learn.microsoft.com/fr-fr/windows-server/identity/laps/laps-technical-reference
As far as the non-standard "Configuration" part is concerned, version 2 of RustHound will automatically retrieve the namingContext in order to retrieve the objects associed (Container,Group,User,etc).
For example, if the domain has the following naming context CN=Configuration,DC=DOMAIN,DC=LOCAL
then RustHound will automatically fetch the stored objects (useful for ADCS and SCCM for example) and the associated ACE.
For information, version 2 of RustHound is almost complete. It will feature a complete restructuring of the Rust code and compatibility with BloodHound-CE.
Version 2 of RustHound will also automatically generate the following ADCS-related files:
Thanks for your suggestion, I'll take it into account and incorporate it into version 2. :smiley:
Hi there!
Great news! I can't wait to see version 2.0, is there a channel/tag/branch alpha 2.0? To avoid multiple modifications of the code what do you think about managing the dump of attributes via a yaml conf file with why not an embedded version in the code.
With the format:
dump:
objectClass:
? attribute_A // Dump attribute_A and ACE write only
secretAttribute: read // Dump only ACE ref to read
We can imagine:
version: 2
defaults: &commonAttrib
? name
? canonicalName
? objectSid
? objectGUID
? objectCategory
? displayName
dump:
user:
<<: *commonAttrib
? accountExpires
? adminCount
? badPasswordTime
? badPwdCount
? cn
? description
? distinguishedName
? isCriticalSystemObject
? lastLogoff
? lastLogon
? lastLogonTimestamp
? logonCount
? logonHours
? memberOf
? msDS-parentdistname
? msDS-PrincipalName
? primaryGroupID
? pwdLastSet
? sAMAccountName
userAccountControl: {"parser": func_pimpMyParser}
? userPassword
? whenChanged
? whenCreated
? serviceprincipalname
computers:
<<: *commonAttrib
msLAPS-Password: {"parser": "func_pimpMyPassword", "ACE":"read"}
msLAPS-EncryptedPassword: read
msLAPS-EncryptedDSRMPassword: read
? serviceprincipalname
group:
<<: *commonAttrib
? groupType
dns:
<<: *commonAttrib
pKICertificateTemplate:
<<: *commonAttrib
? msPKI-Cert-Template-OID
? msPKI-Enrollment-Flag
? msPKI-Minimal-Key-Size
? msPKI-Private-Key-Flag
? msPKI-RA-Application-Policies
? msPKI-RA-Signature
? msPKI-Template-Minor-Revision
? msPKI-Template-Schema-Version
? pKICriticalExtensions
? pKIDefaultCSPs
? pKIDefaultKeySpec
? pKIExpirationPeriod
? pKIKeyUsage
? pKIMaxIssuingDepth
? pKIOverlapPeriod
certificationAuthority:
<<: *commonAttrib
dnsNode:
<<: *commonAttrib
domainDns:
<<: *commonAttrib
? gPLink
? fSMORoleOwner
? maxPwdAge
? minPwdAge
? minPwdLength
? ms-DS-MachineAccountQuota
? pwdHistoryLength
? pwdProperties
groupPolicyContainer:
<<: *commonAttrib
? gPCFileSysPath
? gPCFunctionalityVersion
? gPCMachineExtensionNames
organizationalUnit:
<<: *commonAttrib
? gPLink
It's a good idea, but it would make version 2, which is almost finished, unusable.
I've changed everything to use structures dedicated to each object in the directory in order to optimise the execution of RustHound.
Each object is now structured as follows:
/// User structure
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct User {
#[serde(rename = "ObjectIdentifier")]
object_identifier: String,
#[serde(rename = "IsDeleted")]
is_deleted: bool,
#[serde(rename = "IsACLProtected")]
is_acl_protected: bool,
#[serde(rename = "Properties")]
properties: UserProperties,
#[serde(rename = "PrimaryGroupSID")]
primary_group_sid: String,
#[serde(rename = "SPNTargets")]
spn_targets: Vec<SPNTarget>,
#[serde(rename = "Aces")]
aces: Vec<AceTemplate>,
#[serde(rename = "AllowedToDelegate")]
allowed_to_delegate: Vec<Member>,
#[serde(rename = "HasSIDHistory")]
has_sid_history: Vec<String>,
#[serde(rename = "ContainedBy")]
contained_by: Option<Member>,
}
impl User {
/// New User
pub fn new() -> Self {
Self { ..Default::default() }
}
/// Function to parse and replace value for user object.
pub fn parse(
&mut self,
result: SearchEntry,
domain: &String,
) {
// parse all ldap attribut and change value in User struct
}
}
RustHound version 2.0 is out and the output is now compatible with BloodHound-CE
Changes can be find in v2 branch. :rocket:
https://github.com/NH-RED-TEAM/RustHound/commit/b445723c0330c5b3e5c237d5632625379bf48fbf
LAPS arguments: https://github.com/NH-RED-TEAM/RustHound/blob/v2/src/objects/computer.rs#L266
OMG ! Nice job ! Super-fast development speed 🤯
I'm closing the issue.
In version 2, all the attributes are retrieved and can be analysed in debug mode rusthound ....... -vv
to trace all the LDAP attributes of the retrieved object.
From a pentest I have found that a owned user was able to read an LAPS entry (new one
msLAPS-Password
) but not LAPS Legacy. But Rusthound like Bloodhound doesn't dump all attributes and associated ACE, so this path was not visible, I was blind...The targeted user also had unusual ACLs in non-standard LDAP paths linked to SCCM in
CN=Services,CN=Configuration
.Feature:
--dump-ldap-fields=all
--dump-ldap-fields=default,msLAPS-Password,msLAPS-EncryptedPassword,msLAPS-EncryptedDSRMPassword
--dump-ldap-path-recurssiv=CN=Services,CN=Configuration