Nodejs implementiert Intranet-Penetrationsdienst

Nodejs implementiert Intranet-Penetrationsdienst

Vielleicht fällt es Ihnen schwer, im Internet einen Artikel zu finden, der die Intranet-Penetration auf Code-Ebene erklärt. Ich habe erfolglos danach gesucht und deshalb diesen Artikel geschrieben.

1. Proxy im LAN

Lassen Sie uns zunächst den vorherigen Artikel noch einmal durchgehen. Wie implementiere ich einen Service-Proxy in einem lokalen Netzwerk? Da dies sehr einfach ist, gehen wir direkt zum Code.

const net = erfordern('net')

const proxy = net.createServer(socket => {
  const localServe = neues net.Socket()
  localServe.connect(5502, '192.168.31.130') // Service-Port und IP im LAN.

  socket.pipe(localServe).pipe(socket)
})

Proxy.Listen(80)

Dies ist ein sehr einfacher serverseitiger Proxy. Der Code ist einfach und klar. Wenn Sie Fragen haben, liegt es wahrscheinlich an der Pipe hier. Lassen Sie es mich kurz erklären. Der Socket ist ein Vollduplex-Stream, also ein Datenstrom, der sowohl lesbar als auch beschreibbar sein kann. Wenn der Socket im Code Daten vom Client empfängt, schreibt er diese in den lokalen Server. Wenn der lokale Server Daten hat, schreibt er diese in den Socket und der Socket sendet die Daten dann an den Client.

2. Intranet-Penetration

Ein LAN-Proxy ist einfach, aber die Intranet-Penetration ist nicht so einfach. Es handelt sich jedoch um den Kerncode und erfordert eine erhebliche logische Verarbeitung. Bevor wir es konkret umsetzen, klären wir zunächst die Intranet-Durchdringung.

Was ist Intranet-Penetration?

Einfach ausgedrückt handelt es sich dabei um einen öffentlichen Netzwerkclient, der auf Dienste innerhalb des lokalen Netzwerks zugreifen kann. Beispielsweise wurden Dienste lokal gestartet. Woher kennt der öffentliche Netzwerkclient den lokal gestarteten Server? Hier müssen wir auf die öffentlichen Netzserver zurückgreifen. Woher weiß der öffentliche Netzwerkserver also vom lokalen Dienst? Hierzu ist die Herstellung einer Socket-Verbindung zwischen der lokalen Umgebung und dem Server erforderlich.

Vier Rollen

Durch die obige Beschreibung stellen wir vier Rollen vor.

  1. Wir nennen den öffentlichen Netzwerkclient Client.
  2. Der öffentliche Netzwerkserver heißt ProxyServe, weil er als Proxy fungiert.
  3. Lokaler Dienst mit dem Namen „localServe“.
  4. Der lokale Socket ist lange mit dem Server verbunden. Er ist die Brücke zwischen ProxyServe und LocalServe und ist für den Datentransfer zuständig. Wir haben ihn Bridge genannt.

Um Client und localServe müssen wir uns dabei nicht kümmern, da es sich beim Client um einen Browser oder etwas anderes handeln kann und localServe nur ein gewöhnlicher lokaler Dienst ist. Wir müssen uns nur um ProxyServe und Bridge kümmern. Was wir hier vorstellen, ist immer noch die einfachste Implementierungsmethode und bietet eine Denk- und Denkweise. Beginnen wir also mit der einfachsten.

Brücke

Aus dem Abschnitt zu den vier Rollen wissen wir, dass Bridge eine Socket-Verbindung mit ProxyServe ist und die Datenübertragung ermöglicht. Sehen wir uns den Code an, um die Ideen zu klären.

const net = erfordern('net')

const proxyServe = "10.253.107.245"

const bridge = neues Netz.Socket()
bridge.connect(80, proxyServe, _ => {
  bridge.write('GET /regester?key=sq HTTP/1.1\r\n\r\n')
})

bridge.on('Daten', Daten => {
  const localServer = neues net.Socket()
  localServer.connect(8088, 'localhost', _ => {
    localServer.write(Daten)
    localServer.on('Daten', res => bridge.write(res))
  })
})

