Compare commits

...

13 Commits

Author SHA1 Message Date
EasyMoney322 aa5eda4876 Fix 404 link in eicar.txt (#19912)
Updated the link to EICAR's test-file as the old one returns 404
2025-02-27 16:17:10 +00:00
h00die 689fb49b6e correct password in hashes table (#19911) 2025-02-27 15:15:45 +00:00
jenkins-metasploit c1a81ebf5a automatic module_metadata_base.json update 2025-02-27 14:35:25 +00:00
Diego Ledda 7e0b3af790 Land #19879, Add MsDtypSecurityDescriptor to_sddl_text
Land #19879, Add MsDtypSecurityDescriptor to_sddl_text
2025-02-27 15:28:27 +01:00
Diego Ledda 8c24e98fdd Land #19902, Fix byte to int conversion in MsAdts
Land #19902, Fix byte to int conversion in MsAdts
2025-02-27 15:25:50 +01:00
Metasploit 1d801225df Bump version of framework to 6.4.52 2025-02-27 03:33:05 -06:00
Spencer McIntyre d37039c08f Add tests for byte to int conversions 2025-02-26 09:29:35 -05:00
Spencer McIntyre b853168a89 Make common byte to int conversion functions 2025-02-26 09:29:30 -05:00
Spencer McIntyre fcee4db5d0 Reorder the buffer fields to match windows 2025-02-25 17:44:54 -05:00
Spencer McIntyre 48c4ce56e4 Raise a specific error and update specs 2025-02-14 01:42:22 -05:00
Spencer McIntyre c9dc97c242 Update some modules to print the SDDL 2025-02-13 17:19:43 -05:00
Spencer McIntyre c979d8d477 Add the #to_sddl_text method for security descriptors 2025-02-13 17:19:37 -05:00
Spencer McIntyre 31b8fad08f Allow SIDs to be set by strings 2025-02-11 17:00:46 -05:00
18 changed files with 1091 additions and 575 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
metasploit-framework (6.4.51)
metasploit-framework (6.4.52)
aarch64
abbrev
actionpack (~> 7.0.0)
+1 -1
View File
@@ -90,7 +90,7 @@ memory_profiler, 1.1.0, MIT
metasm, 1.0.5, LGPL-2.1
metasploit-concern, 5.0.3, "New BSD"
metasploit-credential, 6.0.11, "New BSD"
metasploit-framework, 6.4.51, "New BSD"
metasploit-framework, 6.4.52, "New BSD"
metasploit-model, 5.0.2, "New BSD"
metasploit-payloads, 2.0.189, "3-clause (or ""modified"") BSD"
metasploit_data_models, 6.0.6, "New BSD"
+1 -1
View File
@@ -13,4 +13,4 @@ responsible for corrupting the Metasploit Framework installation.
For more information about EICAR, please see the following web site:
http://www.eicar.org/anti_virus_test_file.htm
https://www.eicar.org/download-anti-malware-testfile/
+2 -2
View File
@@ -6802,7 +6802,7 @@
],
"targets": null,
"mod_time": "2025-01-30 17:27:49 +0000",
"mod_time": "2025-02-13 16:46:31 +0000",
"path": "/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb",
"is_install_path": true,
"ref_name": "admin/ldap/ad_cs_cert_template",
@@ -6937,7 +6937,7 @@
],
"targets": null,
"mod_time": "2024-05-02 13:57:13 +0000",
"mod_time": "2025-02-13 16:46:31 +0000",
"path": "/modules/auxiliary/admin/ldap/rbcd.rb",
"is_install_path": true,
"ref_name": "admin/ldap/rbcd",
@@ -201,7 +201,7 @@ This data breaks down to the following table:
| MSCash2 | mscash2-hashcat | `$DCC2$10240#tom#e4e938d12fe5974dc42a90120bd9c90f` | hashcat | mscash2 | | auxiliary/analyze/crack_windows |
| MSSQL (2005) | mssql05_toto | `0x01004086CEB6BF932BC4151A1AF1F13CD17301D70816A8886908` | toto | mssql05 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MSSQL | mssql_foo | `0x0100A607BA7C54A24D17B565C59F1743776A10250F581D482DA8B6D6261460D3F53B279CC6913CE747006A2E3254` | foo | mssql | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MSSQL (2012) | mssql12_Password1! | `0x0200F733058A07892C5CACE899768F89965F6BD1DED7955FE89E1C9A10E27849B0B213B5CE92CC9347ECCB34C3EFADAF2FD99BFFECD8D9150DD6AACB5D409A9D2652A4E0AF16` | Password! | mssql12 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MSSQL (2012) | mssql12_Password1! | `0x0200F733058A07892C5CACE899768F89965F6BD1DED7955FE89E1C9A10E27849B0B213B5CE92CC9347ECCB34C3EFADAF2FD99BFFECD8D9150DD6AACB5D409A9D2652A4E0AF16` | Password1! | mssql12 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MySQL | mysql_probe | `445ff82636a7ba59` | probe | mysql | auxiliary/scanner/mysql/mysql_hashdump | auxiliary/analyze/crack_databases |
| MySQL SHA1 | mysql-sha1_tere | `*5AD8F88516BD021DD43F171E2C785C69F8E54ADB` | tere | mysql-sha1 | auxiliary/scanner/mysql/mysql_hashdump | auxiliary/analyze/crack_databases |
| Oracle | simon | `4F8BC1809CB2AF77` | A | des,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases |
+1 -1
View File
@@ -32,7 +32,7 @@ module Metasploit
end
end
VERSION = "6.4.51"
VERSION = "6.4.52"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash
@@ -284,6 +284,14 @@ module Msf
end
normalized_attribute[0] = time_string
when 66 # String (Nt Security Descriptor)
if attribute_property[:attributesyntax] == '2.5.5.15'
begin
sd = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(entry[attribute_name][0])
normalized_attribute[0] = sd.to_sddl_text(domain_sid: nil)
rescue StandardError => e
elog('failed to parse a binary security descriptor to SDDL', error: e)
end
end
when 127 # Object
else
print_error("Unknown oMSyntax entry: #{attribute_property[:omsyntax]}")
+23
View File
@@ -27,4 +27,27 @@ module Rex::Crypto
def self.rc4(key, value)
Rc4.rc4(key, value)
end
# Returns an integer represented as a byte array. Useful for certain key-related operations.
#
# @param bytes [String] The bytes to convert
# @return [Integer] The converted value.
def self.bytes_to_int(bytes)
bytes.each_byte.reduce(0) { |acc, byte| (acc << 8) | byte }
end
# Returns a byte array represented as a big-endian integer. Useful for certain key-related operations.
#
# @param bytes [String] The bytes to convert
# @return [Integer] The converted value.
def self.int_to_bytes(num)
bytes = []
while num > 0
bytes.unshift(num & 0xff)
num >>= 8
end
bytes.pack("C*")
end
end
+5 -15
View File
@@ -25,8 +25,8 @@ module Rex::Proto::MsAdts
when KEY_USAGE_NGC
result = Rex::Proto::BcryptPublicKey.new
result.key_length = public_key.n.num_bits
n = self.class.int_to_bytes(public_key.n)
e = self.class.int_to_bytes(public_key.e)
n = Rex::Crypto.int_to_bytes(public_key.n.to_i)
e = Rex::Crypto.int_to_bytes(public_key.e.to_i)
result.exponent = e
result.modulus = n
result.prime1 = ''
@@ -136,8 +136,8 @@ module Rex::Proto::MsAdts
when KEY_USAGE_NGC
if raw_key_material.start_with?([Rex::Proto::BcryptPublicKey::MAGIC].pack('I'))
result = Rex::Proto::BcryptPublicKey.read(raw_key_material)
exponent = OpenSSL::ASN1::Integer.new(bytes_to_int(result.exponent))
modulus = OpenSSL::ASN1::Integer.new(bytes_to_int(result.modulus))
exponent = OpenSSL::ASN1::Integer.new(Rex::Crypto.bytes_to_int(result.exponent))
modulus = OpenSSL::ASN1::Integer.new(Rex::Crypto.bytes_to_int(result.modulus))
# OpenSSL's API has changed over time - constructing from DER has been consistent
data_sequence = OpenSSL::ASN1::Sequence([modulus, exponent])
@@ -165,16 +165,6 @@ module Rex::Proto::MsAdts
end
end
def self.int_to_bytes(num)
str = num.to_s(16).rjust(2, '0')
[str].pack('H*')
end
def bytes_to_int(num)
num.unpack('H*')[0].to_i(16)
end
# Sets self.key_hash based on the credential_entries value in the provided parameter
# @param struct [MsAdtsKeyCredentialStruct] Its credential_entries value should have only those required to calculate the key_hash value (no key_id or key_hash)
def calculate_key_hash(struct)
@@ -182,4 +172,4 @@ module Rex::Proto::MsAdts
self.key_hash = sha256.digest(struct.credential_entries.to_binary_s)
end
end
end
end
+446 -333
View File
@@ -5,6 +5,9 @@ require 'ruby_smb'
require 'rex/proto/secauthz/well_known_sids'
module Rex::Proto::MsDtyp
class SDDLParseError < Rex::RuntimeError
end
# [2.4.3 ACCESS_MASK](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/7a53f60e-e730-4dfe-bbe9-b21b62eb790b)
class MsDtypAccessMask < BinData::Record
endian :little
@@ -45,6 +48,120 @@ module Rex::Proto::MsDtyp
ALL = MsDtypAccessMask.new({ gr: 1, gw: 1, gx: 1, ga: 1, ma: 1, as: 1, sy: 1, wo: 1, wd: 1, rc: 1, de: 1, protocol: 0xffff })
NONE = MsDtypAccessMask.new({ gr: 0, gw: 0, gx: 0, ga: 0, ma: 0, as: 0, sy: 0, wo: 0, wd: 0, rc: 0, de: 0, protocol: 0 })
def to_sddl_text
sddl_text_tokens = []
if (protocol & 0b1111111000000000) != 0 || ma == 1 || as == 1
# if one of these conditions are true, we can't reduce this to a set of flags so dump it as hex
return "0x#{to_binary_s.unpack1('L<').to_s(16).rjust(8, '0')}"
end
sddl_text_tokens << 'GA' if ga == 1
sddl_text_tokens << 'GR' if gr == 1
sddl_text_tokens << 'GW' if gw == 1
sddl_text_tokens << 'GX' if gx == 1
file_access_mask = protocol & 0b000111111111
sddl_text_tokens << 'FA' if file_access_mask == 0b000111111111 && de == 1 && rc == 1 && wd == 1 && wo == 1 && sy == 1
sddl_text_tokens << 'FR' if file_access_mask == 0b000010001001
sddl_text_tokens << 'FW' if file_access_mask == 0b000100010110
sddl_text_tokens << 'FX' if file_access_mask == 0b000010100000
# windows does not reduce registry access flags (i.e. KA, KR, KW) so ignore them here to match it
sddl_text_tokens << 'CC' if (protocol & 0b000000000001) != 0 && !sddl_text_tokens.include?('FA') && !sddl_text_tokens.include?('FR')
sddl_text_tokens << 'DC' if (protocol & 0b000000000010) != 0 && !sddl_text_tokens.include?('FA') && !sddl_text_tokens.include?('FW')
sddl_text_tokens << 'LC' if (protocol & 0b000000000100) != 0 && !sddl_text_tokens.include?('FA') && !sddl_text_tokens.include?('FW')
sddl_text_tokens << 'SW' if (protocol & 0b000000001000) != 0 && !sddl_text_tokens.include?('FA') && !sddl_text_tokens.include?('FR')
sddl_text_tokens << 'RP' if (protocol & 0b000000010000) != 0 && !sddl_text_tokens.include?('FA') && !sddl_text_tokens.include?('FW')
sddl_text_tokens << 'WP' if (protocol & 0b000000100000) != 0 && !sddl_text_tokens.include?('FA') && !sddl_text_tokens.include?('FX')
sddl_text_tokens << 'DT' if (protocol & 0b000001000000) != 0 && !sddl_text_tokens.include?('FA')
sddl_text_tokens << 'LO' if (protocol & 0b000010000000) != 0 && !sddl_text_tokens.include?('FA')
sddl_text_tokens << 'CR' if (protocol & 0b000100000000) != 0 && !sddl_text_tokens.include?('FA')
sddl_text_tokens << 'SD' if de == 1 && !sddl_text_tokens.include?('FA')
sddl_text_tokens << 'RC' if rc == 1 && !sddl_text_tokens.include?('FA')
sddl_text_tokens << 'WD' if wd == 1 && !sddl_text_tokens.include?('FA')
sddl_text_tokens << 'WO' if wo == 1 && !sddl_text_tokens.include?('FA')
sddl_text_tokens.join('')
end
def self.from_sddl_text(sddl_text)
if sddl_text =~ /\A0x[0-9a-fA-F]{1,8}\Z/
return self.read([sddl_text.delete_prefix('0x').to_i(16)].pack('L<'))
end
access_mask = self.new
sddl_text.split(/(G[ARWX]|RC|SD|WD|WO|RP|WP|CC|DC|LC|SW|LO|DT|CR|F[ARWX]|K[ARWX]|N[RWX])/).each do |right|
case right
# generic access rights
when 'GA', 'GR', 'GW', 'GX'
access_mask.send("#{right.downcase}=", true)
# standard access rights
when 'RC'
access_mask.rc = true
when 'SD'
access_mask.de = true
when 'WD', 'WO'
access_mask.send("#{right.downcase}=", true)
# directory service object access rights
when 'RP'
access_mask.protocol |= 16
when 'WP'
access_mask.protocol |= 32
when 'CC'
access_mask.protocol |= 1
when 'DC'
access_mask.protocol |= 2
when 'LC'
access_mask.protocol |= 4
when 'SW'
access_mask.protocol |= 8
when 'LO'
access_mask.protocol |= 128
when 'DT'
access_mask.protocol |= 64
when 'CR'
access_mask.protocol |= 256
# file access rights
when 'FA'
access_mask.protocol |= 0x1ff
access_mask.de = true
access_mask.rc = true
access_mask.wd = true
access_mask.wo = true
access_mask.sy = true
when 'FR'
access_mask.protocol |= 0x89
when 'FW'
access_mask.protocol |= 0x116
when 'FX'
access_mask.protocol |= 0xa0
# registry key access rights
when 'KA'
access_mask.protocol |= 0x3f
access_mask.de = true
access_mask.rc = true
access_mask.wd = true
access_mask.wo = true
when 'KR'
access_mask.protocol |= 0x19
when 'KW'
access_mask.protocol |= 0x06
when 'KX'
access_mask.protocol |= 0x19
when 'NR', 'NW', 'NX'
raise SDDLParseError.new('unsupported ACE access right: ' + right)
when ''
else
raise SDDLParseError.new('unknown ACE access right: ' + right)
end
end
access_mask
end
end
# [2.4.2.2 SID--Packet Representation](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861)
@@ -75,6 +192,105 @@ module Rex::Proto::MsDtyp
def rid
sub_authority.last
end
# these can be validated using powershell where ?? is the code
# (ConvertFrom-SddlString -Sddl "O:??").RawDescriptor.Owner
SDDL_SIDS = {
'AA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCESS_CONTROL_ASSISTANCE_OPS, # SDDL_ACCESS_CONTROL_ASSISTANCE_OPS
'AC' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ALL_APP_PACKAGES, # SDDL_ALL_APP_PACKAGES
'AN' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ANONYMOUS_LOGON_SID, # SDDL_ANONYMOUS
'AO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCOUNT_OPS, # SDDL_ACCOUNT_OPERATORS
'AP' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_PROTECTED_USERS}", # SDDL_PROTECTED_USERS
'AU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID, # SDDL_AUTHENTICATED_USERS
'BA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS, # SDDL_BUILTIN_ADMINISTRATORS
'BG' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_GUESTS, # SDDL_BUILTIN_GUESTS
'BO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_BACKUP_OPS, # SDDL_BACKUP_OPERATORS
'BU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_USERS, # SDDL_BUILTIN_USERS
'CA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CERT_ADMINS}", # SDDL_CERT_SERV_ADMINISTRATORS
'CD' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_CERTSVC_DCOM_ACCESS_GROUP, # SDDL_CERTSVC_DCOM_ACCESS
'CG' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID, # SDDL_CREATOR_GROUP
'CN' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CLONEABLE_CONTROLLERS}", # SDDL_CLONEABLE_CONTROLLERS
'CO' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID, # SDDL_CREATOR_OWNER
'CY' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_CRYPTO_OPERATORS, # SDDL_CRYPTO_OPERATORS
'DA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}", # SDDL_DOMAIN_ADMINISTRATORS
'DC' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_COMPUTERS}", # SDDL_DOMAIN_COMPUTERS
'DD' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CONTROLLERS}", # SDDL_DOMAIN_DOMAIN_CONTROLLERS
'DG' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_GUESTS}", # SDDL_DOMAIN_GUESTS
'DU' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_USERS}", # SDDL_DOMAIN_USERS
'EA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_ADMINS}", # SDDL_ENTERPRISE_ADMINS
'ED' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_ENTERPRISE_CONTROLLERS_SID, # SDDL_ENTERPRISE_DOMAIN_CONTROLLERS
'EK' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_KEY_ADMINS}", # SDDL_ENTERPRISE_KEY_ADMINS
'ER' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_EVENT_LOG_READERS_GROUP, # SDDL_EVENT_LOG_READERS
'ES' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_RDS_ENDPOINT_SERVERS, # SDDL_RDS_ENDPOINT_SERVERS
'HA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_HYPER_V_ADMINS, # SDDL_HYPER_V_ADMINS
'HI' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_HIGH_RID}", # SDDL_ML_HIGH
'IS' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_IUSERS, # SDDL_IIS_USERS
'IU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_INTERACTIVE_SID, # SDDL_INTERACTIVE
'KA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_KEY_ADMINS}", # SDDL_KEY_ADMINS
'LA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_ADMIN}", # SDDL_LOCAL_ADMIN
'LG' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_GUEST}", # SDDL_LOCAL_GUEST
'LS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SERVICE_SID, # SDDL_LOCAL_SERVICE
'LU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_LOGGING_USERS, # SDDL_PERFLOG_USERS
'LW' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_LOW_RID}", # SDDL_ML_LOW
'ME' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_RID}", # SDDL_ML_MEDIUM
'MP' => "S-1-16-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_PLUS_RID}", # SDDL_ML_MEDIUM_PLUS
'MU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_MONITORING_USERS, # SDDL_PERFMON_USERS
'NO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_NETWORK_CONFIGURATION_OPS, # SDDL_NETWORK_CONFIGURATION_OPS
'NS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SERVICE_SID, # SDDL_NETWORK_SERVICE
'NU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SID, # SDDL_NETWORK
'OW' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_SID_AUTHORITY}-4", # SDDL_OWNER_RIGHTS
'PA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_POLICY_ADMINS}", # SDDL_GROUP_POLICY_ADMINS
'PO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_PRINT_OPS, # SDDL_PRINTER_OPERATORS
'PS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID, # SDDL_PERSONAL_SELF
'PU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_POWER_USERS, # SDDL_POWER_USERS
'RA' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_RDS_REMOTE_ACCESS_SERVERS, # SDDL_RDS_REMOTE_ACCESS_SERVERS
'RC' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_RESTRICTED_CODE_SID, # SDDL_RESTRICTED_CODE
'RD' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_REMOTE_DESKTOP_USERS, # SDDL_REMOTE_DESKTOP
'RE' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_REPLICATOR, # SDDL_REPLICATOR
'RM' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_BUILTIN_DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_MANAGEMENT_USERS}", # SDDL_RMS__SERVICE_OPERATORS
'RO' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_READONLY_DOMAIN_CONTROLLERS}", # SDDL_ENTERPRISE_RO_DCs
'RS' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RAS_SERVERS}", # SDDL_RAS_SERVERS
'RU' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_PREW2KCOMPACCESS, # SDDL_ALIAS_PREW2KCOMPACC
'SA' => "${DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_SCHEMA_ADMINS}", # SDDL_SCHEMA_ADMINISTRATORS
'SI' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_SYSTEM_SID, # SDDL_ML_SYSTEM
'SO' => Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_SYSTEM_OPS, # SDDL_SERVER_OPERATORS
'SS' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATION_SERVICE_ASSERTED_SID, # SDDL_SERVICE_ASSERTED
'SU' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_SERVICE_SID, # SDDL_SERVICE
'SY' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SYSTEM_SID, # SDDL_LOCAL_SYSTEM
'UD' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_USERMODEDRIVERHOST_ID_BASE_SID, # SDDL_USER_MODE_DRIVERS
'WD' => "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID_AUTHORITY}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_RID}", # SDDL_EVERYONE
'WR' => Rex::Proto::Secauthz::WellKnownSids::SECURITY_WRITE_RESTRICTED_CODE_SID # SDDL_WRITE_RESTRICTED_CODE
}.freeze
private_constant :SDDL_SIDS
def to_sddl_text(domain_sid: nil)
sid = to_s
lookup = domain_sid.blank? ? sid : sid.sub(domain_sid, '${DOMAIN_SID}')
if (sddl_text = self.class.const_get(:SDDL_SIDS).key(lookup)).nil?
sddl_text = sid
end
# these short names aren't supported by all versions of Windows, avoid compatibility issues by not outputting them
sddl_text = sid if %w[ AP CN EK KA ].include?(sddl_text)
sddl_text
end
def self.from_sddl_text(sddl_text, domain_sid:)
# see: https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings
sddl_text = sddl_text.dup.upcase
if SDDL_SIDS.key?(sddl_text)
sid_text = SDDL_SIDS[sddl_text].sub('${DOMAIN_SID}', domain_sid)
elsif sddl_text =~ /^S(-\d+)+/
sid_text = sddl_text
else
raise SDDLParseError.new('invalid SID string: ' + sddl_text)
end
self.new(sid_text)
end
end
# [Universal Unique Identifier](http://pubs.opengroup.org/onlinepubs/9629399/apdxa.htm)
@@ -188,17 +404,18 @@ module Rex::Proto::MsDtyp
string :application_data, read_length: -> { calc_app_data_length }
def calc_app_data_length
ace_header = parent&.header
return 0 if ace_header.nil?
ace_size = ace_header&.ace_size
ace_header = parent&.parent&.header
ace_body = parent&.parent&.body
return 0 if ace_header.nil? || ace_body.nil?
ace_size = ace_header.ace_size
return 0 if ace_size.nil? or (ace_size == 0)
ace_header_length = ace_header.to_binary_s.length
body = parent&.body
if body.nil?
if ace_body.nil?
return 0 # Read no data as there is no body, so either we have done some data misalignment or we shouldn't be reading data.
else
ace_body_length = body.to_binary_s.length
ace_body_length = ace_body.to_binary_s.length
return ace_size - (ace_header_length + ace_body_length)
end
end
@@ -222,6 +439,152 @@ module Rex::Proto::MsDtyp
# Type 16 aka 0x10 is reserved for future use
string :default, read_length: -> { header.ace_size - body.rel_offset }
end
def to_sddl_text(domain_sid: nil)
parts = []
case header.ace_type
when MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
parts << 'A'
when MsDtypAceType::ACCESS_DENIED_ACE_TYPE
parts << 'D'
when MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
parts << 'OA'
when MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
parts << 'OD'
when MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
parts << 'AU'
when MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
parts << 'OU'
else
raise SDDLParseError.new('unknown ACE type: ' + header.ace_type.to_i)
end
ace_flags = ''
ace_flags << 'OI' if header.ace_flags.object_inherit_ace == 1
ace_flags << 'CI' if header.ace_flags.container_inherit_ace == 1
ace_flags << 'IO' if header.ace_flags.inherit_only_ace == 1
ace_flags << 'NP' if header.ace_flags.no_propagate_inherit_ace == 1
ace_flags << 'ID' if header.ace_flags.inherited_ace == 1
ace_flags << 'SA' if header.ace_flags.successful_access_ace_flag == 1
ace_flags << 'FA' if header.ace_flags.failed_access_ace_flag == 1
ace_flags << 'CR' if header.ace_flags.critical_ace_flag == 1
parts << ace_flags
parts << body.access_mask.to_sddl_text
if body[:flags]
parts << (body.flags[:ace_object_type_present] == 1 ? body.object_type.to_s : '')
parts << (body.flags[:ace_inherited_object_type_present] == 1 ? body.inherited_object_type.to_s : '')
else
parts << ''
parts << ''
end
if body.sid?
parts << body.sid.to_sddl_text(domain_sid: domain_sid)
else
parts << ''
end
parts.join(';')
end
def self.from_sddl_text(sddl_text, domain_sid:)
parts = sddl_text.upcase.split(';', -1)
raise SDDLParseError.new('too few ACE fields') if parts.length < 6
raise SDDLParseError.new('too many ACE fields') if parts.length > 7
ace_type, ace_flags, rights, object_guid, inherit_object_guid, account_sid = parts[0...6]
resource_attribute = parts[6]
ace = self.new
case ace_type
when 'A'
ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
when 'D'
ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_ACE_TYPE
when 'OA'
ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
when 'OD'
ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
when 'AU'
ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
when 'OU'
ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
when 'AL', 'OL', 'ML', 'XA', 'SD', 'RA', 'SP', 'XU', 'ZA', 'TL', 'FL'
raise SDDLParseError.new('unsupported ACE type: ' + ace_type)
else
raise SDDLParseError.new('unknown ACE type: ' + ace_type)
end
ace_flags.split(/(CI|OI|NP|IO|ID|SA|FA|TP|CR)/).each do |flag|
case flag
when 'CI'
ace.header.ace_flags.container_inherit_ace = true
when 'OI'
ace.header.ace_flags.object_inherit_ace = true
when 'NP'
ace.header.ace_flags.no_propagate_inherit_ace = true
when 'IO'
ace.header.ace_flags.inherit_only_ace = true
when 'ID'
ace.header.ace_flags.inherited_ace = true
when 'SA'
ace.header.ace_flags.successful_access_ace_flag = true
when 'FA'
ace.header.ace_flags.failed_access_ace_flag = true
when 'TP'
raise SDDLParseError.new('unsupported ACE flag: TP')
when 'CR'
ace.header.ace_flags.critical_ace_flag = true
when ''
else
raise SDDLParseError.new('unknown ACE flag: ' + flag)
end
end
ace.body.access_mask = MsDtypAccessMask.from_sddl_text(rights)
unless object_guid.blank?
begin
guid = MsDtypGuid.new(object_guid)
rescue StandardError
raise SDDLParseError.new('invalid object GUID: ' + object_guid)
end
unless ace.body.respond_to?('object_type=')
raise SDDLParseError.new('setting object type for incompatible ACE type')
end
ace.body.flags.ace_object_type_present = true
ace.body.object_type = guid
end
unless inherit_object_guid.blank?
begin
guid = MsDtypGuid.new(inherit_object_guid)
rescue StandardError
raise SDDLParseError.new('invalid inherited object GUID: ' + inherit_object_guid)
end
unless ace.body.respond_to?('inherited_object_type=')
raise SDDLParseError.new('setting inherited object type for incompatible ACE type')
end
ace.body.flags.ace_inherited_object_type_present = true
ace.body.inherited_object_type = guid
end
unless account_sid.blank?
ace.body.sid = MsDtypSid.from_sddl_text(account_sid, domain_sid: domain_sid)
end
unless resource_attribute.blank?
raise SDDLParseError.new('unsupported resource attribute: ' + resource_attribute)
end
ace
end
end
# [2.4.5 ACL](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/20233ed8-a6c6-4097-aafa-dd545ed24428)
@@ -271,6 +634,46 @@ module Rex::Proto::MsDtyp
rest :buffer, value: -> { build_buffer }
hide :buffer
def to_sddl_text(domain_sid: nil)
sddl_text = ''
sddl_text << "O:#{owner_sid.to_sddl_text(domain_sid: domain_sid)}" if owner_sid?
sddl_text << "G:#{group_sid.to_sddl_text(domain_sid: domain_sid)}" if group_sid?
sddl_text << "D:#{dacl_to_sddl_text(domain_sid: domain_sid)}" if dacl?
sddl_text << "S:#{sacl_to_sddl_text(domain_sid: domain_sid)}" if sacl?
sddl_text
end
def dacl_to_sddl_text(domain_sid: nil)
sddl_text = ''
if !dacl?
sddl_text << 'NO_ACCESS_CONTROL'
else
sddl_text << 'P' if control.pd == 1
sddl_text << 'AR' if control.dc == 1
sddl_text << 'AI' if control.di == 1
sddl_text << dacl.aces.map { |ace| "(#{ace.to_sddl_text(domain_sid: domain_sid)})" }.join
end
sddl_text
end
def sacl_to_sddl_text(domain_sid: nil)
sddl_text = ''
if !sacl?
sddl_text << 'NO_ACCESS_CONTROL'
else
sddl_text << 'P' if control.ps == 1
sddl_text << 'AR' if control.sc == 1
sddl_text << 'AI' if control.si == 1
sddl_text << sacl.aces.map { |ace| "(#{ace.to_sddl_text(domain_sid: domain_sid)})" }.join
end
sddl_text
end
def self.from_sddl_text(sddl_text, domain_sid:)
sacl_set = dacl_set = false
sd = self.new
@@ -280,18 +683,18 @@ module Rex::Proto::MsDtyp
case component
when 'O'
if sd.owner_sid.present?
raise RuntimeError.new('SDDL parse error on extra owner SID')
raise SDDLParseError.new('extra owner SID')
end
sd.owner_sid = self.parse_sddl_sid(value, domain_sid: domain_sid)
sd.owner_sid = MsDtypSid.from_sddl_text(value, domain_sid: domain_sid)
when 'G'
if sd.group_sid.present?
raise RuntimeError.new('SDDL parse error on extra group SID')
raise SDDLParseError.new('extra group SID')
end
sd.group_sid = self.parse_sddl_sid(value, domain_sid: domain_sid)
sd.group_sid = MsDtypSid.from_sddl_text(value, domain_sid: domain_sid)
when 'D'
raise RuntimeError.new('SDDL parse error on extra DACL') if dacl_set
raise SDDLParseError.new('extra DACL') if dacl_set
value.upcase!
dacl_set = true
@@ -309,16 +712,16 @@ module Rex::Proto::MsDtyp
access_control = false
when ''
else
raise RuntimeError.new('SDDL parse error on unknown DACL flag: ' + flag)
raise SDDLParseError.new('unknown DACL flag: ' + flag)
end
end
next unless access_control
sd.dacl = MsDtypAcl.new
sd.dacl.aces = self.parse_sddl_aces(value.delete_prefix(flags), domain_sid: domain_sid)
sd.dacl.aces = self.aces_from_sddl_text(value.delete_prefix(flags), domain_sid: domain_sid)
when 'S'
raise RuntimeError.new('SDDL parse error on extra SACL') if sacl_set
raise SDDLParseError.new('extra SACL') if sacl_set
value.upcase!
sacl_set = true
@@ -336,16 +739,16 @@ module Rex::Proto::MsDtyp
access_control = false
when ''
else
raise RuntimeError.new('SDDL parse error on unknown SACL flag: ' + flag)
raise SDDLParseError.new('unknown SACL flag: ' + flag)
end
end
next unless access_control
sd.sacl = MsDtypAcl.new
sd.sacl.aces = self.parse_sddl_aces(value.delete_prefix(flags), domain_sid: domain_sid)
sd.sacl.aces = self.aces_from_sddl_text(value.delete_prefix(flags), domain_sid: domain_sid)
else
raise RuntimeError.new('SDDL parse error on unknown directive: ' + part[0])
raise SDDLParseError.new('unknown directive: ' + part[0])
end
end
@@ -355,321 +758,18 @@ module Rex::Proto::MsDtyp
class << self
private
def parse_sddl_ace(ace, domain_sid:)
parts = ace.upcase.split(';', -1)
raise RuntimeError.new('SDDL parse error on too few ACE fields') if parts.length < 6
raise RuntimeError.new('SDDL parse error on too many ACE fields') if parts.length > 7
ace_type, ace_flags, rights, object_guid, inherit_object_guid, account_sid = parts[0...6]
resource_attribute = parts[6]
ace = MsDtypAce.new
case ace_type
when 'A'
ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
when 'D'
ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_ACE_TYPE
when 'OA'
ace.header.ace_type = MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
when 'OD'
ace.header.ace_type = MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
when 'AU'
ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
when 'OU'
ace.header.ace_type = MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
when 'AL', 'OL', 'ML', 'XA', 'SD', 'RA', 'SP', 'XU', 'ZA', 'TL', 'FL'
raise RuntimeError.new('SDDL parse error on unsupported ACE type: ' + ace_type)
else
raise RuntimeError.new('SDDL parse error on unknown ACE type: ' + ace_type)
end
ace_flags.split(/(CI|OI|NP|IO|ID|SA|FA|TP|CR)/).each do |flag|
case flag
when 'CI'
ace.header.ace_flags.container_inherit_ace = true
when 'OI'
ace.header.ace_flags.object_inherit_ace = true
when 'NP'
ace.header.ace_flags.no_propagate_inherit_ace = true
when 'IO'
ace.header.ace_flags.inherit_only_ace = true
when 'ID'
ace.header.ace_flags.inherited_ace = true
when 'SA'
ace.header.ace_flags.successful_access_ace_flag = true
when 'FA'
ace.header.ace_flags.failed_access_ace_flag = true
when 'TP'
raise RuntimeError.new('SDDL parse error on unsupported ACE flag: TP')
when 'CR'
ace.header.ace_flags.critical_ace_flag = true
when ''
else
raise RuntimeError.new('SDDL parse error on unknown ACE flag: ' + flag)
end
end
rights.split(/(G[ARWX]|RC|SD|WD|WO|RP|WP|CC|DC|LC|SW|LO|DT|CR|F[ARWX]|K[ARWX]|N[RWX])/).each do |right|
case right
# generic access rights
when 'GA', 'GR', 'GW', 'GX'
ace.body.access_mask.send("#{right.downcase}=", true)
# standard access rights
when 'RC'
ace.body.access_mask.rc = true
when 'SD'
ace.body.access_mask.de = true
when 'WD', 'WO'
ace.body.access_mask.send("#{right.downcase}=", true)
# directory service object access rights
when 'RP'
ace.body.access_mask.protocol |= 16
when 'WP'
ace.body.access_mask.protocol |= 32
when 'CC'
ace.body.access_mask.protocol |= 1
when 'DC'
ace.body.access_mask.protocol |= 2
when 'LC'
ace.body.access_mask.protocol |= 4
when 'SW'
ace.body.access_mask.protocol |= 8
when 'LO'
ace.body.access_mask.protocol |= 128
when 'DT'
ace.body.access_mask.protocol |= 64
when 'CR'
ace.body.access_mask.protocol |= 256
# file access rights
when 'FA'
ace.body.access_mask.protocol |= 0x1ff
ace.body.access_mask.de = true
ace.body.access_mask.rc = true
ace.body.access_mask.wd = true
ace.body.access_mask.wo = true
ace.body.access_mask.sy = true
when 'FR'
ace.body.access_mask.protocol |= 0x89
when 'FW'
ace.body.access_mask.protocol |= 0x116
when 'FX'
ace.body.access_mask.protocol |= 0xa0
# registry key access rights
when 'KA'
ace.body.access_mask.protocol |= 0x3f
ace.body.access_mask.de = true
ace.body.access_mask.rc = true
ace.body.access_mask.wd = true
ace.body.access_mask.wo = true
when 'KR'
ace.body.access_mask.protocol |= 0x19
when 'KW'
ace.body.access_mask.protocol |= 0x06
when 'KX'
ace.body.access_mask.protocol |= 0x19
when 'NR', 'NW', 'NX'
raise RuntimeError.new('SDDL parse error on unsupported ACE access right: ' + right)
when ''
else
raise RuntimeError.new('SDDL parse error on unknown ACE access right: ' + right)
end
end
unless object_guid.blank?
begin
guid = MsDtypGuid.new(object_guid)
rescue StandardError
raise RuntimeError.new('SDDL parse error on invalid object GUID: ' + object_guid)
end
unless ace.body.respond_to?('object_type=')
raise RuntimeError.new('SDDL error on setting object type for incompatible ACE type')
end
ace.body.flags.ace_object_type_present = true
ace.body.object_type = guid
end
unless inherit_object_guid.blank?
begin
guid = MsDtypGuid.new(inherit_object_guid)
rescue StandardError
raise RuntimeError.new('SDDL parse error on invalid object GUID: ' + inherit_object_guid)
end
unless ace.body.respond_to?('inherited_object_type=')
raise RuntimeError.new('SDDL error on setting object type for incompatible ACE type')
end
ace.body.flags.ace_inherited_object_type_present = true
ace.body.inherited_object_type = guid
end
unless account_sid.blank?
ace.body.sid = self.parse_sddl_sid(account_sid, domain_sid: domain_sid)
end
unless resource_attribute.blank?
raise RuntimeError.new('SDDL parse error on unsupported resource attribute: ' + resource_attribute)
end
ace
end
def parse_sddl_aces(aces, domain_sid:)
def aces_from_sddl_text(aces, domain_sid:)
ace_regex = /\([^\)]*\)/
invalid_aces = aces.split(ace_regex).reject(&:empty?)
unless invalid_aces.empty?
raise RuntimeError.new('SDDL parse error on malformed ACE: ' + invalid_aces.first)
raise SDDLParseError.new('malformed ACE: ' + invalid_aces.first)
end
aces.scan(ace_regex).map do |ace_text|
self.parse_sddl_ace(ace_text[1...-1], domain_sid: domain_sid)
MsDtypAce.from_sddl_text(ace_text[1...-1], domain_sid: domain_sid)
end
end
def parse_sddl_sid(sid, domain_sid:)
# see: https://learn.microsoft.com/en-us/windows/win32/secauthz/sid-strings
sid = sid.dup.upcase
# these can be validated using powershell where ?? is the code
# (ConvertFrom-SddlString -Sddl "O:??").RawDescriptor.Owner
case sid
when 'AA' # SDDL_ACCESS_CONTROL_ASSISTANCE_OPS
sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCESS_CONTROL_ASSISTANCE_OPS
when 'AC' # SDDL_ALL_APP_PACKAGES
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_ALL_APP_PACKAGES
when 'AN' # SDDL_ANONYMOUS
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_ANONYMOUS_LOGON_SID
when 'AO' # SDDL_ACCOUNT_OPERATORS
sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ACCOUNT_OPS
when 'AP' # SDDL_PROTECTED_USERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_PROTECTED_USERS}"
when 'AU' # SDDL_AUTHENTICATED_USERS
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
when 'BA' # SDDL_BUILTIN_ADMINISTRATORS
sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS
when 'BG' # SDDL_BUILTIN_GUESTS
sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_GUESTS
when 'BO' # SDDL_BACKUP_OPERATORS
sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_BACKUP_OPS
when 'BU' # SDDL_BUILTIN_USERS
sid = Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_USERS
when 'CA' # SDDL_CERT_SERV_ADMINISTRATORS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CERT_ADMINS}"
when 'CD' # SDDL_CERTSVC_DCOM_ACCESS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_CERTSVC_DCOM_ACCESS_GROUP}"
when 'CG' # SDDL_CREATOR_GROUP
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_GROUP_SID
when 'CN' # SDDL_CLONEABLE_CONTROLLERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CLONEABLE_CONTROLLERS}"
when 'CO' # SDDL_CREATOR_OWNER
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_OWNER_SID
when 'CY' # SDDL_CRYPTO_OPERATORS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_CRYPTO_OPERATORS}"
when 'DA' # SDDL_DOMAIN_ADMINISTRATORS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}"
when 'DC' # SDDL_DOMAIN_COMPUTERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_COMPUTERS}"
when 'DD' # SDDL_DOMAIN_DOMAIN_CONTROLLERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_CONTROLLERS}"
when 'DG' # SDDL_DOMAIN_GUESTS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_GUESTS}"
when 'DU' # SDDL_DOMAIN_USERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_USERS}"
when 'EA' # SDDL_ENTERPRISE_ADMINS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_ADMINS}"
when 'ED' # SDDL_ENTERPRISE_DOMAIN_CONTROLLERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_ENTERPRISE_CONTROLLERS_SID}"
when 'EK' # SDDL_ENTERPRISE_KEY_ADMINS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_KEY_ADMINS}"
when 'ER' # SDDL_EVENT_LOG_READERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_EVENT_LOG_READERS_GROUP}"
when 'ES' # SDDL_RDS_ENDPOINT_SERVERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RDS_ENDPOINT_SERVERS}"
when 'HA' # SDDL_HYPER_V_ADMINS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_HYPER_V_ADMINS}"
when 'HI' # SDDL_ML_HIGH
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_HIGH_RID}"
when 'IS' # SDDL_IIS_USERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_IUSERS}"
when 'IU' # SDDL_INTERACTIVE
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_INTERACTIVE_SID
when 'KA' # SDDL_KEY_ADMINS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_KEY_ADMINS}"
when 'LA' # SDDL_LOCAL_ADMIN
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_ADMIN}"
when 'LG' # SDDL_LOCAL_GUEST
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_USER_RID_GUEST}"
when 'LS' # SDDL_LOCAL_SERVICE
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SERVICE_SID
when 'LU' # SDDL_PERFLOG_USERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_LOGGING_USERS}"
when 'LW' # SDDL_ML_LOW
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_LOW_RID}"
when 'ME' # SDDL_ML_MEDIUM
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_RID}"
when 'MP' # SDDL_ML_MEDIUM_PLUS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_MEDIUM_PLUS_RID}"
when 'MU' # SDDL_PERFMON_USERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_MONITORING_USERS}"
when 'NO' # SDDL_NETWORK_CONFIGURATION_OPS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_NETWORK_CONFIGURATION_OPS}"
when 'NS' # SDDL_NETWORK_SERVICE
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SERVICE_SID
when 'NU' # SDDL_NETWORK
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_NETWORK_SID
when 'OW' # SDDL_OWNER_RIGHTS
sid = "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_CREATOR_SID_AUTHORITY}-4"
when 'PA' # SDDL_GROUP_POLICY_ADMINS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_POLICY_ADMINS}"
when 'PO' # SDDL_PRINTER_OPERATORS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_PRINT_OPS}"
when 'PS' # SDDL_PERSONAL_SELF
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_PRINCIPAL_SELF_SID
when 'PU' # SDDL_POWER_USERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_POWER_USERS}"
when 'RA' # SDDL_RDS_REMOTE_ACCESS_SERVERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RDS_REMOTE_ACCESS_SERVERS}"
when 'RC' # SDDL_RESTRICTED_CODE
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_RESTRICTED_CODE_SID
when 'RD' # SDDL_REMOTE_DESKTOP
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS}"
when 'RE' # SDDL_REPLICATOR
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REPLICATOR}"
when 'RM' # SDDL_RMS__SERVICE_OPERATORS
sid = "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_BUILTIN_DOMAIN_SID}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_REMOTE_MANAGEMENT_USERS}"
when 'RO' # SDDL_ENTERPRISE_RO_DCs
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ENTERPRISE_READONLY_DOMAIN_CONTROLLERS}"
when 'RS' # SDDL_RAS_SERVERS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_RAS_SERVERS}"
when 'RU' # SDDL_ALIAS_PREW2KCOMPACC
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_PREW2KCOMPACCESS}"
when 'SA' # SDDL_SCHEMA_ADMINISTRATORS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_SCHEMA_ADMINS}"
when 'SI' # SDDL_ML_SYSTEM
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_MANDATORY_SYSTEM_SID
when 'SO' # SDDL_SERVER_OPERATORS
sid = "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_RID_SYSTEM_OPS}"
when 'SS' # SDDL_SERVICE_ASSERTED
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATION_SERVICE_ASSERTED_SID
when 'SU' # SDDL_SERVICE
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_SERVICE_SID
when 'SY' # SDDL_LOCAL_SYSTEM
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_LOCAL_SYSTEM_SID
when 'UD' # SDDL_USER_MODE_DRIVERS
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_USERMODEDRIVERHOST_ID_BASE_SID
when 'WD' # SDDL_EVERYONE
sid = "#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_SID_AUTHORITY}-#{Rex::Proto::Secauthz::WellKnownSids::SECURITY_WORLD_RID}"
when 'WR' # SDDL_WRITE_RESTRICTED_CODE
sid = Rex::Proto::Secauthz::WellKnownSids::SECURITY_WRITE_RESTRICTED_CODE_SID
when /^S(-\d+)+/
else
raise RuntimeError, 'SDDL parse error on invalid SID string: ' + sid
end
MsDtypSid.new(sid)
end
end
def initialize_shared_instance
@@ -683,10 +783,10 @@ module Rex::Proto::MsDtyp
def initialize_instance
value = super
@owner_sid = get_parameter(:owner_sid)
@group_sid = get_parameter(:group_sid)
@sacl = get_parameter(:sacl)
@dacl = get_parameter(:dacl)
self.owner_sid = get_parameter(:owner_sid)
self.group_sid = get_parameter(:group_sid)
self.sacl = get_parameter(:sacl)
self.dacl = get_parameter(:dacl)
value
end
@@ -716,16 +816,29 @@ module Rex::Proto::MsDtyp
snap
end
attr_accessor :owner_sid, :group_sid, :sacl, :dacl
def owner_sid=(sid)
sid = MsDtypSid.new(sid) unless sid.nil? || sid.is_a?(MsDtypSid)
@owner_sid = sid
end
def group_sid=(sid)
sid = MsDtypSid.new(sid) unless sid.nil? || sid.is_a?(MsDtypSid)
@group_sid = sid
end
attr_accessor :sacl, :dacl
attr_reader :owner_sid, :group_sid
private
BUFFER_FIELD_ORDER = %i[ sacl dacl owner_sid group_sid ]
def build_buffer
buf = ''
buf << owner_sid.to_binary_s if owner_sid
buf << group_sid.to_binary_s if group_sid
buf << sacl.to_binary_s if sacl
buf << dacl.to_binary_s if dacl
BUFFER_FIELD_ORDER.each do |field_name|
field_value = send(field_name)
buf << field_value.to_binary_s if field_value
end
buf
end
@@ -739,7 +852,7 @@ module Rex::Proto::MsDtyp
return 0 unless instance_variable_get("@#{field}")
offset = buffer.rel_offset
%i[ owner_sid group_sid sacl dacl ].each do |cursor|
BUFFER_FIELD_ORDER.each do |cursor|
break if cursor == field
cursor = instance_variable_get("@#{cursor}")
+1 -1
View File
@@ -42,7 +42,7 @@ module Rex::Proto::Secauthz
SECURITY_BUILTIN_DOMAIN_SID = "#{SECURITY_NT_AUTHORITY}-32"
SECURITY_WRITE_RESTRICTED_CODE_SID = "#{SECURITY_NT_AUTHORITY}-33"
SECURITY_USERMODEDRIVERHOST_ID_BASE_SID = "#{SECURITY_NT_AUTHORITY}-0"
SECURITY_USERMODEDRIVERHOST_ID_BASE_SID = "S-1-5-84-0-0-0-0-0"
SECURITY_ALL_APP_PACKAGES = 'S-1-15-2-1'
SECURITY_MANDATORY_SYSTEM_SID = 'S-1-16-16384'
SECURITY_AUTHENTICATION_SERVICE_ASSERTED_SID = "S-1-18-2"
@@ -339,11 +339,21 @@ class MetasploitModule < Msf::Auxiliary
obj, stored = get_certificate_template
print_status('Certificate Template:')
print_status(" distinguishedName: #{obj['distinguishedname'].first}")
print_status(" displayName: #{obj['displayname'].first}") if obj['displayname'].present?
print_status(" distinguishedName: #{obj['distinguishedname'].first}")
print_status(" displayName: #{obj['displayname'].first}") if obj['displayname'].present?
if obj['objectguid'].first.present?
object_guid = Rex::Proto::MsDtyp::MsDtypGuid.read(obj['objectguid'].first)
print_status(" objectGUID: #{object_guid}")
print_status(" objectGUID: #{object_guid}")
end
if obj['ntsecuritydescriptor'].first.present?
begin
sd = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(obj['ntsecuritydescriptor'].first)
sddl_text = sd.to_sddl_text(domain_sid: get_domain_sid)
rescue StandardError => e
elog('failed to parse a binary security descriptor to SDDL', error: e)
else
print_status(" nTSecurityDescriptor: #{sddl_text}")
end
end
pki_flag = obj['flags']&.first
+39 -14
View File
@@ -167,12 +167,16 @@ class MetasploitModule < Msf::Auxiliary
def action_read(obj)
security_descriptor = obj[ATTRIBUTE]
if security_descriptor.nil?
print_status('The msDS-AllowedToActOnBehalfOfOtherIdentity field is empty.')
print_status("The #{ATTRIBUTE} field is empty.")
return
end
if (sddl = sd_to_sddl(security_descriptor))
vprint_status("#{ATTRIBUTE}: #{sddl}")
end
if security_descriptor.dacl.nil?
print_status('The msDS-AllowedToActOnBehalfOfOtherIdentity DACL field is empty.')
print_status("The #{ATTRIBUTE} DACL field is empty.")
return
end
@@ -211,22 +215,22 @@ class MetasploitModule < Msf::Auxiliary
security_descriptor.dacl.acl_size.clear
unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
fail_with_ldap_error('Failed to update the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
fail_with_ldap_error("Failed to update the #{ATTRIBUTE} attribute.")
end
print_good('Successfully updated the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
print_good("Successfully updated the #{ATTRIBUTE} attribute.")
end
def action_flush(obj)
unless obj[ATTRIBUTE]
print_status('The msDS-AllowedToActOnBehalfOfOtherIdentity field is empty. No changes are necessary.')
print_status("The #{ATTRIBUTE} field is empty. No changes are necessary.")
return
end
unless @ldap.delete_attribute(obj['dn'], ATTRIBUTE)
fail_with_ldap_error('Failed to deleted the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
fail_with_ldap_error("Failed to deleted the #{ATTRIBUTE} attribute.")
end
print_good('Successfully deleted the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
print_good("Successfully deleted the #{ATTRIBUTE} attribute.")
end
def action_write(obj)
@@ -239,26 +243,37 @@ class MetasploitModule < Msf::Auxiliary
end
def _action_write_create(obj, delegate_from)
vprint_status("Creating new #{ATTRIBUTE}...")
security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.new
security_descriptor.owner_sid = Rex::Proto::MsDtyp::MsDtypSid.new('S-1-5-32-544')
security_descriptor.dacl = Rex::Proto::MsDtyp::MsDtypAcl.new
security_descriptor.dacl.acl_revision = Rex::Proto::MsDtyp::MsDtypAcl::ACL_REVISION_DS
security_descriptor.dacl.aces << build_ace(delegate_from['ObjectSid'])
unless @ldap.add_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
fail_with_ldap_error('Failed to create the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
if (sddl = sd_to_sddl(security_descriptor))
vprint_status("New #{ATTRIBUTE}: #{sddl}")
end
print_good('Successfully created the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
unless @ldap.add_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
fail_with_ldap_error("Failed to create the #{ATTRIBUTE} attribute.")
end
print_good("Successfully created the #{ATTRIBUTE} attribute.")
print_status('Added account:')
print_status(" #{delegate_from['ObjectSid']} (#{delegate_from['sAMAccountName']})")
end
def _action_write_update(obj, delegate_from)
vprint_status("Updating existing #{ATTRIBUTE}...")
security_descriptor = obj[ATTRIBUTE]
if (sddl = sd_to_sddl(security_descriptor))
vprint_status("Old #{ATTRIBUTE}: #{sddl}")
end
if security_descriptor.dacl
if security_descriptor.dacl.aces.any? { |ace| ace.body[:sid].to_s == delegate_from['ObjectSid'].to_s }
print_status("Delegation from #{delegate_from['sAMAccountName']} to #{obj['sAMAccountName']} is already enabled.")
print_status("Delegation from #{delegate_from['sAMAccountName']} to #{obj['sAMAccountName']} is already configured.")
end
# clear these fields so they'll be calculated automatically after the update
security_descriptor.dacl.acl_count.clear
@@ -271,10 +286,20 @@ class MetasploitModule < Msf::Auxiliary
security_descriptor.dacl.aces << build_ace(delegate_from['ObjectSid'])
unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
fail_with_ldap_error('Failed to update the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
if (sddl = sd_to_sddl(security_descriptor))
vprint_status("New #{ATTRIBUTE}: #{sddl}")
end
print_good('Successfully updated the msDS-AllowedToActOnBehalfOfOtherIdentity attribute.')
unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, security_descriptor.to_binary_s)
fail_with_ldap_error("Failed to update the #{ATTRIBUTE} attribute.")
end
print_good("Successfully updated the #{ATTRIBUTE} attribute.")
end
def sd_to_sddl(sd)
sd.to_sddl_text
rescue StandardError => e
elog('failed to parse a binary security descriptor to SDDL', error: e)
end
end
+33
View File
@@ -0,0 +1,33 @@
# -*- coding:binary -*-
require 'spec_helper'
RSpec.describe Rex::Crypto do
describe '.bytes_to_int' do
it 'converts an empty byte correctly' do
expect(subject.bytes_to_int("".b)).to eq(0)
end
it 'converts a single null byte correctly' do
expect(subject.bytes_to_int("\x00".b)).to eq(0)
end
it 'converts a single non-null byte correctly' do
expect(subject.bytes_to_int("\x01".b)).to eq(1)
end
it 'converts multiple bytes correctly' do
expect(subject.bytes_to_int("\x01\x02\x03\x04".b)).to eq(16909060)
end
end
describe '.int_to_bytes' do
it 'converts 0 to an empty byte' do
expect(subject.int_to_bytes(0)).to eq("".b)
end
it 'converts to bytes correctly' do
expect(subject.int_to_bytes(1)).to eq("\x01".b)
expect(subject.int_to_bytes(16909060)).to eq("\x01\x02\x03\x04".b)
end
end
end
@@ -0,0 +1,99 @@
RSpec.describe Rex::Proto::MsDtyp::MsDtypAccessMask do
subject(:instance) { described_class.from_sddl_text(sddl_text) }
describe '.from_sddl_text' do
it 'raises an exception on invalid flags' do
expect { described_class.from_sddl_text('XX') }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'unknown ACE access right: XX')
end
context 'when the text is FA' do
let(:sddl_text) { 'FA' }
subject(:instance) { described_class.from_sddl_text(sddl_text) }
it 'sets the protocol to 0x1ff' do
expect(instance.protocol).to eq 0x1ff
end
it 'sets the de flag' do
expect(instance.de).to eq 1
end
it 'sets the rc flag' do
expect(instance.rc).to eq 1
end
it 'sets the wd flag' do
expect(instance.wd).to eq 1
end
it 'sets the wo flag' do
expect(instance.wo).to eq 1
end
it 'sets the sy flag' do
expect(instance.sy).to eq 1
end
it 'does not set the ma flag' do
expect(instance.ma).to eq 0
end
it 'does not set the as flag' do
expect(instance.as).to eq 0
end
end
context 'when the text is KA' do
let(:sddl_text) { 'KA' }
it 'sets the protocol to 0x3f' do
expect(instance.protocol).to eq 0x3f
end
it 'sets the de flag' do
expect(instance.de).to eq 1
end
it 'sets the rc flag' do
expect(instance.rc).to eq 1
end
it 'sets the wd flag' do
expect(instance.wd).to eq 1
end
it 'sets the wo flag' do
expect(instance.wo).to eq 1
end
it 'does not set the sy flag' do
expect(instance.sy).to eq 0
end
it 'does not set the ma flag' do
expect(instance.ma).to eq 0
end
it 'does not set the as flag' do
expect(instance.as).to eq 0
end
end
context 'when the text is 0x00001234' do
let(:sddl_text) { '0x00001234' }
it 'sets the protocol to 0x1234' do
expect(instance.protocol).to eq 0x1234
end
end
end
describe '#to_sddl_text' do
context 'when high protocol bits are set' do
subject(:instance) { described_class.new(protocol: 0x1234) }
it 'dumps the value in hex' do
expect(instance.to_sddl_text).to eq "0x00001234"
end
end
end
end
@@ -0,0 +1,314 @@
RSpec.describe Rex::Proto::MsDtyp::MsDtypAce do
let (:domain_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
describe '.from_sddl_text' do
it 'raises an exception on invalid ACEs' do
expect { described_class.from_sddl_text('', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'too few ACE fields')
expect { described_class.from_sddl_text(';;;;', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'too few ACE fields')
expect { described_class.from_sddl_text(';;;;;', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'unknown ACE type: ')
expect { described_class.from_sddl_text(';;;;;;;', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'too many ACE fields')
end
context 'when parsing the ACE type' do
it 'raises an exception on an invalid type' do
expect { described_class.from_sddl_text('X;;;;;', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'unknown ACE type: X')
end
it 'sets the type correctly for A' do
expect(described_class.from_sddl_text('A;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
end
it 'sets the type correctly for A (case-insensitive)' do
expect(described_class.from_sddl_text('a;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
end
it 'sets the type correctly for D' do
expect(described_class.from_sddl_text('D;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE
end
it 'sets the type correctly for OA' do
expect(described_class.from_sddl_text('OA;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
end
it 'sets the type correctly for OD' do
expect(described_class.from_sddl_text('OD;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
end
it 'sets the type correctly for AU' do
expect(described_class.from_sddl_text('AU;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
end
it 'sets the type correctly for OU' do
expect(described_class.from_sddl_text('OU;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
end
end
context 'when parsing the ACE flags' do
it 'raises an exception on invalid flags' do
expect { described_class.from_sddl_text('A;XX;;;;', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'unknown ACE flag: XX')
end
it 'sets no flags by default' do
expect(described_class.from_sddl_text('A;;;;;', domain_sid: domain_sid).header.ace_flags.snapshot.values.sum).to eq 0
end
it 'sets the flag correctly for CI' do
expect(described_class.from_sddl_text('A;CI;;;;', domain_sid: domain_sid).header.ace_flags.container_inherit_ace).to eq 1
end
it 'sets the flag correctly for CI (case-insensitive)' do
expect(described_class.from_sddl_text('A;ci;;;;', domain_sid: domain_sid).header.ace_flags.container_inherit_ace).to eq 1
end
it 'sets the flag correctly for OI' do
expect(described_class.from_sddl_text('A;OI;;;;', domain_sid: domain_sid).header.ace_flags.object_inherit_ace).to eq 1
end
it 'sets the flag correctly for NP' do
expect(described_class.from_sddl_text('A;NP;;;;', domain_sid: domain_sid).header.ace_flags.no_propagate_inherit_ace).to eq 1
end
it 'sets the flag correctly for IO' do
expect(described_class.from_sddl_text('A;IO;;;;', domain_sid: domain_sid).header.ace_flags.inherit_only_ace).to eq 1
end
it 'sets the flag correctly for ID' do
expect(described_class.from_sddl_text('A;ID;;;;', domain_sid: domain_sid).header.ace_flags.inherited_ace).to eq 1
end
it 'sets the flag correctly for SA' do
expect(described_class.from_sddl_text('A;SA;;;;', domain_sid: domain_sid).header.ace_flags.successful_access_ace_flag).to eq 1
end
it 'sets the flag correctly for FA' do
expect(described_class.from_sddl_text('A;FA;;;;', domain_sid: domain_sid).header.ace_flags.failed_access_ace_flag).to eq 1
end
it 'sets the flag correctly for CR' do
expect(described_class.from_sddl_text('A;CR;;;;', domain_sid: domain_sid).header.ace_flags.critical_ace_flag).to eq 1
end
end
context 'when parsing the ACE rights' do
it 'raises an exception on invalid rights' do
expect { described_class.from_sddl_text('A;;XX;;;', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'unknown ACE access right: XX')
end
it 'sets no rights by default' do
expect(described_class.from_sddl_text('A;;;;;', domain_sid: domain_sid).body.access_mask).to eq Rex::Proto::MsDtyp::MsDtypAccessMask::NONE
end
%w[ GA GR GW GX ].each do |right|
it "sets the rights correctly for #{right}" do
expect(described_class.from_sddl_text("A;;#{right};;;", domain_sid: domain_sid).body.access_mask.send(right.downcase)).to eq 1
end
end
it 'sets the rights correctly for FA' do
expect(described_class.from_sddl_text('A;;FA;;;', domain_sid: domain_sid).body.access_mask.protocol).to eq 0x1ff
expect(described_class.from_sddl_text('A;;FA;;;', domain_sid: domain_sid).body.access_mask.de).to eq 1
expect(described_class.from_sddl_text('A;;FA;;;', domain_sid: domain_sid).body.access_mask.rc).to eq 1
expect(described_class.from_sddl_text('A;;FA;;;', domain_sid: domain_sid).body.access_mask.wd).to eq 1
expect(described_class.from_sddl_text('A;;FA;;;', domain_sid: domain_sid).body.access_mask.wo).to eq 1
expect(described_class.from_sddl_text('A;;FA;;;', domain_sid: domain_sid).body.access_mask.sy).to eq 1
end
it 'sets the rights correctly for KA' do
expect(described_class.from_sddl_text('A;;KA;;;', domain_sid: domain_sid).body.access_mask.protocol).to eq 0x3f
expect(described_class.from_sddl_text('A;;KA;;;', domain_sid: domain_sid).body.access_mask.de).to eq 1
expect(described_class.from_sddl_text('A;;KA;;;', domain_sid: domain_sid).body.access_mask.rc).to eq 1
expect(described_class.from_sddl_text('A;;KA;;;', domain_sid: domain_sid).body.access_mask.wd).to eq 1
expect(described_class.from_sddl_text('A;;KA;;;', domain_sid: domain_sid).body.access_mask.wo).to eq 1
end
end
context 'when parsing the ACE object GUID' do
let (:dummy_guid) { SecureRandom.uuid }
it 'raises an exception when the ACE type is incompatible' do
expect { described_class.from_sddl_text("A;;;#{dummy_guid};;", domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets no object GUID by default' do
expect(described_class.from_sddl_text("OA;;;;;", domain_sid: domain_sid).body.flags.ace_object_type_present).to eq 0
expect(described_class.from_sddl_text("OA;;;;;", domain_sid: domain_sid).body.object_type).to eq '00000000-0000-0000-0000-000000000000'
end
it 'sets the object type' do
expect(described_class.from_sddl_text("OA;;;#{dummy_guid};;", domain_sid: domain_sid).body.flags.ace_object_type_present).to eq 1
expect(described_class.from_sddl_text("OA;;;#{dummy_guid};;", domain_sid: domain_sid).body.object_type).to eq dummy_guid
end
end
context 'when parsing the ACE inherited object GUID' do
let (:dummy_guid) { SecureRandom.uuid }
it 'raises an exception when the ACE type is incompatible' do
expect { described_class.from_sddl_text("A;;;;#{dummy_guid};", domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets no inherited object GUID by default' do
expect(described_class.from_sddl_text("OA;;;;;", domain_sid: domain_sid).body.flags.ace_inherited_object_type_present).to eq 0
expect(described_class.from_sddl_text("OA;;;;;", domain_sid: domain_sid).body.inherited_object_type).to eq '00000000-0000-0000-0000-000000000000'
end
it 'sets the inherited object type' do
expect(described_class.from_sddl_text("OA;;;;#{dummy_guid};", domain_sid: domain_sid).body.flags.ace_inherited_object_type_present).to eq 1
expect(described_class.from_sddl_text("OA;;;;#{dummy_guid};", domain_sid: domain_sid).body.inherited_object_type).to eq dummy_guid
end
end
context 'when parsing the ACE SID' do
let (:dummy_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
it 'calls .from_sddl_text' do
expect(Rex::Proto::MsDtyp::MsDtypSid).to receive(:from_sddl_text).with(dummy_sid, domain_sid: domain_sid).and_call_original
expect(described_class.from_sddl_text("A;;;;;#{dummy_sid}", domain_sid: domain_sid).body.sid).to eq dummy_sid
end
end
end
describe '#to_sddl_text' do
subject(:instance) { described_class.new(header: { ace_type: ace_type, ace_flags: ace_flags }) }
let(:sddl_tokens) { instance.to_sddl_text(domain_sid: domain_sid).split(';') }
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE }
let(:ace_flags) { { } }
context 'when type is ACCESS_ALLOWED_ACE_TYPE' do
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE }
it 'sets the first token to A' do
expect(sddl_tokens[0]).to eq 'A'
end
end
context 'when type is ACCESS_DENIED_ACE_TYPE' do
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE }
it 'sets the first token to D' do
expect(sddl_tokens[0]).to eq 'D'
end
end
context 'when type is ACCESS_ALLOWED_OBJECT_ACE_TYPE' do
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE }
it 'sets the first token to OA' do
expect(sddl_tokens[0]).to eq 'OA'
end
end
context 'when type is ACCESS_DENIED_OBJECT_ACE_TYPE' do
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE }
it 'sets the first token to OD' do
expect(sddl_tokens[0]).to eq 'OD'
end
end
context 'when type is SYSTEM_AUDIT_ACE_TYPE' do
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE }
it 'sets the first token to AU' do
expect(sddl_tokens[0]).to eq 'AU'
end
end
context 'when type is SYSTEM_AUDIT_OBJECT_ACE_TYPE' do
let(:ace_type) { Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE }
it 'sets the first token to OU' do
expect(sddl_tokens[0]).to eq 'OU'
end
end
context 'when the object_inherit_ace flag is set' do
let(:ace_flags) { { object_inherit_ace: true } }
it 'sets the second token to OI' do
expect(sddl_tokens[1]).to eq 'OI'
end
end
context 'when the container_inherit_ace flag is set' do
let(:ace_flags) { { container_inherit_ace: true } }
it 'sets the second token to CI' do
expect(sddl_tokens[1]).to eq 'CI'
end
end
context 'when the inherit_only_ace flag is set' do
let(:ace_flags) { { inherit_only_ace: true } }
it 'sets the second token to IO' do
expect(sddl_tokens[1]).to eq 'IO'
end
end
context 'when the no_propagate_inherit_ace flag is set' do
let(:ace_flags) { { no_propagate_inherit_ace: true } }
it 'sets the second token to NP' do
expect(sddl_tokens[1]).to eq 'NP'
end
end
context 'when the inherited_ace flag is set' do
let(:ace_flags) { { inherited_ace: true } }
it 'sets the second token to ID' do
expect(sddl_tokens[1]).to eq 'ID'
end
end
context 'when the successful_access_ace_flag is set' do
let(:ace_flags) { { successful_access_ace_flag: true } }
it 'sets the second token to SA' do
expect(sddl_tokens[1]).to eq 'SA'
end
end
context 'when the failed_access_ace_flag is set' do
let(:ace_flags) { { failed_access_ace_flag: true } }
it 'sets the second token to FA' do
expect(sddl_tokens[1]).to eq 'FA'
end
end
context 'when the critical_ace_flag is set' do
let(:ace_flags) { { critical_ace_flag: true } }
it 'sets the second token to CR' do
expect(sddl_tokens[1]).to eq 'CR'
end
end
context 'when no flags are set' do
let(:ace_flags) { { } }
it 'leaves the second token blank' do
expect(sddl_tokens[1]).to eq ''
end
end
context 'when all flags are set' do
let(:ace_flags) { {
object_inherit_ace: true,
container_inherit_ace: true,
inherit_only_ace: true,
no_propagate_inherit_ace: true,
inherited_ace: true,
successful_access_ace_flag: true,
failed_access_ace_flag: true,
critical_ace_flag: true
} }
it 'sets the second token to OICIIONPIDSAFACR' do
# this order comes from what was observed on Server 2019
expect(sddl_tokens[1]).to eq 'OICIIONPIDSAFACR'
end
end
end
end
@@ -9,24 +9,24 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
let (:dummy_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
it 'raises an exception when multiple owners are specified' do
expect { described_class.from_sddl_text('O:AUO:AU', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('O:AUO:AU', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'extra owner SID')
end
it 'raises an exception on invalid constant SID strings' do
expect { described_class.from_sddl_text('O:XX', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('O:XX', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'invalid SID string: XX')
end
it 'raises an exception on invalid literal SID strings' do
expect { described_class.from_sddl_text('O:S-###', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('O:S-###', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'invalid SID string: S-###')
end
it 'parses constant SID strings' do
expect(described_class).to receive(:parse_sddl_sid).with('AU', domain_sid: domain_sid).and_call_original
expect(Rex::Proto::MsDtyp::MsDtypSid).to receive(:from_sddl_text).with('AU', domain_sid: domain_sid).and_call_original
expect(described_class.from_sddl_text('O:AU', domain_sid: domain_sid).owner_sid).to eq Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
end
it 'parses literal SID strings' do
expect(described_class).to receive(:parse_sddl_sid).with(dummy_sid, domain_sid: domain_sid).and_call_original
expect(Rex::Proto::MsDtyp::MsDtypSid).to receive(:from_sddl_text).with(dummy_sid, domain_sid: domain_sid).and_call_original
expect(described_class.from_sddl_text("O:#{dummy_sid}", domain_sid: domain_sid).owner_sid).to eq dummy_sid
end
end
@@ -35,24 +35,24 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
let (:dummy_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
it 'raises an exception when multiple groups are specified' do
expect { described_class.from_sddl_text('G:AUG:AU', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('G:AUG:AU', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'extra group SID')
end
it 'raises an exception on invalid constant SID strings' do
expect { described_class.from_sddl_text('G:XX', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('G:XX', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'invalid SID string: XX')
end
it 'raises an exception on invalid literal SID strings' do
expect { described_class.from_sddl_text('G:S-###', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('G:S-###', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'invalid SID string: S-###')
end
it 'parses constant SID strings' do
expect(described_class).to receive(:parse_sddl_sid).with('AU', domain_sid: domain_sid).and_call_original
expect(Rex::Proto::MsDtyp::MsDtypSid).to receive(:from_sddl_text).with('AU', domain_sid: domain_sid).and_call_original
expect(described_class.from_sddl_text('G:AU', domain_sid: domain_sid).group_sid).to eq Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
end
it 'parses literal SID strings' do
expect(described_class).to receive(:parse_sddl_sid).with(dummy_sid, domain_sid: domain_sid).and_call_original
expect(Rex::Proto::MsDtyp::MsDtypSid).to receive(:from_sddl_text).with(dummy_sid, domain_sid: domain_sid).and_call_original
expect(described_class.from_sddl_text("G:#{dummy_sid}", domain_sid: domain_sid).group_sid).to eq dummy_sid
end
end
@@ -61,8 +61,8 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
context 'with an empty definitions' do
let(:instance) { described_class.from_sddl_text('D:', domain_sid: domain_sid) }
it 'calls .parse_sddl_aces' do
expect(described_class).to receive(:parse_sddl_aces).with('', domain_sid: domain_sid).and_return([])
it 'calls .aces_from_sddl_text' do
expect(described_class).to receive(:aces_from_sddl_text).with('', domain_sid: domain_sid).and_return([])
described_class.from_sddl_text('D:', domain_sid: domain_sid)
end
@@ -85,7 +85,7 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
end
it 'raises an exception when multiple values are specified' do
expect { described_class.from_sddl_text('D:D:', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('D:D:', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'extra DACL')
end
it 'sets the P flag' do
@@ -109,8 +109,8 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
context 'with an empty definitions' do
let(:instance) { described_class.from_sddl_text('S:', domain_sid: domain_sid) }
it 'calls .parse_sddl_aces' do
expect(described_class).to receive(:parse_sddl_aces).with('', domain_sid: domain_sid).and_return([])
it 'calls .aces_from_sddl_text' do
expect(described_class).to receive(:aces_from_sddl_text).with('', domain_sid: domain_sid).and_return([])
described_class.from_sddl_text('S:', domain_sid: domain_sid)
end
@@ -133,7 +133,7 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
end
it 'raises an exception when multiple values are specified' do
expect { described_class.from_sddl_text('S:S:', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.from_sddl_text('S:S:', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'extra SACL')
end
it 'sets the P flag' do
@@ -154,202 +154,47 @@ RSpec.describe Rex::Proto::MsDtyp::MsDtypSecurityDescriptor do
end
end
describe '.parse_sddl_ace' do
it 'raises an exception on invalid ACEs' do
expect { described_class.send(:parse_sddl_ace, '', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.send(:parse_sddl_ace, ';;;;;', domain_sid: domain_sid) }.to raise_error(RuntimeError)
expect { described_class.send(:parse_sddl_ace, ';;;;;;;', domain_sid: domain_sid) }.to raise_error(RuntimeError)
describe '#to_sddl_text' do
it 'handles a basic owner and group with a simple DACL' do
sddl_text = 'O:BAG:BUD:PAI(A;;FA;;;BA)'
packed = ['01000494140000002400000000000000340000000102000000000005200000002002000001020000000000052000000021020000020020000100000000001800FF011F0001020000000000052000000020020000'].pack('H*')
instance = described_class.read(packed)
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq sddl_text
end
context 'when parsing the ACE type' do
it 'raises an exception on an invalid type' do
expect { described_class.send(:parse_sddl_ace, 'X;;;;;', domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets the type correctly for A' do
expect(described_class.send(:parse_sddl_ace, 'A;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
end
it 'sets the type correctly for A (case-insensitive)' do
expect(described_class.send(:parse_sddl_ace, 'a;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE
end
it 'sets the type correctly for D' do
expect(described_class.send(:parse_sddl_ace, 'D;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_ACE_TYPE
end
it 'sets the type correctly for OA' do
expect(described_class.send(:parse_sddl_ace, 'OA;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_OBJECT_ACE_TYPE
end
it 'sets the type correctly for OD' do
expect(described_class.send(:parse_sddl_ace, 'OD;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_DENIED_OBJECT_ACE_TYPE
end
it 'sets the type correctly for AU' do
expect(described_class.send(:parse_sddl_ace, 'AU;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_ACE_TYPE
end
it 'sets the type correctly for OU' do
expect(described_class.send(:parse_sddl_ace, 'OU;;;;;', domain_sid: domain_sid).header.ace_type).to eq Rex::Proto::MsDtyp::MsDtypAceType::SYSTEM_AUDIT_OBJECT_ACE_TYPE
end
it 'handles a complex DACL with multiple ACEs' do
sddl_text = 'O:S-1-5-21-123456789-123456789-123456789-519G:S-1-5-21-123456789-123456789-123456789-512D:PAI(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)(A;OICIIO;FA;;;CO)'
packed = ['010004941400000030000000000000004C00000001050000000000051500000015CD5B0715CD5B0715CD5B070702000001050000000000051500000015CD5B0715CD5B0715CD5B0700020000020048000300000000031800FF011F000102000000000005200000002002000000031400FF011F00010100000000000512000000000B1400FF011F00010100000000000300000000'].pack('H*')
instance = described_class.read(packed)
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq sddl_text
end
context 'when parsing the ACE flags' do
it 'raises an exception on invalid flags' do
expect { described_class.send(:parse_sddl_ace, 'A;XX;;;;', domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets no flags by default' do
expect(described_class.send(:parse_sddl_ace, 'A;;;;;', domain_sid: domain_sid).header.ace_flags.snapshot.values.sum).to eq 0
end
it 'sets the flag correctly for CI' do
expect(described_class.send(:parse_sddl_ace, 'A;CI;;;;', domain_sid: domain_sid).header.ace_flags.container_inherit_ace).to eq 1
end
it 'sets the flag correctly for CI (case-insensitive)' do
expect(described_class.send(:parse_sddl_ace, 'A;ci;;;;', domain_sid: domain_sid).header.ace_flags.container_inherit_ace).to eq 1
end
it 'sets the flag correctly for OI' do
expect(described_class.send(:parse_sddl_ace, 'A;OI;;;;', domain_sid: domain_sid).header.ace_flags.object_inherit_ace).to eq 1
end
it 'sets the flag correctly for NP' do
expect(described_class.send(:parse_sddl_ace, 'A;NP;;;;', domain_sid: domain_sid).header.ace_flags.no_propagate_inherit_ace).to eq 1
end
it 'sets the flag correctly for IO' do
expect(described_class.send(:parse_sddl_ace, 'A;IO;;;;', domain_sid: domain_sid).header.ace_flags.inherit_only_ace).to eq 1
end
it 'sets the flag correctly for ID' do
expect(described_class.send(:parse_sddl_ace, 'A;ID;;;;', domain_sid: domain_sid).header.ace_flags.inherited_ace).to eq 1
end
it 'sets the flag correctly for SA' do
expect(described_class.send(:parse_sddl_ace, 'A;SA;;;;', domain_sid: domain_sid).header.ace_flags.successful_access_ace_flag).to eq 1
end
it 'sets the flag correctly for FA' do
expect(described_class.send(:parse_sddl_ace, 'A;FA;;;;', domain_sid: domain_sid).header.ace_flags.failed_access_ace_flag).to eq 1
end
it 'sets the flag correctly for CR' do
expect(described_class.send(:parse_sddl_ace, 'A;CR;;;;', domain_sid: domain_sid).header.ace_flags.critical_ace_flag).to eq 1
end
it 'handles a SACL with auditing' do
sddl_text = 'O:BAG:SYS:PAI(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)'
packed = ['010010A8140000002400000030000000000000000102000000000005200000002002000001010000000000051200000002001C000100000002801400FF010F00010100000000000100000000'].pack('H*')
instance = described_class.read(packed)
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq sddl_text
end
context 'when parsing the ACE rights' do
it 'raises an exception on invalid rights' do
expect { described_class.send(:parse_sddl_ace, 'A;;XX;;;', domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets no rights by default' do
expect(described_class.send(:parse_sddl_ace, 'A;;;;;', domain_sid: domain_sid).body.access_mask).to eq Rex::Proto::MsDtyp::MsDtypAccessMask::NONE
end
%w[ GA GR GW GX ].each do |right|
it "sets the rights correctly for #{right}" do
expect(described_class.send(:parse_sddl_ace, "A;;#{right};;;", domain_sid: domain_sid).body.access_mask.send(right.downcase)).to eq 1
end
end
it 'sets the rights correctly for FA' do
expect(described_class.send(:parse_sddl_ace, 'A;;FA;;;', domain_sid: domain_sid).body.access_mask.protocol).to eq 0x1ff
expect(described_class.send(:parse_sddl_ace, 'A;;FA;;;', domain_sid: domain_sid).body.access_mask.de).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;FA;;;', domain_sid: domain_sid).body.access_mask.rc).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;FA;;;', domain_sid: domain_sid).body.access_mask.wd).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;FA;;;', domain_sid: domain_sid).body.access_mask.wo).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;FA;;;', domain_sid: domain_sid).body.access_mask.sy).to eq 1
end
it 'sets the rights correctly for KA' do
expect(described_class.send(:parse_sddl_ace, 'A;;KA;;;', domain_sid: domain_sid).body.access_mask.protocol).to eq 0x3f
expect(described_class.send(:parse_sddl_ace, 'A;;KA;;;', domain_sid: domain_sid).body.access_mask.de).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;KA;;;', domain_sid: domain_sid).body.access_mask.rc).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;KA;;;', domain_sid: domain_sid).body.access_mask.wd).to eq 1
expect(described_class.send(:parse_sddl_ace, 'A;;KA;;;', domain_sid: domain_sid).body.access_mask.wo).to eq 1
end
it 'handles a complex descriptor with both a DACL and a SACL' do
sddl_text = 'O:BAG:SYD:PAI(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)S:AI(AU;SA;FA;;;BA)'
packed = ['0100149C1400000024000000300000005000000001020000000000052000000020020000010100000000000512000000020020000100000002401800FF011F0001020000000000052000000020020000020034000200000000031800FF011F000102000000000005200000002002000000031400FF011F00010100000000000512000000'].pack('H*')
instance = described_class.read(packed)
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq sddl_text
end
context 'when parsing the ACE object GUID' do
let (:dummy_guid) { SecureRandom.uuid }
it 'raises an exception when the ACE type is incompatible' do
expect { described_class.send(:parse_sddl_ace, "A;;;#{dummy_guid};;", domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets no object GUID by default' do
expect(described_class.send(:parse_sddl_ace, "OA;;;;;", domain_sid: domain_sid).body.flags.ace_object_type_present).to eq 0
expect(described_class.send(:parse_sddl_ace, "OA;;;;;", domain_sid: domain_sid).body.object_type).to eq '00000000-0000-0000-0000-000000000000'
end
it 'sets the object type' do
expect(described_class.send(:parse_sddl_ace, "OA;;;#{dummy_guid};;", domain_sid: domain_sid).body.flags.ace_object_type_present).to eq 1
expect(described_class.send(:parse_sddl_ace, "OA;;;#{dummy_guid};;", domain_sid: domain_sid).body.object_type).to eq dummy_guid
end
it 'handles a NULL DACL (everyone denied)' do
sddl_text = 'O:BAG:BAD:'
packed = ['010004801400000024000000000000003400000001020000000000052000000020020000010200000000000520000000200200000200080000000000'].pack('H*')
instance = described_class.read(packed)
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq sddl_text
end
context 'when parsing the ACE inherited object GUID' do
let (:dummy_guid) { SecureRandom.uuid }
it 'raises an exception when the ACE type is incompatible' do
expect { described_class.send(:parse_sddl_ace, "A;;;;#{dummy_guid};", domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'sets no inherited object GUID by default' do
expect(described_class.send(:parse_sddl_ace, "OA;;;;;", domain_sid: domain_sid).body.flags.ace_inherited_object_type_present).to eq 0
expect(described_class.send(:parse_sddl_ace, "OA;;;;;", domain_sid: domain_sid).body.inherited_object_type).to eq '00000000-0000-0000-0000-000000000000'
end
it 'sets the inherited object type' do
expect(described_class.send(:parse_sddl_ace, "OA;;;;#{dummy_guid};", domain_sid: domain_sid).body.flags.ace_inherited_object_type_present).to eq 1
expect(described_class.send(:parse_sddl_ace, "OA;;;;#{dummy_guid};", domain_sid: domain_sid).body.inherited_object_type).to eq dummy_guid
end
end
context 'when parsing the ACE SID' do
let (:dummy_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
it 'calls .parse_sddl_sid' do
expect(described_class).to receive(:parse_sddl_sid).with(dummy_sid, domain_sid: domain_sid).and_call_original
expect(described_class.send(:parse_sddl_ace, "A;;;;;#{dummy_sid}", domain_sid: domain_sid).body.sid).to eq dummy_sid
end
end
end
describe '.parse_sddl_sid' do
let (:dummy_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
it 'raises an exception on invalid SIDs' do
expect { described_class.send(:parse_sddl_sid, 'S-###', domain_sid: domain_sid) }.to raise_error(RuntimeError)
end
it 'parses constant SID strings (AU)' do
expect(described_class.send(:parse_sddl_sid, 'AU', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.send(:parse_sddl_sid, 'AU', domain_sid: domain_sid)).to eq Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
end
it 'parses constant SID strings (DA)' do
expect(described_class.send(:parse_sddl_sid, 'DA', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.send(:parse_sddl_sid, 'DA', domain_sid: domain_sid)).to eq "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}"
end
it 'parses constant SID strings (AU, case-insensitive)' do
expect(described_class.send(:parse_sddl_sid, 'au', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.send(:parse_sddl_sid, 'au', domain_sid: domain_sid)).to eq Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
end
it 'parses constant SID strings (DA, case-insensitive)' do
expect(described_class.send(:parse_sddl_sid, 'da', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.send(:parse_sddl_sid, 'da', domain_sid: domain_sid)).to eq "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}"
end
it 'parses literal SID strings' do
expect(described_class.send(:parse_sddl_sid, dummy_sid, domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.send(:parse_sddl_sid, dummy_sid, domain_sid: domain_sid)).to eq dummy_sid
it 'handles a protected DACL with inheritance disabled' do
sddl_text = 'O:BAG:BAD:PAI(D;CIIO;FA;;;WD)(A;;FA;;;BA)'
packed = ['010004941400000024000000000000003400000001020000000000052000000020020000010200000000000520000000200200000200340002000000010A1400FF011F0001010000000000010000000000001800FF011F0001020000000000052000000020020000'].pack('H*')
instance = described_class.read(packed)
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq sddl_text
end
end
end
@@ -0,0 +1,56 @@
RSpec.describe Rex::Proto::MsDtyp::MsDtypSid do
describe '.from_sddl_text' do
let (:domain_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
let (:dummy_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
it 'raises an exception on invalid SID literals' do
expect { described_class.from_sddl_text('S-###', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'invalid SID string: S-###')
end
it 'raises an exception on invalid SID short codes' do
expect { described_class.from_sddl_text('XX', domain_sid: domain_sid) }.to raise_error(Rex::Proto::MsDtyp::SDDLParseError, 'invalid SID string: XX')
end
it 'parses constant SID strings (AU)' do
expect(described_class.from_sddl_text('AU', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.from_sddl_text('AU', domain_sid: domain_sid)).to eq Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
end
it 'parses constant SID strings (DA)' do
expect(described_class.from_sddl_text('DA', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.from_sddl_text('DA', domain_sid: domain_sid)).to eq "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}"
end
it 'parses constant SID strings (AU, case-insensitive)' do
expect(described_class.from_sddl_text('au', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.from_sddl_text('au', domain_sid: domain_sid)).to eq Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID
end
it 'parses constant SID strings (DA, case-insensitive)' do
expect(described_class.from_sddl_text('da', domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.from_sddl_text('da', domain_sid: domain_sid)).to eq "#{domain_sid}-#{Rex::Proto::Secauthz::WellKnownSids::DOMAIN_GROUP_RID_ADMINS}"
end
it 'parses literal SID strings' do
expect(described_class.from_sddl_text(dummy_sid, domain_sid: domain_sid)).to be_a Rex::Proto::MsDtyp::MsDtypSid
expect(described_class.from_sddl_text(dummy_sid, domain_sid: domain_sid)).to eq dummy_sid
end
end
describe '#to_sddl_text' do
let (:domain_sid) { "S-1-5-21-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(0xf00000..0xffffffff)}-#{rand(100..999)}" }
subject(:instance) { described_class.new("#{domain_sid}-512") }
context 'when the domain_sid argument is passed' do
it 'reduces the SID to the short form' do
expect(instance.to_sddl_text(domain_sid: domain_sid)).to eq 'DA'
end
end
context 'when the domain_sid argument is not passed' do
it 'does not reduce the SID to the short form' do
expect(instance.to_sddl_text).to eq instance.to_s
end
end
end
end