● Netomize Research — Published May 20, 2026

Adaptive Fingerprinting: HTTP-Basma's Multi-Stage Probing for Granular Server Differentiation

Abstract

In the realm of cybersecurity, accurately identifying and characterizing web servers is crucial for threat detection, vulnerability assessment, and network mapping. We introduce HTTP-Basma, a novel active fingerprinting algorithm that unveils unique server profiles through a multi-layered approach to address this challenge.

Crafted requests, revealing responses. HTTP-Basma sends 8 meticulously designed HTTP probes, eliciting distinctive responses that reflect server configurations.
Dual hashing for versatility. A 38-byte fuzzy hash, verbosus, offers reversibility; a 16-byte one-way hash, pacto, derived from verbosus, enhances privacy and comparison speed.
Clustering & hunting. These hashes empower server clustering, identification of unique and similar servers, and the pursuit of malicious actors with heightened confidence.
Modular design. The algorithm's architecture fosters the addition of new hashing variants, encouraging collaboration and adaptability.

This paper surveys existing HTTP fingerprinting work, then walks through the algorithm's functionality, design, and architecture, before showcasing findings from scanning the top 1 million Majestic websites — including the identification and clustering of several malware families' C&C HTTP servers. Source code and supporting data are publicly available.

Documentation — Internals

HTTP-Basma's algorithm's core idea centers on sending 8 specially crafted HTTP requests with different requirements to solicit different responses from the server. Once the server response is retrieved, the HTTP status line is surgically dissected for all elements and encoded optimally. Additionally, select headers from the server response are checked for encoding as well.

The terms HTTP verb and method are used synonymously throughout this document.

HTTP Requests / Probes

Setting the Request-Line. The default setup only includes the headers specified in each probe — the corresponding HTTP server responses are documented in the HTTP Responses section. All requests are sent independently of each other, without reusing the same socket; the DNS cache is cleared between every request.

P1

GET Normal — Valid Request

The purpose of this standard-conformant request is to generate a ground-truth hash against which all other requests will be evaluated.

GET / HTTP/1.1
Host: <example.com>
P2

GET Invalid HTTP Version Request

This request inexplicably specifies the non-standard HTTP version "4.2," seemingly just to be different from a defined protocol, in an attempt to gauge the server response for any differences from the first request.

GET / HTTP/4.2
Host: <example.com>
P3

GET Random Resource Request

This request attempts to access a non-existent resource on the server by sending a pseudo-randomly generated target resource matching the character class [A-Za-z0-9]{16}. Servers usually respond differently when attempting to request a non-existent resource.

GET /<wAs49YmHZ0PSFKGo> HTTP/1.1
Host: <example.com>
P4

Random Verb Request

This request employs a pseudo-randomly generated 10-character uppercase verb (matching [A-Z]{10}) to observe how the server handles and responds to an unrecognized HTTP method.

<MBBVEIIEXE> / HTTP/1.1
Host: <example.com>
P5

get Lowercase Verb

This request uses the HTTP GET verb in all lowercase ("get"). While technically a valid verb, many servers are configured only to accept it uppercase ("GET") and will likely reject it with a unique status-line and headers.

get / HTTP/1.1
Host: <example.com>
P6

GET Request — Accept-Encoding, Full

Sends a list of compression algorithms the sender understands, in this specific order: aes128gcm, br, <grease_A>, compress, deflate, exi, <grease_B>, gzip, pack200-gzip, x-compress, x-gzip, zstd, identity. The placeholders <grease_A> ([a-z]{8}) and <grease_B> ([a-z0-9]{10}) are pseudo-randomly generated — the server should never pick either grease value.

GET / HTTP/1.1
Host: <example.com>
Accept-Encoding: aes128gcm, br, <grease_A>, compress, deflate, exi, <grease_B>, gzip, pack200-gzip, x-compress, x-gzip, zstd, identity
P7

GET Request — Accept-Encoding, Less

Sends a reduced list of compression algorithms: <grease_A>, compress, deflate, <grease_B>, x-compress, zstd, br. Since gzip is the most likely to be picked up by the server, omitting it here forces the server to behave differently compared to P6.

GET / HTTP/1.1
Host: <example.com>
Accept-Encoding: <grease_A>, compress, deflate, <grease_B>, x-compress, zstd, br
P8

OPTIONS Request

Queries the server for the allowed HTTP methods for a given resource.

OPTIONS / HTTP/1.1
Host: <example.com>

HTTP Responses

Following each request, the server's response is analyzed to extract specific headers and their values. This extracted data undergoes further processing — dissection and encoding — to generate a reversible fingerprint. If a request fails, whether due to no server response or any other reason, the resulting fingerprint for that probe is a sequence of all zeros.

Status-Line Fingerprint 4 bytes

For P1–P5 and P8 probes, the HTTP Status-Line (a.k.a. the Start-Line) attributes are parsed and encoded. Given a response like:

HTTP/1.1 200 OK

the Status-Line consists of three octet sequences separated by a space, ending with CRLF:

<HTTP-Version><SP><Status-Code><SP><Reason-Phrase>\r\n

The Reason-Phrase is optional, per the grammar *( HTAB / SP / VCHAR / obs-text ).

HTTP-Version Fingerprint 1 byte

The HTTP-Version is encoded as two hex characters: the first encodes the protocol-name casing (HTTP=1, http=2, Http=3, other casing=4, missing/malformed=0), and the second encodes the version number (0.8=1, 0.9=2, 1.0=3, 1.1=4, ≥2.0=7, 1.>1=8, 0.<8=9, missing/malformed=0).

Example — "HTTP/1.1" → 14

Status-Code Fingerprint 1 byte

  1. Per RFC, the Status-Code is a 3-digit code.
  2. It is checked against a curated list of 102 known/standardized status codes; if found, the fingerprint is the list index + 1, in hexadecimal. Otherwise it is set to ff.
  3. If the Status-Code is missing or not 3 digits, the fingerprint is 00.
  4. A status code of 302 in a line like HTTP/1.1 302.1 is treated as valid — only the leading 302 is matched. This is a deliberate design decision.
Example — status code 200 is index 9 (0-based) in the table below → fingerprint 0a
Show the full 102-entry status-code table (index → fingerprint mapping)

Reference list used for the Status-Code fingerprint lookup. The associated reason text is documentation only and does not affect the fingerprint. Source: http.dev/status.

FP (hex)CodeCanonical ReasonNote
01100Continue
02101Switching protocols
03102ProcessingWebDAV
04103Early Hints
05110Response is StaleUnofficial/Deprecated
06111Revalidation FailedUnofficial/Deprecated
07112Disconnected OperationUnofficial/Deprecated
08113Heuristic ExpirationUnofficial/Deprecated
09199Miscellaneous WarningUnofficial/Deprecated
0a200OK
0b201Created
0c202Accepted
0d203Non-Authoritative Information
0e204No Content
0f205Reset Content
10206Partial Content
11207Multi-StatusWebDAV
12208Already ReportedWebDAV
13214Transformation AppliedUnofficial/Deprecated
14218This is fineUnofficial
15226IM UsedHTTP Delta encoding
16299Miscellaneous Persistent WarningUnofficial/Deprecated
17300Multiple Choices
18301Moved Permanently
19302Foundwas “Moved Temporarily”
1a303See Other
1b304Not Modified
1c305Use ProxyDeprecated
1d306Switch ProxyReserved, was “Switch Proxy”
1e307Temporary Redirect
1f308Permanent Redirect
20400Bad Request
21401Unauthorized
22402Payment RequiredExperimental
23403Forbidden
24404Not Found
25405Method Not Allowed
26406Not Acceptable
27407Proxy Authentication Required
28408Request Timeout
29409Conflict
2a410Gone
2b411Length Required
2c412Precondition Failed
2d413Content Too Largeor “Payload Too Large”
2e414URI Too Long
2f415Unsupported Media Type
30416Range Not Satisfiable
31417Expectation Failed
32418I'm a teapotwas “I'm a teapot”
33419Page ExpiredUnofficial
34420Method Failureor “Enhance your calm”, Unofficial
35421Misdirected Request
36422Unprocessable Contentor “Unprocessable Entity”, WebDAV
37423LockedWebDAV
38424Failed DependencyWebDAV
39425Too EarlyExperimental
3a426Upgrade Required
3b428Precondition Required
3c429Too Many Requests
3d431Request Header Fields Too Large
3e440Login Time-OutUnofficial
3f444No ResponseUnofficial
40449Retry WithUnofficial
41450Blocked by Windows Parental ControlsUnofficial
42451Unavailable For Legal Reasons
43460Client Closed Connection PrematurelyUnofficial
44463Too Many Forwarded IP AddressesUnofficial
45464Incompatible ProtocolUnofficial
46494Request Header Too LargeUnofficial
47495SSL Certificate ErrorUnofficial
48496SSL Certificate RequiredUnofficial
49497HTTP Request Sent to HTTPS PortUnofficial
4a498Invalid TokenUnofficial
4b499Token Required or Client Closed RequestUnofficial
4c500Internal Server Error
4d501Not Implemented
4e502Bad Gateway
4f503Service Unavailable
50504Gateway Timeout
51505HTTP Version Not Supported
52506Variant Also Negotiates
53507Insufficient StorageWebDAV
54508Loop DetectedWebDAV
55509Bandwidth Limit ExceededUnofficial
56510Not Extended
57511Network Authentication Required
58520Connection Timed OutUnofficial
59521Web Server Is DownUnofficial
5a522Origin Is UnreachableUnofficial
5b523Web Server Is DownUnofficial
5c524A Timeout OccurredUnofficial
5d525SSL Handshake FailedUnofficial
5e526Invalid SSL CertificateUnofficial
5f527Railgun Listener to OriginUnofficial
60529The Service Is OverloadedUnofficial
61530Site FrozenUnofficial
62555User Defined Resource Error
63561UnauthorizedUnofficial
64598Network Read Timeout ErrorUnofficial
65599Network Connect Timeout ErrorUnofficial
66999Request DeniedUnofficial

