Вам может быть сложно найти в Интернете статью, объясняющую проникновение в интранет с уровня кода, я искал и не смог, поэтому я сделал эту статью.
1. Интранет-прокси
Давайте посмотрим на бумаги, как добиться сервисного агента в локальной сети? Потому что это очень просто, так, прямо в коде.
const net = require('net')
const proxy = net.createServer(socket => {
const localServe = new net.Socket()
localServe.connect(5502, '192.168.31.130') // 局域网内的服务端口及ip。
socket.pipe(localServe).pipe(socket)
})
proxy.listen(80)
Это очень простой серверный агент, код простой и понятный.Если у вас есть какие-либо вопросы, считается, что труба (PIPE), просто. Socket — это полнодуплексный поток, который представляет собой поток данных с возможностью чтения и записи. В коде, когда сокет получает данные клиента, он записывает данные в локальный сервер.Когда локальный сервер имеет данные, он записывает данные в сокет, и сокет отправляется клиенту.
2. Проникновение в интранет
Локальный сетевой прокси прост, а проникновение в интранет не так просто, однако это основной код, требующий значительной логической обработки. Перед конкретной реализацией разберемся с проникновением в интранет.
Что такое проникновение в интранет?
Проще говоря, это клиент общедоступной сети, который может получать доступ к службам в локальной сети. Например, локально запущенная служба. Как клиент общедоступной сети может узнать локальную подачу? Здесь необходимо использовать общедоступный сетевой сервер. Так как же общедоступный сетевой сервер узнает о локальной службе? Это требует установления сокетного соединения между локальным сервером и сервером.
четыре роли
Из приведенного выше описания мы выявляем четыре роли.
- Клиент общедоступной сети, мы называем его client.
- Сервер общедоступной сети из-за его роли прокси называется proxyServe.
- Локальная служба с именем localServe.
- Сокет между локалом и сервером длинно подключен, это мост между proxyServe и localServe, он отвечает за передачу данных, мы называем его bridge.
Среди них клиент и localServe нам не нужны, потому что клиент может быть браузером или другим, localServe — это обычный локальный сервис. Нам нужно заботиться только о proxyServe и мосте. То, что мы представляем здесь, по-прежнему является самой простой реализацией, обеспечивающей способ мышления и мышления, поэтому давайте начнем с самого простого.
bridge
Из раздела о четырех ролях мы знаем, что мост — это сокетное соединение с proxyServe, и это передача данных.
const net = require('net')
const proxyServe = '10.253.107.245'
const bridge = new net.Socket()
bridge.connect(80, proxyServe, _ => {
bridge.write('GET /regester?key=sq HTTP/1.1\r\n\r\n')
})
bridge.on('data', data => {
const localServer = new net.Socket()
localServer.connect(8088, 'localhost', _ => {
localServer.write(data)
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. Затем для доступа к localServe через prxoyServe нужно написать следующее:example.com/sq. Зачем так писать? Конечно, это всего лишь определение.После того, как вы разберетесь с кодом в этой статье, вы сможете изменить такое соглашение.
Что ж, позвольте мне увидеть следующий код ключа:
proxyServe
Хотя proxyServe здесь является упрощенным примером кода, он все же немного сложен, требуется много усилий, чтобы полностью понять его и сделать доступным в сочетании с вашим собственным бизнесом. Здесь я разбиваю код на части и пытаюсь объяснить понятно, давайте дадим кодовому блоку имя для простоты объяснения.
Блок кода 1: createServe
Основная функция этого блока - создать прокси-сервис, установить сокетное соединение с клиентом и мостом, сокет прослушивает запросы данных и выполняет логическую обработку в функции обратного вызова. Конкретный код выглядит следующим образом:
const net = require('net')
const bridges = {} // 当有bridge建立socket连接时,缓存在这里
const clients = {} // 当有client建立socket连接时,缓存在这里,具体数据结构看源代码
net.createServer(socket => {
socket.on('data', data => {
const request = data.toString()
const url = request.match(/.+ (?<url>.+) /)?.groups?.url
if (!url) return
if (isBridge(url)) {
regesterBridge(socket, url)
return
}
const { bridge, key } = findBridge(request, url)
if (!bridge) return
cacheClientRequest(bridge, key, socket, request, url)
sendRequestToBridgeByKey(key)
})
}).listen(80)
Взгляните на логику кода в мониторинге данных:
- Преобразуйте данные запроса в строку.
- Найдите URL-адрес от запроса и закончите запрос напрямую, если URL не найден.
- Судя по URL, является ли это бриджем, если да, то регистрируйте бридж, иначе считайте это клиентским запросом.
- Проверьте, есть ли в клиентском запросе зарегистрированный мост — помните, это прокси-сервис, и если зарегистрированного моста нет, запрос считается недействительным.
- Кэшировать этот запрос.
- Затем отправьте запрос на мостик.
Совмещая код и логическую сортировку, вы должны понимать, но у вас могут возникнуть вопросы по поводу 5, а затем разбирать их по одному.
Блок кода 2: isBridge
Чтобы определить, является ли это запросом на регистрацию моста, очень просто написать здесь, но для реального бизнеса могут быть определены более точные данные.
function isBridge (url) {
return url.startsWith('/regester?')
}
Третий блок кода: regesterBridge
Просто, посмотрите на код и объясните:
function regesterBridge (socket, url) {
const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
bridges[key] = socket
socket.removeAllListeners('data')
}
- Найдите ключ моста для регистрации по URL.
- Кэшировать измененное соединение сокета.
- Удалите мониторинг данных моста — каждый сокет в кодовом блоке 1 имеет функцию обратного вызова по умолчанию для мониторинга данных, говорящую, что если ее не удалить, это вызовет последующую путаницу данных.
Четвертый блок кода: findBridge
Когда логика доходит до блока кода 4, это означает, что это уже клиентский запрос.Тогда нужно сначала найти соответствующий ему бридж.Если бриджа нет, то сначала нужно зарегистрировать бридж, а затем пользователь должен инициировать запрос клиента позже. код показывает, как показано ниже:
function findBridge (request, url) {
let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key
let bridge = bridges[key]
if (bridge) return { bridge, key }
const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer
if (!referer) return {}
key = referer.split('//')[1].split('/')[1]
bridge = bridges[key]
if (bridge) return { bridge, key }
return {}
}
- Сопоставьте ключ моста для проксирования с URL-адресом и верните соответствующий мост и ключ, когда они будут найдены.
- Если не найдёте, поищите по рефереру в шапке запроса, если найдёте, верните бридж и ключ.
- Не можем найти, мы знаем, что этот запрос будет завершен в первом блоке кода.
Пятый блок кода: cacheClientRequest
Здесь выполняется код, указывающий, что это уже клиентский запрос. Сначала мы кэшируем этот запрос. При кэшировании мы будем кэшировать соответствующий мост и привязку ключа запроса вместе, чтобы облегчить последующие операции.
Почему клиентские запросы Cache?
В текущей схеме мы хотим, чтобы запросы и ответы были попарно заказаны. Мы знаем, что сетевые передачи передаются в фрагментах. В настоящее время, если мы не контролируем сопряжение и упорядочение запросов и ответов на уровне приложений, он приведет к путанице между пакетами. В настоящее время, если в будущем есть лучший раствор, не стоит заставлять запрос и реагирование данных, которые нужно управлять на уровне приложений, а слой TCP / IP можно доверять.
После разговора о причине, давайте сначала посмотрим на код кеша, здесь он относительно прост, но сложность заключается в получении запросов один за другим и возвращении всего ответа по порядку.
function cacheClientRequest (bridge, key, socket, request, url) {
if (clients[key]) {
clients[key].requests.push({bridge, key, socket, request, url})
} else {
clients[key] = {}
clients[key].requests = [{bridge, key, socket, request, url}]
}
}
- Сначала мы оцениваем, есть ли уже кеш клиентских запросов под ключом, соответствующим мосту, и если да, то проталкиваем его.
- Если нет, то создаем объект и инициализируем в нем этот запрос.
Следующий шаг самый сложный, вынуть кеш запроса, отправить его в бридж, мониторить ответ бриджа, до конца ответа удалить данные мониторинга бриджа, а потом пробовать выносить следующий запрос, повторяйте вышеуказанные действия до тех пор, пока клиент не обработает все запросы.
Шестой блок кода: sendRequestToBridgeByKey
В конце пятого блока кода дается общее описание блока. Вы можете сначала немного понять это и посмотреть на код ниже, потому что в коде будут некоторые суждения о целостности ответа.Если вы удалите их, код будет легче понять. Во всей схеме мы не обрабатывали целостность запроса, причина в том, что запрос в основном находится в пределах размера пакета данных, если это не интерфейс загрузки файла, мы не будем его обрабатывать на данный момент. код будет сложнее.
function sendRequestToBridgeByKey (key) {
const client = clients[key]
if (client.isSending) return
const requests = client.requests
if (requests.length <= 0) return
client.isSending = true
client.contentLength = 0
client.received = 0
const {bridge, socket, request, url} = requests.shift()
const newUrl = url.replace(key, '')
const newRequest = request.replace(url, newUrl)
bridge.write(newRequest)
bridge.on('data', data => {
const response = data.toString()
let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code
if (code) {
code = parseInt(code)
if (code === 200) {
let contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength
if (contentLength) {
contentLength = parseInt(contentLength)
client.contentLength = contentLength
client.received = Buffer.from(response.split('\r\n\r\n')[1]).length
}
} else {
socket.write(data)
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
return
}
} else {
client.received += data.length
}
socket.write(data)
if (client.contentLength <= client.received) {
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
}
})
}
- Выньте клиента, соответствующего ключу моста, из client.
- Определите, есть ли у клиента отправляемый запрос, и если да, завершите выполнение. Если нет, продолжайте.
- Определить, есть ли запрос под клиентом, если есть, продолжить, если нет, завершить выполнение.
- Возьмите первый из очереди, который содержит запрошенный сокет и буферизованный мост.
- Замените согласованные данные и отправьте окончательные данные запроса на мост.
- Прослушайте ответ данных моста.
- получить код ответа
- Если ответ равен 200, мы получаем длину содержимого, если она есть, мы выполняем некоторую операцию инициализации этого запроса. При настройке длины запроса указывается длина отправленного запроса.
- Если это не 200, мы отправляем данные клиенту и завершаем запрос, удаляем мониторинг данных и рекурсивно вызываем sendRequestToBridgeByKey.
- Если код не получен, мы считаем этот ответ не в первый раз, а затем добавляем его длину в отправленное поле.
- Затем мы отправляем эти данные клиенту.
- Затем оцените, совпадает ли длина ответа с длиной отправленных данных.Если это соответствует, установите статус отправки данных клиента на false, удалите мониторинг данных и рекурсивно вызовите sendRequestToBridgeByKey.
- получить код ответа
На данный момент основная логика кода закончилась.
Суммировать
Поняв этот код, вы можете расширить его, чтобы обогатить код для вашего использования. Разобравшись с этим кодом, можете ли вы придумать другие сценарии использования? Можно ли использовать эту идею в удаленном управлении?Если вы хотите управлять клиентом, вы можете найти вдохновение в этом коде.
Этот набор кода может быть сложным. Возможно, вам потребуется знать все о tcp/ip и http, а также знать некоторые ключевые заголовки запросов и некоторую ключевую информацию об ответах. Конечно, чем больше вы знаете о http, тем лучше.
Если есть что сообщить, пишите.
исходный код проксисервера
const net = require('net')
const bridges = {}
const clients = {}
net.createServer(socket => {
socket.on('data', data => {
const request = data.toString()
const url = request.match(/.+ (?<url>.+) /)?.groups?.url
if (!url) return
if (isBridge(url)) {
regesterBridge(socket, url)
return
}
const { bridge, key } = findBridge(request, url)
if (!bridge) return
cacheClientRequest(bridge, key, socket, request, url)
sendRequestToBridgeByKey(key)
})
}).listen(80)
function isBridge (url) {
return url.startsWith('/regester?')
}
function regesterBridge (socket, url) {
const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
bridges[key] = socket
socket.removeAllListeners('data')
}
function findBridge (request, url) {
let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key
let bridge = bridges[key]
if (bridge) return { bridge, key }
const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer
if (!referer) return {}
key = referer.split('//')[1].split('/')[1]
bridge = bridges[key]
if (bridge) return { bridge, key }
return {}
}
function cacheClientRequest (bridge, key, socket, request, url) {
if (clients[key]) {
clients[key].requests.push({bridge, key, socket, request, url})
} else {
clients[key] = {}
clients[key].requests = [{bridge, key, socket, request, url}]
}
}
function sendRequestToBridgeByKey (key) {
const client = clients[key]
if (client.isSending) return
const requests = client.requests
if (requests.length <= 0) return
client.isSending = true
client.contentLength = 0
client.received = 0
const {bridge, socket, request, url} = requests.shift()
const newUrl = url.replace(key, '')
const newRequest = request.replace(url, newUrl)
bridge.write(newRequest)
bridge.on('data', data => {
const response = data.toString()
let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code
if (code) {
code = parseInt(code)
if (code === 200) {
let contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength
if (contentLength) {
contentLength = parseInt(contentLength)
client.contentLength = contentLength
client.received = Buffer.from(response.split('\r\n\r\n')[1]).length
}
} else {
socket.write(data)
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
return
}
} else {
client.received += data.length
}
socket.write(data)
if (client.contentLength <= client.received) {
client.isSending = false
bridge.removeAllListeners('data')
sendRequestToBridgeByKey(key)
}
})
}