Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
afeena committed Jan 30, 2018
2 parents c0e892d + 0f28fb4 commit d55cdda
Show file tree
Hide file tree
Showing 47 changed files with 513 additions and 336 deletions.
5 changes: 2 additions & 3 deletions bin/tanner
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/python3.5
import argparse
import os
import tanner

from tanner.config import TannerConfig
from tanner import server
from tanner.utils import logger
Expand All @@ -26,7 +25,7 @@ def main():
logger.Logger.create_logger(debug_log_file_name, error_log_file_name, __package__)
print("Debug logs will be stored in", debug_log_file_name)
print("Error logs will be stored in", error_log_file_name)
if TannerConfig.get('LOCALLOG', 'enabled') == True:
if TannerConfig.get('LOCALLOG', 'enabled') is True:
print("Data logs will be stored in", TannerConfig.get('LOCALLOG', 'PATH'))
tanner = server.TannerServer()
tanner.start()
Expand Down
5 changes: 4 additions & 1 deletion bin/tannerapi
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/python3.5
import argparse

from tanner.api import server
from tanner.config import TannerConfig
import argparse


def main():
parser = argparse.ArgumentParser()
Expand All @@ -13,5 +15,6 @@ def main():
api = server.ApiServer()
api.start()


if __name__ == "__main__":
main()
4 changes: 3 additions & 1 deletion bin/tannerweb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/python3.5
import argparse

from tanner.web import server
from tanner.config import TannerConfig
import argparse


def main():
Expand All @@ -14,5 +15,6 @@ def main():
tannerweb = server.TannerWebServer()
tannerweb.start()


if __name__ == "__main__":
main()
9 changes: 7 additions & 2 deletions docs/source/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ There are 8 different sections :
:port: The port at which Tanner Web UI is running
* **API**

:host: The host at which Tanner API is running
:port: The port at which Tanner API is running
:Host: The host at which Tanner API is running
:Port: The port at which Tanner API is running
* **PHPOX**

:Host: The host at which PHPOX is running
:Port: The port at which PHPOX is running
* **REDIS**

:host: The host address at which redis is running
Expand Down Expand Up @@ -78,6 +82,7 @@ If no file is specified, following json will be used as default:
'TANNER': {'host': '0.0.0.0', 'port': 8090},
'WEB': {'host': '0.0.0.0', 'port': 8091},
'API': {'host': '0.0.0.0', 'port': 8092},
'PHPOX': {'host': '0.0.0.0', 'port': 8088},
'REDIS': {'host': 'localhost', 'port': 6379, 'poolsize': 80, 'timeout': 1},
'EMULATORS': {'root_dir': '/opt/tanner'},
'EMULATOR_ENABLED': {'sqli': 'True', 'rfi': 'True', 'lfi': 'True', 'xss': 'True', 'cmd_exec': 'True'},
Expand Down
12 changes: 12 additions & 0 deletions docs/source/emulators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,23 @@ It emulates `Command Execution`_ vulnerability. This attack is detected with pat
* The ``command`` is executed in a docker container safely.
* Results from container is injected into the index page.

PHP Code Injection Emulator
~~~~~~~~~~~~~~~~~~~~~~~~~~~
It emulates `PHP code injection`_ vuln. Usually, this type of vuln is found where user input is directly passed to
functions like eval, assert. To mimic the functionality, user input is converted to the following code
``<?php eval('$a = user_input'); ?>`` and then passed to phpox to get php code emulation results.

CRLF Emulator
~~~~~~~~~~~~~
It emulates `CRLF`_ vuln. The attack is detected using ``\r\n`` pattern in the input. The parameter which looks suspicious
is injected as a header with parameter name as header name and param value as header value.

