Nodejs がイントラネット侵入サービスを実装

Nodejs がイントラネット侵入サービスを実装

おそらく、コード レベルからイントラネット侵入を説明する記事をインターネットで見つけるのは難しいでしょう。私は探しましたが見つからなかったので、この記事を書きました。

1. LAN内のプロキシ

まず前回の記事を見直してみましょう。ローカル エリア ネットワーク内でサービス プロキシを実装するにはどうすればよいでしょうか。これは非常に単純なので、すぐにコードを見てみましょう。

定数net = require('net')

const proxy = net.createServer(socket => {
  const localServe = 新しい net.Socket()
  localServe.connect(5502, '192.168.31.130') // LAN 内のサービス ポートと IP。

  socket.pipe(localServe).pipe(ソケット)
})

プロキシ.listen(80)

これは非常にシンプルなサーバー側プロキシです。コードはシンプルで明確です。質問がある場合は、おそらくここのパイプです。簡単に説明しましょう。ソケットは全二重ストリーム、つまり読み取りと書き込みの両方が可能なデータ ストリームです。コードでは、ソケットがクライアントからデータを受信すると、そのデータを localSever に書き込みます。localSever にデータがある場合、そのデータがソケットに書き込まれ、ソケットはそのデータをクライアントに送信します。

2. イントラネットの浸透

LAN プロキシは簡単ですが、イントラネットへの侵入はそれほど簡単ではありません。ただし、これはコアコードであり、かなりの論理処理が必要です。具体的に実装する前に、まずイントラネットの浸透度を整理しましょう。

イントラネット侵入とは何ですか?

簡単に言えば、ローカル エリア ネットワーク内のサービスにアクセスできるパブリック ネットワーク クライアントです。たとえば、サービスはローカルで開始されました。パブリック ネットワーク クライアントは、ローカルで開始されたサーバーをどのようにして認識するのでしょうか?ここでは、パブリック ネットワーク サーバーに依存する必要があります。では、パブリック ネットワーク サーバーはどのようにしてローカル サービスを認識するのでしょうか?これには、ローカルとサーバー間のソケット リンクを確立する必要があります。

4つの役割

上記の説明を通じて、4つの役割を紹介します。

  1. パブリック ネットワーク クライアントを client と名付けます。
  2. パブリック ネットワーク サーバーはプロキシとして機能するため、proxyServe という名前が付けられています。
  3. localServe という名前のローカル サービス。
  4. ローカル ソケットは、サーバーに長く接続されています。これは proxyServe と localServe の間のブリッジであり、データ転送を担当します。これをブリッジと名付けました。

このうち、クライアントと localServe については考慮する必要はありません。クライアントはブラウザなどであり、localServe は通常のローカル サービスにすぎないからです。注意する必要があるのは proxyServe と bridge だけです。ここで紹介するのは、やはり最もシンプルな実装方法であり、考え方や考え方を提供するものなので、まずは最もシンプルなものから始めましょう。


4 つの役割のセクションから、ブリッジは proxyServe とのソケット接続であり、データの転送であることがわかります。コードを見て、アイデアを整理してみましょう。

定数net = require('net')

定数 proxyServe = '10.253.107.245'

const ブリッジ = 新しい net.Socket()
ブリッジ.connect(80, proxyServe, _ => {
  bridge.write('GET /regester?key=sq HTTP/1.1\r\n\r\n')
})

ブリッジ.on('データ', データ => {
  const localServer = 新しい net.Socket()
  localServer.connect(8088, 'localhost', _ => {
    localServer.write(データ)
    localServer.on('data', res => bridge.write(res))
  })
})

コードは明確で読みやすく、印象に残ります。ネットライブラリをインポートし、パブリックネットワークアドレスを宣言し、ブリッジを作成し、ブリッジを proxyServe に接続し、成功したら、ローカルサービスを proxyServe に登録します。次に、ブリッジはデータをリッスンし、リクエストが到着すると、ローカルサービスとの接続を作成します。成功したら、リクエストデータは localServe に送信され、同時にレスポンスデータがリッスンされ、レスポンスストリームがブリッジに書き込まれます。

