A common issue noted by security testers is that the Apache web server is reporting a version in headers which is older than the latest available one.

This usually leads to CVE listings detailing the vulnerabilities present in that version of the product, and a typical challenge for black-box testers is establishing what modules are enabled on the servers, as the vulnerabilities are commonly present in modules and not the core Apache server.

So the question is, are there any general techniques which can be used on a black-box security test to identify/enumerate enabled modules on a running Apache server?

3 Answers 3


are there any general techniques which can be used on a black-box security test to identify/enumerate enabled modules on a running Apache server?

tl;dr: you cannot detect all installed/enabled modules, and you need to analyze headers manually, if you dont want to X-MISS something

we use a custom tool to investigate all server-headers; this tool, written in python, simulates a browser-request and displays everything send back from the server (examples below)

beside apache/module-version also X-Powered-by is interesting, because, if not filtered correctly, it might give you a hint on application-servers (if any) or php-version used (google: php easteregg). rack (as in ruby-on-rails), for example, leaks a lot of headers, showing that rails is used (X-Rack-*)

oh, and header-analysis is done manually; i dont know of any tool that might work like (if header X then alert Y); it's just experience

edit 1

detecting installed modules, based on headers

  • apache-only, since nginx only displays nginx or ǹginx/VERSION
  • please note: installed modules might also be displayed on default-error-pages, like the following extract from a html-page of a default 404-error-page, when ServerSignature On is set: <address>Apache/2.2.22 (Debian) mod_fcgid/2.3.6 PHP/5.4.4-14+deb7u8 mod_python/3.3.1 Python/2.7.3 Server at localhost Port 80</address>

  • if apache displays the installed/enabled modules depends on the setting of a config-option, called ServerTokens; from the config:

    # ServerTokens
    # This directive configures what you return as the Server HTTP response
    # Header. The default is 'Full' which sends information about the OS-Type
    # and compiled in modules.
    # Set to one of:  Full | OS | Minimal | Minor | Major | Prod
    # where Full conveys the most information, and Prod the least.
    # examples
    # ServerTokens Full
    Server: Apache/2.2.22 (Debian) mod_fcgid/2.3.6 PHP/5.4.4-14+deb7u8 mod_python/3.3.1 Python/2.7.3
    # examples
    # ServerTokens OS
    Server: Apache/2.2.22 (Debian)
    # examples
    # ServerTokens Minimal
    Server: Apache/2.2.22
    # examples
    # ServerTokens Minor
    Server: Apache/2.2
    # examples
    # ServerTokens Major
    Server: Apache/2
    # examples
    # ServerTokens Prod
    Server: Apache
  • additionally, not all loaded modules are displayed with the server-header (compare SeverTokens Full vs the following extract from apache2ctl -S -M from the same server

    Loaded Modules:
     core_module (static)
     log_config_module (static)
     logio_module (static)
     version_module (static)
     mpm_prefork_module (static)
     http_module (static)
     so_module (static)
     alias_module (shared)
     auth_basic_module (shared)
     authn_file_module (shared)
     authz_default_module (shared)
     authz_groupfile_module (shared)
     authz_host_module (shared)
     authz_user_module (shared)
     autoindex_module (shared)
     cgi_module (shared)
     deflate_module (shared)
     dir_module (shared)
     env_module (shared)
     fcgid_module (shared)
     headers_module (shared)
     mime_module (shared)
     evasive20_module (shared)
     negotiation_module (shared)
     php5_module (shared)
     python_module (shared)
     reqtimeout_module (shared)
     rewrite_module (shared)
     setenvif_module (shared)
     status_module (shared)
    Syntax OK

examples, full headers

  • google-headers

    [+] sending now:
    GET / HTTP/1.1
    Host: www.google.com
    Connection: close
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; fr-FR; rv:1.7.3) Gecko/20040910
    [+] lines ins answer: 17  
    [+] www.google.com :: 
    HTTP/1.1 302 Found
    Cache-Control: private
    Content-Type: text/html; charset=UTF-8
    Location: http://www.google.de/?gfe_rd=cr&ei=GzpBU8fYBIGKtQaAo4GYCA
    Content-Length: 258
    Date: Sun, 06 Apr 2014 11:27:23 GMT
    Server: GFE/2.0
    Alternate-Protocol: 80:quic
    Connection: close
  • server-headers, leaking php-version, vulnerable to php easter egg

    [+] sending now:
    GET / HTTP/1.1
    Host: www.cs.utexas.edu
    Connection: close
    User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:0.9.8) Gecko/20020204
    > ssl-info
    ('', 443)
    ('DHE-RSA-AES256-SHA', 'TLSv1/SSLv3', 256)
    > do you want to check with openssl? [y|N] : 
    [+] lines ins answer: 14  
    [+] www.cs.utexas.edu :: 
    HTTP/1.1 200 OK
    Date: Sun, 06 Apr 2014 12:36:16 GMT
    Server: Apache/2.2.14 (Ubuntu)
    X-Powered-By: PHP/5.3.2-1ubuntu4.23
    Set-Cookie: SESScf56....; path=/; domain=.cs.utexas.edu
    Last-Modified: Sun, 06 Apr 2014 12:33:53 GMT
    ETag: "4eb53eded35299af56b68617202f579c"
    Expires: Sun, 19 Nov 1978 05:00:00 GMT
    Cache-Control: must-revalidate
    Connection: close
    Transfer-Encoding: chunked
    Content-Type: text/html; charset=utf-8
  • server-headers, reverse-proxy infront of possibly rails, leaking rack-version, possibly outdated and vulnerable to some already release exploits

    [+] sending now:
    GET / HTTP/1.1
    Host: XXXXX.COM
    Connection: close
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; de-AT; rv:1.3b) Gecko/20030210
    > ssl-info
    ('', 443)
    ('ECDHE-RSA-AES128-SHA256', 'TLSv1/SSLv3', 128)
    > do you want to check with openssl? [y|N] : 
    [+] lines ins answer: 18  
    [+] XXXXX.COM ::
    HTTP/1.1 200 OK
    Date: Sun, 06 Apr 2014 12:38:02 GMT
    Server: Apache
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    Pragma: no-cache
    Expires: Fri, 01 Jan 1990 00:00:00 GMT
    X-Runtime: 11
    X-Powered-By: rack 1.2
    Set-Cookie: _mailservice_session=BAh7BzoPc...-; path=/; HttpOnly; secure
    ETag: "813d4fb6faa576ffd22fbbe44abfa0d9"
    Content-Length: 17654
    Status: 200 OK
    Vary: Accept-Encoding
    Connection: close

