contrib.nodejsscan.archive_path_overwrite.zip_path_overwrite2

profile photo of returntocorpreturntocorp
Author
99
Download Count*
License

Insecure ZIP archive extraction can result in arbitrary path over write and can result in code injection.

Run Locally

Run in CI

Defintion

rules:
  - id: zip_path_overwrite2
    patterns:
      - pattern-either:
          - pattern-inside: |
              $X = require('unzip');
              ...
          - pattern-inside: |
              $X = require('unzipper');
              ...
      - pattern-inside: |
          $UNZIP.Parse(...).on('entry', function $FUNC($ENTRY) {
              ...
          }, ...);
      - pattern-not: |
          if ($FILENAME.indexOf('..'));
      - pattern-not: >
          $FS.createWriteStream($PATH.join(..., $PATH.basename($FILENAME, ...)));
      - pattern-not: |
          $FS.writeFile($PATH.join(..., $PATH.basename($FILENAME, ...)));
      - pattern-not: |
          $FS.writeFileSync($PATH.join(..., $PATH.basename($FILENAME, ...)));
      - pattern-either:
          - pattern: |
              $FS.createWriteStream($FIL, ...);
          - pattern: |
              $FS.writeFile($FIL, ...);
          - pattern: |
              $FS.writeFileSync($FIL, ...);
    message: Insecure ZIP archive extraction can result in arbitrary path over write
      and can result in code injection.
    languages:
      - javascript
    severity: ERROR
    metadata:
      owasp: A05:2017 - Broken Access Control
      cwe: "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path
        Traversal')"
      category: security
      technology:
        - node.js
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]

Examples

archive_path_overwrite.js

//Ref: https://snyk.io/research/zip-slip-vulnerability
const fs = require('fs');
const unzip = require('unzip');

fs.createReadStream('archive.zip')
    .pipe(unzip.Parse())
    .on('entry', entry => {
        const fileName = entry.path;
        // Arbitrary file overwrite
        // ruleid:zip_path_overwrite
        entry.pipe(fs.createWriteStream(fileName));
    });

fs.createReadStream('archive.zip')
    .pipe(unzip.Parse())
    .on('entry', entry => {
        const fileName = entry.path;
        // Arbitrary file overwrite
        // ruleid:zip_path_overwrite
        entry.pipe(fs.writeFileSync(fileName));
    });

fs.readFile('path/to/archive.zip', function (err, zipContents) {
    unzip.Parse(zipContents).on('entry', function (entry) {
        var fileName = 'output/path/' + entry.path;
        // Arbitrary file overwrite
        // ruleid:zip_path_overwrite2
        fs.writeFileSync(fileName, entry.contents);
    });
});

//admzip
const fs = require('fs');
var AdmZip = require('adm-zip');
var zip = new AdmZip("archive.zip");
var zipEntries = zip.getEntries();
// ruleid:admzip_path_overwrite
zipEntries.forEach(function (zipEntry) {
    fs.createWriteStream(zipEntry.entryName);
});

// ruleid:admzip_path_overwrite
zip.getEntries().forEach(function (zipEntry) {
    fs.writeFileSync(zipEntry.entryName);
});

// tar-stream overwrite
const tar = require('tar-stream');
const extract = tar.extract();

extract.on('entry', (header, stream, next) => {
    // ruleid:tar_path_overwrite
    const out = fs.createWriteStream(header.name);
    stream.pipe(out);
    stream.on('end', () => {
        next();
    })
    stream.resume();
})

tar.extract().on('entry', (header, stream, next) => {
    // ruleid:tar_path_overwrite
    const out = fs.writeFileSync(header.name);
    stream.pipe(out);
    stream.on('end', () => {
        next();
    })
    stream.resume();
})

///unzipper lib
fs.createReadStream('./bad.tar').pipe(extract);
const fs = require('fs');
const unzipper = require('unzipper');

fs.createReadStream('path/to/archive.zip')
    .pipe(unzipper.Parse())
    .on('entry', function (entry) {
        var fileName = entry.path;
        // ruleid:zip_path_overwrite
        entry.pipe(fs.createWriteStream(fileName));
    });