HTTP-Reason Fingerprint 2 bytes

Handling the HTTP-Reason requires a nuanced approach with specific design decisions that may intentionally deviate from strict RFC standards for practical reasons.

  1. The algorithm first successfully matches HTTP-Version and Status-Code, then uses flexible parsing for the reason phrase — strict adherence to the standard status-line format would cause probing checks to fail against many real-world responses with minor discrepancies. This is exercised via: ^HTTP/\d\.\d \d{3}([^]*) (case-insensitive).
  2. If the HTTP-Reason is empty, the fingerprint is 0000.
  3. If not empty, and it contains any character outside the restricted achars set (obs-text / SP·VCHAR / HTAB), the fingerprint is tagged 0001 (Status-Code followed by SP) or 0002 (otherwise).
  4. Otherwise, the HTTP-Reason is hashed with FNV-1a and the upper two bytes of the hash are taken as the fingerprint.
Example — "OK" → FNV-1a = 0x85e4b82f → fingerprint 85e4

FNV-1a was chosen for its simplicity, speed, small hash size, and collision resistance. Scans of the top 1 million Majestic websites found no collisions from truncating to the upper two bytes, and no collisions between real hash values and the reserved tags 0001/0002.

Status-Line fingerprint = HTTP-Version | Status-Code | HTTP-Reason = 14 | 0a | 85e4 = 140a85e4

P3 / P4 Probes — Reason-Phrase Exception

Some servers echo the generated resource string (P3) or the rejected verb (P4) back into the Reason-Phrase to signal absence/prohibition. In these cases the echoed string is stripped from the Reason-Phrase before the FNV-1a hash is applied.

P3 examples — ~451 of the top-million Majestic servers exhibit this behaviour (generated resource string underlined in the original data):