.. _RFI: https://en.wikipedia.org/wiki/File_inclusion_vulnerability#Remote_File_Inclusion
.. _PHPox: https://github.com/mushorg/phpox
.. _LFI: https://en.wikipedia.org/wiki/File_inclusion_vulnerability#Local_File_Inclusion
.. _XSS: https://en.wikipedia.org/wiki/Cross-site_scripting
.. _SQL injection: https://en.wikipedia.org/wiki/SQL_injection
.. _Command Execution: https://www.owasp.org/index.php/Command_Injection
.. _PHP Code Injection: https://www.owasp.org/index.php/Code_Injection
.. _CRLF: https://www.owasp.org/index.php/CRLF_Injection
.. _manual: https://github.com/client9/libinjection/wiki/doc-sqli-python
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from distutils.core import setup

setup(name='Tanner',
version='0.4.0',
version='0.5.0',
description='He who flays the hide',
author='MushMush Foundation',
author_email='[email protected]',
Expand Down
2 changes: 1 addition & 1 deletion tanner/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.4.0'
__version__ = '0.5.0'
34 changes: 17 additions & 17 deletions tanner/api/api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import json
import logging
import operator
import asyncio
import asyncio_redis


class Api:
def __init__(self, redis_client):
self.logger = logging.getLogger('tanner.api.Api')
Expand All @@ -26,11 +26,11 @@ async def return_snare_stats(self, snare_uuid):

result['total_sessions'] = len(sessions)
result['total_duration'] = 0
result['attack_frequency'] = {'sqli' : 0,
'lfi' : 0,
'xss' : 0,
'rfi' : 0,
'cmd_exec' : 0}
result['attack_frequency'] = {'sqli': 0,
'lfi': 0,
'xss': 0,
'rfi': 0,
'cmd_exec': 0}

for sess in sessions:
result['total_duration'] += sess['end_time'] - sess['start_time']
Expand All @@ -53,7 +53,7 @@ async def return_snare_info(self, uuid, count=-1):
query_res[i] = json.loads(val)
return query_res

async def return_session_info(self, sess_uuid, snare_uuid= None):
async def return_session_info(self, sess_uuid, snare_uuid=None):
query_res = []
if snare_uuid:
snare_uuids = [snare_uuid]
Expand Down Expand Up @@ -82,25 +82,25 @@ async def return_sessions(self, filters):
match_count = 0
for filter_name, filter_value in filters.items():
try:
if(self.apply_filter(filter_name, filter_value, sess)):
if (self.apply_filter(filter_name, filter_value, sess)):
match_count += 1
except KeyError:
return 'Invalid filter : %s' % filter_name

if match_count == len(filters):
matching_sessions.append(sess)
matching_sessions.append(sess)

return matching_sessions

def apply_filter(self, filter_name, filter_value, sess):
available_filters = {'user_agent' : operator.contains,
'peer_ip' : operator.eq,
'attack_types' : operator.contains,
'possible_owners' : operator.contains,
'start_time' : operator.le,
available_filters = {'user_agent': operator.contains,
'peer_ip': operator.eq,
'attack_types': operator.contains,
'possible_owners': operator.contains,
'start_time': operator.le,
'end_time': operator.ge,
'snare_uuid' : operator.eq
}
'snare_uuid': operator.eq
}

try:
if available_filters[filter_name] is operator.contains:
Expand Down
6 changes: 4 additions & 2 deletions tanner/api/server.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import asyncio
import logging

from tanner.api import api
from aiohttp import web

from tanner.api import api
from tanner import redis_client
from tanner.config import TannerConfig


class ApiServer:
def __init__(self):
self.logger = logging.getLogger('tanner.api.ApiServer')
Expand Down Expand Up @@ -93,4 +95,4 @@ def start(self):
app = self.create_app(loop)
host = TannerConfig.get('API', 'host')
port = int(TannerConfig.get('API', 'port'))
web.run_app(app, host=host, port=port)
web.run_app(app, host=host, port=port)
45 changes: 38 additions & 7 deletions tanner/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
import sys

LOGGER = logging.getLogger(__name__)

config_template = {'DATA': {'db_config': '/opt/tanner/db/db_config.json', 'dorks': '/opt/tanner/data/dorks.pickle',
'user_dorks': '/opt/tanner/data/user_dorks.pickle'},
'TANNER': {'host': '0.0.0.0', 'port': 8090},
'WEB': {'host': '0.0.0.0', 'port': 8091},
'API': {'host': '0.0.0.0', 'port': 8092},
'PHPOX': {'host': '0.0.0.0', 'port': 8088},
'REDIS': {'host': 'localhost', 'port': 6379, 'poolsize': 80, 'timeout': 1},
'EMULATORS': {'root_dir': '/opt/tanner'},
'EMULATOR_ENABLED': {'sqli': True, 'rfi': True, 'lfi': True, 'xss': True, 'cmd_exec': True},
'SQLI': {'type':'SQLITE', 'db_name': 'tanner_db', 'host':'localhost', 'user':'root', 'password':'user_pass'},
'EMULATOR_ENABLED': {'sqli': True, 'rfi': True, 'lfi': True, 'xss': True, 'cmd_exec': True,
'php_code_injection': True, "crlf": True},
'SQLI': {'type': 'SQLITE', 'db_name': 'tanner_db', 'host': 'localhost', 'user': 'root',
'password': 'user_pass'},
'DOCKER': {'host_image': 'busybox:latest'},
'LOGGER': {'log_debug': '/opt/tanner/tanner.log', 'log_err': '/opt/tanner/tanner.err'},
'MONGO': {'enabled': False, 'URI': 'mongodb://localhost'},
'HPFEEDS': {'enabled': False, 'HOST': 'localhost', 'PORT': 10000, 'IDENT': '', 'SECRET': '', 'CHANNEL': 'tanner.events'},
'HPFEEDS': {'enabled': False, 'HOST': 'localhost', 'PORT': 10000, 'IDENT': '', 'SECRET': '',
'CHANNEL': 'tanner.events'},
'LOCALLOG': {'enabled': False, 'PATH': '/tmp/tanner_report.json'},
'CLEANLOG': {'enabled': False}
}
Expand All @@ -37,13 +42,39 @@ def set_config(config_path):

@staticmethod
def get(section, value):
res = None
if TannerConfig.config is not None:
try:
convert_type = type(config_template[section][value])
res = convert_type(TannerConfig.config.get(section, value))
convert_type = type(config_template[section][value])
if convert_type is bool:
res = TannerConfig.config.getboolean(section, value)
else:
res = convert_type(TannerConfig.config.get(section, value))
except (configparser.NoOptionError, configparser.NoSectionError):
LOGGER.warning("Error in config, default value will be used. Section: %s Value: %s", section, value)
res = config_template[section][value]
return res

else:
res = config_template[section][value]
return res

@staticmethod
def get_section(section):
res = {}
if TannerConfig.config is not None:
try:
sec = TannerConfig.config[section]
for k, v in sec.items():
convert_type = type(config_template[section][k])
if convert_type is bool:
res[k] = TannerConfig.config[section].getboolean(k)
else:
res[k] = convert_type(v)
except (configparser.NoOptionError, configparser.NoSectionError):
LOGGER.warning("Error in config, default value will be used. Section: %s Value: %s", section)
res = config_template[section]

else:
return config_template[section][value]
res = config_template[section]

return res
67 changes: 41 additions & 26 deletions tanner/emulators/base.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
import asyncio
import mimetypes
import re
import urllib.parse
import yarl

from tanner import __version__ as tanner_version
from tanner.config import TannerConfig
from tanner.emulators import lfi, rfi, sqli, xss, cmd_exec
from tanner.emulators import lfi, rfi, sqli, xss, cmd_exec, php_code_injection, crlf
from tanner.utils import patterns


class BaseHandler:
def __init__(self, base_dir, db_name, loop=None):
self.emulator_enabled = TannerConfig.get_section('EMULATOR_ENABLED')

self.emulators = {
'rfi': rfi.RfiEmulator(base_dir, loop) if TannerConfig.get('EMULATOR_ENABLED', 'rfi') else None,
'lfi': lfi.LfiEmulator() if TannerConfig.get('EMULATOR_ENABLED', 'lfi') else None,
'xss': xss.XssEmulator() if TannerConfig.get('EMULATOR_ENABLED', 'xss') else None,
'sqli': sqli.SqliEmulator(db_name, base_dir) if TannerConfig.get('EMULATOR_ENABLED', 'sqli') else None,
'cmd_exec': cmd_exec.CmdExecEmulator() if TannerConfig.get('EMULATOR_ENABLED', 'cmd_exec') else None
}
self.get_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'cmd_exec']
self.post_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'cmd_exec']
'rfi': rfi.RfiEmulator(base_dir, loop) if self.emulator_enabled['rfi'] else None,
'lfi': lfi.LfiEmulator() if self.emulator_enabled['lfi'] else None,
'xss': xss.XssEmulator() if self.emulator_enabled['xss'] else None,
'sqli': sqli.SqliEmulator(db_name, base_dir) if self.emulator_enabled['sqli'] else None,
'cmd_exec': cmd_exec.CmdExecEmulator() if self.emulator_enabled['cmd_exec'] else None,
'php_code_injection': php_code_injection.PHPCodeInjection(loop) if self.emulator_enabled[
'php_code_injection'] else None,
'crlf': crlf.CRLFEmulator() if self.emulator_enabled['crlf'] else None
}

