code: /**
   * This file is part of Eclipse Steady.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * SPDX-License-Identifier: Apache-2.0
   * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
   */
  package org.eclipse.steady.malice;
  
  import java.io.BufferedInputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.file.Path;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipInputStream;
  
  import org.apache.commons.compress.archivers.ArchiveEntry;
  import org.apache.commons.compress.archivers.ArchiveInputStream;
  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
  import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
  import org.apache.logging.log4j.Logger;
  import org.eclipse.steady.shared.util.DirUtil;
  import org.eclipse.steady.shared.util.StringUtil;
  import org.eclipse.steady.shared.util.VulasConfiguration;
  
  /**
   * Checks whether archives are subject to the ZipSlip vulnerability.
   * Supports the following archive types:
   * - ZIP and derivates JAR, WAR, EAR, AAR
   * - TAR and TAR.GZ
   *
   * See https://github.com/snyk/zip-slip-vulnerability
   */
  public class ZipSlipAnalyzer implements MaliciousnessAnalyzer {
  
    private static final Logger log =
        org.apache.logging.log4j.LogManager.getLogger(ZipSlipAnalyzer.class);
  
    private Path destinationPath =
        VulasConfiguration.getGlobal()
            .getTmpDir()
            .toAbsolutePath()
            .resolve(StringUtil.getRandonString(10));
  
    /** {@inheritDoc} */
    @Override
    public MaliciousnessAnalysisResult isMalicious(File _file) {
      MaliciousnessAnalysisResult mal = null;
      final String name = _file.getName();
  
      try (final InputStream fis = new FileInputStream(_file)) {
        // Analyze
        if (name.endsWith(".zip")
            || name.endsWith(".jar")
            || name.endsWith(".war")
            || name.endsWith(".ear")
            || name.endsWith(".aar")) {
          mal = this.isMalicious(new ZipInputStream(fis), false);
        } else if (name.endsWith(".tar")) {
          mal = this.isMalicious(new TarArchiveInputStream(fis), false);
        } else if (name.endsWith(".tar.gz")) {
          mal =
              this.isMalicious(
                  new TarArchiveInputStream(
                      new GzipCompressorInputStream(new BufferedInputStream(fis))),
                  false);
        } else {
          log.warn("Cannot analyze [" + _file.toPath().toAbsolutePath() + "]");
        }
  
        // Log
        if (mal != null) {
          mal.setReason("[" + _file.toPath().toAbsolutePath() + "] " + mal.getReason());
          if (mal.isMalicious()) log.warn(mal.getReason());
          else log.debug(mal.getReason());
        }
      } catch (FileNotFoundException fnfe) {
        log.error("File [" + _file.getAbsolutePath() + "] cannot be found");
      } catch (IOException ioe) {
        log.error(
            ioe.getClass().getSimpleName()
                + " when opening ["
                + _file.getAbsolutePath()
                + "]: "
                + ioe.getMessage(),
            ioe);
      }
      return mal;
    }
  
    /** {@inheritDoc} */
    @Override
    public MaliciousnessAnalysisResult isMalicious(InputStream _is, boolean _log) {
      final StringBuffer buffer = new StringBuffer();
      int count = 0;
  
      final MaliciousnessAnalysisResult mal = new MaliciousnessAnalysisResult();
      mal.setAnalyzer(this.getClass().getName());
  
      if (_is instanceof ZipInputStream) {
        while (true) {
          try {
            final ZipEntry e = ((ZipInputStream) _is).getNextEntry();
            if (e == null) break;
            count++;
            final boolean is_below =
                DirUtil.isBelowDestinationPath(this.destinationPath, e.getName());
            if (!is_below) {
              mal.setResult(1);
              if (buffer.length() > 0) buffer.append(", ");
              buffer.append(e.getName());
            }
          } catch (IOException ioe) {
            log.error(
                ioe.getClass().getSimpleName() + " when looping archive entries: " + ioe.getMessage(),
                ioe);
          }
        }
      } else if (_is instanceof ArchiveInputStream) {
        while (true) {
          try {
            final ArchiveEntry e = ((ArchiveInputStream) _is).getNextEntry();
            if (e == null) break;
            count++;
            final boolean is_below =
                DirUtil.isBelowDestinationPath(this.destinationPath, e.getName());
            if (!is_below) {
              mal.setResult(1);
              if (buffer.length() > 0) buffer.append(", ");
              buffer.append(e.getName());
            }
          } catch (IOException ioe) {
            log.error(
                ioe.getClass().getSimpleName() + " when looping archive entries: " + ioe.getMessage(),
                ioe);
          }
        }
      }
  
      // Log
      if (mal.isMalicious()) {
        mal.setReason(
            "Archive is subject to ZipSlip vulnerability, the following file(s) would be extracted"
                + " outside of an intended target folder:");
        mal.appendReason(buffer.toString(), " ");
        if (_log) log.warn(mal.getReason());
      } else {
        mal.setReason(
            "Archive is NOT subject to ZipSlip vulnerability, all ["
                + count
                + "] archive entries would be extracted inside or below an intended target folder");
        if (_log) log.debug(mal.getReason());
      }
  
      return mal;
    }
  }
  