LoginSignup
4
7

More than 5 years have passed since last update.

自ホスト以外からのTCPリクエストを拒否する

Posted at

WebサーバーなどTCP接続を受け付けるアプリケーションを動かす場合、server.listen(3000, '0.0.0.0'); のように、リクエストを受け付けるIPアドレス(0.0.0.0)とPort番号(3000)を 指定することがある。

0.0.0.0 は任意のIPアドレスで受け付けられる ワイルドカード の意味で、
自ホスト以外からのリクエストを受け付ける必要がある場合には 0.0.0.0 が使用されることが多いと思われる。

ただ、 0.0.0.0 で任意のIPアドレスからのリクエストを受け付けられるようにすると、セキュリティ的に 望まないリクエストも受け付けてしまうので、制限したいことがある。

この記事では、その方法を紹介する。

自ホスト以外からのリクエストを拒否する方法

以下の3通りがある

0.0.0.0 の代わりに 127.0.0.1 で listen する

アプリケーションコード

const net = require('net');

const server = net.createServer();

server.on('listening', () => {
    const {
        address,
        port,
    } = server.address();
    console.info(`Server started. (IP:port = ${address}:${port})`);
});

server.on('connection', connection => {
    connection.end('HTTP/1.0 200 OK\r\n\r\nok');
});

server.listen(3000, '127.0.0.1');

※ 動作確認を簡単にするためHTTPサーバーの機能をTCPレベルAPIで実装した
(雑に作ったので本番への流用はNG)

起動ログ

$ node index.js
Server started. (IP:port = 127.0.0.1:3000)

lissten状況(抜粋)

127.0.0.1:3000 で受け付けている

> netstat -an

アクティブな接続

  プロトコル  ローカル アドレス      外部アドレス           状態
  TCP         127.0.0.1:3000         0.0.0.0:0              LISTENING

※ 外部アドレス は 状態が LISTENING の場合 0.0.0.0:0 と表示される

リクエストの受付可否

ループバックアドレス (127.0.0.1)

:o: つながる

$ curl --silent --show-error http://127.0.0.1:3000/
ok

ループバックアドレス2 (127.0.0.2)

:x: つながらない

$ curl --silent --show-error http://127.0.0.2:3000/
curl: (7) Failed to connect to 127.0.0.2 port 3000: Connection refused

ローカル エリア接続のIP (192.168.0.13)

:x: つながらない

$ curl --silent --show-error http://192.168.0.13:3000/
curl: (7) Failed to connect to 192.168.0.13 port 3000: Connection refused

0.0.0.0 で listen しつつ OSの Firewall で弾く

省略(各OSの手順に沿って設定してください)

0.0.0.0 で listen しつつ アプリケーション側で弾く

方法

TCP接続(connection) の 接続先IPアドレス, 接続元IPアドレス の情報を使って、自ホスト外かどうか判定する

  • 接続先IPアドレス: connection.localAddress
    • クライアントから見てリクエスト先は サーバーから見て local
  • 接続元IPアドレス: connection.remoteAddress
    • クライアントからは サーバーから見て remote

自ホストからリクエストの条件

以下のいずれか

  • remoteAddress が 127.0.0.1
    • 127.0.0.0/8 (127.0.0.1127.1.2.3 など) 宛にリクエストした場合
  • remoteAddress と localAddress が等しい
    • 自 IPアドレス宛の通信 (192.168.0.13192.168.99.1 など)

ソースコード

const {
    remoteAddress,
    localAddress,
} = connection;

// 自ホストからのリクエスト
// 1. 127.0.0.0/8 (127.0.0.1 や 127.1.2.3 など) 宛にリクエストした場合
// 2. 自 IPアドレス宛の通信 (192.168.0.13 や  192.168.99.1 など)
const fromSelfHost = remoteAddress === '127.0.0.1' || remoteAddress === localAddress;