残りについてはあまり説明する必要がありません。結局のところ、これは単なるサンプル コードです。ただし、サンプル コードには /regester?key=sq というセクションがあります。このキーは非常に便利です。ここでは key=sq です。次に、ロール クライアントがプロキシ サービスを介してローカル サービスにアクセスするときに、proxyServe がブリッジに対応し、localServe に対応できるように、このキーをパスに追加する必要があります。

たとえば、lcoalServe は http://localhost:8088、rpoxyServe は example.com、登録キーは sq です。次に、prxoyServe を介して localServe にアクセスする場合は、次のように記述する必要があります: example.com/sq。なぜこのように書くのでしょうか?もちろん、これは単なる定義です。この記事のコードを理解したら、この規則を変更できます。

それでは、次のキーコードを見てみましょう。

プロキシサーブ

ここで紹介する proxyServe は簡略化されたサンプルコードではありますが、説明するのはまだ少し複雑です。十分に理解し、自分の業務と組み合わせて使えるコードにするには、ある程度の努力が必要です。ここではコードをいくつかの部分に分割して、わかりやすく説明します。説明を容易にするために、コード ブロックに名前を付けます。
コードブロック 1: createServe

このブロックの主な機能は、プロキシ サービスを作成し、クライアントとブリッジとのソケット リンクを確立し、ソケット上のデータ要求をリッスンし、コールバック関数で論理処理を実行することです。具体的なコードは次のとおりです。

定数net = require('net')

const bridges = {} // ブリッジがソケット接続を確立すると、ここにキャッシュされます。const clients = {} // クライアントがソケット接続を確立すると、ここにキャッシュされます。具体的なデータ構造については、ソースコードを参照してください。net.createServer(socket => {
  socket.on('data', データ => {
    const リクエスト = data.toString()
    const url = request.match(/.+ (?<url>.+) /)?.groups?.url
    
    if (!url) 戻り値

    if (isBridge(url)) {
      regesterBridge(ソケット、URL)
      戻る
    }

    const { ブリッジ、キー } = findBridge(リクエスト、URL)
    if (!bridge) 戻り値

    cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL)

    キーによるブリッジへのリクエストの送信(キー)
  })
}).listen(80)

データ モニターのコード ロジックを見てみましょう。

  1. リクエストデータを文字列に変換します。
  2. リクエスト内の URL を探します。URL が見つからない場合は、リクエストを直接終了します。
  3. URL を通じてブリッジかどうかを判断します。ブリッジである場合は、ブリッジを登録します。そうでない場合は、クライアント要求と見なします。
  4. クライアント要求に登録済みのブリッジがあるかどうかを確認します。これはプロキシ サービスであり、登録済みのブリッジがない場合、要求は無効とみなされることに注意してください。
  5. このリクエストをキャッシュします。
  6. 次に、ブリッジにリクエストを送信します。

コードとロジックを組み合わせると、理解できるはずですが、5については疑問が残るかもしれません。1つずつ整理してみましょう。

コードブロック 2: isBridge

ブリッジ登録リクエストであるかどうかを判断する方法は非常に簡単ですが、実際のビジネスでは、より正確なデータが定義される場合があります。

関数isBridge(url){
  url.startsWith('/regester?') を返します
}

コードブロック 3: regesterBridge
簡単です。コードを見て説明してください:

関数regesterBridge(ソケット、URL){
  定数キー = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
  ブリッジ[キー] = ソケット
  socket.removeAllListeners('データ')
}
  1. URL を通じて登録するブリッジのキーを見つけます。
  2. ソケット接続をキャッシュします。
  3. ブリッジのデータ監視を削除します - コード ブロック 1 では、各ソケットにデフォルトのデータ監視コールバック関数があります。削除しないと、後続のデータが混乱します。

コードブロック 4: findBridge

ロジックがコード ブロック 4 に到達すると、これはすでにクライアント要求であることを意味します。次に、まず対応するブリッジを見つける必要があります。ブリッジがない場合は、まずブリッジを登録する必要があり、その後、ユーザーはクライアント要求を開始する必要があります。コードは次のとおりです。