edit 2

headers might tell way more; the following snippet, gathered during a blackbox-security-analysis, lead to identifying additional servers that shouldn be visible, which lead to detecting an old-forgotten, outdated firewall with a vulnerable smtp-service running; full-root-access to that box and access to the internal network within 30 minutes

    X-Cache: MISS from proxy232.xxxx.com
    X-Cache-Lookup: MISS from proxy232.xxxx.com:80
    Via: 1.1 proxy232.xxxx.com:80 (squid/2.7.STABLE3)
  • Do I miss the part where the tool gives info about the installed apache modules? How for instance would I know from the output if mod_rewrite.c was enabled or not? Commented Apr 6, 2014 at 14:31
  • @humanityANDpeace: see my edit; tl;dr: you cannot detect all installed/enabled modules Commented Apr 6, 2014 at 15:06
  • @thatguyfromoverthere mod_security module might block characters like " ' in the URL and allows to deduce that obviously such module is installed Commented May 1, 2020 at 10:40
  • @humanityANDpeace you would detect it if you have something like http://www.example.com/app/main instead of http://www.example.com/app/main.py. On the other side if it rewrites just foo.php to bar.php then the only way to detect that is to search for some redirects (from foo.php to bar.php that's a bit questionable though) or by analysing error messages (from python / php / ...) if you can trigger some bugs by manipulating parameters (e.g. send wrong types like input[]=42 instead of input=42). But designing such a generic tool for leaking might be a bit problematic... Commented May 1, 2020 at 10:46
  • @thatguyfromoverthere additionally it is possible to detect if open_basedir directive is enabled, but you'll require some arbitrary file read vulnerability for that (this might be only interesting if you want to achieve RCE on that system but you have no luck and leaked code doesn't contain any RCE-vectors) Commented May 1, 2020 at 11:00

This is done during the fingerprinting step of the blackbox pentest, and is usually achieved with vulnerability scanners (e.g. Nikto, Nessus, Acunetix, Qualys) through signatures, pattern-matching and/or information leakage from the Apache server.

Personnaly I go with a scan first, and if that's not enough and there's something potentially interesting I want to investigate, I go manual mode (which always starts with Google). The methodology is then really depending on what the module is doing, I'm not aware of any general technique for that purpose and I don't this it exists.


This is exactly what a PCI Compliance test is for. Testing what for them is a black-box (i.e. your server) and telling you what they find and whether a fix is required (i.e. they have a level which defines whether a fix is mandatory to consider your box actually safe.)

