Skip to content

Plugins

Tib3rius edited this page Jan 22, 2022 · 3 revisions

Plugins

AutoRecon uses a plugin system to perform all port and service scanning. By default, plugins are located in the "plugins" directory. All plugins must be contained within files that have a .py extension. If a plugin file starts with an underscore, it will not get loaded by AutoRecon, making it easy to disable certain plugin files.

Each plugin file can contain multiple plugins, and the plugin set which comes with AutoRecon groups plugins into files based on the service they run against (e.g. all the HTTP plugins are in plugins/http.py).

There are two types of plugin that AutoRecon supports: PortScan and ServiceScan. PortScan plugins are provided a Target object which represents a target being scanned by AutoRecon (e.g. 127.0.0.1). They are expected to perform a port / service identification scan and return a list of Service objects which represents the services running on the target. ServiceScan plugins are provided with a Service object and are expected to perform further service enumeration scans.

PortScan Plugin

The following is an example PortScan plugin that scans the top 1000 TCP ports:

from autorecon import PortScan

class QuickTCPPortScan(PortScan):

    def __init__(self):
        super().__init__()
        self.name = "Top TCP Ports"
        self.type = 'tcp'
        self.tags = ["default", "default-port-scan"]
        self.priority = 0

    async def run(self, target):
        if target.ports:
            if target.ports['tcp']:
                process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p ' + target.ports['tcp'] + ' -oN "{scandir}/_custom_ports_tcp_nmap.txt" -oX "{scandir}/xml/_custom_ports_tcp_nmap.xml" {address}', blocking=False)
            else:
                return []
        else:
            process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False)
        services = await target.extract_services(stdout)
        await process.wait()
        return services

Here is a breakdown:

from autorecon import PortScan

This simply imports the PortScan class from AutoRecon, something that is required to write a valid PortScan plugin.

class QuickTCPPortScan(PortScan):

    def __init__(self):
        super().__init__()
        self.name = "Top TCP Ports"
        self.type = 'tcp'
        self.tags = ["default", "default-port-scan"]
        self.priority = 0

Each plugin is defined as a class. If you are familiar with object-oriented programming, you'll understand this. If not, just know that a class name ("QuickTCPPortScan" in this case) has to be unique. The parentheses after the class name tell AutoRecon that this is a PortScan plugin.

Every plugin has a number of methods / functions that it must define. The first is the init method, which must call super().__init__() before anything else.

The last four lines define attributes of the plugin. Technically, only self.name is required. This is the name which AutoRecon will use when referring to the plugin in its output, and so should ideally be kept short. It should also be unique, but does not have to be the same as the class name.

self.type, if set, tells AutoRecon that this plugin should be used for scanning specific ports if the user has used the --ports argument. The value distinguishes whether AutoRecon should use this plugin for TCP or UDP ports.

self.tags defines a list of tags that the plugin belongs to. By default, all plugins are tagged as "default" only, meaning the plugin will run if no tags are specified on the command line. If you override the tags list, you should include the "default" tag if you want the plugin to run by default.

self.priority is a number which defaults to 1, and sets the order in which plugins are run. If you want a plugin to run before the others, set the priority attribute to a number less than 1 (negative numbers and decimals are allowed). Conversely, if you want a plugin to run after the others, set the priority attribute to a number greater than 1.

    async def run(self, target):
        if target.ports:
            if target.ports['tcp']:
                process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -p ' + target.ports['tcp'] + ' -oN "{scandir}/_custom_ports_tcp_nmap.txt" -oX "{scandir}/xml/_custom_ports_tcp_nmap.xml" {address}', blocking=False)
            else:
                return []
        else:
            process, stdout, stderr = await target.execute('nmap {nmap_extra} -A --osscan-guess --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/xml/_quick_tcp_nmap.xml" {address}', blocking=False)
        services = await target.extract_services(stdout)
        await process.wait()
        return services

The "run" method is actually a coroutine, identified by the "async" keyword before the definition. This means it will run asynchronously (i.e. concurrently) with other methods. This method is passed a Target object via the "target" argument in the definition.

This plugin is used for specific port scanning, so it does a quick check to see if there are TCP ports to scan. If there are, it executes a slightly different command than if there are not (by default, it scans the top 1000 TCP ports).