DomainHTTP Response — Status-Line
800pharm.comHTTP/1.1 404 Page not found: /MAAdqL9vaPTTAmbP
accessmore.comHTTP/1.1 404 /hReEdqHe1qUTO9J5
altruja.deHTTP/1.1 404 Event not found with slug: v3JfOERNPFmd9dPm
billedbladet.dkHTTP/1.1 404 Not Found "/O3N6TJCym8xbd3h6"
bilpriser.dkHTTP/1.1 400 Invalid path /Z9pvfZYXzJBxtDf3 was requested
dostoyanieplaneti.ruHTTP/1.1 404 Component Not Found (https://dostoyanieplaneti.ru/yA6Hf6Q9VL427nOO)
eltonjohn.comHTTP/1.1 404 The page you are looking for may not exist or may have moved: ZZL4EeviqSCqiXIg
pilkanozna.plHTTP/1.1 404 Nie znaleziono komponentu (http://pilkanozna.pl/2mc5yTBgrefQjPxp)
timeform.comHTTP/1.1 404 Couldn't find a subheader with url segments hMMsHWKC90K74VHE and .

P4 examples — ~163 of the top-million Majestic servers exhibit this behaviour (generated verb underlined in the original data):

DomainHTTP Response — Status-Line
vetrf.ruHTTP/1.1 501 Method BRRKZNQVXV is not defined in RFC 2068 and is not supported by the Servlet API
viafrance.comHTTP/1.1 501 Unsupported method ('VWYEQKTUPY')
viapresse.comHTTP/1.1 405 HTTP method 'ZWHAXAYFXN' is not allowed !
walkabout.com.auHTTP/1.1 405 Method not allowed: IDGXOBDKYA
warzone.comHTTP/1.1 501 Method JTZTIUDBKI is not defined in RFC 2068 and is not supported by the Servlet API
cibersur.comHTTP/1.1 501 El Metodo TTTXYVEHBS no esta definido en la especificacion RFC 2068 y no es soportado por la API Servlet
dancenter.deHTTP/1.1 405 Method ZQPEXTSQAU not implemented.
opony.com.plHTTP/1.1 405 Request method 'LQTMOQKIRL' not supported
una.frHTTP/1.1 501 Le mode VEHCPLTMUN n'est pas défini dans la RFC 2068 et n'est pas supporté par l'API Servlet

Content-Length & Transfer-Encoding Fingerprint (C-L_T-E) 1 byte

With probes P2–P5 and P8, the algorithm looks for the Content-Length header first; if absent, it falls back to Transfer-Encoding. Computation depends on the Status-Line first passing validation.

  1. If the Status-Line fails validation, the fingerprint is 99.
  2. If neither header exists: fingerprint is 00 if the body is empty, otherwise 1 + encode_length(size), where length encodes as 0 (=0), 1 (=1), or 2 (>1).
  3. If Content-Length exists: fingerprint = encode_name(cl_name) | encode_length(value), where the name-casing digit is 2=Content-Length, 3=content-length, 4=Content-length, 5=content-Length, 6=any other casing that lowercases to content-length.
  4. If Transfer-Encoding with value "chunked" exists: fingerprint = encode_name(te_name) | encode_length(body size), where the name-casing digit is 7=Transfer-Encoding, 8=transfer-encoding, 9=Transfer-encoding, a=transfer-Encoding, b=any other casing.
Content-Length: 326 → 22

Canonical "Content-Length" casing (2) + length >1 (2).

Transfer-Encoding: chunked → 72

Canonical "Transfer-Encoding" casing (7) + body length >1 (2).

Strict-Transport-Security (HSTS) Header Fingerprint 1 byte

For probe P1, the HSTS header is fingerprinted in detail. It signals that a site must only be accessed over HTTPS, e.g. Strict-Transport-Security: max-age=31536000; includeSubDomains; preload. If the Status-Line is invalid the fingerprint is 99; if the header is absent, it is 00. Otherwise, only the first 7 bits are used, bit-encoding these attributes (all occurrences accounted for):

BitMeaning
0max-age attribute exists
1max-age == 0 (matches ^max-age=0+$)
2max-age value is empty
3includeSubDomains exists
4preload exists
5more than three fields/attributes present (catches duplicates/extras)
6at least one empty attribute exists (e.g. max-age=25;;)
max-age=31536000; includeSubDomains; preload → bitset 0011001 → 19

If the bitset is all zero (HSTS present with no attributes) the fingerprint is set to ff.

Content-Encoding (CE) Header Fingerprint 1 byte

For probes P6 and P7, the Content-Encoding response header is fingerprinted against the compression algorithms offered in Accept-Encoding (the 13-item A-E_List: aes128gcm, br, <grease_1>, compress, deflate, exi, <grease_2>, gzip, pack200-gzip, x-compress, x-gzip, zstd, identity). Invalid Status-Line → 99; header absent → 00. All occurrences are accounted for. Bit layout:

BitsMeaning
0–3index of the value in A-E_List + 1, or 0x0f if not in the list
4value contains an empty sequence (e.g. Content-Encoding: ,)
5–7count of additional compression algorithms returned (capped at 7; forced to 7 if it would otherwise collide with fingerprint 99)
Content-Encoding: deflate → bitset 00000101 → 05

Allow, Access-Control-Allow-Methods & Public Headers Fingerprint (AAP) 3 bytes

In probe P7, the response is examined for Allow, Access-Control-Allow-Methods (ACAM), and Public headers (collectively AACAMP). Allowed verbs across every occurrence of these headers are collected, bit-encoded, normalized, and FNV-1a hashed — producing a 3-byte fingerprint: AAP_FP = <1-byte bitset+count><2-byte FNV-1a hash>. Invalid Status-Line → 999999.

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Allow: OPTIONS,GET,HEAD,Trace
Allow: POST
Public: POST, TRACE
  1. Collect & sort verbs per header type: Allow = {GET, HEAD, OPTIONS, POST, Trace}; ACAM = {GET, OPTIONS, POST}; Public = {POST, TRACE}.
  2. Concatenate in order Allow → ACAM → Public: GETHEADOPTIONSPOSTTraceGETOPTIONSPOSTPOSTTRACE
  3. FNV-1a hash, take upper two bytes: 0xd02f8e3fd02f (or 0000 if no verbs exist).

The 1-byte bitset+count prefix: bit 0 = Allow header exists, bit 1 = ACAM exists, bit 2 = Public exists, bits 3–7 = total verb count across all headers (capped at 31). For the example above this is 01001111 = 4f.

Final AAP_FP for the example above → 4fd02f

Connection Header Fingerprint (Cnx_FP) 2 bytes

For probes P2, P3, P4, P5 and P7, a 2-byte bit-encoded fingerprint tracks whether the Connection header value is Keep-Alive or Close (checked case-insensitively). Every occurrence is registered, even where a server sends the header more than once with conflicting values (e.g. Connection: keep-alive, close or a repeated header). Format: Cnx_FP = <1-byte keep-alive bitset><1-byte close bitset> — each byte uses its first 5 bits, one per probe.

BitProbeKeep-Alive
0P2Set/Unset
1P3Set/Unset
2P4Set/Unset
3P5Set/Unset
4P7Set/Unset
BitProbeClose
0P2Set/Unset
1P3Set/Unset
2P4Set/Unset
3P5Set/Unset
4P7Set/Unset

Verbosus Fingerprint 38 bytes

The verbosus fingerprint is a 38-byte (76-character) string: 01[0-9A-Fa-f]{74}. The initial byte, "01," indicates the hash version, while the subsequent 37 bytes contain the combined fingerprints from all probes (P1–P8) and the Connection fingerprint (Cnx_FP). Version "02" is reserved for the Pacto fingerprint and cannot be reused.

Verbosus_fp = P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | Cnx_FP
ProbeLenFingerprint components (concatenated)
P1 — GET Normal, Valid Request5Status-Line (4) | Strict-Transport-Security (1)
P2 — GET Invalid HTTP Version5Status-Line (4) | Content-Length (1)
P3 — GET Random Resource5Status-Line (4) | Content-Length (1)
P4 — Random Verb5Status-Line (4) | Content-Length (1)
P5 — get Lowercase Verb5Status-Line (4) | Content-Length (1)
P6 — Accept-Encoding (Full)1Content-Encoding
P7 — Accept-Encoding (Less)1Content-Encoding
P8 — OPTIONS Request8Status-Line (4) | Content-Length (1) | Allow/ACAM/Public (3)
Cnx_FP (P2,P3,P4,P5,P7)2Connection: Keep-Alive (1) | Connection: Close (1)
P1|…|P8|CONX37

Using the HTTP-Basma tool against example.com:

HTTPBasma.exe --domain example.com --port 80
Verbosus_fp: 01140a85e4001420958a22142494d6221320958a221420958a220800144d9e7f220000001b04

Dissected:

P1: 140a85e400   P2: 1420958a22   P3: 142494d622   P4: 1320958a22
P5: 1420958a22   P6: 08           P7: 00           P8: 144d9e7f22000000
Cn: 1b04

The tool can emit CSV/JSON with the full dissection: fingerprint (p1_fp…), status line (p1_sl…), and raw response headers (p1_rh…) per probe, plus an experimental response-header fingerprint per probe (p1_rh_fp…, see Tool). In CSV/JSON output, P6/P7/P8 are represented as p6f_fp, p6l_fp, and p7a_fp respectively.

Pacto Fingerprint 32 bytes

pacto_fp = <1-byte pacto identifier><first 15 bytes of SHA-256(verbosus_fp)>

For example.com, the Pacto fingerprint is:

025d3bb410ad49ad67327bf930ef5a8a

The leading 02 marks it as a Pacto fingerprint. The remaining 15 bytes are the first 15 bytes of the SHA-256 hash of the verbosus fingerprint excluding its version byte — i.e. SHA-256 of 140a85e4001420958a22142494d6221320958a221420958a220800144d9e7f220000001b04, truncated to 15 bytes, then prefixed with 02.

If verbosus_fp is all zeros, pacto_fp is likewise set to all zeros.

Tool — HTTP-Basma

HTTP-Basma is a C++ tool developed to showcase the practicality and viability of this algorithm. It leverages Chilkat's library for all HTTP socket interactions and other supporting classes. The tool includes a demangler feature that can dissect and reverse the verbosus fuzzy-hash into a comprehensive JSON object.

Some tool output uses slightly different probe labels, but the underlying order is consistent: P1→P1, P2→P2, P3→P3, P4→P4, P5→P5, P6→P6F, P7→P6L, P8→P7a.

When requesting a given domain/IP, results can be saved to a CSV or JSON file with detailed information about server response headers and each probe's fingerprint. The tool supports HTTP redirects (enabled by default).

Experimental response-header fingerprint

An experimental algorithm fingerprints the full set of response headers (for comparison purposes), used for the p1_rh_fpp8_rh_fp columns:

  1. Collect all response headers except Connection: Keep-Alive, Connection: Close, and any header starting with X- (checked case-insensitively).
  2. Save all unique header names into a vector.
  3. Sort the vector alphabetically.
  4. Concatenate all sorted header names into one string.
  5. FNV-1a hash the string and take the full 32-bit hash as the fingerprint.
  6. If no headers are present, the fingerprint is zero.
Date: Wed, 19 Mar 2025 15:02:53 GMT
Content-Type: text/html;charset=UTF-8
Cache-Control: max-age=1
Connection: keep-alive
X-Powered-By: ASP.net
Content-Length: 0
Sever: Microsoft-IIS/10.0

Sorted, concatenated string: "Cache-ControlContent-LengthContent-TypeDateSever" → FNV-1a → df0062c6

The Demangler

The tool's demangler (-i / --demangle_json) takes a verbosus fingerprint and reconstructs the attributes of each probe as a JSON object. To reverse FNV-1a hashes back into human-readable values, it consults two local lookup databases — options.csv (allowed HTTP methods) and status_line_db.csv (status-line reason phrases) — both compiled from a scan of the top 1 million Majestic websites. If either file is missing, the corresponding hash-reversal feature is automatically disabled.

Demangling the verbosus fingerprint for example.com:

01140a85e4001420958a22142494d6221320958a221420958a220800144d9e7f220000001b04
"sl_reversed_db": {
  "http_version": "HTTP/1.1",
  "status_code": [200, 404, 403, 500, 204, 999, 888, 603],
  "http_reason": "OK"
}

The status_code array holds multiple codes because different servers reuse the same reason phrase for different status codes, producing identical FNV-1a hashes.

For domain 0009.in — verbosus 01140a85e400145183b23200000000001420958a321420958a320202140a85e430216e12100d — the Allow-header data demangles to:

"allow_hdr": {
  "fp": "216e12",
  "hdr": {
    "fp": "21",
    "methods": {
      "total": 4,
      "total_cmt": "total number of allowed methods across all allow headers' types",
      "hash": "6e12"
    },
    "hdrs": ["allow"]
  },
  "hdrs_value": {
    "allow": ["OPTIONS,HEAD,GET,POST"]
  }
}

The allow array's methods (OPTIONS,HEAD,GET,POST) correspond to FNV-1a hash 6e12, retrieved from options.csv.

The Comparator Option

The comparator (-C / --compare) compares two verbosus fingerprints and prints the differences across each probe's major components.

HTTPBasma.exe --compare 01140a85e40014514bd522142494d622144d9e7f221420958a220800140a85e4202931de1609,01140a85e40014514bd522142494d622144d9e7f221420958a220800140a85e42029e4a01609
< FPrnt-1 Vs. FPrnt-2 >
[ P1 ] [ P2 ] [ P3 ] [ P4 ] [ P5 ] [ P6F ] [ P6L ] [ P7a ] {Allow Header(s) Hash}
allow header: 31de != e4a0

The diff isolates a mismatch in the hash component of the P7a (Allow-header) probe.

Scanning a List of Domains

For batch scanning of domains, IPs, or URLs, supply the list via -f / --file and choose an output format with -c/--csv or -j/--json (one is required). Scanning runs sequentially by default; add -P/--parallel to parallelize.

  • Each domain must be on its own line; lines starting with ;, //, or # are skipped.
  • -p/--port or -s/--ssl apply the same port/SSL setting to every domain in the file.
  • Port and SSL can instead be inferred per-domain from its URL: an https prefix sets SSL and port 443; the default port is 80 otherwise; a trailing colon sets an explicit port (e.g. https://www.nohereorthere.com:8083 → SSL on, port 8083). The same applies to -d/--domain.

Experimentation

For experimentation, we scanned the top 1 million Majestic websites (986,910 reachable sites) as well as several malware C&C servers and frameworks, from February 2024 to late March 2024 — server fingerprints may have since changed.

986,910
Majestic sites scanned
8
probes per target
~680
unique reason phrases observed
5
malware C&C frameworks fingerprinted

Observations

  • Amazon.com and similar servers return varying response-header sets across identical requests — likely CDN/load-balancer intermediaries injecting request-specific headers (e.g. a Cloudflare cache miss adds X-Cache: Miss from cloudfront). Standalone servers keep a consistent header set.
  • Header-name casing can change across requests — observed for connection, server, content-type, and date, sometimes returned fully lowercase.
  • The presence of the Connection header is inconsistent across repeated queries to the same server.
  • Per spec, a 405 Method Not Allowed response should include an Allow header listing supported methods — thousands of servers fail to do so.

C&C RAT Frameworks Fingerprints

FrameworkVerbosusPactoFP rate
CobaltStrikeA011420958a0014514bd5221420958a221420958a221420958a2200001420958a22000000001f02464ae8b7d86f82c9918e2c2b9d6b9172 / 986,910
QakBotB01140a85e400140a85e4220000000000000000000000000000000000140a85e422000000000002d8b85b0eb91688f2d989ba28dc40b98 / 986,910
VShellC01140a85e400140a85e422000000000000000000000000000000000000000000000000000000028a00645c26f58fd00470d0350361f297 / 986,910
HavocD01142494d60914514bd522142494d6221420958a701420958a220000140e04922032c37f1609020769322f3d94ac2f258ddf5ce085020
BruteRatelE01140a85e40014512f3612140a85e422140a85e422140a85e4220000140a85e42200000000010207292309a7a7e798e417d69df5f2a523 / 986,910

Top 1 Million Majestic Websites

All requests sent over port 80, no SSL, follow-redirect enabled.

PropertyVerbosusPacto
YouTube
37 TLDs
01140a85e4011320958a22142494d67214254c5e2214254c5e22080014254c5e22000000000002cc5be6d05192e17de041538508bc22
Google
231 TLDs + Maps & Video
01140a85e4001320958a22142494d62214254c5e2214254c5e22080014254c5e2211696f0000020867968f811db2a53640846e56e28e
Facebook
102 TLDs across Facebook, Instagram, Oculus, WhatsApp, Messenger, Meta
01140a85e411140a85e412140a85e4721420958a221420958a220c0c140a85e472000000120d02487e07a6c0f178da22b974a4965dd3
Twitter / X
11 services
01140a85e401130a85e412140a85e4821420958a321420958a320805140a85e482000000000d02c9ff1f429859e3fafd20096bbe2deb
Yahoo
18 TLDs + services
01140a85e40114515c9322142494d672140a85e422140a85e4220808140a85e4220000001e0002d3208e7deed3237ccdf577d5a8103d
Pornhub
20 TLDs + services
010000000000000000000000000000001425d6cf321420958a3200001425d6cf320000000000025ea95c23041d1df40bcc2bc867d812
Amazon
Least reliable of them all — varies across requests (e.g. one server uses Transfer-Encoding, another Content-Length)
v1: 01140a85e41914514bd522142494d6721420958a221420958a2208001420958a20000000120d
v2: 01140a85e40014514bd522142494d6721420958a221420958a2208001423a9a822000000120d
02738a61e52960b24ed77a47aa05cc19 (both)

Top 10 Fingerprints (Top 1-Million Majestic Websites)

#VerbosusPactoCount
101140a85e40014514bd522142494d6221425d6cf221420958a2208001425d6cf2200000016090212b0b256d2ec384e0d16859573c74059,085
201140a85e40014514bd522142494d67214254c5e721420958a22080014254c5e7200000016090212b0b256d2ec384e0d16859573c74042,385
301140a85e40014514bd522142494d672140a85e4721420958a220202140a85e47200000016090201ff6c288831a114f0cdb5f9ef46a328,991
4011423a9a80014514bd5221423a9a8221425d6cf221420958a2208001423a9a822000000160902fe0e8205642eb07bdc8f83b028c80318,281
501000000000014514bd522000000000000000000001420958a2200000000000000000000000902e19199d985ef687795639662f5e7cc14,974
601140a85e40014514bd522142494d672140a85e4721420958a220800140a85e472000000160902211726ab32f439fcad5b8e55a4ed3111,813
701140a85e400140a85e472142494d672140a85e472140a85e4720800140a85e472000000000002814f248da80d4220da551fbe68e0c711,040
8011423a9a80014514bd5221423a9a8221423a9a8221420958a2200001423a9a8220000001609022daa4e2c505ae4a6ee04f3a8c98c869,957
901140a85e40014514bd522142494d67214254c5e721420958a22020214254c5e7200000016090246d98c10269e02096cdc1024b12d4a8,345
10011423a9a80014514bd5221423a9a8221423a9a8221420958a2208001423a9a822000000001f02ebabc0dbf3212783eb5109e94518ec5,846

Some Interesting Content-Encoding Values

Values seen across the top 1 million Majestic websites that deviate from the standard, highlighting a lack of uniform adherence.

Content-Encoding: UTF-8abcmouse.com
Content-Encoding: utf-8cta.int
Content-Encoding: (empty)feedmelinks.com, steem.com, steem.io
Content-Encoding: identity, compressgminagostynin.pl
Content-Encoding: br,gzipmadamemadeline.com
Content-Encoding: gzip, gziprusprofile.ru
Content-Encoding: �identityshoowbiz.ru
content-encoding: compress,gzipthptduongdong.com
content-encoding: AnyTrashurlgalleries.net

Some Interesting Options Allow Header Values

P8 (OPTIONS) elicited unusual Allow-header values that either don't follow standard comma-separated syntax or are rarely seen — not all servers adhere to the specification. Of particular interest: servers responding with an empty Allow header, signalling the resource currently accepts no HTTP methods.

Allow header value
*
GET;HEAD;OPTIONS
GET, GET
OPTIONS, GET, HEAD, POST
HEAD,HEAD,GET,HEAD,POST,OPTIONS,TRACE
CLI, GET, POST
HEAD;HEAD;HEAD;GET
GET|POST|HEAD
ACL,BIND,CHECKOUT,CONNECT,COPY,DELETE,GET,HEAD,LINK,LOCK,M-SEARCH,MERGE,MKACTIVITY,MKCALENDAR,MKCOL,MOVE,NOTIFY,PATCH,POST,PROPFIND,PROPPATCH,PURGE,PUT,REBIND,REPORT,SEARCH,SUBSCRIBE,TRACE,UNBIND,UNLINK,UNLOCK,UNSUBSCRIBE
CHECKOUT,CONNECT,COPY,DELETE,GET,HEAD,LOCK,M-SEARCH,MERGE,MKACTIVITY,MKCOL,MOVE,NOTIFY,PATCH,POST,PROPFIND,PROPPATCH,PURGE,PUT,REPORT,SEARCH,SUBSCRIBE,TRACE,UNLOCK,UNSUBSCRIBE
OPTIONS, GET, HEAD, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, PUT, DELETE, MOVE, LOCK, UNLOCK, BIND, REBIND, UNBIND, VERSION-CONTROL
,HEAD,POST,GET,HEAD,OPTIONS
*, *
POST,,HEAD,mщfU,GET,HEAD,OPTIONS,,HEAD,,HEAD
GET,HEAD;GET, POST, HEAD, OPTIONS, PUT, DELETE
GET,GET,GET
get, post, head
get, head, delete, options
get
get, post
[DELETE, POST, GET, OPTIONS, PUT, PATCH]
REDIRECT GET HEAD POST PUT DELETE OPTIONS SEND FORWARD FAIL
(empty, no value)
GET POST HEAD
GET HEAD OPTIONS

Access-Control-Allow-Methods — interesting values

ACAM header value
"PUT, GET, POST, DELETE, OPTIONS"
true
https://eu-app.contentstack.com
GET, POST, PATCH, PUT, DELETE, OPTIONS;GET,POST,PUT,DELETE,OPTIONS
GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, SCRIPT
GET;GET, POST, PUT, DELETE, PATCH, PURGE, HEAD, OPTIONS, FETCH;GET, POST, PUT, DELETE, PATCH, PURGE, HEAD, OPTIONS, FETCH
Content-Type
*;GET, POST, PUT, DELETE, OPTIONS
*;GET
*;*
'HEAD, GET, POST, PUT, PATCH, DELETE'
*
(empty, no value)
One notable example returned the literal value true and, in a separate response, leaked an origin URL (https://eu-app.contentstack.com) directly into the header value.

Public header — interesting values

Public header value
put, trace, options, delete, post, get, head
OPTIONS, TRACE, GET, HEAD, POST (alongside X-Frame-Options: SAMEORIGIN)
OPTIONS, TRACE, GET, HEAD, POST;GET,POST,HEAD

Status-Line Reason Phrases

A sample of the distinct status-line reason phrases observed — approximately 680 unique phrases were returned in total across the servers analyzed:

unknown status, unknown method, unknown, undefined, page non trouvée, page non trouv, or worse, okay, ok, object_not_exists, nicht gefunden, method not implemented, “Your user agent is banned. Please try with another web browser.”, “You are banned from this site. Please contact via a different client configuration if you believe that this is a mistake.”, “You are banned from this site.”, UNSUPPORTED MEDIA TYPE, Task [] not found, Really in Trouble, Oops! We can't find that page., Oooooops, Notfound, NotFound, Not-Found, Not in ip whitelisting, Not implemented, Nie znaleziono komponentu, Nicht gefunden, Naugty not nice!, Naughty not nice!, NOT_FOUND, NOTOK, NOT SUPPORTED METHOD, NOT IMPLEMENTED, NOT FOUND, NO MATCHING ROUTE for parameters {}, NO CONTENT, NAI, Internal Server Error, Internal Error, Interdit, Insufficient Storage, INTERNAL SERVER ERROR, I'm sorry you has been banned, I'm a teapot., I'm a teapot, I'M A TEAPOT, HttpException, Gone Walkabout, Gone, Get out of here!, Gebied niet gevonden, Gateway error, Gateway Timeout, Gateway Time-out, GONE, GATEWAY_TIMEOUT, Friendly Not Found

Conclusion

HTTP fingerprinting a server to generate a specific impression for identification is an ad-hoc approach of trial and error. However, creating a truly effective fingerprint — one that's reversible, modular, shareable, compact, and optimized — requires sophisticated techniques like bit-encoding, hashing, and data transformation. The HTTP-Basma verbosus fingerprint format is the first algorithm to successfully achieve these objectives.

This paper showcased HTTP-Basma's practical application and feasibility by successfully grouping fingerprints into clusters based on server configurations and behaviours, validated against both the top one million Majestic websites and malicious C&C servers.

HTTP-Basma has limitations, including vulnerability to false positives and false negatives. A minor alteration — such as replacing a status-line reason phrase — can change the fingerprint for an otherwise-identical server; depending on interpretation, that evasion technique itself constitutes an identity change requiring a new fingerprint. Inconsistencies in whether a server returns the Connection header across different execution times also produce different fingerprints.

Bibliography

  1. Kotarak, A. (2003, May 23). WebServerFP-Source.zip. Retrieved May 14, 2026, from PACKET STORM: packetstorm.news/files/id/31157
  2. Lee, D. W. (2001). HMAP: A Technique and Tool For Remote Identification of HTTP Servers. University of California, Computer Science. Davis: Computer Security Lab. Retrieved May 14, 2026, from seclab.cs.ucdavis.edu
  3. Ruef, M. (2007, December 6). httprecon project — advanced web server fingerprinting. Retrieved May 14, 2026, from computec.ch: computec.ch/projekte/httprecon
  4. Shah, S. (2004, May 19). An Introduction to HTTP fingerprinting. Retrieved May 14, 2026, from Net Square: net-square.com/httprint_paper