All posts

FetchMap: A Port Scanner Idea using Fetch

The Fetch API is a JavaScript application programming interface (API) for accessing and manipulating HTTP requests and responses. It was designed as a better alternative to the older XMLHttpRequest API which is now si…

In todays article, I wanted to introduce the JavaScript Fetch API and the idea of using it to create a port scanner by interpreting the various error codes that the Fetch command will log. Lets firstly explain what Fetch is.

The Fetch API is a JavaScript application programming interface (API) for accessing and manipulating HTTP requests and responses. It was designed as a better alternative to the older XMLHttpRequest API which is now since deprecated on most major browsers. With Fetch, you can programmatically fetch resources from a server, send data to a server, and handle responses in various formats like JSON, XML and text. Fetch is my go to for when I need to get JSON data, especially from large online databases.

From a programmatic view, Fetch is promise-based meaning that the the methods/functions that you implement fetch into must be asynchronous. Asynchronous simply means that not all lines of code will be executed synchronously, some may complete faster than others but there are ways to force some sort of synchronicity as you will soon see. Promise-based builds upon this concept of asynchronous by returning an promise object that lets you know the current status of your task. Here’s an example of why this is important to have, lets say we make an API request to a database with entries in the tens of millions, it will take some time for that database to return some data to us, so it will return to us a promise that it will get try to retrieve the data and send that to us. In the mean time we can perform some other tasks or we can programmatically await further execution of our program until we retrieve that data. The await operator is what allows us to wait for a promise to return before we continue to execute our code.

Fetch’s greatest strength is in its simplicity and customization, take this below example where we use fetch information associated with an IP address from Triton, a small project of mines that tries to make domain, IP and ASN information more easily accessible to the world.

// 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);
}

You can also open the following link to get domain information from Google. https://api.triton.coligo.dev/dn/google.com

If everything goes well you should get the following logged or shown in your browser,

{
    "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"
}

Its nice that we can retrieve website information and interact with API’s using Fetch but what does this have to do with scanning ports of a remote host? Well this is where the fun begins as we will use Fetch in a way it wasn’t intended for. By interpreting specific error messages, we can make a determination of open, closed and filtered ports.

There are 3 main errors that we will need to interpret, those being EPROTO, ECONNREFUSED and connections that simply timeout. EPROTO, is an error we see when the port responds with a protocol mismatch error. The reason why we see this is being with Fetch we are still making HTTP requests, so if we try to connect to port 23 which is Telnet and it is open to requests we will likely see a protocol mismatch error. ECONNREFUSED follows a similar approach, whereby the server responds to us refusing the connection out right. This is a bit harder to interpret but if the server is responding to us on that port, it may mean that some service is running on that port or we may be getting blocked by some sort of WAF or similar. Lastly we have the timeout which may mean that our connection’s are getting dropped and we’re not getting informed of it or the port/host may down.

Below is some example code I built using a bit of BEEF’s implementation of it with 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);
});

As you can see, its a lot of guess work and it will not always be accurate but the results can be quite interesting especially when you compare it to that from Nmap. While this isn’t as powerful as something like Nmap it does make you wonder what else can you do with these powerful API’s and methods available to you. I will continue to work on it and see if I can improve on our scanning capabilities, in the mean time I hope you enjoyed this brief article and stay curious!

Discussion

No comments yet