Target objects have an "execute" method which can be used to execute commands on the underlying OS. As this method is asynchronous, it must be awaited using the "await" keyword. This method returns three things: a Process object, a custom CommandStreamReader which reads standard output, and a custom CommandStreamReader which reads standard error.

For this example, you do not need to worry about the Process object or stderr. Instead, you can use the "extract_services" method of the Target object to generate a list of Service objects by giving it the "stdout" CommandStreamReader. The final line simply returns this list of Service objects back to AutoRecon for further processing.

ServiceScan Plugin

The following is an example ServiceScan plugin which performs a simple Curl request to a website:

from autorecon import ServiceScan

class Curl(ServiceScan):

    def __init__(self):
        super().__init__()
        self.name = "Curl"
        self.tags = ['default', 'safe', 'http']

    def configure(self):
        self.add_option("path", default="/", help="The path on the web server to curl. Default: %(default)s")
        self.match_service_name('^http')
        self.match_service_name('^nacn_http$', negative_match=True)
        self.add_pattern('(?i)Powered by [^\n]+')

    async def run(self, service):
        if service.protocol == 'tcp':
            await service.execute('curl -sSik {http_scheme}://{addressv6}:{port}' + self.get_option('path'), outfile='{protocol}_{port}_{http_scheme}_curl.html')

Here is a breakdown:

from autorecon import ServiceScan

This simply imports the ServiceScan class from AutoRecon, something that is required to write a valid ServiceScan plugin.

class Curl(ServiceScan):

    def __init__(self):
        super().__init__()
        self.name = "Curl"
        self.tags = ['default', 'safe', 'http']

Each plugin is defined as a class. If you are familiar with object-oriented programming, you'll understand this. If not, just know that a class name ("Curl" in this case) has to be unique. The parentheses after the class name tell AutoRecon that this is a ServiceScan plugin.

Every plugin has a number of methods / functions that it must define. The first is the init method, which must call super().__init__() before anything else.

The last two lines define attributes of the plugin. Technically, only self.name is required. This is the name which AutoRecon will use when referring to the plugin in its output, and so should ideally be kept short. It should also be unique, but does not have to be the same as the class name.

self.tags defines a list of tags that the plugin belongs to. By default, all plugins are tagged as "default" only, meaning the plugin will run if no tags are specified on the command line. If you override the tags list, you should include the "default" tag if you want the plugin to run by default.

    def configure(self):
        self.add_option("path", default="/", help="The path on the web server to curl. Default: %(default)s")
        self.match_service_name('^http')
        self.match_service_name('^nacn_http$', negative_match=True)
        self.add_pattern('(?i)Powered by [^\n]+')

Plugins can have a configure(self) method if they need to configure something with AutoRecon. In the case of ServiceScan plugins, the configure method is mandatory, as plugins must at the very least add a service name to match against.

The self.add_option line configures a user option called "path" which defaults to "/" (the webroot). Users can change this at runtime using the command-line option --curl.path or by setting the following in their config file:

[curl]
path = "/newpath"

Many different types of options can be set. Check out the API documentation for more details.

The next two lines tell AutoRecon to run this plugin if it matches the regular expression '^http', and to not run it if it matches the regular expression '^nacn_http$'.

The final line adds a pattern which AutoRecon will attempt to match against output from this plugin only. It attempts to match HTTP response headers that disclose the language the website is using.

    async def run(self, service):
        if service.protocol == 'tcp':
            await service.execute('curl -sSik {http_scheme}://{addressv6}:{port}' + self.get_option('path'), outfile='{protocol}_{port}_{http_scheme}_curl.html')

The "run" method is actually a coroutine, identified by the "async" keyword before the definition. This means it will run asynchronously (i.e. concurrently) with other methods. This method is passed a Service object via the "service" argument in the definition.

Since the curl command (which this plugin runs) does not work against UDP, there is a quick check to confirm the protocol of the service is TCP.

Service objects have an "execute" method which can be used to execute commands on the underlying OS. As this method is asynchronous, it must be awaited using the "await" keyword. This method returns three things: a Process object, a custom CommandStreamReader which reads standard output, and a custom CommandStreamReader which reads standard error.

For this example, and for most ServiceScan plugins, you do not need to worry about any of these returned values.

Clone this wiki locally