[Scala] [Java] I tried to fix the directory traversal vulnerability when unzipping Zip4j by bytecode operation.

Do you know Zip-Slip, a directory traversal vulnerability when unzipping Zip?

I will leave the details to other articles such as ↓, but this time I will try to fix the library called zip4j by bytecode operation. The impact of the Zip Slip directory traversal vulnerability affects many Java projects

By the way, the vulnerability countermeasure version of each library that handles Zip is listed here. snyk/zip-slip-vulnerability: Zip Slip Vulnerability (Arbitrary file write through archive extraction) It seems that zip4j does not have a jar distribution ... If you take the stance of distributing the source code and making it into a jar by yourself, I think that it is likely that you will not use the latest version ...

↓ zip-slip test data zip-slip-vulnerability/archives at master · snyk/zip-slip-vulnerability

Code I tried to fix

import javassist._
import javassist.expr.ExprEditor
import javassist.expr.MethodCall
import net.lingala.zip4j.core.ZipFile

/**
  *Zip4j dynamic security patch with bytecode rewriting
  */
object Zip4jSecurity {
  def main(args: Array[String]): Unit = {
    patch()

    //Test if validation is working. zip-An exception occurs due to slip validation.
    val zipFile = new ZipFile("/home/momose/Documents/zip-slip.zip")
    zipFile.extractAll("/home/momose/Documents/tmp/")
  }

  /**
    *Apply the patch
    */
  def patch(): Unit = {
    val cp = ClassPool.getDefault
    val classLoader = Thread.currentThread().getContextClassLoader()
    cp.appendClassPath(new LoaderClassPath(classLoader))
    val cc = cp.get("net.lingala.zip4j.unzip.Unzip")
    val zipSlipValidationEditor = new Zip4jSecurity.ZipSlipValidationEditor
    cc.instrument(zipSlipValidationEditor)
    //Register with class loader
    cc.toClass(null, clazz.getProtectionDomain)
  }

  private class ZipSlipValidationEditor extends ExprEditor {
    //Flags that apply only to the first method call
    private var unedited = true

    override def edit(m: MethodCall): Unit = {
      if (unedited &&
        m.getClassName.equals("net.lingala.zip4j.model.CentralDirectory") &&
        m.getMethodName.equals("getFileHeaders")) {
        //Validation code
        val statement ="""
java.io.File outputDir = new java.io.File(outPath);
java.util.List fileHeaders = $0.getFileHeaders($$);
for( int i=0; i<fileHeaders.size(); i++ ){
    net.lingala.zip4j.model.FileHeader e = (net.lingala.zip4j.model.FileHeader)fileHeaders.get(i);
    if (!new java.io.File(outputDir, e.getFileName()).getCanonicalPath().startsWith(outputDir.getCanonicalPath())) {
            throw new RuntimeException("Illegal ZIP");
    }
}
$_ = fileHeaders;"""
        m.replace(statement)
        unedited = false
      }
    }
  }

}

Execution result

Exception in thread "main" java.lang.RuntimeException:Illegal ZIP
	at net.lingala.zip4j.unzip.Unzip.extractAll(Unzip.java:50)
	at net.lingala.zip4j.core.ZipFile.extractAll(ZipFile.java:488)
	at net.lingala.zip4j.core.ZipFile.extractAll(ZipFile.java:451)
	at com.github.momosetkn.zip.Zip4jSecurity$.main(Zip4jSecurity.scala:18)
	at com.github.momosetkn.zip.Zip4jSecurity.main(Zip4jSecurity.scala)

It validates the result of the Java method getCanonicalPath, such as realpath in C and Linux commands. ↓ Reference materials IDS02 \ -J . Normalize pathnames before validation

Only when used in the net.lingala.zip4j.core.ZipFile # extractAll method Vulnerabilities may develop in other cases because we have not confirmed that they will be countermeasures. I want you to test it yourself.

↓ It is prepared in the net.lingala.zip4j.model.CentralDirectory#getFileHeaders () method call of this part. zip4j/Unzip.java at master · supasate/zip4j

Impressions

With Scala, string literals should be easy to handle. Also, it would be nice if all exceptions were treated like unchecked exceptions. When rewriting with javassist, it is awkward to write in Java code that matches the Java version to be rewritten.

Recommended Posts

[Scala] [Java] I tried to fix the directory traversal vulnerability when unzipping Zip4j by bytecode operation.
I tried to translate the error message when executing Eclipse (Java)
[Java] I tried to make a maze by the digging method ♪
I tried to implement the Euclidean algorithm in Java
I tried to implement the like function by asynchronous communication
[JDBC] I tried to access the SQLite3 database from Java.
I tried to summarize the basics of kotlin and java
I tried to build the environment little by little using docker
I want to judge the necessity of testing by comparing the difference of class files when refactoring Java
I tried to check the operation of gRPC server with grpcurl
I tried to summarize the methods of Java String and StringBuilder
I tried to move the Java compatible FaaS form "Fn Project"
I tried to display the calendar on the Eclipse console using Java.
By checking the operation of Java on linux, I was able to understand compilation and hierarchical understanding.
I tried to interact with Java
I tried to explain the method
I tried the Java framework "Quarkus"
I tried to summarize Java learning (1)
I tried to summarize Java 8 now
I tried to summarize the stumbling points when developing an Android application
I tried connecting the dot counter to the MZ platform by serial communication
[Introduction to Java] I tried to summarize the knowledge that I think is essential
I want to get the IP address when connecting to Wi-Fi in Java
I tried to summarize the methods used
I tried to summarize Java lambda expressions
I tried the new era in Java
I tried to implement the Iterator pattern
I tried to summarize the Stream API
I went to the Java Women's Club # 1
A story when I tried to make a video by linking Processing and Resolume
What I tried when I wanted to get all the fields of a bean
I want you to use Scala as Better Java for the time being
[JavaScript] The strongest case when I tried to summarize the parts I do not understand
[Small story] I tried to make the java ArrayList a little more convenient