Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa5eda4876 | |||
| 689fb49b6e | |||
| c1a81ebf5a | |||
| 7e0b3af790 | |||
| 8c24e98fdd | |||
| 1d801225df | |||
| d37039c08f | |||
| b853168a89 | |||
| fcee4db5d0 | |||
| 48c4ce56e4 | |||
| c9dc97c242 | |||
| c979d8d477 | |||
| 31b8fad08f |
+1
-1
@@ -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
@@ -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
@@ -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/
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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]}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user