В сегодняшней статье я хотел представить JavaScript Fetch API и идею его использования для создания сканера портов путем интерпретации различных кодов ошибок, которые будет регистрировать команда Fetch. Давайте сначала объясним, что такое Fetch.

Fetch API — это интерфейс прикладного программирования JavaScript (API) для доступа и управления HTTP-запросами и ответами. Он был разработан как лучшая альтернатива старому XMLHttpRequest API, который теперь устарел в большинстве основных браузеров. С помощью Fetch вы можете программно извлекать ресурсы с сервера, отправлять данные на сервер и обрабатывать ответы в различных форматах, таких как JSON, XML и текст. Я использую Fetch, когда мне нужно получить данные JSON, особенно из больших онлайн-баз данных.

С программной точки зрения Fetch основан на промисах, что означает, что методы/функции, в которые вы реализуете выборку, должны быть асинхронными. Асинхронность просто означает, что не все строки кода будут выполняться синхронно, некоторые из них могут выполняться быстрее, чем другие, но, как вы скоро увидите, есть способы добиться некоторой синхронности. Основанный на промисе строится на этой концепции асинхронности, возвращая объект промиса, который позволяет узнать текущий статус вашей задачи. Вот пример того, почему это важно иметь, скажем, мы делаем запрос API к базе данных с записями в десятки миллионов, этой базе данных потребуется некоторое время, чтобы вернуть нам некоторые данные, поэтому она вернется к нам. обещание, что он попытается получить данные и отправить их нам. Тем временем мы можем выполнять некоторые другие задачи или программно ожидать дальнейшего выполнения нашей программы, пока мы не получим эти данные. Оператор await — это то, что позволяет нам дождаться возврата обещания, прежде чем мы продолжим выполнение нашего кода.

Наибольшая сила Fetch заключается в его простоте и настройке. Возьмем приведенный ниже пример, где мы используем информацию для извлечения, связанную с IP-адресом, от Triton, небольшого проекта шахт, который пытается сделать информацию о домене, IP и ASN более доступной для всего мира.

// Asynchronous function that retrieves Google domain information from Triton.
async function getTritonJSONData() {
  // JSON object that contains our custom request headers
  const init = {
    headers: {
      "content-type": "application/json;charset=UTF-8",
    },
  };

  // Our Fetch request, where we try to retrieve domain information for google.com
  const response = await fetch(
    "https://api.triton.coligo.dev/dn/google.com",
    init
  );

  // Retriveing JSON data from our Fetch response
  const jsonData = await response.json();

  // Logging to the console for readability
  console.log(jsonData);
}

Вы также можете открыть следующую ссылку, чтобы получить информацию о домене от Google. https://api.triton.coligo.dev/dn/google.com

Если все пойдет хорошо, вы должны увидеть или показать следующее в своем браузере:

{
    "handle": "2138514_DOMAIN_COM-VRSN",
    "ldhName": "GOOGLE.COM",
    "status": [
        "client delete prohibited",
        "client transfer prohibited",
        "client update prohibited",
        "server delete prohibited",
        "server transfer prohibited",
        "server update prohibited"
    ],
    "secureDNS": {
        "delegationSigned": false
    },
    "nameservers": [
        {
            "objectClassName": "nameserver",
            "ldhName": "NS1.GOOGLE.COM"
        },
        {
            "objectClassName": "nameserver",
            "ldhName": "NS2.GOOGLE.COM"
        },
        {
            "objectClassName": "nameserver",
            "ldhName": "NS3.GOOGLE.COM"
        },
        {
            "objectClassName": "nameserver",
            "ldhName": "NS4.GOOGLE.COM"
        }
    ],
    "role": [
        "registrar"
    ],
    "registrarName": "MarkMonitor Inc.",
    "pubIDName": "IANA Registrar ID",
    "pubIDNum": "292",
    "registration": "1997-09-15T04:00:00Z",
    "expiration": "2028-09-14T04:00:00Z",
    "lastChanged": "2019-09-09T15:39:04Z",
    "rdapUpdate": "2023-07-03T12:55:10Z"
}

Хорошо, что мы можем получать информацию о веб-сайте и взаимодействовать с API с помощью Fetch, но какое это имеет отношение к сканированию портов удаленного хоста? Вот тут-то и начинается самое интересное, поскольку мы будем использовать Fetch не так, как предполагалось. Интерпретируя определенные сообщения об ошибках, мы можем определить открытые, закрытые и отфильтрованные порты.