関数 findBridge (リクエスト、URL) {
  key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key とします。
  bridge = bridges[キー]とします
  if (bridge) return { bridge, key }

  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer
  if (!referer) が {} を返す

  キー = referer.split('//')[1].split('/')[1]
  bridge = 橋[キー]
  if (bridge) return { bridge, key }

  戻る {}
}

  • URL からプロキシするブリッジのキーを照合し、見つかった場合は対応するブリッジとキーを返します。
  • 見つからない場合は、リクエスト ヘッダーのリファラーで検索します。見つかった場合は、ブリッジとキーが返されます。
  • いずれも見つかりません。このリクエストはコード ブロック 1 で終了することが分かっています。

コードブロック 5: cacheClientRequest

ここでのコード実行は、それがすでにクライアント リクエストであることを示しています。まず、このリクエストをキャッシュします。キャッシュするときに、後続の操作を容易にするために、リクエストに対応するブリッジとキー バインディングもキャッシュします。

クライアント要求をキャッシュする理由は何ですか?

現在のソリューションでは、リクエストとレスポンスの両方がペアで順序付けられることを期待しています。ネットワーク伝送が断片化していることは周知の事実です。現状では、アプリケーション層でリクエストとレスポンスをペアにして順序付けて制御しないと、データパケット間の混乱が生じてしまいます。これが現状です。将来的にもっと良い解決策が見つかったら、アプリケーション層でデータの要求と応答を強制的に順序通りにするのではなく、TCP/IP 層を信頼することができます。
理由を説明した後、まずは比較的単純なキャッシュ コードを見てみましょう。複雑なのは、リクエストを 1 つずつ取り出して、レスポンス全体を順番に返す点です。

関数cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL){
  if (クライアント[キー]) {
    クライアント[キー].requests.push({ブリッジ、キー、ソケット、リクエスト、URL})
  } それ以外 {
    クライアント[キー] = {}
    クライアント[キー].requests = [{ブリッジ、キー、ソケット、リクエスト、URL}]
  }
}

まず、ブリッジに対応するキーの下にクライアント要求キャッシュがすでに存在するかどうかを判断します。存在する場合は、それをプッシュします。

そうでない場合は、オブジェクトを作成し、このリクエストを初期化します。

次のステップは最も複雑で、リクエスト キャッシュを取り出してブリッジに送信し、現在の応答が終了するまでブリッジの応答をリッスンします。次に、ブリッジのデータ監視を削除し、次のリクエストを取り出すことを試み、クライアントからのすべてのリクエストが処理されるまで上記のアクションを繰り返します。

コードブロック 6: sendRequestToBridgeByKey

コード ブロック 5 の最後に、ブロックの概要説明が示されます。まず少し理解してから次のコードを見てください。コードにはレスポンスの整合性の判断がいくつかあるからです。これらを削除すると、コードが理解しやすくなります。ソリューション全体では、リクエストの整合性は処理しませんでした。これは、ファイル アップロード インターフェイスでない限り、リクエストは基本的にデータ パケットのサイズ内であるためです。ファイル アップロード インターフェイスは現時点では処理しません。そうでない場合、コードがより複雑になります。

関数 sendRequestToBridgeByKey (キー) {
  const client = クライアント[キー]
  if (client.isSending) 戻り値

  const リクエスト = client.requests
  if (requests.length <= 0) 戻り値

  client.isSending = true
  クライアント.コンテンツ長 = 0
  クライアント受信 = 0

  const {ブリッジ、ソケット、リクエスト、URL} = リクエスト.shift()

  const newUrl = url.replace(キー、'')
  const newRequest = request.replace(url, newUrl)

  bridge.write(新しいリクエスト)
  ブリッジ.on('データ', データ => {
    定数レスポンス = data.toString()

    code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code とします。
    if (コード) {
      コード = parseInt(コード)
      (コード === 200) の場合 {
        contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength とします。
        if (コンテンツの長さ) {
          コンテンツの長さ = parseInt(コンテンツの長さ)
          client.contentLength = コンテンツの長さ
          client.received = Buffer.from(response.split('\r\n\r\n')[1]).length
        }
      } それ以外 {
        socket.write(データ)
        client.isSending = false
        bridge.removeAllListeners('データ')
        キーによるブリッジへのリクエストの送信(キー)
        戻る
      }
    } それ以外 {
      クライアントが受信したデータ += データの長さ
    }

    socket.write(データ)

    (client.contentLength <= client.received) の場合 {
      client.isSending = false
      bridge.removeAllListeners('データ')
      キーによるブリッジへのリクエストの送信(キー)
    }
  })
}

