Skip to content

Developers

Ali Razmjoo edited this page Jan 28, 2021 · 52 revisions

We gladly support and appreciate anyone is interested to contribute to the OWASP Nettacker Project. Overall developers may focus on developing core framework, modules or payloads, language libraries and media. After reading this document you should be able to get the basic knowledge to start developing. Please consider that we are using PEP8 python code style and using Codacy to figure the code quality. In addition, GitHub Actions will check your PR automatically on several Python versions (2.x, 3.x). Before sending your PR, make sure you added code-based documentation to your codes and read the PR template. If you use any code/library/module with a license, add the license into external license file.


Contribution Guidelines

These are the guidelines you need to keep in mind while contributing:

  • The code must have been thoroughly tested in your local development environment.
  • The code must be both python 2 and 3 compatible.
  • The code must follow the PEP8 styling guidelines with a 4 spaces indentation.
  • Each pull request should have only one commit related to each update.
  • Please open different pull requests for individual updates.
  • The commit messages should be meaningful.
  • Be sure to add the concerned documentation for the added feature.
  • The branch in the pull request must be up-to-date with the master of Upstream.
  • Please follow the clean code guidelines 1 and 2.

For any doubts regarding the guidelines please contact the project leaders.

Roadmap

Developers always could be aware of the OWASP Nettacker project roadmap by checking

To contribute OWASP Nettacker. Existing Issues, Tasks, Code Style Issues are great opportunity to start developing the OWASP Nettacker.

Creating Media

We appreciated all kind of media to demonstrate the OWASP Nettacker in any language and environment. It is a great activity to help us grow our framework and get more publicity. Currently, we collected a few media on Media page. Feel free to post your Media on this page.

Contribute to Language Libraries

OWASP Nettacker is using multi-language libraries (default English) to create a better user experience. Currently we are supporting Greek/el, French/fr, English/en, Dutch/nl, Pashto/ps, Turkish/tr, German/de, Korean/ko, Italian/it, Japanese/ja, Persian/fa, Armenian/hy, Arabic/ar, Chinese(Simplified)/zh-cn, Vietnamese/vi, Russian/ru, Hindi/hi, Urdu/ur, Indonesian/id, Spanish/es, Hebrew/iw) languages. If you are an expert in one these languages, It would be a great favor to contribute to one of these. If any language you want to contribute is not listed, feel free to follow the below steps to add it.

The first thing you should know is language libraries located in lib/language. When the user tries to select a language, the framework will look into that lib directory to list the existing languages, and selecting message from those libraries, if the framework could not find the messages in the specified language, English will automatically select by the framework. The naming structure for the messaging libraries is messages + "_" + language short name + ".py" e.g. messages_en.py.

Add a New Language Library

In some cases language library does not exist, you can create a new file and add it to the framework.

  • 1- Goto lib/language
  • 2- Name your message library e.g. messages_fa.py
  • 3- Copy the default structure into the file.
#!/usr/bin/env python
# -*- coding: utf-8 -*-


def all_messages():
    """
    keep all messages in fa
    Returns:
        all messages in JSON
    """
    return \
        {}
  • 4- start adding the messages from English library.
def all_messages():
    """
    keep all messages in fa
    Returns:
        all messages in JSON
    """
    return \
        {
            "scan_started": "انجین Nettacker اجرا شد ...\n\n",
            "options": "python nettacker.py [گزینه ها]",
            "help_menu": "Show Nettacker Help Menu"
            ...
    }
  • 5- Please notice that you should not change the key value like scan_started, options and etc. you just need to modify the Values.

Modify/Update Language Libraries

To contribute the existing libraries, You may go to lib/language select the file you want to contribute and

  • 1- Translate English messages to the selected language.
  • 2- Compare the language library with English library and add new messages to this library and translate them.
  • 3- Modify the translated messages to better translations.

Payloads

There are a few payloads now available in OWASP Nettacker library that would help you to use them in specific modules. Each payload has its own usage and completely separated from other ones! You can see the available payloads* and their usages in the lib/payload.

  • Example