Есть 3 основные ошибки, которые нам нужно интерпретировать: EPROTO, ECONNREFUSED и соединения, которые просто истекают по тайм-ауту. EPROTO — это ошибка, которую мы видим, когда порт отвечает ошибкой несоответствия протокола. Причина, по которой мы видим это, заключается в том, что с помощью Fetch мы все еще делаем HTTP-запросы, поэтому, если мы попытаемся подключиться к порту 23, который является Telnet и открыт для запросов, мы, вероятно, увидим ошибку несоответствия протокола. ECONNREFUSED использует аналогичный подход, при котором сервер отвечает нам отказом в соединении. Это немного сложнее интерпретировать, но если сервер отвечает нам на этом порту, это может означать, что какая-то служба работает на этом порту, или мы можем быть заблокированы каким-то WAF или подобным. Наконец, у нас есть тайм-аут, который может означать, что наше соединение разрывается, и мы не получаем информации об этом, или порт/хост может быть недоступен.

Ниже приведен пример кода, который я создал, используя его реализацию в BEEF с помощью Node JS.

const fetch = require("node-fetch");
// Timeout Function Used In BEEF Fetch Port Scanner
var controller = new AbortController();
var signal = controller.signal;
setTimeout(() => {
  controller.abort();
}, 5000);

// Set of ports we would like to scan
var default_ports = [
  1, 5, 7, 9, 15, 20, 21, 22, 23, 25, 26, 29, 33, 37, 42, 43, 53, 67, 68, 69,
  70, 76, 79, 80, 88, 90, 98, 101, 106, 109, 110, 111, 113, 114, 115, 118, 119,
  123, 129, 132, 133, 135, 136, 137, 138, 139, 143, 144, 156, 158, 161, 162,
  168, 174, 177, 194, 197, 209, 213, 217, 219, 220, 223, 264, 315, 316, 346,
  353, 389, 413, 414, 415, 416, 440, 443, 444, 445, 453, 454, 456, 457, 458,
  462, 464, 465, 466, 480, 486, 497, 500, 501, 516, 518, 522, 523, 524, 525,
  526, 533, 535, 538, 540, 541, 542, 543, 544, 545, 546, 547, 556, 557, 560,
  561, 563, 564, 625, 626, 631, 636, 637, 660, 664, 666, 683, 740, 741, 742,
  744, 747, 748, 749, 750, 751, 752, 753, 754, 758, 760, 761, 762, 763, 764,
  765, 767, 771, 773, 774, 775, 776, 780, 781, 782, 783, 786, 787, 799, 800,
  801, 808, 871, 873, 888, 898, 901, 953, 989, 990, 992, 993, 994, 995, 996,
  997, 998, 999, 1000, 1002, 1008, 1023, 1024, 1080, 8080, 8443, 8050, 3306,
  5432, 1521, 1433, 3389, 10088,
];

// The host we would like to scan, I use nmap by default.
let host = "scanme.nmap.org";

// Main loop that scans the default ports of the host we specified and then logs the state of each port based on the error received.

// General Idea
// Open Port Errors: EPROTO, ECONNREFUSED
// Closed/Filered Ports: Aborted

default_ports.forEach((port) => {
  // Fetch request with custom headers that allows us to scan the host without errors.
  fetch("https://" + host + ":" + port, {
    method: "GET",
    mode: "no-cors",
    signal: signal,
  })
    .then((response) => response)
    .then((data) => {
      console.log(
        "Port " +
          port +
          "  open" +
          "  Return Code: " +
          data.status +
          "Server Type: " +
          data.headers.get("server")
      );
    })
    .catch((err) => {
      if (signal.aborted === true) {
        console.log("Port " + port + "  closed/filtered");
      } else if (err.code == "EPROTO" || err.code == "ECONNREFUSED") {
        console.log(
          "Port " + port + "  open(?)" + "  Return Code: " + err.code
        );
      }
      console.log(err);
    });
  setTimeout(function () {}, 1000);
});

Как видите, здесь много догадок, и они не всегда будут точными, но результаты могут быть довольно интересными, особенно если сравнить их с результатами Nmap. Хотя это не так мощно, как что-то вроде Nmap, оно заставляет задуматься, что еще вы можете сделать с этими мощными API и доступными вам методами. Я продолжу работать над этим и посмотрю, смогу ли я улучшить наши возможности сканирования, а пока я надеюсь, что вам понравилась эта короткая статья, и вы остаетесь любопытными!