-
-
Notifications
You must be signed in to change notification settings - Fork 754
Developers
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, Travis-CI will check your PR automatically on several Python versions (2.x, 3.x).
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.
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
.
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.
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.
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]:
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.
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!
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 isdir_scan
, you must name the directory asdir
. The next step is to create the__init__.py
andengine.py
files._scan
will be added at the end of your module name automatically while running the framework.
-
Any type of module (
vuln
,scan
orbrute
), must have thestart
andextra_requirements_dict
functions. Thestart
function is the main function for the modules andextra_requirements_dict
defines the module inputs which are to be taken from the users through the command line. -
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 isdir_scan
your key names should bedir_scan_keys
. -
All keys must have
array
values even if they have one value. -
See an example
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 would look like this
def extra_requirements_dict():
return {}
- 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 anarray 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
- 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 thelog_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 thetarget_type
isHTTP
, you must convert it intoDOMAIN
orIP
type by usingtarget_to_host
function. -
USERNAME
in non-brute modules isNone
so leave it empty.'USERNAME': ''
, and same is the case forPASSWORD
. -
PORT
must be integer, or if it doesn't really matter (likeviewdns_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 usenow
function in module. -
CATEGORY
is the category of your module. -
SCAN_ID
andSCAN_CMD
are passed in thestart
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 not0
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. - Python example
from core.alert import *
from core.targets import target_type
from core.targets import target_to_host
from core._time import now
save = open(log_in_file, 'a')
save.write(json.dumps({'HOST': target_to_host(target), 'USERNAME': '', 'PASSWORD': '',
'PORT': int(target.rsplit(':')[2].rsplit('/')[0]), 'TYPE': 'dir_scan',
'DESCRIPTION': messages(language, 38).format(target, r.status_code, r.reason),
'TIME': now(), 'CATEGORY': "scan", 'SCAN_ID': scan_id, 'SCAN_CMD': scan_cmd}) + '\n')
save.close()
-
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 indir_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
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
(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", 0))
output: [+] Nettacker engine started ...
You must use language
variable instead of "en"
to replace selected language from user or default input.
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.
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.
- 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 ...
- Note: You don't need to update
readme.md
orsetup.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