├── README.md └── weblogicpassworddecryptor.jy /README.md: -------------------------------------------------------------------------------- 1 | WebLogic Server password decryptor 2 | ================================== 3 | 4 | Description 5 | ----------- 6 | A simple script to decrypt stored passwords from Oracle WebLogic Server configuration files. 7 | 8 | Features 9 | -------- 10 | * Supports AES and 3DES encrypted passwords 11 | 12 | Prerequisites 13 | ----- 14 | ### 1. Jython 15 | `apt-get install jython` or download it [here](http://www.jython.org/downloads.html) 16 | 17 | ### 2. BouncyCastle cryptographic provider setup 18 | Download the latest BouncyCastle provider jar file ([such as this one](https://www.bouncycastle.org/fr/download/bcprov-jdk15on-153.jar)) and place it into the following folder: `$JAVA_HOME/jre/lib/ext` (run `$ jython -v` to see which java instance you use) 19 | 20 | ### 3. `SerializedSystemIni.dat` file 21 | The `SerializedSystemIni.dat` file related to your WebLogic domain, as this is a **per-domain file** containing encryption keys. 22 | It is usually stored into the following folder: `~/wls/user_projects/domains//security/` 23 | 24 | ### 4. An encrypted password or `config.xml` file 25 | I guess you might already have one if you came here. 26 | The `config.xml` file is usually stored into the following folder: `~/wls/user_projects/domains//config/` 27 | 28 | Options 29 | ------- 30 | ``` 31 | $ jython weblogicpassworddecryptor.jy -h 32 | Usage: weblogicpassworddecryptor.jy [options] 33 | Version: 1.0 34 | 35 | Options: 36 | -h, --help show this help message and exit 37 | 38 | Mandatory parameters: 39 | -s SERIALIZEDSYSTEMINI_FILE, --serializedsystemini-file=SERIALIZEDSYSTEMINI_FILE 40 | path to the SerializedSystemIni.dat file containing 41 | the machine-unique salt to decrypt the master-key, and 42 | keys to decrypt passwords. Ex. -s 43 | ./SerializedSystemIni.dat 44 | -p ENCRYPTED_PASSWORD, --encrypted-password=ENCRYPTED_PASSWORD 45 | an encrypted password, starting by either {AES} or 46 | {3DES}. Ex. -p 47 | {AES}6ZP0QttrG2K97NXbm9qz+KtaO4xGG8i6DP+2vopT2Cs= 48 | 49 | Optional parameters: 50 | -c CONFIG_XML_FILE, --config-xml-file=CONFIG_XML_FILE 51 | path to a config.xml file containing encrypted 52 | passwords. Ex: -c ./config.xml 53 | -v, --verbosity verbosity level, repeat it to increase the level { -v 54 | INFO, -vv DEBUG } (default verbosity ERROR) 55 | ``` 56 | 57 | Examples 58 | -------- 59 | #### 3DES-encrypted password 60 | ``` 61 | $ jython weblogicpassworddecryptor.jy -s SerializedSystemIni_3DES.dat -p {3DES}vNxF1kIDgtydLoj5offYBQ== 62 | 63 | [+] encrypted: {3DES}vNxF1kIDgtydLoj5offYBQ== 64 | [+] decrypted: Password1 65 | ``` 66 | 67 | #### AES-encrypted passwords from a `config.xml` file 68 | ``` 69 | $ jython weblogicpassworddecryptor.jy -s SerializedSystemIni.dat -c config.xml 70 | 71 | [+] found encrypted value: {AES}ZPtRzLpxmwaFjehjSPzjZfHoOu4DAq/ZKD4IBYmaCA/dm1SGHAgJTkhIP2SJzG4blKGYlkRSwOLxOpNsoOdaBrcZFnsKzN7KKPo+xyq7FhFf2BgvQwzGuykt8Wfb9aQb 72 | [+] encrypted: {AES}ZPtRzLpxmwaFjehjSPzjZfHoOu4DAq/ZKD4IBYmaCA/dm1SGHAgJTkhIP2SJzG4blKGYlkRSwOLxOpNsoOdaBrcZFnsKzN7KKPo+xyq7FhFf2BgvQwzGuykt8Wfb9aQb 73 | [+] decrypted: 0x06a50109696b77f796217ac8435fb9309481c21e145c25925c8a3d5b79c4849c 74 | 75 | [+] found encrypted value: {AES}6ZP0QttrG2K97NXbm9qz+KtaO4xGG8i6DP+2vopT2Cs= 76 | [+] encrypted: {AES}6ZP0QttrG2K97NXbm9qz+KtaO4xGG8i6DP+2vopT2Cs= 77 | [+] decrypted: weblogic2014 78 | 79 | [+] found encrypted value: {AES}QOujDfm62cZk3k32OAlcEzelVPK/zknVPyBivOuNXTvwq2hNLf0fIXJLdbZz12Kv 80 | [+] encrypted: {AES}QOujDfm62cZk3k32OAlcEzelVPK/zknVPyBivOuNXTvwq2hNLf0fIXJLdbZz12Kv 81 | [+] decrypted: 0x157bb1759727a87eca6fb02cfe 82 | ``` 83 | 84 | Changelog 85 | --------- 86 | * version 1.0 - 07/24/2016: Initial commit 87 | 88 | Copyright and license 89 | --------------------- 90 | weblogicpassworddecryptor is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 91 | 92 | weblogicpassworddecryptor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 93 | 94 | See the GNU General Public License for more details. 95 | 96 | You should have received a copy of the GNU General Public License along with weblogicpassworddecryptor. 97 | If not, see http://www.gnu.org/licenses/. 98 | 99 | Greetings 100 | --------- 101 | * Eric Gruber at NetSPI for his [cool post](https://blog.netspi.com/decrypting-weblogic-passwords/) and all previous researchers on this topic 102 | 103 | Contact 104 | ------- 105 | * Thomas Debize < tdebize at mail d0t com > -------------------------------------------------------------------------------- /weblogicpassworddecryptor.jy: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This file is part of weblogicpassworddecryptor. 4 | # 5 | # Copyright (C) 2016, Thomas Debize 6 | # All rights reserved. 7 | # 8 | # weblogicpassworddecryptor is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # weblogicpassworddecryptor is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU Lesser General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public License 19 | # along with weblogicpassworddecryptor. If not, see . 20 | from __future__ import with_statement 21 | 22 | from javax.crypto import * 23 | from javax.crypto.spec import * 24 | from java.security import * 25 | from javax.xml.xpath import * 26 | from org.xml.sax import * 27 | 28 | import sys 29 | import base64 30 | import logging 31 | 32 | # Script version 33 | VERSION = '1.0' 34 | 35 | # OptionParser imports 36 | from optparse import OptionParser 37 | from optparse import OptionGroup 38 | 39 | # Options definition 40 | parser = OptionParser(usage="%prog [options]\nVersion: " + VERSION) 41 | 42 | main_grp = OptionGroup(parser, 'Mandatory parameters') 43 | main_grp.add_option('-s', '--serializedsystemini-file', help = 'path to the SerializedSystemIni.dat file containing the machine-unique salt to decrypt the master-key, and keys to decrypt passwords. Ex. -s ./SerializedSystemIni.dat', nargs = 1) 44 | main_grp.add_option('-p', '--encrypted-password', help = 'an encrypted password, starting by either {AES} or {3DES}. Ex. -p {AES}6ZP0QttrG2K97NXbm9qz+KtaO4xGG8i6DP+2vopT2Cs=', nargs = 1) 45 | 46 | optional_grp = OptionGroup(parser, 'Optional parameters') 47 | optional_grp.add_option('-c', '--config-xml-file', help = 'path to a config.xml file containing encrypted passwords. Ex: -c ./config.xml', nargs = 1, default = None) 48 | optional_grp.add_option('-v', '--verbosity', help = 'verbosity level, repeat it to increase the level { -v INFO, -vv DEBUG } (default verbosity ERROR)', action = 'count', default = 0) 49 | 50 | parser.option_groups.extend([main_grp, optional_grp]) 51 | 52 | # Importing BouncyCastle provider 53 | try: 54 | from org.bouncycastle.jce.provider import BouncyCastleProvider 55 | Security.addProvider(BouncyCastleProvider()) 56 | except: 57 | print "[!] You need to copy the BouncyCastle provider 'bcprov-.jar' into your '$JAVA_HOME/jre/lib/ext' folder to have appropriate cryptographic algorithms" 58 | sys.exit(1) 59 | 60 | # Logger definition 61 | LOGLEVELS = {0 : logging.ERROR, 1 : logging.INFO, 2 : logging.DEBUG} 62 | logger_output = logging.StreamHandler(sys.stdout) 63 | logger_output.setFormatter(logging.Formatter('[%(levelname)s] %(message)s')) 64 | logger = logging.getLogger("General") 65 | logger.addHandler(logger_output) 66 | 67 | def parse_serialized_system_ini(serializedsystemini): 68 | global logger 69 | 70 | salt = None 71 | encryption_key_3des = None 72 | encryption_key_aes = None 73 | 74 | with open(serializedsystemini, 'rb') as fd : 75 | length_salt = int(fd.read(1).encode('hex'), 16) 76 | 77 | salt = fd.read(length_salt) 78 | logger.info('salt length: %s, value: %s' % (length_salt, repr(salt))) 79 | 80 | version = int(fd.read(1).encode('hex'), 16) 81 | if version != -1: 82 | # good version and probably 3des 83 | logger.info('encryption type: %s (%s)' % (repr(version), 'AES' if version == 2 else '3DES')) 84 | 85 | length_encryption_key_3des = int(fd.read(1).encode('hex'), 16) 86 | encryption_key_3des = fd.read(length_encryption_key_3des) 87 | logger.info('3DES encryption key length: %s, value: %s' % (length_encryption_key_3des, repr(encryption_key_3des))) 88 | 89 | if version >= 2: 90 | # good version and aes 91 | length_encryption_key_aes = int(fd.read(1).encode('hex'), 16) 92 | encryption_key_aes = fd.read(length_encryption_key_aes) 93 | logger.info('AES encryption key length: %s, value: %s' % (length_encryption_key_aes, repr(encryption_key_aes))) 94 | 95 | fd.close() 96 | 97 | return salt, encryption_key_3des, encryption_key_aes 98 | 99 | 100 | def decrypt_master_key(salt, encrypted_key): 101 | global logger 102 | 103 | hardcoded_key = "0xccb97558940b82637c8bec3c770f86fa3a391a56" 104 | 105 | keyFactory = SecretKeyFactory.getInstance('PBEWITHSHAAND128BITRC2-CBC', Security.getProvider("BC")) 106 | logger.debug("master key decryption SecretKeyFactory provider: '%s'" % keyFactory.getProvider()) 107 | pbeKeySpec = PBEKeySpec(hardcoded_key, salt, 5) 108 | secretKey = keyFactory.generateSecret(pbeKeySpec) 109 | logger.debug("secretKey value: %s" % repr(secretKey.getEncoded().tostring())) 110 | 111 | pbeParameterSpec = PBEParameterSpec(salt, 0) 112 | 113 | cipher = Cipher.getInstance('PBEWITHSHAAND128BITRC2-CBC', Security.getProvider("BC")) 114 | logger.debug("master key decryption Cipher provider: '%s'" % cipher.getProvider()) 115 | cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec) 116 | master_key = cipher.doFinal(encrypted_key) 117 | logger.info("master key value: %s" % repr(master_key.tostring())) 118 | 119 | return master_key 120 | 121 | 122 | def decrypt_aes_password(salt, key_aes, encrypted): 123 | global logger 124 | decrypted = None 125 | 126 | encrypted = encrypted.replace("{AES}", "") 127 | encrypted_iv_and_password = base64.b64decode(encrypted) 128 | 129 | master_key = decrypt_master_key(salt, key_aes) 130 | secretKeySpec = SecretKeySpec(master_key, "AES") 131 | 132 | iv = encrypted_iv_and_password[0:16] 133 | encrypted_password = encrypted_iv_and_password[16:] 134 | 135 | ivParameterSpec = IvParameterSpec(iv) 136 | outCipher = Cipher.getInstance("AES/CBC/PKCS5Padding") 137 | logger.debug("AES password decryption Cipher provider: '%s'" % outCipher.getProvider()) 138 | try: 139 | outCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) 140 | cleartext = outCipher.doFinal(encrypted_password) 141 | decrypted = cleartext.tostring().decode('utf-8') 142 | except: 143 | logger.error("Could not decrypt that password\n") 144 | finally: 145 | return decrypted 146 | 147 | 148 | def decrypt_3des_password(salt, key_3des, encrypted): 149 | global logger 150 | decrypted = None 151 | 152 | encrypted = encrypted.replace("{3DES}", "") 153 | encrypted_password = base64.b64decode(encrypted) 154 | 155 | master_key = decrypt_master_key(salt, key_3des) 156 | secretKeySpec = SecretKeySpec(master_key,"DESEDE") 157 | 158 | iv = salt+salt 159 | ivParameterSpec = IvParameterSpec(iv) 160 | outCipher = Cipher.getInstance("DESEDE/CBC/PKCS5Padding") 161 | logger.debug("3DES password decryption Cipher provider: '%s'" % outCipher.getProvider()) 162 | outCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) 163 | try: 164 | cleartext = outCipher.doFinal(encrypted_password) 165 | decrypted = cleartext.tostring().decode('utf-8') 166 | except: 167 | logger.error("Could not decrypt that password\n") 168 | finally: 169 | return decrypted 170 | 171 | 172 | def print_decrypted_password(salt, encrypted_password, key_aes, key_3des): 173 | if encrypted_password: 174 | if encrypted_password.startswith("{AES}") and key_aes: 175 | # encryption type: AES 176 | print '[+] encrypted: %s' % encrypted_password 177 | decrypted = decrypt_aes_password(salt, key_aes, encrypted_password) 178 | if decrypted: 179 | print '[+] decrypted: %s\n' % decrypted 180 | elif encrypted_password.startswith("{AES}") and not(key_aes): 181 | print '[!] no AES key could have been found in the provided SerializedSystemIni.dat file\n' 182 | 183 | if encrypted_password.startswith("{3DES}") and key_3des: 184 | # encryption type: 3DES 185 | print '[+] encrypted: %s' % encrypted_password 186 | decrypted = decrypt_3des_password(salt, key_3des, encrypted_password) 187 | if decrypted: 188 | print '[+] decrypted: %s\n' % decrypted 189 | elif encrypted_password.startswith("{3DES}") and not(key_3des): 190 | print '[!] no 3DES key could have been found in the provided SerializedSystemIni.dat file\n' 191 | 192 | 193 | def parse_config_xml_file(config_xml_file, salt, key_aes, key_3des): 194 | global logger 195 | 196 | # I wanted to use python xml.etree but it seems that my xpath expression is not supported... :( 197 | xpath = XPathFactory.newInstance().newXPath() 198 | expression = ".//*[starts-with(text(), '{AES}')] | .//*[starts-with(text(), '{3DES}')]" 199 | inputSource = InputSource(config_xml_file) 200 | nodes = xpath.evaluate(expression, inputSource, XPathConstants.NODESET) 201 | 202 | for i in xrange(nodes.getLength()): 203 | node = nodes.item(i) 204 | node_name = node.getNodeName() 205 | encrypted_password = node.getTextContent() 206 | print "[+] found encrypted value: <%s>%s" % (node_name, encrypted_password, node_name) 207 | print_decrypted_password(salt, encrypted_password, key_aes, key_3des) 208 | 209 | 210 | def main(options, arguments): 211 | """ 212 | Dat main 213 | """ 214 | global parser, LOGLEVELS, logger 215 | 216 | try : 217 | options.log_level = LOGLEVELS[options.verbosity] 218 | logger.setLevel(options.log_level) 219 | except : 220 | parser.error("Please specify a valid log level") 221 | 222 | if not(options.serializedsystemini_file): 223 | parser.error("Please specify a path to the SerializedSystemIni.dat file") 224 | 225 | else: 226 | (salt, key_3des, key_aes) = parse_serialized_system_ini(options.serializedsystemini_file) 227 | 228 | if options.encrypted_password: 229 | print_decrypted_password(salt, options.encrypted_password, key_aes, key_3des) 230 | 231 | if options.config_xml_file: 232 | parse_config_xml_file(options.config_xml_file, salt, key_aes, key_3des) 233 | 234 | return None 235 | 236 | if __name__ == "__main__" : 237 | options, arguments = parser.parse_args() 238 | main(options, arguments) --------------------------------------------------------------------------------