All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.joern.scanners.kotlin.PathTraversals.scala Maven / Gradle / Ivy

package io.joern.scanners.kotlin

import io.joern.scanners._
import io.joern.console._
import io.joern.dataflowengineoss.queryengine.EngineContext
import io.joern.dataflowengineoss.semanticsloader.Semantics
import io.joern.dataflowengineoss.language._
import io.joern.macros.QueryMacros._
import io.shiftleft.semanticcpg.language._

object PathTraversals extends QueryBundle {
  implicit val engineContext: EngineContext = EngineContext(Semantics.empty)
  implicit val resolver: ICallResolver      = NoResolve

  @q
  def unzipDirectoryTraversal(): Query =
    Query.make(
      name = "unzip-directory-traversal",
      author = Crew.claudiu,
      title = "Zip entries not checked before unzipping",
      description = "-",
      score = 4,
      withStrRep({ cpg =>
        val zipEntryTypeFullNames =
          List("org.apache.commons.compress.archivers.ArchiveEntry", "java.util.zip.ZipEntry")
        def zipEntryParams =
          cpg.method.parameter.typeFullNameExact(zipEntryTypeFullNames: _*)
        def fileOutStreamInit =
          cpg.method.fullNameExact("java.io.FileOutputStream.:void(java.io.File)").callIn
        def pathStartsWithCalls =
          cpg.method.fullNameExact("java.nio.file.Path.startsWith:boolean(java.nio.file.Path)").callIn

        def uncheckedZipEntryParameters = zipEntryParams.filter { param =>
          pathStartsWithCalls.argument(0).reachableByFlows(param).isEmpty
        }
        fileOutStreamInit.filter { call =>
          call.argument.reachableByFlows(uncheckedZipEntryParameters).nonEmpty
        }
      }),
      tags = List(QueryTags.pathTraversal, QueryTags.android),
      multiFileCodeExamples = MultiFileCodeExamples(
        positive = List(
          List(
            CodeSnippet(
              """
                |import java.io.*
                |import java.nio.file.Paths
                |import java.util.zip.ZipFile
                |
                |fun unzip(zipFilePath: File, destDirectory: String) {
                |    val BUFFER_SIZE = 4096
                |    ZipFile(zipFilePath).use { zip ->
                |        zip.entries().asSequence().forEach { entry ->
                |            val zipEntryInputStream = zip.getInputStream(entry)
                |
                |            val fileForEntry = File(destDirectory, entry.getName())
                |            val entryOutStream = FileOutputStream(fileForEntry)
                |
                |            val bos = BufferedOutputStream(entryOutStream)
                |            val bytesIn = ByteArray(BUFFER_SIZE)
                |            var read: Int
                |            while (zipEntryInputStream.read(bytesIn).also { read = it } != -1) {
                |                bos.write(bytesIn, 0, read)
                |            }
                |            bos.close()
                |
                |            zipEntryInputStream.close()
                |        }
                |    }
                |}
                |
                |fun main() {
                |    // to make a slippery zip:
                |    // 1. create a file /tmp/zip/extract/slip.txt
                |    // 2. inside /tmp/zip/extract run `zip slip.zip ../slip.txt`
                |    val zp = File("/tmp/zip/slip.zip")
                |    unzip(zp, "/tmp/zip/extract")
                |}
                |""".stripMargin,
              "Positive.kt"
            )
          )
        ),
        negative = List(
          List(
            CodeSnippet(
              """
              |import java.io.*
              |import java.nio.file.Paths
              |import java.util.zip.ZipFile
              |
              |fun unzip(zipFilePath: File, destDirectory: String) {
              |    val BUFFER_SIZE = 4096
              |    ZipFile(zipFilePath).use { zip ->
              |        zip.entries().asSequence().forEach { entry ->
              |            val zipEntryInputStream = zip.getInputStream(entry)
              |
              |            val fileForEntry = File(destDirectory, entry.getName())
              |            if (!fileForEntry.toPath().normalize().startsWith(Paths.get(destDirectory))) {
              |                throw Exception("Whatever's in this zip, it's not good.")
              |            }
              |            val entryOutStream = FileOutputStream(fileForEntry)
              |
              |            val bos = BufferedOutputStream(entryOutStream)
              |            val bytesIn = ByteArray(BUFFER_SIZE)
              |            var read: Int
              |            while (zipEntryInputStream.read(bytesIn).also { read = it } != -1) {
              |                bos.write(bytesIn, 0, read)
              |            }
              |            bos.close()
              |
              |            zipEntryInputStream.close()
              |        }
              |    }
              |}
              |""".stripMargin,
              "Negative.kt"
            )
          )
        )
      )
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy