One of the key aspects of penetration testing is the automation of routine actions. Someone writes standalone programs for this, but now there are many products that allow you to extend their functionality with the help of additional modules. An example of a free vulnerability scanner with custom modules support is OpenVAS, but this article is about Nessus, which isn't free, but in my humble opinion, finds more and generates fewer false positives (although OpenVAS started as a fork of Nessus, but that was a long time ago). Let's get started.
Most Nessus modules are just plain text files written in some kind of scripting language and .nasl extension. Some modules are shipped in a binary form with .nbin extension, but let's not talk about those. The default paths for modules (or plugins) are the following (https://community.tenable.com/s/article/Location-of-Plugin-Directory):
1 2 |
Windows: C:\ProgramData\Tenable\Nessus\nessus\plugins Linux: /opt/nessus/lib/nessus/plugins |
There're lots of modules, more than 100 thousand, some of them were written a long time ago (judging by the code) and are almost not updated, although they implement scanning basics (for example, searching for common directory names), while often they are not parameterized in any way, which makes it necessary to manually edit the code, for example, in order to expand the built-in dictionary. But in this diversity you can always find a sample code to implement some specific function for your module.
As an example, we will write a trivial module for searching the database administration script Adminer (https://github.com/vrana/adminer/). There is a similar Nessus module for phpMyAdmin (https://phpmyadmin.net/).
Our module will search Adminer through possible paths, detect the version and set the resulting severity depending on it (before 4.6.3 version it was possible to read arbitrary files on a server with LOAD DATA LOCAL INFILE, more info on this issue is here).
It is worth noting that the documentation on the available API is peculiar and fragmentary, same actions in standard modules are sometimes implemented in different ways for no apparent reason, naming of some internal functions is questionable, possibly due to legacy code layer that Tenable (Nessus developer) does not clean out (maybe they don’t want to rewrite old modules and keep it for compatibility).
Here we go. Our module code begins with a unique internal identifier for it, as well as a description, release date, severity, CVE, etc. Practically everything is optional and serves only for convenient presentation in the web interface (modules can also be launched via console). I recommend using Ruby syntax highlighting. Actually modules are written in NASL, but resulting highlighting is acceptable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# Adminer script discovery include("compat.inc"); if(description) { script_id(10797107); script_version("$Revision: 0.1 $"); script_cvs_date("$Date: 2019/04/10 13:37:00 $"); script_name(english: "Adminer Database Management Detection"); script_set_attribute(attribute: "synopsis", value: "Adminer Database Management script was detected on the remote host."); script_set_attribute(attribute: "description", value: "Adminer Database Management script was detected on the remote host."); script_set_attribute(attribute: "solution", value: "Restrict access to the script, if desired."); script_set_attribute(attribute: "risk_factor", value: "Low"); script_set_attribute(attribute:"plugin_publication_date", value: "2019/04/10"); script_set_attribute(attribute:"plugin_type", value: "remote"); script_end_attributes(); script_summary(english: "Reports if Adminer (https://www.adminer.org/) was discovered on the remote host."); script_category(ACT_GATHER_INFO); script_copyright(english: "This script is Copyright (C) Kaimi (https://kaimi.io)"); script_family(english: "CGI abuses"); script_dependencie("webmirror.nasl"); script_exclude_keys("Settings/disable_cgi_scanning"); script_require_keys("Settings/enable_web_app_tests"); script_require_ports("Services/www"); script_timeout(1800); exit(0); } |
Since the code is simple, I will describe the heart of the matter in paragraphs after the code fragments. In the fragment above, we set a unique identifier, version, date, description, and other attributes of the module. The attribute list is not complete, if there is a need to add an associated CVE, CVSS, etc. — see the examples in the standard modules. At the end we set the category of the module - "CGI abuses" - and several conditions. We need the webmirror.nasl module to run before the start of our module. This module searches for standard paths and stores them in some global structures that can then be used by other modules. Next, we set the conditions so that our module does not start if CGI scanning is explicitly disabled by the scanning policy (https://www.tenable.com/blog/nessus-web-application-scanning-new-plugins-configuration) or web applications testing is disabled. Next, we add a requirement, so that Nessus will scan for and identify ports of web servers before our module starts. Finally, we set the running timeout for our module.
This is how the information about module looks like in the Nessus web interface.
To search for possible paths, we need a list of versions, as well as possible naming. All of this can be obtained from the official Adminer repository on Github - https://github.com/vrana/adminer/. I have created a complete list of possible naming, taking into account all available languages: https://github.com/kaimi-io/web-fuzz-wordlists/blob/master/adminer.txt. In our module, we will use a shortened version, which, if necessary, can be extended, and we will partially generate this list, instead of hardcoding the contents. But at first we will add some more extra things:
1 2 3 4 5 6 7 8 |
include("audit.inc"); include("global_settings.inc"); include("misc_func.inc"); include("http.inc"); app = "Adminer"; port = get_http_port(default: 80); |
We include modules that contain global variables and implementation of some necessary functions. Next, we get the port, where Nessus has detected a running web server. Now, to draw the rest of the fucking owl the main code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
prefix_list = make_list("adminer-", "editor-", "adminer"); # Versions up to current for 4/10/2019 version_list = make_list("","4.7.1","4.7.0","4.6.3","4.6.2","4.6.1","4.6.0","4.5.0","4.4.0","4.3.1","4.3.0","4.2.5","4.2.4","4.2.3","4.2.2","4.2.1","4.2.0","4.1.0","4.0.3","4.0.2","4.0.1","4.0.0","3.7.1","3.7.0","3.6.4","3.6.3","3.6.2","3.6.1","3.6.0","3.5.1","3.5.0","3.4.0","3.3.4","3.3.3","3.3.1","3.3.0","3.2.2","3.2.1","3.2.0","3.1.0","3.0.1","3.0.0"); # More languages are available according to https://github.com/vrana/adminer suffix_list = make_list("-en.php", "-mysql-en.php", "-mysql.php", ".php", "/"); found_list = make_list(); found_list_ver = make_list(); found_ctr = 0; high_severity = 0; dirs = list_uniq(make_list(cgi_dirs(), "")); foreach dir (dirs) { path = dir + '/'; foreach prefix (prefix_list) { foreach version (version_list) { foreach suffix (suffix_list) { # path/ + adminer- + 4.1.0 + -en.php url = path + prefix + version + suffix; res = http_send_recv3( method : "GET", port : port, item : url ); if (isnull(res)) continue; match = pregmatch(pattern: '<span class="version">([0-9\\.]+)</span>', string: res[2], icase: TRUE); if (!empty_or_null(match)) { # LOAD DATA LOCAL INFILE disabled since 4.6.3 if (match[1] =~ "^(?:[1-4]\.[0-6]\.[0-2]|[1-3]\.[0-9]\.[0-9])$") high_severity = 1; found_list[found_ctr] = url; found_list_ver[found_ctr] = match[1]; found_ctr++; } } } } } if (found_ctr > 0) { report = NULL; if (report_verbosity > 0) { report += '\nThe following instance of Adminer was detected on the remote host: \n'; report += '\n'; for (i = 0; i < found_ctr; i++) { url = found_list[i]; version = found_list_ver[i]; report += 'Adminer Version\t: ' + version + '\n'; report += 'URL\t\t: ' + build_url(port: port, qs: url) + '\n'; report += '\n'; } if (high_severity) report += '\nDiscovered versions may be susceptible to LOAD DATA LOCAL INFILE attack\n'; } if (high_severity) security_hole(port: port, extra: report); else security_note(port: port, extra: report); } else { audit(AUDIT_WEB_APP_NOT_AFFECTED, app, build_url(port: port, qs:"/")); } |
That's a large code snippet. In general, based on the repository, we have compiled a list of versions, prefixes, and naming suffixes of the paths where Adminer can be located. We use several arrays to store the paths, the versions, and a total count of detections. For a port with a web server, we launch the scanning process using the paths detected earlier. Next, we build an URI and send a regular GET request. Pay attention to the name of the function to send a GET request and recall the passage above about legacy code and the quality of the API. We retrieve the version using the regular expression and put it into arrays. We also set a flag if an old version was found, which is vulnerable to unauthorized server file reading. After checking all the directories, we generate a report, where we specify paths and detected versions. Depending on results we return, that nothing was found (AUDIT_WEB_APP_NOT_AFFECTED), a low level vulnerability (if version >= 4.6.3) or a high level one.
Here are the results of our module run in the web interface:
As I've mentioned before, the module can also be started from the command line as follows:
1 |
/opt/nessus/bin/nasl -t 192.168.1.1 ./adminer.nasl |
You can read more about the Nessus command line arguments here.
Finally, after the module is completed and moved to the proper location with other modules, it is necessary to update the plugins database via the web interface or from the console (https://community.tenable.com/s/article/Rebuild-the-Plugin-Database):
1 2 3 4 |
/opt/nessus/sbin/nessuscli fix --set nasl_no_signature_check=yes service nessusd stop /opt/nessus/sbin/nessusd -R service nessusd start |
First command will disable signature check for Nessus plugins. Everything is finished and ready for use. For those who want to read a more structured description of the module development process, you can look through the book Nessus Network Auditing, pretty old, but gives an idea of the process and the available API description.
Ready-to-use module: adminer_detect.nasl.zip
This has helped me. Well done.