Skip to main content

Command Palette

Search for a command to run...

HTTP-Basma - Clustering Verbosus Fingerprints

Updated
8 min readView as Markdown
N

We empower businesses with cutting-edge software and expert services to navigate the complexities of today's cyber landscape. Secure your network with cutting-edge software and services that ensure your safety and peace of mind!

The design and architecture of the verbosus fingerprint (vfp) in HTTP-Basma allows for deeper inspection of every field of every probe. The reversibility property of the fingerprint coupled with the demanlging feature open the door for granular clustering, for grouping similar HTTP(S) servers at specific distances, despite certain differences in the vfp. For more information about how the algorithm works, the source code, and release binaries, check the official public GitHub repository.

A live version of HTTP-Basma is already available at https://httpbasma.netomize.ca/#cluster, with all the clustering features that we'll discuss in this blog post.

Cluster by Tags

The first clustering option is the cluster by tags, to group the database servers that match your tags by their vfp. Each cluster is one unique fingerprint shared by a set of servers, a quick way to see which infrastructure a campaign reuses. It is all unique vfps per x tag(s).

The platform provides the option to query all unique tags in the live db with their occurrences that you could use to filter against for clustering, as shown in the following screenshot:

This works by clicking on the Get all unique tags button, with the option to drill down into individual tags using the Filter tags box. The SERVERS column shows the total number of servers that share this tag. You may populate the TAGS input box by clicking on any of the listed tags, with the possibility to add more than one tag, and choosing the proper matching (MATCH) operator for your use case.

It is suggested that you leave the MAX CLUSTERS and MAX SERVERS/CLUSTER input boxes set to 0 so that you get all the possible clusters in return, with all servers per clusters, and some additional metadata. Explore the interface to get a sense of all the provided capabilities.

And to cluster by the provided tag(s), click on the Cluster by tags button. For example, if we cluster by the tag adaptixc2, an advanced redteam toolkit, we get the following two clusters (as of July 14, 2026):

Servers Verbosus fingerprints
11 01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001
6 0100000000000000000000000000000000000000000000000000000000000000000000000000

You may drill down into any of the listed clusters for additional metadata, by clicking on the cluster's vfp.

And here's the full JSON output, containing all the servers that belong to each of the clusters:

{
  "tags": [
    "adaptixc2"
  ],
  "total_clusters": 2,
  "clusters": [
    {
      "fp_verbosus": "0100000000000000000000000000000000000000000000000000000000000000000000000000",
      "total_servers": 6,
      "servers": [
        {
          "server": "89.125.255.29",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "38.147.173.24",
          "path": "",
          "port": 8562,
          "is_ssl": true
        },
        {
          "server": "156.225.22.201",
          "path": "",
          "port": 1337,
          "is_ssl": true
        },
        {
          "server": "202.95.8.92",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "146.70.87.96",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "146.70.87.237",
          "path": "",
          "port": 443,
          "is_ssl": true
        }
      ]
    },
    {
      "fp_verbosus": "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001",
      "total_servers": 11,
      "servers": [
        {
          "server": "llmscience.top",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "4.236.165.30",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "8.136.13.87",
          "path": "",
          "port": 7001,
          "is_ssl": true
        },
        {
          "server": "20.157.116.151",
          "path": "",
          "port": 8000,
          "is_ssl": true
        },
        {
          "server": "23.95.220.192",
          "path": "",
          "port": 43999,
          "is_ssl": true
        },
        {
          "server": "185.190.142.66",
          "path": "",
          "port": 4321,
          "is_ssl": true
        },
        {
          "server": "2.26.229.254",
          "path": "",
          "port": 4433,
          "is_ssl": true
        },
        {
          "server": "23.227.203.205",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "38.132.122.145",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "91.132.161.21",
          "path": "",
          "port": 443,
          "is_ssl": true
        },
        {
          "server": "23.227.203.191",
          "path": "",
          "port": 443,
          "is_ssl": true
        }
      ]
    }
  ],
  "total_servers_all_clusters": 17
}

The vfp with all zeros represent dead servers, where the server failed to return any data for all the probes. Or, it could be that the server was down at the time of fingerprinting it.

To get a sense of what every byte in the vfp "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001" accounts for, you may demangle it under the Demangle tab, and you'll get a beautifully dissected structure of every byte, per probe.

To check if the vfp "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001" matches any of the Majestic Million HTTP-Basma database for false positive, head to the DB Match tab and search the database; you'll get 73 total matches with only the first 50 displayed (this is configurable in the Match limit box).

Cluster by structural distance