The way it is done is fairly simple. You write a software that opens and connects to an HTTP port, you send a GET, POST, and other methods, and analyze the answer. The answer includes the headers and the content and, if you want to go to that level, the TCP/IP socket reaction (i.e. one test they like to do is to check whether TCP numbers are considered sequential, if so it makes it easier for a man in the middle to intercept future connections. i.e. they connect once, get number N, then know that the next number will be N + 1. It's a tad be more complicated, but it is one of the things that can allow you to piggyback on someone's else connection...)

In general, at least from what I've seen so far, it is done against all the CVE that exist. Unfortunately, for at least the few PCI compliance companies I've worked with, they do not seem to be proactive and tell you about things that you should do ahead of receiving a CVE... (although at least they do not limit themselves to just CVEs, they often use other sources too.)

I think "that guy from over there" gives you good examples. However, although Apache2 modules can be a liability, most vulnerabilities are in scripts that are added, such as PHP or Perl. Most of the attacks I can see on my websites check to see whether you are running Wordpress, Drupal, Joomla, phpBB... or one of my favorite: phpMyAdmin. Those are often way easier to penetrate than an Apache2 module.

However, either way the technique is the same.

Send some HTTP/1.0 or HTTP/1.1 GET to determine basic things that exist on that website, and move on from there. For example:

wget -S http://www.example.com/ >example.com 2>&1

And as you can see, we save all the output to example.com. Then run various tests to see what headers are available, and possibly what's in the body (i.e. most commonly, what you find in the <meta ...> tags,) you can check how long it took to get the page, whether you got a Location: ..., etc. A simple example, is to use sed to extract the Apache version is available.

By default an Apache2 server is likely to return something like this:

Server: Apache/2.4.18 (Ubuntu)

With tools such as sed (if you know perl, much better...) you can:

sed -e '/^  Server: Apache\//!d' \
    -e '/^  Server: Apache\// s/^  Server: Apache\/\([^\s]*\)\s.*/\1/' \

The result will be:


In most cases, if you are to repeat the work over and over again (i.e. automate the tests,) writing it as a script that you can re-run again and again is the best way to do it. (i.e. you run a test, see a problem, get the test to fail over it, ask the developer to fix the problem, re-run the test, if it still fails, just ask the developer again...) Obviously, if you do not trust your ability to write perfect regular expressions, you'd want to verify that your scripts do what they are expected to do, especially over time as other things change.

Actually, you probably want many scripts with a "driver script". i.e. script (1) can test feature A, then script (2) feature B, etc. Save various information and as you reach script (1001), you can check much more advanced features of, says, Wordpress because by now you know that this website runs that system and it is version 1.2.3 so you know that there is possibly a hack you can use to gain access of the computer and script (1001) is that running that hack to see whether it works or not.

And trying to hide the things hackers use to know what you are running is pretty much impossible. Being flagrant, as with the Server: ... header in my example above, is certainly not a good idea, even if it is possible for a good hacker to determine what system your website is running anyway. But there are many things you just cannot hide. For example, a Drupal site always has a /node page. Although you could hide that one, there are so many possibilities like that (a good one is /cron.php under Drupal! You really cannot get rid of that one, although you could rename it...), that in the end, you just cannot hide them all and hackers will still be able to determine that you have a Drupal site and at least its major version and most of its modules. (i.e. so, really, big waste of time trying to hide anything. Better have the latest secure version...) Wordpress has the well know wp-admin and wp-include directories. If you get a 403 for wp-include, chances are that website is running Wordpress. If you get a 404 instead, chances are that this website is not running Wordpress.

Similarly, although I would say it is a bit more complicated, receiving various headers is not unlikely to reveal some part of the Apache2 server settings, probably not all of them, though. As mentioned in the other answer, by default Apache2 and PHP puts signatures in your headers, for example. If you turn on the language support for errors, then you'll get a form of Vary header...

Other modules may not output a specific header, but they will add various functionalities that you can test by accessing/triggering that functionality. If it happens, then it is installed. If it does not happen, it is not installed. As you know, there are quite a few modules, so it is quite a bit of a job to go through all of them. But I'll give a simple example: if you can connect to port 443 and a valid SSL connection ensue (whether verifiable or not), then one of the SSL modules is installed.

As a side note, CMS systems like Wordpress and Drupal also tweak the returned headers. So you have to be careful as both mechanisms may overlap. The priority for most headers is given to the scripts (a few headers are forcibly managed by Apache2 because it is in control of the corresponding features, such as the Keep-Alive feature, since Apache2 holds the client socket.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .