|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +module Msf::Ui::Console::CommandDispatcher::Db::Certs |
| 4 | + # |
| 5 | + # Tab completion for the certs command |
| 6 | + # |
| 7 | + # @param str [String] the string currently being typed before tab was hit |
| 8 | + # @param words [Array<String>] the previously completed words on the command line. words is always |
| 9 | + # at least 1 when tab completion has reached this stage since the command itself has been completed |
| 10 | + def cmd_certs_tabs(str, words) |
| 11 | + if words.length == 1 |
| 12 | + @@certs_opts.option_keys.select { |opt| opt.start_with?(str) } |
| 13 | + end |
| 14 | + end |
| 15 | + |
| 16 | + def cmd_certs_help |
| 17 | + print_line 'List Pkcs12 certificate bundles in the database' |
| 18 | + print_line 'Usage: certs [options] [username[@domain_upn_format]]' |
| 19 | + print_line |
| 20 | + print @@certs_opts.usage |
| 21 | + print_line |
| 22 | + end |
| 23 | + |
| 24 | + @@certs_opts = Rex::Parser::Arguments.new( |
| 25 | + ['-v', '--verbose'] => [false, 'Verbose output'], |
| 26 | + ['-d', '--delete'] => [ false, 'Delete *all* matching pkcs12 entries'], |
| 27 | + ['-h', '--help'] => [false, 'Help banner'], |
| 28 | + ['-i', '--index'] => [true, 'Pkcs12 entry ID(s) to search for, e.g. `-i 1` or `-i 1,2,3` or `-i 1 -i 2 -i 3`'], |
| 29 | + ) |
| 30 | + |
| 31 | + def cmd_certs(*args) |
| 32 | + return unless active? |
| 33 | + |
| 34 | + entries_affected = 0 |
| 35 | + mode = :list |
| 36 | + id_search = [] |
| 37 | + username = nil |
| 38 | + verbose = false |
| 39 | + @@certs_opts.parse(args) do |opt, _idx, val| |
| 40 | + case opt |
| 41 | + when '-h', '--help' |
| 42 | + cmd_certs_help |
| 43 | + return |
| 44 | + when '-v', '--verbose' |
| 45 | + verbose = true |
| 46 | + when '-d', '--delete' |
| 47 | + mode = :delete |
| 48 | + when '-i', '--id' |
| 49 | + id_search = (id_search + val.split(/,\s*|\s+/)).uniq # allows 1 or 1,2,3 or "1 2 3" or "1, 2, 3" |
| 50 | + else |
| 51 | + # Anything that wasn't an option is a username to search for |
| 52 | + username = val |
| 53 | + end |
| 54 | + end |
| 55 | + |
| 56 | + pkcs12_results = pkcs12_search(username: username, id_search: id_search) |
| 57 | + |
| 58 | + print_line('Pkcs12') |
| 59 | + print_line('======') |
| 60 | + |
| 61 | + if mode == :delete |
| 62 | + result = pkcs12_storage.delete_pkcs12(ids: pkcs12_results.map(&:id)) |
| 63 | + entries_affected = result.size |
| 64 | + end |
| 65 | + |
| 66 | + if pkcs12_results.empty? |
| 67 | + print_line('No Pkcs12') |
| 68 | + print_line |
| 69 | + return |
| 70 | + end |
| 71 | + |
| 72 | + if verbose |
| 73 | + pkcs12_results.each.with_index do |pkcs12_result, index| |
| 74 | + print_line "Certificate[#{index}]:" |
| 75 | + print_line pkcs12_result.openssl_pkcs12.certificate.to_s |
| 76 | + print_line pkcs12_result.openssl_pkcs12.certificate.to_text |
| 77 | + print_line |
| 78 | + end |
| 79 | + else |
| 80 | + tbl = Rex::Text::Table.new( |
| 81 | + { |
| 82 | + 'Columns' => ['id', 'username', 'realm', 'subject', 'issuer', 'CA', 'ADCS Template'], |
| 83 | + 'SortIndex' => -1, |
| 84 | + 'WordWrap' => false, |
| 85 | + 'Rows' => pkcs12_results.map do |pkcs12| |
| 86 | + [ |
| 87 | + pkcs12.id, |
| 88 | + pkcs12.username, |
| 89 | + pkcs12.realm, |
| 90 | + pkcs12.openssl_pkcs12.certificate.subject.to_s, |
| 91 | + pkcs12.openssl_pkcs12.certificate.issuer.to_s, |
| 92 | + pkcs12.ca, |
| 93 | + pkcs12.adcs_template |
| 94 | + ] |
| 95 | + end |
| 96 | + } |
| 97 | + ) |
| 98 | + print_line(tbl.to_s) |
| 99 | + end |
| 100 | + |
| 101 | + if mode == :delete |
| 102 | + print_status("Deleted #{entries_affected} #{entries_affected > 1 ? 'entries' : 'entry'}") if entries_affected > 0 |
| 103 | + end |
| 104 | + end |
| 105 | + |
| 106 | + |
| 107 | + # @param [String, nil] username Search for pkcs12 associated with this username |
| 108 | + # @param [Array<Integer>, nil] id_search List of pkcs12 IDs to search for |
| 109 | + # @param [Workspace] workspace to search against |
| 110 | + # @option [Symbol] :workspace The framework.db.workspace to search against (optional) |
| 111 | + # @return [Array<>] |
| 112 | + def pkcs12_search(username: nil, id_search: nil, workspace: framework.db.workspace) |
| 113 | + pkcs12_results = [] |
| 114 | + |
| 115 | + if id_search.present? |
| 116 | + begin |
| 117 | + pkcs12_results += id_search.flat_map do |id| |
| 118 | + pkcs12_storage.pkcs12( |
| 119 | + workspace: workspace, |
| 120 | + id: id |
| 121 | + ) |
| 122 | + end |
| 123 | + rescue ActiveRecord::RecordNotFound => e |
| 124 | + wlog("Record Not Found: #{e.message}") |
| 125 | + print_warning("Not all records with the ids: #{id_search} could be found.") |
| 126 | + print_warning('Please ensure all ids specified are available.') |
| 127 | + end |
| 128 | + elsif username.present? |
| 129 | + realm = nil |
| 130 | + if username.include?('@') |
| 131 | + username, realm = username.split('@', 2) |
| 132 | + end |
| 133 | + pkcs12_results += pkcs12_storage.pkcs12( |
| 134 | + workspace: workspace, |
| 135 | + username: username, |
| 136 | + realm: realm |
| 137 | + ) |
| 138 | + else |
| 139 | + pkcs12_results += pkcs12_storage.pkcs12( |
| 140 | + workspace: workspace |
| 141 | + ) |
| 142 | + end |
| 143 | + |
| 144 | + pkcs12_results.sort_by do |pkcs12| |
| 145 | + [pkcs12.realm, pkcs12.username] |
| 146 | + end |
| 147 | + end |
| 148 | + |
| 149 | + # @return [Msf::Exploit::Remote::Kerberos::Ticket::Storage::ReadWrite] |
| 150 | + def pkcs12_storage |
| 151 | + @pkcs12_storage ||= Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework) |
| 152 | + end |
| 153 | + |
| 154 | +end |
0 commit comments