This is the most advanced clustering feature employed by HTTP-Basma platform, it is a weighted structural distance. It tries to answer the question, given a set of verbosus fingerprints, which ones are structurally similar and which are different?

The platform provides two granularity options of measuring the distance: at the fields level with weighted leaves or probe level (count of probes that differ at all). The probe granularity is obvious and doesn't merit further elaboration. It is the fields granularity with weighted leaves that requires explanation. This level of clustering is possible thanks to the elegant engineering of the vfp, which allows for fine-grained inspection of every byte in the fingerprint.

Every pair of fingerprints has a single number: their weighted structural distance. It is computed on the demangled fields (the same per-probe decomposition the Compare tab produces), not on the raw hex, and clustered by a WEIGHTED count of differing fields. So a status-code change can count differently from a reason-phrase change, and "differs in N fields / N probes" is meaningful.

So distance is how many fields differ, each counted by its weight. Identical fingerprints have distance 0; the more fields that differ (and the heavier those fields), the larger the distance.

For exact matching, it uses the graph's maximal cliques (via Bron–Kerbosch with pivoting + degeneracy ordering, Eppstein–Löffler–Strash). Every member of a group is mutually that distance from every other member. And, since clique enumeration is NP-hard in the worst case, the max clique degeneracy is limited to 20 with a minimum group size of 2. In case the degeneracy cap is above 20, the distance falls back to the graph's connected components (union-find). A component means "linked by a chain of same-distance edges," not "all mutually that distance". This is not an exact match.

Essentially, we are building the graph whose edges join the fingerprint pairs at exactly that distance, then enumerate its groups.

This feature is accessible under the Cluster tab:

You paste in or upload your list of unique vfps, configure the granularity, return and response settings, along with the weights & algorithm settings, click on Cluster by distance, and you get all the distances with their groups. The weights account for every attribute checked for in every probe:

Keep in mind that only unique and non-zero vfps are checked for.

For example, setting the weights of both the KEEP-ALIVE and CLOSE fields to zero effectively neutralizes them. Consequently, any two fingerprints that differ solely by these fields will yield an identical distance metric and cluster into the same group.

A typical workflow for this clustering type includes the retrieval of all unique vfps for some tag(s) using the cluster by tags type, click on Copy fingerprints button, to copy only the vfps (minus all the other metadata) to the clipboard, and paste the data in the input box under the cluster by structural distance tab, and proceed from there.

For demonstration, let's take the Havoc tag as an example, and as of July 14, 2026, we have the following clusters:

Servers Verbosus fingerprints
8 0100000000000000000000000000000000000000000000000000000000000000000000000000
2 01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001
2 01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f
1 011423945400142394542214239454221423945422142394542200001423945422000000001f
1 01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001

The all zeros fingerprint will be rejected/skipped either way, so, we are left with 4 clusters. Before using the cluster by structural distance type, try the Compare functionality to visualize the actual differences between them.

If we set the we granularity, return type, response and the weights as follows:

We get the following JSON object containing all the distances with all the possible groups:

{
    "max_distance": 40.0,
    "distance_groups": [
        {
            "distance": 5.5,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001",
                    "01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f"
                ]
            ]
        },
        {
            "distance": 6.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001",
                    "01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001"
                ]
            ]
        },
        {
            "distance": 9.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "011423945400142394542214239454221423945422142394542200001423945422000000001f",
                    "01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f"
                ]
            ]
        },
        {
            "distance": 11.5,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "01142494d60014514bd522142494d622142494d622142494d6220000142494d622000000001f",
                    "01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001"
                ]
            ]
        },
        {
            "distance": 14.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "011423945400142394542214239454221423945422142394542200001423945422000000001f",
                    "01142494d60014512f3612142494d622142494d622142494d6220000142494d6220000000001"
                ]
            ]
        },
        {
            "distance": 20.0,
            "exact": true,
            "count": 1,
            "groups": [
                [
                    "011423945400142394542214239454221423945422142394542200001423945422000000001f",
                    "01142494d60914512f3612142494d620142494d620142494d6200800142494d6200000000001"
                ]
            ]
        }
    ]
}

The question you should ask yourself, these vfps are tagged as Havoc vfps, but are all of them Havoc vfps? To answer this question, we check the distance with respect to the granularity and weights parameters. Anything above a distance of 5.5 requires further validation.

Conclusion

In this blog post, we've showcased the clustering capabilities of HTTP-Basma using the clustering by tags type and the advanced structural distance type using the per-probe or weighted fields granularity.


Mohamad Mokbel

July 14, 2026