
ブラウザベースの高速ネットワーク発見モジュール
このプロジェクトはメンテナンスされなくなりました。
netmap.js は、ブラウザベースのホスト発見とポートスキャン機能を提供し、ウェブサイト訪問者のネットワークをマッピングできるようにします。
es6-promise-pool を利用して、ブラウザが許可する最大数の同時接続を効率的に実行するため、非常に高速です。
私は取り組んでいるアイデアのためにブラウザベースのポートスキャナーが必要でした。既存のモジュールをインポートするか、BeEF のような他のプロジェクトからコピーペーストするだけの簡単な作業だと思っていました。
しかし、適切なすぐに使える npm モジュールはなく、BeEF の port_scanner モジュールは(執筆時点で)不正確で低速であり、Chromium では動作しませんでした。
そのため netmap.js は、最新のすべてのブラウザで動作する、ある程度最適化された「ping」スイーパー兼 TCP スキャナーです。
npm install --save netmap.js
家庭環境でよくある候補のリストから、ウェブサイト訪問者のゲートウェイの IP アドレスを特定してみましょう。
import NetMap from 'netmap.js'
const netmap = new NetMap()
const hosts = ['192.168.0.1', '192.168.0.254', '192.168.1.1', '192.168.1.254']
netmap.pingSweep(hosts).then(results => {
console.log(results)
})
{
"hosts": [
{ "host": "192.168.0.1", "delta": 1003, "live": false },
{ "host": "192.168.0.254", "delta": 1001, "live": false },
{ "host": "192.168.1.1", "delta": 18, "live": true },
{ "host": "192.168.1.254", "delta": 1002, "live": false }
],
"meta": {}
}
ホスト 192.168.1.1 がライブであるように見えます。
いくつかのホストで開いている TCP ポートを見つけてみましょう。
import NetMap from 'netmap.js'
const netmap = new NetMap()
const hosts = ['192.168.1.1', '192.168.99.100', 'google.co.uk']
const ports = [80, 443, 8000, 8080, 27017]
netmap.tcpScan(hosts, ports).then(results => {
console.log(results)
})
{
"hosts": [
{
"host": "192.168.1.1",
"control": "22",
"ports": [
{ "port": 443, "delta": 15, "open": false },
{ "port": 8000, "delta": 19, "open": false },
{ "port": 8080, "delta": 21, "open": false },
{ "port": 27017, "delta": 26, "open": false },
{ "port": 80, "delta": 95, "open": true }
]
},
{
"host": "192.168.99.100",
"control": "1001",
"ports": [
{ "port": 8080, "delta": 40, "open": true },
{ "port": 80, "delta": 1001, "open": false },
{ "port": 443, "delta": 1000, "open": false },
{ "port": 8000, "delta": 1004, "open": false },
{ "port": 27017, "delta": 1000, "open": false }
]
},
{
"host": "google.co.uk",
"control": "1001",
"ports": [
{ "port": 443, "delta": 67, "open": true },
{ "port": 80, "delta": 159, "open": true },
{ "port": 8000, "delta": 1001, "open": false },
{ "port": 8080, "delta": 1002, "open": false },
{ "port": 27017, "delta": 1000, "open": false }
]
}
],
"meta": {}
}
最初は結果が矛盾しているように見えるかもしれません。
192.168.1.1 はローカルネットワークセグメント上の組み込み Linux マシン(ルーター)であり、開いているポートは 80 だけです。80 でエラーになるまでにかかる時間が、他の閉じたポートに比べて約 5 倍長かったことがわかります。
192.168.99.100 はホストオンリー VM で、ポート 8080 が開いています。google.co.uk は外部ホストで、443 と 80 の両方が開いています。これらのケースでは、ブラウザは開いているポートでは比較的迅速にエラーをスローする一方、閉じたポートでは単にタイムアウトしました。これがいつ発生するかは、後述の 理論 セクションで説明します。
ポートを開いているか閉じているかとタグ付けするために、netmap.js は閉じていると想定される「コントロール」ポート(デフォルトでは 45000)をスキャンします。そして、コントロール時間を使って他のポートのステータスを判断します。delta/control の比率が設定値(デフォルト 0.8)より大きい場合、そのポートは閉じていると見なされます(要するに、コントロール時間から 20% 以上の差があればポートは開いていることを意味します)。
ブラウザは接続を拒否するポートのブラックリスト(FTP、SSH、SMTP など)を保持しています。デフォルトのプロトコル(http)を使用して netmap.js でこれらのポートをスキャンしようとすると、非常に短いタイムアウトが発生します。短いタイムアウトは通常、ポートが閉じていることを示しますが、ブラックリストに載っているポートの場合は意味がありません。
以下のソースからブラックリストを確認できます。
Firefox 61(およびおそらく他のブラウザ)以前は、http の代わりに ftp プロトコルを使用して接続を確立することで、この制限を回避できました。NetMap をインスタンス化するときにオプションオブジェクトで protocol を指定できます。ftp を使用する場合、開いているポートはタイムアウトし、閉じているポートは比較的迅速にエラーになることを想定してください。ftp スキャンは、このドキュメントで説明する TCP RST パケットに関する制限の対象でもあります。
ftp のような「レガシー」プロトコルからのサブリソースリクエストは、Chromium ではしばらく前からブロックされています。
netmap.js が提供する「ping」スイープ機能は、ローカルネットワークセグメント上のライブな *nix ベースのホスト(他のコンピュータ、電話、ルーター、プリンターなど)を迅速に見つけるのに非常に優れています。
ただし、実装上の理由から、TCP RST パケットが返されない場合は機能しません。典型的には次のような場合です。
この理由については、下記の 理論 セクションで説明します。
この制限は TCP スキャン機能には影響せず、上記のホストがライブかどうかを判断するには、そのホストで開いているポートを見つけることで可能です。
全体的に、このモジュールは私がウェブ上で見つけた他のコードよりも正確で高速であることがわかりました。とはいえ、ブラウザからネットワークをマッピングするというアイデア全体は、本質的に不安定です。結果は環境によって異なります。
NetMap コンストラクタNetMap コンストラクタは、次の設定が可能なオプションオブジェクトを受け取ります。
protocol(デフォルト http、ポートブラックリスト を参照して ftp に設定する理由を確認)timeout(デフォルト 1000 ミリ秒)import NetMap from 'netmap.js'
const netmap = new NetMap({
protocol: 'http',
timeout: 3000
})
pingSweep()pingSweep() メソッドは、指定されたホストの配列がライブかどうかを判断します。ポートへの接続がタイムアウトした場合、そのホストはオフラインと見なされます(制限については"Ping"スイープを、理論については標準ケースを参照してください)。
このメソッドは次のパラメータを受け取ります。
hosts - スキャンするホストの配列(IP アドレスまたはホスト名)options - 次のプロパティを持つオブジェクト:
maxConnections - 同時接続の最大数(デフォルトでは Chrome で 10、他のブラウザで 17 - ブラウザがサポートする最大同時接続数)port - スキャンするポート(デフォルト 45000)プロミスを返します。
netmap.pingSweep(['192.168.1.1'], {
maxConnections: 5,
port: 80
}).then(results => {
console.log(results)
})
tcpScan()tcpScan() メソッドは、一連のターゲットに対してポートスキャンを実行します。これがどのように行われるかについては、標準ケースを読んでください。
このメソッドは次のパラメータを受け取ります。
hosts - スキャンするホストの配列(IP アドレスまたはホスト名)ports - スキャンするポートのリスト(1〜65535 の整数、ブラックリスト のポートは避けてください)options - 次のプロパティを持つオブジェクト:
maxConnections - 同時接続の最大数(デフォルト 6 - ブラウザが許可するドメインあたりの最大接続数)portCallback - 個々の host:port の組み合わせのスキャンが完了したときに実行するコールバックcontrolPort - ベースラインのクローズドポートデルタを決定するためにスキャンするポート(デフォルト 45000)controlRatio - ポートが閉じていると見なされるためのコントロールデルタとの類似度(パーセンテージ、デフォルト 0.8、例を参照)プロミスを返します。
netmap.tcpScan(['192.168.1.1'], [80, 27017], {
maxConnections: 5,
portCallback: result => {
console.log(result)
},
controlPort: 45000,
controlRatio: 0.8
}).then(results => {
console.log(results)
})
出力の解釈については例を確認してください。
このセクションでは、モジュールの発見技術の背後にある理論について簡単に説明します。
このモジュールは Image オブジェクトを使用して、テスト対象の http://{host}:{port} URL のシリーズであるクロスオリジンリソースをリクエストしようとします。ブラウザがエラーを発生させるまでの時間(delta)、または一定のタイムアウト値後もエラーが発生しないことは、調査中のホストとポートの状態について洞察を提供します。
ライブホストは、閉じているポートに接続しようとすると、通常は比較的迅速に TCP RST パケットで応答します。
ポートが開いている場合、たとえ HTTP サーバーが実行されていなくても、ブラウザは完全な TCP 接続を確立し、その後提供された URL から画像を取得できないことに気付くまでのオーバーヘッドにより、エラーが発生するまでに少し時間がかかります。
オフラインホストは当然、RST で応答せず、完全な TCP 接続の確立も許可しません。ブラウザはしばらく接続を試みてからタイムアウトします(約 90 秒)。netmap.js はデフォルトで 1000 ミリ秒待機した後にタイムアウトします。
まとめると:
delta を持つdelta を持つ標準ケースは、TCP ポートスキャンの例のホスト 192.168.1.1 で示されています。
RST が返されないケース一部のホスト(google.co.uk や Windows ホストなど)や一部のネットワーク設定(VirtualBox ホストオンリーネットワークなど)では、閉じているポートにアクセスしたときに TCP RST パケットが返されません。
これらのケースでは、閉じているポートは通常タイムアウトする一方、開いているポートはすぐにエラーを発生させます。
したがって、pingSweep() メソッドの実装は、RST パケットが返されない場合には信頼できません。
まとめると、何らかの理由で TCP RST パケットが返されない場合:
delta を持つpingSweep() は、閉じているポートのタイムアウトと「死んでいる」ホストのタイムアウトを区別できない特殊なケースは、TCP ポートスキャンの例のホスト 192.168.99.100 と google.co.uk で示されています。
WebSocket と AJAX でもネットワークをマッピングできるはずだということはよく文書化されています。
私は試してみました(また、BeEF を調整して WebSocket と AJAX のみで port_scanner モジュールを試しました)。どちらの方法も完全に信頼性の低い結果しか得られませんでした。
これに関して私が見落としている点があれば、お知らせください。