SECURITY INFLUENCERS BLOG

Security influencers provide real-world insight and “in-the-trenches” experiences on topics ranging from application security to DevOps and risk management

START FREE TRIAL

Pulling Back the Curtain On: Zip File Overwrites

Zip file overwrites are a cool but rare vulnerability that can occur on apps that work with user-supplied zip files. The folks at Snyk recently found a slew of libraries that do that, and there is a lot of history in this attack vector. In fact, a particular unzipping program continually got abused in the early 2000s with bypasses.

Quick Primer on Zip File Overwrite

Let’s talk about a simple attack. You have some code that looks like this:

InputStream zipInputStreamFromUser = request.getInputStream(); // this is untrusted 
...
ZipEntry entry = getNextZipEntry(zipInputStreamFromUser);
File fileToWrite = new File(entry.getName());// we used the name of the file from the zip
writeToFile(fileToWrite, getContentsOf(entry)); // we used the file contents from the zip

Not many apps process zip files, which is why you don’t usually find this vulnerability. Now, suppose I send you a zip file with one entry that looks like this:

process-zip-files-1

The code above will overwrite the contents of the /etc/passwd file with malicious contents allowing a passwordless root login. Privileges will probably stop an attack this blatant, but generally the path from arbitrary file writes into arbitrary code execution isn’t super complicated. Here are some dumb ideas off the top of my head:

  • In many apps you can just write a new JSP file/overwrite an existing JSP file and hit it with your browser
  • Write a new WAR file to your $APP_SERVER/apps directory and watch the server automatically load it
  • Add a new entry to $HOME/.bash_profile to curl some rootkit
  • Overwrite the app server code binary (think jboss-server.jar) with a malware-laden copy
  • I’m sure by the time you read my first 4 you’ve thought of 3 better ones

In an app of any complexity, there will be plenty of opportunity for chaining this to RCE.

How Does a WAF Block It?

I don’t think they do, but I don’t have one to test. Maybe they could pre-parse the zip themselves? I bet they just punt and don’t do anything.

I don’t see anything in modsecurity, maybe someone else knows? Try asking your WAF vendor! I think you might get something like:

waf

How Does RASP Block It?

Basically, we want to prevent access to java.util.zip.ZipEntry that have backtracking characters (“..”) in them. Choosing the APIs to instrument to do stuff like this is a learned art. You want to choose points that aren’t called super often, and thus incur performance impact. At the same time, you want to choose a point in the code path that can’t be avoided, and thus offers the best security. Sometimes, these are slightly at odds, and you’ll have to instrument multiple points.

The APIs We Want to Protect

There are two of the APIs we chose to protect, and how we will protect them:

api-graph-1

You can make adding these sensors sound really easy or really hard. If I’m at a party and you hear me say, “use binary instrumentation to add a synchronized callback to a globally visible static dispatcher, and make sure you leave the stack in the same condition you found it,'' you can tell I’m trying to impress someone.

I’ll just tell you the truth, here. In truth, most of the code is pretty easy. Most of the work is in the research to find the one or two perfect places to put the sensors without breaking things or slowing the app down. Let’s take the first API we want to protect, ZipFile#getEntry(String) and offer a quick and dirty code example.

Some Dirty, Reductive Code Examples

The first piece of code we need is the thing that’s going to check the name of the entry for backtracking characters. This is the “dispatcher” or “callback” code.

// ZipFileCallback.java 
public final class ZipFileCallback {
/* Our instrumented code will call this method.*/
public static void onZipEntryBeingOpened(final String entryName) {
if(entryName != null && entryName.contains(“..”)) {
throw new SecurityException(“found backtracking in zip entry”);
}
}
}

Next we need the code that is responsible for modifying the ZipFile type in total:

// ZipFileProtectionClassVisitor.java 
public final class ZipFileProtectionClassVisitor extends ClassVisitor {
 
@Override
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
// check if it’s the getEntry(String) method we want to instrument!
if ("getEntry".equals(name) &&
"(Ljava/lang/String;)Ljava/util/zip/ZipEntry;".equals(desc)) {
// give ourselves an event to insert code on method enter
mv = new ZipFileProtectionMethodVisitor(dispatcherAccessor, mv, access, name,
desc);
}
return mv;
}
}

Finally, we have the code that is responsible for modifying the getEntry(String) method and inserting the callback:

// ZipFileProtectionClassVisitor.java 
public final class ZipFileProtectionMethodVisitor extends AdviceAdapter {

@Override
public void onMethodEnter() {
// load the first argument, the string with the zip entry name
visitIntInsn(Opcodes.ALOAD, 1);
// call the dispatcher / callback method!
visitMethodInsn(Opcodes.INVOKESTATIC, “ZipFileCallback”,
“onZipEntryBeingOpened”, “(Ljava/lang/String)V”, false);
}
}

It’s a long way between what I showed you here and a full working example, but these are the interesting parts, representing how to change, what the changes are, and what those changes do. Hopefully, you can see for yourself the impact of a RASP on normal app functionality, it’s not a Cirque du Soleil act to re-balance the stack, and the performance is measured in just a few extra instructions.

Most importantly, consider this -- which is more work, and which will lead to better security outcomes?

Option #1: No RASP. Let’s go ask all your development teams to understand their risk of zip file overwrite issues in their custom code, libraries, app server, etc. Ask them to watch out! (eyeroll)

Option #2: With RASP. Deploy our agent, put feet up, listen to Risky Business! Bonus -- less paperwork for you as the next pen test you have will produce a lot less findings. =)

Here's a screenshot of us finding this in a sample app:

a notification from Contrast showing an evil zip being seen

Choose RASP!

Arshan Dabirsiaghi, Co-Founder, Chief Scientist

Arshan Dabirsiaghi, Co-Founder, Chief Scientist

Arshan is an accomplished security researcher with 10+ years of experience advising large organizations about application security. Arshan has released popular application security tools, including AntiSamy and JavaSnoop.

SUBSCRIBE TO THE BLOG

Learn how to unify security strategy across & development operations. See how to set up a CAS program with only eight activities!

Download the Handbook