クライアントの中からブリッジキーに対応するクライアントを取り出します。
クライアントに送信中のリクエストがあるかどうかを判断します。ある場合は、実行を終了します。そうでない場合は、続行します。
クライアントの下にリクエストがあるかどうかを判断します。リクエストがある場合は続行し、ない場合は実行を終了します。
要求されたソケットとキャッシュされたブリッジを含むキューの最初のものを取得します。
合意したデータを置き換え、最終的な要求データをブリッジに送信します。
ブリッジからのデータ応答をリッスンします。

  • 応答コードを取得する
    • 応答が 200 の場合、そこからコンテンツの長さを取得します。 取得できた場合は、このリクエストに対していくつかの初期化操作を実行します。リクエストの長さを設定します。送信されたリクエストの長さを設定します。
    • 200でない場合は、データをクライアントに送信し、リクエストを終了し、データ監視を削除し、sendRequestToBridgeByKeyを再帰的に呼び出します。
  • コードが取得されない場合、この応答は最初のものではないと想定し、その長さが送信フィールドに追加されます。
  • その後、このデータをクライアントに送信します。
  • 次に、応答の長さが送信されたデータの長さと一致しているかどうかを判断します。一致している場合は、クライアントのデータ送信ステータスを false に設定し、データ監視を削除して、sendRequestToBridgeByKey を再帰的に呼び出します。

この時点で、コアコードロジックは完成しました。

要約する

このコード セットを理解した後は、それを拡張して独自の用途に合わせてコードを充実させることができます。このコードセットを理解した後、他の使用シナリオを思いつくことができますか?このアイデアはリモート コントロールにも使用できますか? クライアントを制御したい場合は、このコードからインスピレーションを得ることができます。
このコード セットは難しいかもしれません。TCP/IP だけでなく、HTTP、いくつかの重要な要求ヘッダー、いくつかの重要な応答情報についてもすべて知っておく必要があるかもしれません。もちろん、HTTP について知っていることが増えれば増えるほど、より良くなります。
何かご連絡事項がございましたらメッセージを残してください。

proxyServe ソースコード

定数net = require('net')

const ブリッジ = {}
定数クライアント = {}

net.createServer(ソケット => {
  socket.on('data', データ => {
    const リクエスト = data.toString()
    const url = request.match(/.+ (?<url>.+) /)?.groups?.url
    
    if (!url) 戻り値

    if (isBridge(url)) {
      regesterBridge(ソケット、URL)
      戻る
    }

    const { ブリッジ、キー } = findBridge(リクエスト、URL)
    if (!bridge) 戻り値

    cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL)

    キーによるブリッジへのリクエストの送信(キー)
  })
}).listen(80)

関数isBridge(url){
  url.startsWith('/regester?') を返します
}

関数regesterBridge(ソケット、URL){
  定数キー = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
  ブリッジ[キー] = ソケット
  socket.removeAllListeners('データ')
}

関数 findBridge (リクエスト、URL) {
  key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key とします。
  bridge = bridges[キー]とします
  if (bridge) return { bridge, key }

  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer
  if (!referer) が {} を返す

  キー = referer.split('//')[1].split('/')[1]
  bridge = 橋[キー]
  if (bridge) return { bridge, key }

  戻る {}
}

関数cacheClientRequest(ブリッジ、キー、ソケット、リクエスト、URL){
  if (クライアント[キー]) {
    クライアント[キー].requests.push({ブリッジ、キー、ソケット、リクエスト、URL})
  } それ以外 {
    クライアント[キー] = {}
    クライアント[キー].requests = [{ブリッジ、キー、ソケット、リクエスト、URL}]
  }
}

