CVE-2020-11079 (JavaScript, published 2020-05-28T19:15Z)
Description: value: node-dns-sync (npm module dns-sync) through 0.2.0 allows execution of arbitrary commands . This issue may lead to remote code execution if a client of the library calls the vulnerable method with untrusted input. This has been fixed in 0.2.1.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-77 \u2014 Improper Neutralization of Special Elements used in a Command ('Command Injection')
The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
Repository: https://github.com/skoranga/node-dns-sync
Files changed:
.eslintrc
.gitignore
.travis.yml
CHANGELOG.md
LICENSE
README.md
benchmarks/README.md
benchmarks/package.json
benchmarks/perf.js
index.js
lib/dns-sync.js
package.json
scripts/dns-lookup-script.js
test/test.js
Commit message: allowing only valid rrtypes


Vulnerable code:

Fixed code:
{
    "extends": [
      "eslint:recommended"
    ],
    "env": {
      "node": true,
      "browser": false,
      "es6": false,
      "mocha": true
    },
    "rules": {
        "no-useless-escape": 0
    }
  }
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz

pids
logs
results

npm-debug.log
node_modules
language: node_js

node_js:
  - "6"
  - "8"
  - "10"
  - "12"
  - "14"
CHANGELOG
==========

0.2.1
-----

- Allowing only valid rrtypes as per https://nodejs.org/api/dns.html#dns_dns_resolve_hostname_rrtype_callback.

0.2.0
-----

- Updating dependencies

0.1.3
-----

- Added support for resolving AAAA and NS records
- Upgrading to latest shelljs dependency
The MIT License (MIT)

Copyright (c) 2014 skoranga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
node-dns-sync
=============