self.get_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'cmd_exec', 'crlf']
self.post_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'cmd_exec', 'crlf']
self.cookie_emulators = ['sqli']

def extract_get_data(self, path):
Expand Down Expand Up @@ -47,17 +54,18 @@ async def get_emulation_result(self, session, data, target_emulators):
for param_id, param_value in data.items():
for emulator in target_emulators:
if TannerConfig.get('EMULATOR_ENABLED', emulator):
possible_detection = self.emulators[emulator].scan(param_value) if param_value else None
if possible_detection:
if detection['order'] < possible_detection['order']:
detection = possible_detection
if emulator not in attack_params:
attack_params[emulator] = []
attack_params[emulator].append(dict(id=param_id, value=param_value))
possible_detection = self.emulators[emulator].scan(param_value) if param_value else None
if possible_detection:
if detection['order'] < possible_detection['order']:
detection = possible_detection
if emulator not in attack_params:
attack_params[emulator] = []
attack_params[emulator].append(dict(id=param_id, value=param_value))

if detection['name'] in self.emulators:
emulation_result = await self.emulators[detection['name']].handle(attack_params[detection['name']], session)
detection['payload'] = emulation_result
if emulation_result:
detection['payload'] = emulation_result

return detection

Expand All @@ -84,11 +92,11 @@ async def handle_get(self, session, data):
detection = {'name': 'index', 'order': 1}
# check attacks against get parameters
possible_get_detection = await self.get_emulation_result(session, get_data, self.get_emulators)
if possible_get_detection and detection['order'] < possible_get_detection['order'] :
if possible_get_detection and detection['order'] < possible_get_detection['order']:
detection = possible_get_detection
# check attacks against cookie values
possible_cookie_detection = await self.handle_cookies(session, data)
if possible_cookie_detection and detection['order'] < possible_cookie_detection['order'] :
if possible_cookie_detection and detection['order'] < possible_cookie_detection['order']:
detection = possible_cookie_detection

return detection
Expand All @@ -109,12 +117,19 @@ async def emulate(self, data, session):
else:
detection = await self.handle_get(session, data)

if 'payload' in detection and type(detection['payload']) is dict:
injectable_page = self.set_injectable_page(session)
if injectable_page is None:
injectable_page = '/index.html'
detection['payload']['page'] = injectable_page

if 'payload' not in detection:
detection['type'] = 1
elif 'payload' in detection:
if 'status_code' not in detection['payload']:
detection['type'] = 2
if detection['payload']['page']:
injectable_page = self.set_injectable_page(session)
if injectable_page is None:
injectable_page = '/index.html'
detection['payload']['page'] = injectable_page
else:
detection['type'] = 3
detection['version'] = tanner_version
return detection

async def handle(self, data, session):
Expand Down
Loading

0 comments on commit d55cdda

Please sign in to comment.