アプリケーションコード

  • 自ホストからのリクエストか判定
    • 自ホストからのリクエスト: OKレスポンスを返却
    • ホスト外からのリクエスト: NGレスポンスを返却
      • 許可しないリクエストには NGレスポンスを返すようにした(確認しやすくするため)
  • (リクエスト元・先などの情報をログ出力)
    • remoteAddress: 接続元IPアドレス(クライアント)
    • localAddress: 接続先IPアドレス(サーバー側)
    • requestLine: HTTP リクエストの1行目
    • fromSelfHost: 自ホストからのリクエストかどうか
const net = require('net');

const server = net.createServer();

server.on('listening', () => {
    const {
        address,
        port,
    } = server.address();
    console.info(`Server started. (IP:port = ${address}:${port})`);
});

const okHttpResponse = `HTTP/1.0 200 OK

ok
`;

const ngHttpResponse = `HTTP/1.0 403 Forbidden

ng
`;

server.on('connection', connection => {
    const {
        remoteAddress,
        localAddress,
    } = connection;

    // 自ホストからのリクエスト
    // 1. 127.0.0.0/8 (127.0.0.1 や 127.1.2.3 など) 宛にリクエストした場合
    // 2. 自 IPアドレス宛の通信 (192.168.0.13 や  192.168.99.1 など)
    const fromSelfHost = remoteAddress === '127.0.0.1' || remoteAddress === localAddress;


    connection.once('data', data => {
        const requestLine = data.toString().split(/[\r\n]/)[0];
        console.info({
            remoteAddress,
            localAddress,
            requestLine,
            fromSelfHost,
        });
    });

    connection.end(fromSelfHost ? okHttpResponse : ngHttpResponse);
});

server.listen(3000, '0.0.0.0');

※ 動作確認を簡単にするためHTTPサーバーの機能をTCPレベルAPIで実装した
(雑に作ったので本番への流用はNG)

起動ログ

$ node index.js
Server started. (IP:port = 0.0.0.0:3000)

lissten状況(抜粋)

0.0.0.0:3000 で受け付けている

> netstat -an

アクティブな接続

  プロトコル  ローカル アドレス      外部アドレス           状態
  TCP         0.0.0.0:3000           0.0.0.0:0              LISTENING

※ 外部アドレス は 状態が LISTENING の場合 0.0.0.0:0 と表示される

リクエストの受付可否

ループバックアドレス (127.0.0.1)

:o: つながる

$ curl --silent --show-error http://127.0.0.1:3000/
ok
アプリケーションログ

remoteAddress(127.0.0.1) が 127.0.0.1 と等しいためホスト内からのリクエストと判定されている(fromSelfHost: true)

{ remoteAddress: '127.0.0.1',
  localAddress: '127.0.0.1',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: true }

ループバックアドレス2 (127.0.0.2)

:o: つながる

$ curl --silent --show-error http://127.0.0.2:3000/
ok

アプリケーションログ

remoteAddress(127.0.0.1) が 127.0.0.1 と等しいためホスト内からのリクエストと判定されている(fromSelfHost: true)

{ remoteAddress: '127.0.0.1',
  localAddress: '127.0.0.2',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: true }

ローカル エリア接続のIP (192.168.0.13)

:o: つながる

$ curl --silent --show-error http://192.168.0.13:3000/
ok
アプリケーションログ

remoteAddress(192.168.0.13) が localAddress(192.168.0.13) と等しいためホスト内からのリクエストと判定されている(fromSelfHost: true)

{ remoteAddress: '192.168.0.13',
  localAddress: '192.168.0.13',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: true }

外部からのリクエスト

同一ネットワークに接続中のスマートフォンのブラウザからリクエスト

:x: (つながるが)アプリケーションに拒否された(NGレスポンスが返る)

レスポンス
ng
アプリケーションログ

remoteAddress(192.168.0.128) が localAddress(192.168.0.13) と異なるためホスト外からのリクエストと判定されている(fromSelfHost: false) → NGレスポンスを返却

{ remoteAddress: '192.168.0.128',
  localAddress: '192.168.0.13',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: false }
{ remoteAddress: '192.168.0.128',
  localAddress: '192.168.0.13',
  requestLine: 'GET /favicon.ico HTTP/1.1',
  fromSelfHost: false }

※ ブラウザからアクセスしたため、 /favicon.ico 宛のリクエストも存在する

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7