[![Build Status](https://travis-ci.org/skoranga/node-dns-sync.png)](https://travis-ci.org/skoranga/node-dns-sync)

Sync/Blocking DNS resolve. Main usecase is in node server startup.

How to Use
-------

```javascript
var dnsSync = require('dns-sync');

console.log(dnsSync.resolve('www.paypal.com'));     //should return the IP address
console.log(dnsSync.resolve('www.yahoo.com'));
console.log(dnsSync.resolve('www.non-host.something')); //should return null

console.log(dnsSync.resolve('www.google.com', 'AAAA')); //should return AAAA records
console.log(dnsSync.resolve('google.com', 'NS'));   //should return NS record
```
### how to

cd benchmarks
npm install
node perf.js


### Perf report

***Node@v0.12.0***

 **With shelljs - ~0.3.0**

```
 success case x 8.20 ops/sec ±1.32% (25 runs sampled)
 failure case x 8.21 ops/sec ±1.59% (25 runs sampled)

 34% CPU and ≈66MB memory footprint
```

**With shelljs - ~0.4.0**

```
 success case x 9.37 ops/sec ±1.42% (28 runs sampled)
 failure case x 9.39 ops/sec ±1.42% (28 runs sampled)

  2% CPU and ≈20MB memory footprint
```
{
  "name": "dns-sync-perf",
  "description": "dns-sync",
  "version": "0.0.1",
  "devDependencies": {
    "benchmark" : "^2.0.0",
    "microtime": "^2.0.0"
  },
  "scripts": {
    "test": "node perf.js"
  }
}
'use strict';

var Benchmark = require('benchmark');

var dnsSync = require('../index');


var suite = new Benchmark.Suite();

suite.add('success case', function() {
    return dnsSync.resolve('www.example.com');
})
.add('failure case', function() {
  return dnsSync.resolve('www.example.con');
})
.on('cycle', function(event) {
  console.log(String(event.target));
})
.run({ 'async': true });
'use strict';

module.exports = require('./lib/dns-sync');
'use strict';

var util = require('util'),
    path = require('path'),
    shell = require('shelljs'),
    debug = require('debug')('dns-sync');

//source - http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
var ValidHostnameRegex = new RegExp("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$");

// https://nodejs.org/api/dns.html#dns_dns_resolve_hostname_rrtype_callback
var RRecordTypes = [
    'A',
    'AAAA',
    'NS',
    'NAPTR',
    'CNAME',
    'SOA',
    'SRV',
    'PTR',
    'MX',
    'TXT',
    'ANY'];

function isValidHostName(hostname) {
    return ValidHostnameRegex.test(hostname);
}
/**
 * Resolve hostname to IP address,
 * returns null in case of error
 */
module.exports = {
    lookup: function lookup(hostname) {
        return module.exports.resolve(hostname);
    },
    resolve: function resolve(hostname, type) {
        var nodeBinary = process.execPath;

        if (!isValidHostName(hostname)) {
            console.error('Invalid hostname:', hostname);
            return null;
        }
        if (typeof type !== 'undefined' && RRecordTypes.indexOf(type) === -1) {
            console.error('Invalid rrtype:', type);
            return null;
        }

        var scriptPath = path.join(__dirname, "../scripts/dns-lookup-script"),
            response,
            cmd = util.format('"%s" "%s" %s %s', nodeBinary, scriptPath, hostname, type || '');

        response = shell.exec(cmd, {silent: true});
        if (response && response.code === 0) {
            return JSON.parse(response.stdout);
        }
        debug('hostname', "fail to resolve hostname " + hostname);
        return null;
    }
};
{
  "name": "dns-sync",
  "version": "0.2.1",
  "description": "dns-sync",
  "main": "index.js",
  "scripts": {
    "test": "mocha",
    "lint": "eslint ."
  },
  "homepage": "https://github.com/skoranga/node-dns-sync",
  "repository": {
    "type": "git",
    "url": "git@github.com:skoranga/node-dns-sync.git"
  },
  "keywords": [
    "dns sync",
    "server startup",
    "nodejs"
  ],
  "author": "Sanjeev Koranga",
  "license": "MIT",
  "readmeFilename": "README.md",
  "dependencies": {
    "debug": "^4",
    "shelljs": "~0.8"
  },
  "devDependencies": {
    "mocha": "^6",
    "eslint": "^6",
    "eslint-plugin-standard": "^4"
  }
}
'use strict';

var dns = require('dns'),
    debug = require('debug')('dns-sync'),
    name, type, fn;

for (var i = 0; i < process.argv.length; i++) {
    if (process.argv[i].indexOf('dns-lookup-script') >= 0) {
        name = process.argv[i + 1];
        type = process.argv[i + 2];
        fn = type ? dns.resolve.bind(dns, name, type) : dns.lookup.bind(dns, name);
        break;
    }
}

fn(function (err, ip) {
    if (err) {
        debug(err);
        process.exit(1);
    } else {
        debug(name, 'resolved to', ip);
        process.stdout.write(JSON.stringify(ip));
    }
});
'use strict';

var assert = require('assert'),
    fs = require('fs'),
    dnsSync = require('../index');

describe('dns sync', function () {

    it('should resolve dns', function () {
        assert.ok(dnsSync.resolve('www.example.com'));
        assert.ok(dnsSync.resolve('www.paypal.com'));
        assert.ok(dnsSync.resolve('www.google.com'));
        assert.ok(dnsSync.resolve('www.yahoo.com'));
    });

    it('should fail to resolve dns', function () {
        assert.ok(!dnsSync.resolve('www.example.con'));
        assert.ok(!dnsSync.resolve('www.paypal.con'));
        assert.ok(!dnsSync.resolve('www.not-google.first'));
        assert.ok(!dnsSync.resolve('www.hello-yahoo.next'));
    });

    it('should fail to resolve invalid dns', function () {
        assert.ok(!dnsSync.resolve("$(id > /tmp/foo)'"));
        assert.ok(!dnsSync.resolve("cd /tmp; rm -f /tmp/echo; env 'x=() { (a)=>' bash -c \"echo date\"; cat /tmp/echo"));
        assert.ok(!dnsSync.resolve("$(grep -l -z '[^)]=() {' /proc/[1-9]*/environ | cut -d/ -f3)'"));
    });

    it('should resolve AAAA records if asked', function () {
        var aaaa = dnsSync.resolve('www.google.com', 'AAAA');
        assert.ok(aaaa);
        assert.ok(aaaa[0].match(/^([0-9a-f]{2,4}(:|$))+/));
        assert.ok(dnsSync.resolve('www.google.com') !== aaaa);
    });

    it('should resolve NS records if asked', function () {
        var ns = dnsSync.resolve('google.com', 'NS');
        assert.ok(ns);
        assert.ok(ns.length >= 1);
        assert.ok(ns[0].match(/^ns[0-9]+\.google\.com$/));
    });

    it('should fail to resolve if invalid record is asked', function () {
        var rs1 = dnsSync.resolve('www.google.com', 'Test');
        var rs2 = dnsSync.resolve('www.google.com', ' && touch test.txt');
        assert.ok(!rs1);
        assert.ok(!rs2);
        assert.ok(!fs.existsSync('test.txt'));
    });
});