In [1]: from lib.payload.shellcode.generator.linux_x86.system.engine import start

In [2]: from lib.payload.shellcode.encoder.linux_x86.system.add_random.engine import start as encode

In [3]: from lib.payload.shellcode.opcoder.linux_x86.engine import convert

In [4]: print(start("ls -la"))
push   $0xb
pop    %eax
cltd
push   %edx
push $0x616c9090
pop %ecx
shr    $0x10,%ecx
push %ecx

push $0x2d20736c

mov    %esp,%esi
push   %edx
push   $0x632d9090
pop    %ecx
shr    $0x10,%ecx
push   %ecx
mov    %esp,%ecx
push   %edx
push   $0x68
push   $0x7361622f
push   $0x6e69622f
mov    %esp,%ebx
push   %edx
push   %edi
push   %esi
push   %ecx
push   %ebx
mov    %esp,%ecx
int    $0x80

In [5]: print(encode(start("ls -la")))
xor %edx,%edx

push   %edx

push $0x71304b6f
pop %ebx
push $0xfc3badf
pop %eax
neg %eax
add %ebx,%eax
push %eax

pop %ecx
shr    $0x10,%ecx
push %ecx


push $0x70383849
pop %ebx
push $0x4317c4dd
pop %eax
neg %eax
add %ebx,%eax
push %eax


mov    %esp,%esi
push   %edx

push $0x69536879
pop %ebx
push $0x625d7e9
pop %eax
neg %eax
add %ebx,%eax
push %eax

pop    %ecx
shr    $0x10,%ecx
push   %ecx
mov    %esp,%ecx
push   %edx
push   $0x68

push $0x7944454c
pop %ebx
push $0x5e2e31d
pop %eax
neg %eax
add %ebx,%eax
push %eax


push $0x79377630
pop %ebx
push $0xace1401
pop %eax
neg %eax
add %ebx,%eax
push %eax

mov    %esp,%ebx
push   %edx
push   %edi
push   %esi
push   %ecx
push   %ebx
mov    %esp,%ecx
push $0x9
pop %eax
add $0x02,%eax

cltd

int    $0x80

In [6]: print(convert(start("ls -la")))
\x6a\x0b\x58\x99\x52\x68\x90\x90\x6c\x61\x59\xc1\xe9\x10\x51\x68\x6c\x73\x20\x2d\x89\xe6\x52\x68\x90\x90\x2d\x63\x59\xc1\xe9\x10\x51\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x57\x56\x51\x53\x89\xe1\xcd\x80

In [7]: print(convert(encode(start("ls -la"))))
\x31\xd2\x52\x68\x59\x5a\x55\x6d\x5b\x68\xc9\xc9\xe8\x0b\x58\xf7\xd8\x01\xd8\x50\x59\xc1\xe9\x10\x51\x68\x53\x38\x67\x6a\x5b\x68\xe7\xc4\x46\x3d\x58\xf7\xd8\x01\xd8\x50\x89\xe6\x52\x68\x44\x7a\x33\x7a\x5b\x68\xb4\xe9\x05\x17\x58\xf7\xd8\x01\xd8\x50\x59\xc1\xe9\x10\x51\x89\xe1\x52\x6a\x68\x68\x67\x59\x67\x75\x5b\x68\x38\xf7\x05\x02\x58\xf7\xd8\x01\xd8\x50\x68\x34\x53\x52\x76\x5b\x68\x05\xf1\xe8\x07\x58\xf7\xd8\x01\xd8\x50\x89\xe3\x52\x57\x56\x51\x53\x89\xe1\x6a\x0a\x58\x83\xc0\x01\x99\xcd\x80

In [8]:

Contribute to Payloads

If you are interested to develop the existing payload, Feel free to send your PR. Please consider that to update the usages and readme files.

Add New Payload

As well, you can contribute your idea to create a new payload. Payloads must contain readme.md file to demonstrate the usage and purposes. Remember, you must add __init__.py for each subdirectory you create!

