Skip to content

CMS Detection and Fingerprinting

8 min read Created Mar 28, 2026 Last updated Jul 10, 2026

CMS detection identifies which content management system and version is running before applying specific enumeration tooling. WordPress, Drupal, and Joomla have dedicated pages: this page covers the initial detection step and fingerprinting for other platforms including Magento, Typo3, Umbraco, and custom frameworks. Correct identification prevents wasting time with wrong tooling and surfaces version-specific CVEs immediately.

Detection relies on several overlapping signals. No single indicator is conclusive on its own. Work through all available layers before committing to a platform:

  • HTTP response headers: Server, X-Powered-By, X-Generator
  • HTML meta generator tag: often includes platform and version
  • Cookie names: fe_typo_user (TYPO3), UMB_UCONTEXT (Umbraco)
  • URL patterns: /wp-admin/, /administrator/, /?q=node
  • Known static file paths: /CHANGELOG.txt, /readme.html, /magento_version

Misidentification sends enumeration in the wrong direction and means any tooling you run will produce irrelevant output.

Start with automated tools. They parse multiple signals in a single pass and usually return a confident answer. Fall back to manual path probing only when automation is blocked or returns conflicting results. WAFs, custom error pages, and aggressive header stripping all degrade automated results and require manual follow-up.

whatweb

whatweb is the primary tool and usually identifies the CMS in a single request. Aggression level 3 sends additional follow-up requests to confirm initial findings. This increases detection accuracy at the cost of more traffic. Use level 1 when noise matters. The output line includes the HTTP status code, detected plugins with versions shown in square brackets, server software, and the page title. Focus on the plugin names: they map directly to dedicated enumeration workflows. If whatweb returns a version number, treat that as your primary lead. Missing version information is common on patched or hardened installs and means you need to probe version-disclosure endpoints manually.

┌──(kali㉿kali)-[~]
└─$ whatweb -a 3 http://$TARGET_IP:$PORT/ --log-brief=-
http://10.10.10.50/ [200 OK] Magento[2.4.3], PHP[7.4.3], nginx[1.18], Title[Home Page]
Explain command
-a 3 Sets aggression level to 3 (aggressive, more requests sent).
http://$TARGET_IP:$PORT/ Target URL with variable host IP and port to fingerprint.
--log-brief=- Outputs brief log format to stdout (dash = standard output).
┌──(kali㉿kali)-[~]
└─$ whatweb -a 3 https://$DOMAIN/ 2>/dev/null
Explain command
-a 3 Set aggression level to 3 (aggressive, sends more requests).
https://$DOMAIN/ Target URL with placeholder domain to fingerprint.
2>/dev/null Redirect stderr to /dev/null to suppress error messages.

When whatweb identifies a CMS, cross-reference the version against known CVEs. The version number alone often determines the exploitation path. Run the version through searchsploit immediately. If whatweb returns a version range or no version at all, note the CMS type and probe version-disclosure endpoints specific to that platform. A missing version in the whatweb output is not a dead end: it means the next step is manual version probing rather than CVE lookup.

Manual Fingerprinting by CMS

When whatweb is inconclusive, probe CMS-specific paths manually. Each platform has unique indicators. whatweb returns inconclusive results when a WAF intercepts and rewrites responses, when the server returns a generic error page for all paths, or when the application strips technology-identifying headers. In those cases, request known installation paths directly. A 200 or 302 on a CMS-specific path is strong evidence even when the response body contains no version string. A 403 on an admin path also confirms the platform is likely present: the application is protecting the route, which means the route exists. Combine at least two signals before concluding which CMS is running.

┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/magento_version
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ | grep -i 'magento\|mage'
┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{http_code}' http://$TARGET_IP:$PORT/index.php/admin/
Magento/2.4.3 (Community)
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allows insecure SSL connections by skipping certificate verification.
http://$TARGET_IP:$PORT/magento_version Target URL with variable host and port to probe Magento version endpoint.
http://$TARGET_IP:$PORT/ Target base URL with variable host and port.
-i Case-insensitive pattern matching in grep.
'magento\|mage' Grep pattern matching Magento-related strings in the response body.
-o /dev/null Discards the response body by redirecting output to null.
-w '%{http_code}' Outputs only the HTTP status code after the request completes.
http://$TARGET_IP:$PORT/index.php/admin/ Target URL pointing to the Magento admin panel path.
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ | grep -i 'typo3\|typo'
┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{http_code}' http://$TARGET_IP:$PORT/typo3/
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/typo3conf/LocalConfiguration.php
Explain command
-s Silent mode; suppresses progress and error output.
-k Allows insecure SSL/TLS connections, skipping cert validation.
http://$TARGET_IP:$PORT/ Target URL with variable host and port placeholders.
-i Case-insensitive pattern matching in grep.
'typo3\|typo' Grep pattern matching either 'typo3' or 'typo' strings.
-o /dev/null Discards response body by redirecting output to /dev/null.
-w '%{http_code}' Prints only the HTTP response status code after request.
http://$TARGET_IP:$PORT/typo3/ Target URL pointing to the TYPO3 CMS admin directory.
http://$TARGET_IP:$PORT/typo3conf/LocalConfiguration.php Target URL for TYPO3 local config file containing sensitive data.
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ | grep -i 'umbraco'
┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{http_code}' http://$TARGET_IP:$PORT/umbraco/
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/umbraco/webservices/legacyAjaxCalls.asmx
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allow insecure SSL/TLS connections, skipping certificate verification.
http://$TARGET_IP:$PORT/ Target URL with variable host IP and port placeholders.
-i 'umbraco' Case-insensitive grep filter for the string 'umbraco'.
-o /dev/null Discards response body by redirecting output to /dev/null.
-w '%{http_code}' Outputs only the HTTP response status code after the request.
http://$TARGET_IP:$PORT/umbraco/ Target URL pointing to the Umbraco CMS root path.
http://$TARGET_IP:$PORT/umbraco/webservices/legacyAjaxCalls.asmx Target URL for the Umbraco legacy AJAX web service endpoint.
┌──(kali㉿kali)-[~]
└─$ curl -skI http://$TARGET_IP:$PORT/ | grep -iE 'x-shopify|x-powered-by|server'
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ | grep -i 'shopify\|cdn.shopify'
Explain command
-s Silent mode; suppresses progress and error output.
-k Skips SSL/TLS certificate verification.
-I Sends a HEAD request to fetch headers only.
http://$TARGET_IP:$PORT/ Target URL using variable IP and port placeholders.
-iE Case-insensitive match with extended regex support.
'x-shopify|x-powered-by|server' Regex pattern matching common tech-disclosure headers.
-i Performs case-insensitive string matching.
'shopify\|cdn.shopify' Pattern matching Shopify platform indicators in response body.

Generator Meta Tag and HTTP Headers

Many CMS platforms disclose themselves in the HTML meta generator tag or custom HTTP response headers. The generator meta tag appears in the page head section and often includes both the platform name and the version string. Hardened installations frequently remove this tag, but the corresponding HTTP headers are often overlooked by administrators. The X-Powered-By and X-Generator headers survive in many configurations where the meta tag has been removed. Check both layers on every target. When neither the meta tag nor the custom headers are present, move to cookie name fingerprinting next.

┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ | grep -i 'generator'
<meta name="generator" content="TYPO3 CMS" />
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allow insecure TLS connections, skipping certificate verification.
http://$TARGET_IP:$PORT/ Target URL constructed from IP and port variable placeholders.
-i Makes the grep pattern match case-insensitive.
'generator' Pattern to search for 'generator' meta tag in the HTML response.
┌──(kali㉿kali)-[~]
└─$ curl -skI http://$TARGET_IP:$PORT/ | grep -iE 'x-powered-by|x-cms|x-generator|x-drupal|x-joomla'
X-Powered-By: TYPO3 CMS
Explain command
-s Silent mode; suppresses progress meter and error messages.
-k Allows insecure TLS connections, skipping certificate verification.
-I Sends a HEAD request, fetching only HTTP response headers.
http://$TARGET_IP:$PORT/ Target URL built from variable IP and port placeholders.
-iE Case-insensitive match using extended regex pattern.
'x-powered-by|x-cms|x-generator|x-drupal|x-joomla' Regex pattern matching common CMS/framework fingerprinting headers.

Session cookie names are CMS-specific and often reveal the platform without any other indicators. Cookies persist even when administrators strip response headers and remove the generator meta tag. This makes them a reliable signal on hardened targets where other methods return nothing. Send a HEAD request to the root path and inspect the Set-Cookie headers. The cookie name alone can confirm the platform. If the response sets multiple cookies, check each one against known CMS patterns before drawing a conclusion.

┌──(kali㉿kali)-[~]
└─$ curl -skI http://$TARGET_IP:$PORT/ | grep -i 'set-cookie'
Set-Cookie: PHPSESSID=abc123; path=/
Set-Cookie: fe_typo_user=xyz789; path=/
Explain command
-s Silent mode; suppresses progress and error output.
-k Allow insecure TLS connections, skipping certificate verification.
-I Send HEAD request and fetch HTTP headers only.
http://$TARGET_IP:$PORT/ Target URL built from variable IP and port placeholders.
-i Makes the grep pattern match case-insensitive.
'set-cookie' Filter pattern to display only Set-Cookie header lines.

Common cookie name indicators:

Cookie name

Platform

fe_typo_user

TYPO3

PHPSESSID + frontend

Magento

UMB_UCONTEXT

Umbraco

ASP.NET_SessionId

.NET / Umbraco / Kentico

PHPSESSID only

PHP app, check further

A bare PHPSESSID without any accompanying CMS-specific cookie only confirms the application uses PHP sessions. It does not identify the platform. Continue to the path-probing methods above when this is the only cookie present. An ASP.NET_SessionId without UMB_UCONTEXT could be Kentico, Sitecore, or another .NET CMS. In those cases, look for CMS-specific paths in page source or use whatweb at a higher aggression level to resolve the ambiguity.

After identifying the CMS and version, search searchsploit and exploit-db for known vulnerabilities before moving to vulnerability discovery. Filter results by the exact version number. Prioritize authentication bypass and RCE entries first. If no direct match exists, check whether vulnerabilities for nearby minor versions are listed: many CVEs apply across multiple patch levels and the exact version boundary is worth confirming against the CVE details. When searchsploit returns nothing useful, search the NVD directly using the CMS name and version as keywords.

┌──(kali㉿kali)-[~]
└─$ searchsploit typo3 2>&1 | head -20
Explain command
2>&1 Redirects stderr (fd 2) to stdout (fd 1), merging both streams.
head -20 Outputs only the first 20 lines of the piped input.

References