【问题标题】:Portscan in Node doesn't work if port range is too wide如果端口范围太宽,Node 中的端口扫描将不起作用
【发布时间】:2016-04-24 04:08:26
【问题描述】:

我正在尝试在 nodejs 中编写一个简单的端口扫描脚本。您可以针对 scanme.nmap.org 运行此脚本。通过运行nmap 本身,我知道该主机有四个开放端口:22、80、9929、31337。

我的脚本是:

var net = require('net');
var host = 'scanme.nmap.org';
var max = 35000;

function scanPort(port) {
    try {
        var c = new net.Socket();
        c.setTimeout(5000);
        c.connect({
            port: port,
            host: host
        })

        c.on('connect', function () {
            console.log(port);
        })

        c.on('error', function (err) {
            c.destroy();
        })

        c.on('timeout', function () {
            c.destroy();
        })
    } catch (err) {
        console.error(err);
    }
}

for (i = 1; i<=max; i++) {
    scanPort(i);
}

如果我从i = 1 开始循环,脚本将检测端口 22 和 80,之后,它将以退出代码 0 退出。它不会检测其他两个端口。如果我使用变量i,即跳过第一个端口并设置i = 9500,它将正确检测端口9929。如果我设置i = 31000,也会发生同样的情况,检测到端口31337。

我真的不明白为什么它没有按预期工作。我怀疑某些系统限制导致了一些错误,但我尝试将扫描功能包装在 try 块中,但未能检测到任何错误。

【问题讨论】:

  • 在错误和超时回调中添加一个console.log。 try catch 对异步代码没有帮助。
  • 您可能应该增加打开文件描述符的最大数量。这取决于操作系统。

标签: javascript node.js sockets


【解决方案1】:

您可能试图一次打开过多的连接并耗尽资源或在接收服务器处被阻塞以使其泛滥。

因为您所有的套接字内容都是异步的,所以您的 for 循环会立即尝试打开 35,000 个套接字。虽然可以将服务器配置为执行此操作,但它需要非常特殊的配置,并且使用每个套接字需要低内存方案(您在这里没有)。

因此,一个简单的解决方案是将同时打开的套接字数量限制在某个合理的范围内。

这里有一些代码,最初打开固定数量的套接字,然后随着每个套接字完成,它会打开另一个,保持固定数量的打开和活动套接字。当前设置为立即打开100 套接字。您可以在您的特定环境和操作系统中进行试验,看看您可以在不产生问题的情况下使该数字达到多高:

var net = require('net');
var host = 'scanme.nmap.org';
var low = 1;
var high = 35000;

var maxInFlight = 100;


function scanPort(port) {
    return new Promise(function(resolve, reject) {
        var c = new net.Socket();
        c.setTimeout(5000);
        c.connect({
            port: port,
            host: host
        })

        c.on('connect', function () {
            console.log(port);
            c.destroy();
            resolve(port);
        })

        c.on('error', function (err) {
            c.destroy();
            reject(err);
        })

        c.on('timeout', function () {
            c.destroy();
            reject({code: "Timeout", port: port});
        })
    });
}

var cntr = low;
var inFlightCnt = 0;

function run() {
    while (inFlightCnt < maxInFlight && cntr <= high) {
        ++inFlightCnt;
        scanPort(cntr++).then(function(port) {
            --inFlightCnt;
            run();
        }, function(err) {
            --inFlightCnt;
            run();
        });
    }
}

run();

注意:当一次设置为100 时,这需要一段时间才能通过所有端口(最多 35,000 个)。它确实找到了您提到的四个开放端口。


而且,这是另一个版本,它收集有关每个端口以及它如何失败/成功的信息,以便您在最后得到一个转储。它还在控制台中显示进度,因此您可以查看它是否仍在运行以及距离完成还有多远:

var net = require('net');
var host = 'scanme.nmap.org';

var low = 1;
var high = 35000;

var maxInFlight = 200;


function scanPort(port) {
    return new Promise(function(resolve, reject) {
        var c = new net.Socket();
        c.setTimeout(15000);
        c.connect({
            port: port,
            host: host
        })

        c.on('connect', function () {
            c.destroy();
            resolve(port);
        })

        c.on('error', function (err) {
            c.destroy();
            reject(err);
        })

        c.on('timeout', function () {
            c.destroy();
            reject({code: "timeout", port: port});
        })
    });
}

var cntr = low;
var inFlightCnt = 0;

var openPorts = [];
var timeouts = [];
var refused = [];
var otherErrors = [];

function run() {
    while (inFlightCnt < maxInFlight && cntr <= high) {
        ++inFlightCnt;
        scanPort(cntr++).then(function(port) {
            --inFlightCnt;
            openPorts.push(port);
            console.log(openPorts);
            run();
        }, function(err) {
            if (err.code === "timeout" || err.code === "ETIMEDOUT") {
                timeouts.push(err.port);
            } else if (err.code === "ECONNREFUSED") {
                refused.push(err.port);
            } else {
                otherErrors.push(err.port);
            }
            console.log(err.code + ": " + err.port);
            --inFlightCnt;
            run();
        });
    }
    // if we are all done here, log the open ports
    if (inFlightCnt === 0 && cntr >= high) {
        console.log("open: " + JSON.stringify(openPorts));
        console.log("timeouts: " +  JSON.stringify(timeouts));
        console.log("otherErrors: " +  JSON.stringify(otherErrors));
    }
}

run();

这是我在带有节点 v4.0.0 的 Windows 10 上运行它时生成的输出:

open: [22,80,9929,31337]
timeouts: [25,135,136,137,138,139,445,974,984,972,965,963,964,978,985,980,975,981,987,971,960,977,1000,992,990,986,991,997,1391,1384,7455,7459,7450,7506,7512,23033,23736,33635,33640,33638,33641,33634,33636,33633]
otherErrors: []

我不知道为什么有些端口会超时。大多数得到ECONNREFUSED,这是您对未打开端口的期望,少数从net 库中得到ETIMEDOUT,并且从代码中的超时中得到一些超时(我增加到15秒)。

另外,请注意我在它们连接后添加了一个销毁。您让那些套接字打开,当只有几个连接时,这在这里工作正常,但如果连接很多端口,则可能会出现问题。


注意事项:在测试这个程序时,我运行了很多次,在我运行它大约第 20 次之后,我的互联网连接中断了一段时间,并且需要重新启动我的电缆调制解调器让事情再次正常工作。我怀疑电缆调制解调器内部的某些东西在测试时无法一次又一次地处理这种快速的请求洪流。但是,康卡斯特也可能由于异常活动(大量端口请求)而关闭了我的连接。

当然,这可能都是巧合,而我的小中断可能与我正在做的事情无关,但时间似乎太紧密了,我认为这只是巧合。

可以进一步修改此程序,以通过每秒计量不超过 N 个端口探测来“减慢速度”。

【讨论】:

  • 添加了一个新版本的代码,用于收集每个套接字上的数据,所以最后你可以看到一个摘要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-05
  • 2021-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-28
相关资源
最近更新 更多