How to Add New Modules

Modules can be separated into 4 categories, brute, scan, vuln and graph. Currently, we are not focusing on the development of the graph module.

Let's start with analyzing a scan module dir_scan to see that if you intend to add a scan type module, what all you have to do?

  • First of all, you need to go to lib/scan and create a new directory and name it as your module name. For e.g. If your module name is dir_scan, you must name the directory as dir. The next step is to create the __init__.py and engine.py files. _scan will be added at the end of your module name automatically while running the framework.

Engine Modules Structure

  • Any type of module (vuln, scan or brute), must have the start and extra_requirements_dict functions. The start function is the main function of the modules and extra_requirements_dict defines the module inputs which are to be taken from the users through the command line.

Extra Requirements Dict

  • extra_requirements_dict function must return a JSON, (return an empty JSON if your module does not have any options).
  • The name of the keys in JSON must start with your module name, so if your module name is dir_scan your key names should be dir_scan_keys.
  • All keys must have array values even if they have one value.
def extra_requirements_dict():
    return {
        "dir_scan_ports": ["80"],
        "dir_scan_http_method": ["GET"],
        "dir_scan_random_agent": ["True"],
        "dir_scan_list": ["~adm", "~admin", "~administrator", "~amanda", "~apache", "~bin", "~ftp", "~guest", "~http",
                          "~httpd", "~log", "~logs", "~lp", "~mail", "~nobody", "~operator", "~root", "~sys", "~sysadm",
                          "~sysadmin", "~test", "~tmp", "~user", "~webmaster", "~www", "wp-admin", "wp-login.php",
                          "administrator", "~backup", "backup.sql", "database.sql", "backup.zip", "backup.tar.gz",
                          "backup", "backup-db", "mysql.sql", "phpmyadmin", "admin", "administrator", "server-status",
                          "server-info", "info.php", "php.php", "info.php", "phpinfo.php", "test.php", ".git",
                          ".htaccess", ".htaccess.old", ".htaccess.save", ".htaccess.txt", ".php-ini", "php-ini",
                          "FCKeditor", "FCK", "editor", "Desktop.ini", "INSTALL", "install", "install.php", "update",
                          "upgrade", "upgrade.php", "update.php", "LICENSE", "LICENSE.txt", "Server.php", "WS_FTP.LOG",
                          "WS_FTP.ini", "WS_FTP.log", "Web.config", "Webalizer", "webalizer", "config.php",
                          "config.php.new", "config.php~", "controlpanel", "cpanel", "favicon.ico", "old", "php-error",
                          "php.ini~", "php.ini", "php.log", "robots.txt", "security", "webdav", "1"]
    }
  • A module with no options should be like this
def extra_requirements_dict():
    return {}