Der Code ist klar und lesbar, sogar eingängig. Importieren Sie die Netzbibliothek, deklarieren Sie die öffentliche Netzwerkadresse, erstellen Sie eine Brücke, verbinden Sie die Brücke mit ProxyServe und registrieren Sie nach erfolgreichem Abschluss den lokalen Dienst bei ProxyServe. Anschließend wartet die Brücke auf Daten und stellt bei Eingang einer Anforderung eine Verbindung mit dem lokalen Dienst her. Nach erfolgreichem Abschluss werden die Anforderungsdaten an LocalServe gesendet, gleichzeitig werden die Antwortdaten abgehört und der Antwortstrom in die Brücke geschrieben.

Zum Rest gibt es nicht viel zu erklären, schließlich handelt es sich hier nur um Beispielcode. Im Beispielcode gibt es jedoch einen Abschnitt /regester?key=sq. Dieser Schlüssel ist sehr nützlich. Hier key=sq. Wenn der Rollenclient dann über den Proxydienst auf den lokalen Dienst zugreift, muss dieser Schlüssel dem Pfad hinzugefügt werden, damit der ProxyServe der Bridge und somit dem lokalen Dienst entsprechen kann.

Beispiel: lcoalServe ist: http://localhost:8088, rpoxyServe ist example.com und der registrierte Schlüssel ist sq. Wenn Sie dann über prxoyServe auf localServe zugreifen möchten, müssen Sie es wie folgt schreiben: example.com/sq. Warum so schreiben? Natürlich ist dies nur eine Definition. Nachdem Sie den Code in diesem Artikel verstanden haben, können Sie diese Konvention ändern.

Schauen wir uns also die folgenden Schlüsselcodes an:

ProxyServe

Obwohl der ProxyServe hier ein vereinfachter Beispielcode ist, ist er dennoch etwas kompliziert zu erklären. Wenn Sie ihn vollständig verstehen und mit Ihrem eigenen Unternehmen kombinieren möchten, um einen verwendbaren Code zu erstellen, müssen Sie einige Anstrengungen unternehmen. Hier teile ich den Code in Teile auf und versuche, ihn klar zu erklären. Wir geben den Codeblöcken Namen, um die Erklärung zu erleichtern.
Codeblock 1: createServe

Die Hauptfunktion dieses Blocks besteht darin, einen Proxy-Dienst zu erstellen, eine Socket-Verbindung mit dem Client und der Bridge herzustellen, die Datenanforderung auf dem Socket abzuhören und eine logische Verarbeitung in der Rückruffunktion durchzuführen. Der spezifische Code lautet wie folgt:

const net = erfordern('net')

const bridges = {} // Wenn eine Bridge eine Socket-Verbindung herstellt, wird sie hier zwischengespeichert const clients = {} // Wenn ein Client eine Socket-Verbindung herstellt, wird sie hier zwischengespeichert. Die spezifische Datenstruktur finden Sie im Quellcode net.createServer(socket => {
  socket.on('Daten', Daten => {
    const request = data.toString()
    const url = request.match(/.+ (?<url>.+) /)?.groups?.url
    
    wenn (!url) zurückgeben

    wenn (isBridge(url)) {
      regesterBridge(Socket, URL)
      zurückkehren
    }

    const { bridge, key } = findBridge(Anfrage, URL)
    wenn (!bridge) return

    cacheClientRequest(Bridge, Schlüssel, Socket, Anfrage, URL)

    sendRequestToBridgeByKey(Schlüssel)
  })
}).listen(80)

Schauen Sie sich die Code-Logik im Datenmonitor an:

  1. Konvertiert die Anforderungsdaten in eine Zeichenfolge.
  2. Suchen Sie in der Anfrage nach der URL. Wenn die URL nicht gefunden wird, brechen Sie die Anfrage direkt ab.
  3. Stellen Sie anhand der URL fest, ob es sich um eine Brücke handelt. Wenn ja, registrieren Sie die Brücke. Andernfalls betrachten Sie es als eine Client-Anforderung.
  4. Überprüfen Sie, ob für die Clientanforderung registrierte Bridges vorhanden sind. Bedenken Sie, dass es sich hierbei um einen Proxy-Dienst handelt. Wenn keine registrierte Bridge vorhanden ist, wird die Anforderung als ungültig betrachtet.
  5. Diese Anfrage zwischenspeichern.
  6. Senden Sie dann die Anfrage an die Brücke.