関数 sendRequestToBridgeByKey (キー) {
  const client = クライアント[キー]
  if (client.isSending) 戻り値

  const リクエスト = client.requests
  if (requests.length <= 0) 戻り値

  client.isSending = true
  クライアント.コンテンツ長 = 0
  クライアント受信 = 0

  const {ブリッジ、ソケット、リクエスト、URL} = リクエスト.shift()

  const newUrl = url.replace(キー、'')
  const newRequest = request.replace(url, newUrl)

  bridge.write(新しいリクエスト)
  ブリッジ.on('データ', データ => {
    定数レスポンス = data.toString()

    code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code とします。
    if (コード) {
      コード = parseInt(コード)
      (コード === 200) の場合 {
        contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength とします。
        if (コンテンツの長さ) {
          コンテンツの長さ = parseInt(コンテンツの長さ)
          client.contentLength = コンテンツの長さ
          client.received = Buffer.from(response.split('\r\n\r\n')[1]).length
        }
      } それ以外 {
        socket.write(データ)
        client.isSending = false
        bridge.removeAllListeners('データ')
        キーによるブリッジへのリクエストの送信(キー)
        戻る
      }
    } それ以外 {
      クライアントが受信したデータ += データの長さ
    }

    socket.write(データ)

    (client.contentLength <= client.received) の場合 {
      client.isSending = false
      bridge.removeAllListeners('データ')
      キーによるブリッジへのリクエストの送信(キー)
    }
  })
}

これで、Nodejs によるイントラネット侵入サービスの実装に関するこの記事は終了です。より関連性の高い Node イントラネット侵入コンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • イントラネットの浸透-----突破口を開く方法
  • 外部アクセスステップ分析を可能にするC# Webアプリケーションのデバッグ
  • ウェブからイントラネットへの浸透プロセスの詳細な説明

<<:  MySQL サーバー 5.7.20 のインストールと設定方法のグラフィック チュートリアル

>>:  Docker を使用した MySQL のデプロイの詳細説明 (データ永続化)

推薦する

垂直方向の中央揃えをエレガントに実現する方法を教えます(推奨)

序文CSS で水平方向と垂直方向に中央揃えする方法はたくさんあります。この記事で紹介する方法は非常に...

MySQL IFNULL判定問題の解決方法

問題: mybatis によって返される null 型のデータが消え、フロントエンドの表示にエラーが...

Nginxのアクセスボリューム制御の詳細な説明

目的リクエスト アクセス ボリュームを制御するための Nginx ngx_http_limit_co...

JSで画面録画機能を作成する

OBS studioかっこいいですが、 JavaScriptもっとかっこいいです。では、 JavaS...

Reactドラッグフックを実装するための100行以上のコード

序文ソースコードは合計で 100 行強しかありません。これを読めば、react-dnd などの成熟し...

WindowsとLinux間でファイルを転送する方法

WindowsとLinux間のファイル転送(1)WinSCPを使用して、WindowsファイルをLi...

vue 動的コンポーネント

目次1. コンポーネント2. キープアライブ2.1 問題点2.2 キープアライブを使って解決する2....

Vueはechartを使用してラベルと色をカスタマイズします

この記事では、参考までに、echartを使用してタグと色をカスタマイズするVueの具体的なコードを紹...

jQueryのコア機能とイベント処理の詳細な説明

目次イベントページの読み込みイベント委任イベントの切り替えイベント要約するイベントページの読み込み1...

Vue における Vue.use() の原理と基本的な使用法

目次序文1. 例で理解する2. ソースコードを分析する3. まとめ要約する序文他の人のコンポーネント...

JavaScriptはオブジェクトの不要なプロパティを削除します

目次例方法1: 削除方法2: 分解補充する要約するThinking シリーズは、10 分で実用的なプ...

MySQL 5.7 MGR シングルマスター決定マスターノード方式の詳細説明

当銀行のMGRは年末に開始されます。公式文書を読んだり、毎日テストを受けたりしなければなりません。毎...

純粋な CSS3 で蝶が羽ばたく様子を再現する例

純粋なCSS3で蝶が羽ばたく様子を再現。まずはその効果をご覧ください どうですか?効果はかなりいいで...

HTML チュートリアル: よく使われる HTML タグのコレクション (6)

関連記事:初心者が学ぶ HTML タグ (5)導入された HTML タグは、必ずしも XHTML 仕...

プロファイルを使用して遅い SQL を分析する MySQL の詳細な説明 (グループ左結合はサブクエリよりも効率的です)

プロファイルを使用して遅いSQLを分析するMySQL の SQL パフォーマンス アナライザーの主な...