Entry Point

  • Let's go check the start function. This function is the first (and last) function that the core framework would call (passing information to it). I will now explain the use
  • Note: users, passwds will be passed to the module in an array or None` type!
def start(target, users, passwds, ports, timeout_sec, thread_number, num, total, log_in_file, time_sleep, language,
          verbose_level, show_version, check_update, socks_proxy, retries, ping_flag, methods_args, scan_id,
          scan_cmd):  # Main function

Log in Database or File

Generally, you must use __log_in_file function which located in core/log.py.

def __log_into_file(filename, mode, data, language, final=False):
    """
    write a content into a file (support unicode) and submit logs in database. if final=False its writing log in
    the database.
    Args:
        filename: the filename
        mode: writing mode (a, ab, w, wb, etc.)
        data: content
        language: language
        final: True if it's final report otherwise False (default False)
    Returns:
        True if success otherwise None
    """
  • After calling the start function, it depends on your module, the operations your module performs on that data. But you MUST log your result by saving it in the log_in_file with the defined structure.
  • Structure for log_in_file is JSON with
{"USERNAME": "", "CATEGORY": "scan", "HOST": "z3r0d4y.com", "SCAN_ID": "000882204a189bf07d95b3e941c9e848", "DESCRIPTION": "http://z3r0d4y.com:80/robots.txt found! (200:OK)", "SCAN_CMD": "nettacker.py -i z3r0d4y.com -m dir_scan -o f.json", "TIME": "2018-01-15 14:02:50", "PASSWORD": "", "TYPE": "dir_scan", "PORT": 80}
  • HOST is the target, but if the target_type is HTTP, you must convert it into DOMAIN or IP type by using target_to_host function.
  • USERNAME in non-brute modules is None so leave it empty. 'USERNAME': '', and same is the case for PASSWORD.
  • PORT must be integer, or if it doesn't really matter (like viewdns_reverse_ip_scan module), then leave it empty 'PORT': ''.
  • TYPE is your module_name.
  • DESCRIPTION must contain a description of the event. (For example file found, or port is open).
  • TIME is the time at whoch the event happened. You can use now function in module.
  • CATEGORY is the category of your module.
  • SCAN_ID and SCAN_CMD are passed in the start function!. You can use them directly as variables (scan_id, scan_cmd).
  • DO NOT FORGET TO PUT \n (new line) at the end of event!
  • if verbose_level is not 0 and you didn't have any result/event, at the end you may write a single message to convey that you didn't find anything! (in the description) and leave the port empty.

Database Event

If you writing an event, you must use __log_into_file same as below example.

from core.alert import *
from core.targets import target_type
from core.targets import target_to_host
from core._time import now
from core.log import __log_into_file

data = json.dumps(
                    {'HOST': host, 'USERNAME': '', 'PASSWORD': '', 'PORT': port, 'TYPE': 'port_scan',
                     'DESCRIPTION': messages(language, "port/type").format(port, "STEALTH"), 'TIME': now(),
                     'CATEGORY': "scan", 'SCAN_ID': scan_id,
                     'SCAN_CMD': scan_cmd}) + '\n'
__log_into_file(log_in_file, 'a', data, language)

File IO

If you want write something into a file, You must use __log_into_file with final=True input same as below example.

from core.alert import *
from core.targets import target_type
from core.targets import target_to_host
from core._time import now
from core.log import __log_into_file

data = "Some Data"
__log_into_file(log_in_file, 'a', data, language, final=True)

Multi-threaded Module

  • Some of the modules do not need to be multi-threaded (e.g. viewdns_reverse_ip_tool_scan) but many need to be! You can see an example of a multi-threaded module in dir_scan or other modules, and use their pattern.
        for port in ports:
            port = int(port)
            if target_type(target) == 'SINGLE_IPv4' or target_type(target) == 'DOMAIN':
                url = 'http://{0}:{1}/'.format(target, str(port))
            else:
                if target.count(':') > 1:
                    error(messages(language, 105))
                    from core.color import finish
                    finish()
                    sys.exit(1)
                http = target.rsplit('://')[0]
                host = target_to_host(target)
                path = "/".join(target.replace('http://', '').replace('https://', '').rsplit('/')[1:])
                url = http + '://' + host + ':' + str(port) + '/' + path
            if test(url, retries, timeout_sec, user_agent, extra_requirements["dir_scan_http_method"][0],
                    socks_proxy, verbose_level, trying, total_req, total, num, port, language) is 0:
                for idir in extra_requirements["dir_scan_list"]:
                    # check target type
                    if target_type(target) == 'SINGLE_IPv4' or target_type(target) == 'DOMAIN':
                        url = 'http://{0}:{1}/{2}'.format(target, str(port), idir)
                    else:
                        http = target.rsplit('://')[0]
                        host = target_to_host(target)
                        path = "/".join(target.replace('http://', '').replace('https://', '').rsplit('/')[1:])
                        url = http + '://' + host + ':' + str(port) + '/' + path + '/' + idir

                    if random_agent_flag is True:
                        user_agent = {'User-agent': random.choice(user_agent_list)}
                    t = threading.Thread(target=check,
                                         args=(url, user_agent, timeout_sec, log_in_file, language, time_sleep,
                                               thread_tmp_filename, retries,
                                               extra_requirements["dir_scan_http_method"][0], socks_proxy, scan_id,
                                               scan_cmd))
                    threads.append(t)
                    t.start()
                    trying += 1
                    if verbose_level is not 0:
                        info(messages(language, 72).format(trying, total_req, num, total, target_to_host(target), port,
                                                           'dir_scan'))
                    while 1:
                        try:
                            if threading.activeCount() >= max:
                                time.sleep(0.01)
                            else:
                                break
                        except KeyboardInterrupt:
                            break
                            break
            else:
                warn(messages(language, 109).format(url))

        # wait for threads
        kill_switch = 0
        kill_time = int(timeout_sec / 0.1) if int(timeout_sec / 0.1) is not 0 else 1
        while 1:
            time.sleep(0.1)
            kill_switch += 1
            try:
                if threading.activeCount() is 1 or kill_switch is kill_time:
                    break
            except KeyboardInterrupt:
                break

Alerts

If you want to print something, you need to use core/alert.py. It has several options. info can be used to print information.

from core.alert import info
info('your information')

output:[+] your information

Please keep in mind that it automatically adds \n to end of your information.

warn can be used for displaying warning messages.

from core.alert import warn
warn('your information')

output:[!] your warning message

Please keep in mind that it automatically adds \n to end of your information.

error can be used for displaying error messages.

from core.alert import error
error('your information')

output: [X] your error message

Please keep in mind that it automatically adds \n to end of your information.

write can be used for any kind of message you want to sys.stdout.write

from core.alert import write
write('your information')

output: your information

Messages

messages (core/alert.py), you may use it to display a message in the selected language. you cannot just print your message directly through the alerts function. messages must be saved in message_[language].py, if messages function cannot find your message in the selected language it will automatically print it in en (English).

from core.alert import info
from core.alert import messages

info(messages("en", "scan_started"))
# in the modules you may use language variable to define the selected language.
# info(messages(language, "scan_started"))

output: [+] Nettacker engine started ...

You must use language variable instead of "en" to replace selected language from user or default input.

Define or Convert the Module Target Type

In the next step, you may check for the target type in start function.

from core.targets import target_type
from core.targets import target_to_host

def start...
    if target_type(target) != 'SINGLE_IPv4' or target_type(target) != 'DOMAIN' or target_type(target) != 'HTTP':
        if target_type(target) == 'HTTP':
            target = target_to_host(target)
    else:
        warn(messages(language, 69).format('dir_scan', target))

For now (16/11/2017) we have a few target types (RANGE_IPv4, SINGLE_IPv4, CIDR_IPv4, DOMAIN, UNKNOW). modules never will access the RANGE_IPv4, CIDR_IPv4, so you do not need to check them.

Defaults

After checking the target type, you may replace the user or default inputs with your default values in extra_requirements_dict(). you may simply do that with a simple for loop.

For e.g.

        if methods_args is not None:
            for extra_requirement in extra_requirements_dict():
                if extra_requirement in methods_args:
                    new_extra_requirements[extra_requirement] = methods_args[extra_requirement]
        extra_requirements = new_extra_requirements

Now you can use start function to use inputs and values, but it's best to check if they are valid or not. We do this in all our modules, here is an example of dir_scan module.

        http_methods = ["GET", "HEAD"]
        if extra_requirements["dir_scan_http_method"][0] not in http_methods:
            warn(messages(language, 110))
            extra_requirements["dir_scan_http_method"] = ["GET"]
        if ports is None:
            ports = extra_requirements["dir_scan_ports"]
        random_agent_flag = True
        if extra_requirements["dir_scan_random_agent"][0] == "False":
            random_agent_flag = False

Please consider that you should NOT interrupt user scan, so if inputs were invalid, just replace it with the corresponding valid default value and warn the user.

Use Socks Proxy for Outgoing Connections

  • You can use this code, right before the connections to use the socks proxy (if enabled) for your connections.
import socket
import socks
from lib.socks_resolver.engine import getaddrinfo
codes...

            if socks_proxy is not None:
                socks_version = socks.SOCKS5 if socks_proxy.startswith('socks5://') else socks.SOCKS4
                socks_proxy = socks_proxy.rsplit('://')[1]
                if '@' in socks_proxy:
                    socks_username = socks_proxy.rsplit(':')[0]
                    socks_password = socks_proxy.rsplit(':')[1].rsplit('@')[0]
                    socks.set_default_proxy(socks_version, str(socks_proxy.rsplit('@')[1].rsplit(':')[0]),
                                            int(socks_proxy.rsplit(':')[-1]), username=socks_username,
                                            password=socks_password)
                    socket.socket = socks.socksocket
                    socket.getaddrinfo = getaddrinfo
                else:
                    socks.set_default_proxy(socks_version, str(socks_proxy.rsplit(':')[0]),
                                            int(socks_proxy.rsplit(':')[1]))
                    socket.socket = socks.socksocket
                    socket.getaddrinfo = getaddrinfo
codes ...

Adding a fuzzing module (OWASP Nettacker HTTP Fuzzer module usage)

Any module that uses HTTP fuzzing techniques needs to use OWASP Nettacker HTTP Fuzzer library to make the requests. You will need to call the __repeater() function from the lib/http_fuzzer/engine file.

The __repeater() function in the http_fuzzer library takes in the following inputs:

  • request_template: the sample template of the request(to be supplied by the module)
  • parameters: the payload in form of [[1,2,3], [1,2,3],...]
  • condition: the condition to be evaluated. eg: response.status_code == 200
  • sample_event: the template for the event that will be logged into the database
  • message: the message that you want to display in the terminal when success
  • counter_message: the message that you want to display if nothing is found
  • target: the target to be attacked
  • ports: the ports to be fuzzed (Supplied by the user)
  • default_ports: if the user doesn't supply ports, these are to be fuzzed by default
  • other args: retries, time_sleep, timeout_sec, thread_number, log_in_file, time_sleep, language, verbose_level, socks_proxy, scan_id, scan_cmd, thread_tmp_filename (These are supplied to the start function of your module automatically)

Calling the __repeater() function:

For fuzzing, you need to call the __repeater() function. This will take the inputs as given above and will evaluate the given condition. These are the variables that can be used to get outputs in the condition.
response: This variable holds the response of the requests library after the request is made
payload: This variable holds the corresponding payload to which the result was true. This is an array with the parameters in the order which you give to the function
You need to keep in mind that all the parameters in the sample request, sample message, sample event will need to be written accordingly so that they may be executed properly. Here is an example of some variables:

  • request_template: Here are some valid examples of request_template:
request_template = """{0} __target_locat_here__{{0}} HTTP/1.1 \nUser-Agent: {1}\n\n""".format(extra_requirements["pma_scan_http_method"][0], user_agent)

This is in case if a pma scan which fuzzes the URL for phpmyadmin panel URLs. Here is how this request template works. At first, {0} is filled with the scan method. Let us suppose this to be GET. Now __target_locat_here__ is filled with the target URL along with ports automatically by the library. After this {{0}} will be reduced to {0} due to use of format. This {0} will be filled corresponding to the elements in the array of 0th position of the parameters array, which in this case is the array of urls. User agent will be filled here and the effective request template sent will be:

request_template = """GET __target_locat_here__{0} HTTP/1.1 \nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.5) Gecko/20060719 Firefox/1.5.0.5\n\n"""

As told earlier, __target_locat_here__ will be filled by the library according to the target and the ports that you supply. {0} will be filled with the elements of parameters[0] array, which is an array like this: [extra_requirements["pma_scan_list"]]. In case of multiple fuzzing parameters, you can append to the parameters array and the library will take care of the rest.

  • parameters This is the next main important part of the fuzzing library. All the arrays of the fuzzing parameters will be passed here and the library will take all the permutations from the avaliable arrays and fuzz the URL for it. Let me give you an example. Let us say, you have a POST request to fuzz usernames and passwords. Naturally, you want to test for all possible combinations of usernames and passwords.
request_template = """POST __target_locat_here__ HTTP/1.1 \nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.5) Gecko/20060719 Firefox/1.5.0.5\n\nusername={0}&password={1}
"""

The corresponding parameter array can be: parameters = [[username1, username2,....],[pass1, pass2....]]. Note that the order is important as the request will be formed from the order given below and the output will also be in the same order.

  • condition This is the most important part of the fuzzing module. After fuzzing, you certainly need to evaluate a condition and according to that condition, you log data into the database. Some conditions may be that if response to any of the request results in a status code with 200 or 403, you need to log a positive event. This is done using the condition argument. You send an executable condition with the following variables:
    • response: This is the response from the requests library
    • payload: This is an array containing the permutation of parameters corresponding to the response Let me give an example. In case of pma_scan, we needed to check if the status code was 200, 401 or 403. This can be done as follows:
condition = "response.status_code in [201, 401, 403]"

condition needs to be a string that can be executed. This demonstrates that we can use the response for the evaluation of the condition. Similarly, you can also use the payload variable if needed in the condition.

  • sample_event The sample event is a dictionary containing the event that needs to be logged into the db in case the condition that you evaluated returns True. Each key of the sample event is evaluated just like the condition and you can use response and payload variable here too. Here is an example from the pma scan:
sample_event = {
            'HOST': target_to_host(target),
            'USERNAME': '',
            'PASSWORD': '',
            'PORT': 'PORT',
            'TYPE': 'pma_scan',
            'DESCRIPTION': "\"" + message + "\"" + """.format(response.url, response.status_code, response.reason)""",
            'TIME': now(),
            'CATEGORY': "scan",
            'SCAN_ID': scan_id,
            'SCAN_CMD': scan_cmd
        }

The 'PORT' key needs to specified as 'PORT' if you want to log ports alongside. In this example, DESCRIPTION will be evaluated to the corresponding value and others will remain the same as they are strings already.

  • message This is the message that will be displayed in the terminal in case of success. You can use response and payload variable here too. An example of this is in the pma scan is:
sample_message = "\"" + message + "\"" + """.format(response.url, response.status_code, response.reason)"""

This is similar to description of the sample event and will be treated the same.

  • counter_message This is the message that will be displayed on the terminal and logged into the db if no condition returns True and the verbose level is greater than 0 Other variables are self explanatory, but if you need any help, feel free to contact me. If you find any issue please open an issue.
  • Note: You don't need to update readme.md or setup.py for your new module, these files will be updated daily or after a release.
  • Note: It's best to check one of the modules, see the structure, edit it and create a new module. Recommended modules: smtp_brute, ssh_brute, tcp_connect_port_scan, subdomain_scan, dir_scan, ftp_brute, viewdns_reverse_ip_scan, subdomain_scan, heartbleed_vuln

Service Scanner

Service scanner is a part of port_scan module now, it sends a junk data to a port and receives all data by socket, then it checks to find signatures from two existing dictionaries, it's useful when user want to find customized services in network. (for instance, ssh port could be 2222 instead of 22).

Contribute to the Service Scanner

There are many services which are unknown yet in this scanner, and we need to find and add the signatures, we always appreciate any contribution to improve this section.

How to Add a Signature to Service Scanner

To add signatures, you may take a look at the conditions in engine.py.

there are three types of conditions, first one is and conditions. it contains an array of conditions, conditions type could be a string or an array. if the condition type is the string, then it must be in the response data, if it's array, one of the strings in the array must be in the response data. for instance:

[True and True [True or False or True] and True]

a real example condition:

ports_services_and_condition = {
    "http": [["HTTP/0.9", "HTTP/1.0", "HTTP/1.1", "HTTP/2.0"]],
    "ftp": ["FTP", ["214", "220", "530", "230", "502", "500"]],
    "ssh": ["SSH"],
    "telnet": ["Telnet"],
    "smtp": ["SMTP", ["220", "554", "250"]],
    "imap": ["IMAP"],
    "mariadb": ["MariaDB"],
    "mysql": ["MySQL"],
}

the second type of condition is or, it contains an array of conditions, conditions type could be a string or an array. if the condition is a string, one of the strings must be in response data or if it's an array, all of the strings in the array must be in the response data. for instance:

[True or True or [True and False and True] or True]

a real example condition:

ports_services_or_condition = {
    "http": ["400 Bad Request", "401 Unauthorized", "302 Found", "Server: cloudflare", "404 Not Found", "HTML", "Content-Length:", "Content-Type:"],
    "ftp": [["Pure-FTPd", "----------\r\n"], "\r\n220-You are user number", ["orks FTP server", "VxWorks VxWorks"],
            "530 USER and PASS required", "Server ready.\r\n5", "Invalid command: try being more creative", "220 Hotspot FTP server (MikroTik 6.27) ready", "220 SHARP MX-M264N Ver 01.05.00.0n.16.U FTP server.",
            "220 Microsoft FTP Service", "220 FTP Server ready.", "220 Microsoft FTP Service", "220 Welcome to virtual FTP service.", "220 DreamHost FTP Server", "220 FRITZ!BoxFonWLAN7360SL(UI) FTP server ready."],
    "ssh": ["-OpenSSH_", "\r\nProtocol mism", "_sshlib GlobalSCAPE\r\n", "\x00\x1aversion info line too long"],
    "telnet": ["Welcome to Microsoft Telnet Service", "no decompiling or reverse-engineering shall be allowed",
               "is not a secure protocol", "recommended to use Stelnet", "Login authentication"],
    "smtp": ["Server ready", "SMTP synchronization error", "220-Greetings", "ESMTP Arnet Email Security", "SMTP 2.0",
             "Fidelix Fx2020"],
    "imap": ["BAD Error in IMAP command received by server", "IMAP4rev1 SASL-IR", "OK [CAPABILITY IMAP4rev1",
             "LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE NAMESPACE AUTH=PLAIN AUTH=LOGIN]",
             "LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN AUTH=LOGIN AUTH=DIGEST-MD5 AUTH=CRAM-MD5]"],
    "mariadb": ["is not allowed to connect to this MariaDB server", "5.5.52-MariaDB", "5.5.5-10.0.34-MariaDB"],
    "mysql": ["is not allowed to connect to this MySQL server"]
}

The third type of condition is regex matching. There are some services that have a distinctive regex pattern. For eg: 5.5.10-MariaDB, 5.5.11-MariaDB, etc can be covered by the regex [\d.]+[\d][\d]-MariaDB. This way you can save time writing a thing over and over again, but please make sure that your regex is valid. It can be confirmed by compiling the regex using python or using an online tool, otherwise it will be ignored!!
a real life example:

ports_services_regex = {
    "http": ["HTTP\/[\d.]+\s+[\d]+", ],  # checks for any pattern of type HTTP/1.0 200 OK, etc.
    "ftp": ["FTP\/[\d.]+\s+[\d]+"],  # similar to above in HTTP
    "ssh": ["SSH-([\d.]+)-OpenSSH_([\w._-]+)[ -]{1,2}Debian[ -_](.*ubuntu.*)", ],
    "mysql": [".\0\0\0\xff..Host .* is not allowed to connect to this MySQL server$", ],
    "mariadb": ["[\d.]+[\d][\d]-MariaDB", ],
}

You can collect the data and find services from shodan.io, or use discovery() on your network and find out which port/service is UNKNOWN. then grab some signatures and add it to the framework.

In [1]: from lib.payload.scanner.service.engine import discovery

In [2]: discovery("127.0.0.1")
Out[2]:
{80: 'http',
 443: 'http/ssl',
 445: 'UNKNOWN',
 902: 'UNKNOWN',
 912: 'UNKNOWN',
 2179: 'UNKNOWN',
 3306: 'mariadb',
 6000: 'UNKNOWN'}
Clone this wiki locally