Wenn Sie den Code und die Logik kombinieren, sollten Sie in der Lage sein, es zu verstehen, aber möglicherweise haben Sie Fragen zu 5. Lassen Sie uns diese nacheinander klären.

Codeblock 2: isBridge

Die Methode zur Bestimmung, ob es sich um eine Brückenregistrierungsanforderung handelt, ist sehr einfach. Für das reale Geschäft können jedoch genauere Daten definiert werden.

Funktion isBridge (URL) {
  returniere url.startsWith('/regester?')
}

Codeblock drei: registerBridge
Ganz einfach, schauen Sie sich den Code an und erklären Sie:

Funktion regesterBridge (Socket, URL) {
  const Schlüssel = url.match(/(^|&|\?)Schlüssel=(?<Schlüssel>[^&]*)(&|$)/?.Gruppen?.Schlüssel
  Brücken[Schlüssel] = Sockel
  socket.removeAllListeners('Daten')
}
  1. Den Schlüssel der zu registrierenden Bridge finden Sie über die URL.
  2. Zwischenspeichern Sie die Socket-Verbindung.
  3. Entfernen Sie die Datenüberwachung der Brücke - In Codeblock 1 verfügt jeder Socket über eine standardmäßige Rückruffunktion zur Datenüberwachung. Wenn diese nicht entfernt wird, werden nachfolgende Daten durcheinander gebracht.

Codeblock 4: findBridge

Wenn die Logik Codeblock 4 erreicht, bedeutet dies, dass dies bereits eine Client-Anforderung ist. Dann muss zuerst die entsprechende Brücke gefunden werden. Wenn keine Brücke vorhanden ist, muss zuerst die Brücke registriert werden. Anschließend muss der Benutzer die Client-Anforderung initiieren. Der Code lautet wie folgt:

Funktion findBridge (Anfrage, URL) {
  let Schlüssel = url.match(/\/(?<Schlüssel>[^\/\?]*)(\/|\?|$)/)?.Gruppen?.Schlüssel
  let bridge = Brücken [Schlüssel]
  if (Brücke) return { Brücke, Schlüssel }

  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/?.groups?.referer
  wenn (!referer) return {}

  Schlüssel = Referrer.split('//')[1].split('/')[1]
  bridge = Brücken[Schlüssel]
  if (Brücke) return { Brücke, Schlüssel }

  zurückkehren {}
}

  • Ordnen Sie den Schlüssel der Brücke zu, die mit der URL kommuniziert werden soll, und geben Sie die entsprechende Brücke und den entsprechenden Schlüssel zurück, falls diese gefunden wurden.
  • Wenn es nicht gefunden wird, suchen Sie im Referrer im Anforderungsheader danach. Wenn es gefunden wird, werden die Brücke und der Schlüssel zurückgegeben.
  • Keiner von ihnen kann gefunden werden. Wir wissen, dass diese Anfrage in Codeblock 1 beendet wird.

Codeblock 5: cacheClientRequest

Die Codeausführung hier zeigt an, dass es sich bereits um eine Client-Anforderung handelt. Wir cachen diese Anforderung zunächst. Beim Cachen cachen wir auch die der Anforderung entsprechende Bridge und Tastenkombinationen, um nachfolgende Vorgänge zu erleichtern.

Warum Client-Anfragen zwischenspeichern?

In der aktuellen Lösung hoffen wir, dass sowohl Anfragen als auch Antworten paarweise angeordnet sind. Wir wissen, dass die Netzwerkübertragung fragmentiert ist. Wenn wir derzeit die Anforderung und Antwort nicht paarweise und in der richtigen Reihenfolge auf der Anwendungsebene steuern, führt dies zu Verwirrung zwischen den Datenpaketen. Dies ist die aktuelle Situation. Wenn es später eine bessere Lösung gibt, können wir der TCP/IP-Schicht vertrauen, anstatt die Anforderung und Antwort der Daten in der Anwendungsschicht in der richtigen Reihenfolge zu erzwingen.
Nachdem wir die Gründe erklärt haben, schauen wir uns zunächst den Cache-Code an. Er ist hier relativ einfach. Die Komplexität besteht darin, die Anforderungen einzeln herauszunehmen und die gesamte Antwort der Reihe nach zurückzugeben.

Funktion cacheClientRequest (Bridge, Schlüssel, Socket, Anfrage, URL) {
  wenn (Kunden[Schlüssel]) {
    Clients[Schlüssel].Requests.push({Bridge, Schlüssel, Socket, Anfrage, URL})
  } anders {
    Kunden[Schlüssel] = {}
    Clients[Schlüssel].Anfragen = [{Bridge, Schlüssel, Socket, Anfrage, URL}]
  }
}

Wir ermitteln zunächst, ob unter dem der Brücke entsprechenden Schlüssel bereits ein Client-Anforderungscache vorhanden ist. Wenn ja, fügen wir ihn ein.

Wenn nicht, erstellen wir ein Objekt und initialisieren diese Anfrage.

Der nächste Schritt ist der komplizierteste. Er besteht darin, den Anforderungscache zu entfernen, ihn an die Brücke zu senden und die Antwort der Brücke abzuhören, bis die aktuelle Antwort endet. Löschen Sie dann die Datenüberwachung der Brücke, versuchen Sie, die nächste Anforderung zu entfernen, und wiederholen Sie die obigen Aktionen, bis alle Anforderungen des Clients verarbeitet sind.

Codeblock sechs: sendRequestToBridgeByKey

Am Ende des fünften Codeblocks wird eine zusammenfassende Beschreibung des Blocks gegeben. Sie können es zunächst ein wenig verstehen und sich dann den folgenden Code ansehen, da der Code einige Beurteilungen der Antwortintegrität enthält. Wenn Sie diese entfernen, ist der Code leichter zu verstehen. In der gesamten Lösung haben wir die Anforderungsintegrität nicht verarbeitet, da eine Anforderung grundsätzlich die Größe eines Datenpakets hat, es sei denn, es handelt sich um eine Datei-Upload-Schnittstelle, die wir jetzt nicht verarbeiten werden. Andernfalls wird der Code komplizierter.

Funktion sendRequestToBridgeByKey (Schlüssel) {
  const client = clients[Schlüssel]
  wenn (client.isSending) return

  const Anfragen = Client.Anfragen
  wenn (Anfragen.Länge <= 0) return

  client.isSending = true
  client.contentLength = 0
  client.empfangen = 0

  const {bridge, socket, request, url} = Anfragen.shift()

  const newUrl = url.replace(Schlüssel, '')
  const newRequest = request.replace(url, neueUrl)

  bridge.write(neueAnfrage)
  bridge.on('Daten', Daten => {
    const Antwort = data.toString()

    let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/?.groups?.code
    wenn (Code) {
      Code = parseInt(Code)
      wenn (Code === 200) {
        let contentLength = response.match(/\r\nInhaltslänge: (?<contentLength>.+)\r\n/?.groups?.contentLength
        if (Inhaltslänge) {
          Inhaltslänge = parseInt(Inhaltslänge)
          client.contentLength = Inhaltslänge
          Client.empfangen = Puffer.von(Antwort.split('\r\n\r\n')[1]).Länge
        }
      } anders {
        socket.schreiben(Daten)
        client.isSending = false
        bridge.removeAllListeners('Daten')
        sendRequestToBridgeByKey(Schlüssel)
        zurückkehren
      }
    } anders {
      Client.empfangen += Daten.Länge
    }

    socket.schreiben(Daten)

    wenn (Client.Inhaltslänge <= Client.Empfangen) {
      client.isSending = false
      bridge.removeAllListeners('Daten')
      sendRequestToBridgeByKey(Schlüssel)
    }
  })
}

Nehmen Sie aus den Clients den Client heraus, der dem Bridge-Schlüssel entspricht.
Stellen Sie fest, ob vom Client eine Anfrage gesendet wird. Wenn ja, beenden Sie die Ausführung. Wenn nicht, fahren Sie fort.
Bestimmen Sie, ob eine Anforderung unter dem Client vorliegt. Wenn ja, fahren Sie fort. Wenn nicht, beenden Sie die Ausführung.
Nehmen Sie den ersten aus der Warteschlange, der den angeforderten Socket und die zwischengespeicherte Brücke enthält.
Ersetzen Sie die vereinbarten Daten und senden Sie die endgültigen Anforderungsdaten an die Brücke.
Warten Sie auf Datenantworten von der Brücke.

  • Abrufen des Antwortcodes
    • Wenn die Antwort 200 ist, ermitteln wir daraus die Inhaltslänge. Wenn ja, führen wir einige Initialisierungsvorgänge für diese Anfrage durch. Legen Sie die Anforderungslänge fest. Legen Sie die Länge der gesendeten Anforderung fest.
    • Wenn es nicht 200 ist, senden wir die Daten an den Client, beenden die Anforderung, entfernen die Datenüberwachung und rufen rekursiv sendRequestToBridgeByKey auf
  • Wenn kein Code erhalten wird, gehen wir davon aus, dass diese Antwort nicht die erste ist, daher wird ihre Länge zum gesendeten Feld hinzugefügt.
  • Diese Daten übermitteln wir anschließend an den Auftraggeber.
  • Bestimmen Sie dann, ob die Länge der Antwort mit der Länge der gesendeten Daten übereinstimmt. Wenn dies der Fall ist, setzen Sie den Datensendestatus des Clients auf „false“, entfernen Sie die Datenüberwachung und rufen Sie rekursiv „sendRequestToBridgeByKey“ auf.

An diesem Punkt ist die Kerncodelogik abgeschlossen.

Zusammenfassen

Nachdem Sie diesen Codesatz verstanden haben, können Sie ihn erweitern und den Code für Ihren eigenen Gebrauch bereichern. Können Sie sich nach dem Verständnis dieses Codesatzes andere Verwendungsszenarien dafür vorstellen? Kann diese Idee auch für die Fernsteuerung verwendet werden? Wenn Sie den Client steuern möchten, können Sie sich von diesem Code inspirieren lassen.
Dieser Codesatz kann schwierig sein. Sie müssen möglicherweise alles über TCP/IP sowie HTTP, einige wichtige Anforderungsheader und einige wichtige Antwortinformationen wissen. Natürlich ist es besser, je mehr Sie über HTTP wissen.
Wenn Sie etwas mitzuteilen haben, hinterlassen Sie bitte eine Nachricht.

ProxyServe-Quellcode

const net = erfordern('net')

const Brücken = {}
const Kunden = {}

net.createServer(socket => {
  socket.on('Daten', Daten => {
    const request = data.toString()
    const url = request.match(/.+ (?<url>.+) /)?.groups?.url
    
    wenn (!url) zurückgeben

    wenn (isBridge(url)) {
      regesterBridge(Socket, URL)
      zurückkehren
    }

    const { bridge, key } = findBridge(Anfrage, URL)
    wenn (!bridge) return

    cacheClientRequest(Bridge, Schlüssel, Socket, Anfrage, URL)

    sendRequestToBridgeByKey(Schlüssel)
  })
}).listen(80)

Funktion isBridge (URL) {
  returniere url.startsWith('/regester?')
}

Funktion regesterBridge (Socket, URL) {
  const Schlüssel = url.match(/(^|&|\?)Schlüssel=(?<Schlüssel>[^&]*)(&|$)/?.Gruppen?.Schlüssel
  Brücken[Schlüssel] = Sockel
  socket.removeAllListeners('Daten')
}

Funktion findBridge (Anfrage, URL) {
  let Schlüssel = url.match(/\/(?<Schlüssel>[^\/\?]*)(\/|\?|$)/)?.Gruppen?.Schlüssel
  let bridge = Brücken [Schlüssel]
  if (Brücke) return { Brücke, Schlüssel }

  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/?.groups?.referer
  wenn (!referer) return {}

  Schlüssel = Referrer.split('//')[1].split('/')[1]
  bridge = Brücken[Schlüssel]
  if (Brücke) return { Brücke, Schlüssel }

  zurückkehren {}
}

Funktion cacheClientRequest (Bridge, Schlüssel, Socket, Anfrage, URL) {
  wenn (Kunden[Schlüssel]) {
    Clients[Schlüssel].Requests.Push({Bridge, Schlüssel, Socket, Anfrage, URL})
  } anders {
    Kunden[Schlüssel] = {}
    Clients[Schlüssel].Anfragen = [{Bridge, Schlüssel, Socket, Anfrage, URL}]
  }
}

Funktion sendRequestToBridgeByKey (Schlüssel) {
  const client = clients[Schlüssel]
  wenn (client.isSending) return

  const Anfragen = Client.Anfragen
  if (Anfragen.Länge <= 0) return

  client.isSending = true
  client.contentLength = 0
  client.empfangen = 0

  const {bridge, socket, request, url} = Anfragen.shift()

  const newUrl = url.replace(Schlüssel, '')
  const newRequest = request.replace(url, neueUrl)

  bridge.write(neueAnfrage)
  bridge.on('Daten', Daten => {
    const Antwort = data.toString()

    let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/?.groups?.code
    wenn (Code) {
      Code = parseInt(Code)
      wenn (Code === 200) {
        let contentLength = response.match(/\r\nInhaltslänge: (?<contentLength>.+)\r\n/?.groups?.contentLength
        if (Inhaltslänge) {
          Inhaltslänge = parseInt(Inhaltslänge)
          client.contentLength = Inhaltslänge
          Client.empfangen = Puffer.von(Antwort.split('\r\n\r\n')[1]).Länge
        }
      } anders {
        socket.schreiben(Daten)
        client.isSending = false
        bridge.removeAllListeners('Daten')
        sendRequestToBridgeByKey(Schlüssel)
        zurückkehren
      }
    } anders {
      Client.empfangen += Daten.Länge
    }

    socket.schreiben(Daten)

    wenn (Client.Inhaltslänge <= Client.Empfangen) {
      client.isSending = false
      bridge.removeAllListeners('Daten')
      sendRequestToBridgeByKey(Schlüssel)
    }
  })
}

Dies ist das Ende dieses Artikels über die Implementierung des Intranet-Penetrationsdienstes durch Nodejs. Weitere relevante Inhalte zur Intranet-Penetration von Node finden Sie in früheren Artikeln auf 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Intranet-Penetration ----- So gelingt der Durchbruch
  • C#-Webanwendungsdebugging, um die Analyse externer Zugriffsschritte zu ermöglichen
  • Eine detaillierte Erklärung des Prozesses der Durchdringung vom Web zum Intranet

<<:  Grafisches Tutorial zur Installation und Konfiguration von MySQL Server 5.7.20

>>:  Detaillierte Erläuterung der Bereitstellung von MySQL mit Docker (Datenpersistenz)

Artikel empfehlen

Implementierung der Installation von Docker in einer Win10-Umgebung

1. Rufen Sie die offizielle Docker-Website auf Ge...

WeChat-Applet implementiert Suchfunktion und springt zur Suchergebnisseite

Suchseite: search.wxml-Seite: <view class=&quo...

JavaScript-Canvas zum Laden von Bildern

In diesem Artikel wird der spezifische Code von J...

Ausführliche Erläuterung der HTML-Grundlagen (Teil 2)

1. Liste Der Listen-UL- Container wird mit einer ...

Vue implementiert die Drag & Drop-Sortierfunktion der Seiten-Div-Box

vue implementiert die Drag & Drop-Sortierfunk...

So schreiben Sie DROP TABLE in verschiedene Datenbanken

So schreiben Sie DROP TABLE in verschiedene Daten...

So installieren Sie MongoDB 4.2 mit Yum auf CentOS8

1. Erstellen Sie eine Repo-Datei Lesen Sie die of...

Analyse des Tutorials zur Implementierung der Remote-Anmeldung unter Linux

Linux wird im Allgemeinen als Server verwendet un...

Analyse der Prinzipien und Verwendung von Linux-Hardlinks und Softlinks

Im Linux-System gibt es einen Dateityp namens Lin...

Ideen zum Erstellen von Welleneffekten mit CSS

Zuvor habe ich mehrere Möglichkeiten vorgestellt,...

So erstellen Sie einen MySQL PXC-Cluster

Inhaltsverzeichnis 1. Einführung in PXC 1.1 Einfü...