diff --git a/bin/tanner b/bin/tanner
index a47aea7e..cfd33344 100644
--- a/bin/tanner
+++ b/bin/tanner
@@ -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
@@ -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()
diff --git a/bin/tannerapi b/bin/tannerapi
index 49c143b8..b99320b3 100644
--- a/bin/tannerapi
+++ b/bin/tannerapi
@@ -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()
@@ -13,5 +15,6 @@ def main():
api = server.ApiServer()
api.start()
+
if __name__ == "__main__":
main()
diff --git a/bin/tannerweb b/bin/tannerweb
index 99f494b4..8587095c 100644
--- a/bin/tannerweb
+++ b/bin/tannerweb
@@ -1,7 +1,8 @@
#!/usr/bin/python3.5
+import argparse
+
from tanner.web import server
from tanner.config import TannerConfig
-import argparse
def main():
@@ -14,5 +15,6 @@ def main():
tannerweb = server.TannerWebServer()
tannerweb.start()
+
if __name__ == "__main__":
main()
diff --git a/docs/source/config.rst b/docs/source/config.rst
index a61890ce..e03d2fec 100644
--- a/docs/source/config.rst
+++ b/docs/source/config.rst
@@ -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
@@ -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'},
diff --git a/docs/source/emulators.rst b/docs/source/emulators.rst
index 90bb5da6..18405d99 100644
--- a/docs/source/emulators.rst
+++ b/docs/source/emulators.rst
@@ -82,6 +82,16 @@ 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
+```` 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
@@ -89,4 +99,6 @@ It emulates `Command Execution`_ vulnerability. This attack is detected with pat
.. _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
diff --git a/setup.py b/setup.py
index f0fadee7..7d67010f 100644
--- a/setup.py
+++ b/setup.py
@@ -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='glastopf@public.honeynet.org',
diff --git a/tanner/__init__.py b/tanner/__init__.py
index 222c11cf..2b8877c5 100644
--- a/tanner/__init__.py
+++ b/tanner/__init__.py
@@ -1 +1 @@
-__version__ = '0.4.0'
\ No newline at end of file
+__version__ = '0.5.0'
diff --git a/tanner/api/api.py b/tanner/api/api.py
index 11f2adb0..48ddf0c2 100644
--- a/tanner/api/api.py
+++ b/tanner/api/api.py
@@ -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')
@@ -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']
@@ -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]
@@ -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:
diff --git a/tanner/api/server.py b/tanner/api/server.py
index 9fb1dc20..ddfb6de1 100644
--- a/tanner/api/server.py
+++ b/tanner/api/server.py
@@ -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')
@@ -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)
\ No newline at end of file
+ web.run_app(app, host=host, port=port)
diff --git a/tanner/config.py b/tanner/config.py
index 1b0d0ae2..e69db282 100644
--- a/tanner/config.py
+++ b/tanner/config.py
@@ -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}
}
@@ -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
diff --git a/tanner/emulators/base.py b/tanner/emulators/base.py
index b57a3c47..243e93dd 100644
--- a/tanner/emulators/base.py
+++ b/tanner/emulators/base.py
@@ -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):
@@ -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
@@ -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
@@ -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):
diff --git a/tanner/emulators/cmd_exec.py b/tanner/emulators/cmd_exec.py
index b751ef42..807e0749 100644
--- a/tanner/emulators/cmd_exec.py
+++ b/tanner/emulators/cmd_exec.py
@@ -1,13 +1,11 @@
-import asyncio
-import yarl
-
from tanner.utils import docker_helper
from tanner.utils import patterns
+
class CmdExecEmulator:
def __init__(self):
self.helper = docker_helper.DockerHelper()
-
+
async def create_attacker_env(self, session):
container_name = 'attacker_' + session.sess_uuid.hex
container = await self.helper.create_container(container_name)
@@ -17,16 +15,16 @@ async def create_attacker_env(self, session):
async def get_cmd_exec_results(self, container, cmd):
execute_result = await self.helper.execute_cmd(container, cmd)
- result = dict(value= execute_result, page= '/index.html')
+ result = dict(value=execute_result, page=True)
return result
def scan(self, value):
detection = None
if patterns.CMD_ATTACK.match(value):
- detection = dict(name= 'cmd_exec', order= 3)
+ detection = dict(name='cmd_exec', order=3)
return detection
- async def handle(self, attack_params, session= None):
+ async def handle(self, attack_params, session=None):
container = await self.create_attacker_env(session)
result = await self.get_cmd_exec_results(container, attack_params[0]['value'])
- return result
\ No newline at end of file
+ return result
diff --git a/tanner/emulators/crlf.py b/tanner/emulators/crlf.py
new file mode 100644
index 00000000..3e12111c
--- /dev/null
+++ b/tanner/emulators/crlf.py
@@ -0,0 +1,18 @@
+from tanner.utils import patterns
+
+
+class CRLFEmulator:
+
+ def scan(self, value):
+ detection = None
+ if patterns.CRLF_ATTACK.match(value):
+ detection = dict(name='crlf', order=2)
+ return detection
+
+ def get_crlf_results(self, attack_params):
+ headers = {attack_params[0]['id']: attack_params[0]['value']}
+ return headers
+
+ async def handle(self, attack_params, session):
+ result = self.get_crlf_results(attack_params)
+ return dict(value='', page=True, headers=result)
diff --git a/tanner/emulators/lfi.py b/tanner/emulators/lfi.py
index baba3973..d6b9ebb5 100644
--- a/tanner/emulators/lfi.py
+++ b/tanner/emulators/lfi.py
@@ -1,4 +1,3 @@
-import asyncio
import shlex
from tanner.utils import docker_helper
@@ -10,13 +9,13 @@ def __init__(self):
self.helper = docker_helper.DockerHelper()
async def get_lfi_result(self, container, file_path):
- #Terminate the string with NULL byte
+ # Terminate the string with NULL byte
if '\x00' in file_path:
file_path = file_path[:file_path.find('\x00')]
- cmd = 'cat {file}'.format(file= shlex.quote(file_path))
+ cmd = 'cat {file}'.format(file=shlex.quote(file_path))
execute_result = await self.helper.execute_cmd(container, cmd)
- #Nulls are not printable, so replace it with another line-ender
+ # Nulls are not printable, so replace it with another line-ender
execute_result = execute_result.replace('\x00', '\n')
return execute_result
@@ -28,12 +27,13 @@ async def setup_virtual_env(self):
def scan(self, value):
detection = None
if patterns.LFI_ATTACK.match(value):
- detection = dict(name= 'lfi', order= 2)
+ detection = dict(name='lfi', order=2)
return detection
async def handle(self, attack_params, session=None):
result = None
container = await self.setup_virtual_env()
if container:
- result = await self.get_lfi_result(container, attack_params[0]['value'])
+ lfi_result = await self.get_lfi_result(container, attack_params[0]['value'])
+ result = dict(value=lfi_result, page=False)
return result
diff --git a/tanner/emulators/mysqli.py b/tanner/emulators/mysqli.py
index bf55ca84..1109bab4 100644
--- a/tanner/emulators/mysqli.py
+++ b/tanner/emulators/mysqli.py
@@ -1,39 +1,35 @@
-import asyncio
-
from tanner.utils import mysql_db_helper
-from tanner import config
-
class MySQLIEmulator:
- def __init__(self, db_name):
- self.db_name = db_name
- self.helper = mysql_db_helper.MySQLDBHelper()
+ def __init__(self, db_name):
+ self.db_name = db_name
+ self.helper = mysql_db_helper.MySQLDBHelper()
- async def setup_db(self, query_map):
- db_exists = await self.helper.check_db_exists(self.db_name)
- if not db_exists:
- await self.helper.setup_db_from_config(self.db_name)
- query_map = await self.helper.create_query_map(self.db_name)
- return query_map
+ async def setup_db(self, query_map):
+ db_exists = await self.helper.check_db_exists(self.db_name)
+ if not db_exists:
+ await self.helper.setup_db_from_config(self.db_name)
+ query_map = await self.helper.create_query_map(self.db_name)
+ return query_map
- async def create_attacker_db(self, session):
- attacker_db_name = 'attacker_' + session.sess_uuid.hex
- attacker_db = await self.helper.copy_db(self.db_name,
- attacker_db_name
- )
- session.associate_db(attacker_db)
- return attacker_db
+ async def create_attacker_db(self, session):
+ attacker_db_name = 'attacker_' + session.sess_uuid.hex
+ attacker_db = await self.helper.copy_db(self.db_name,
+ attacker_db_name
+ )
+ session.associate_db(attacker_db)
+ return attacker_db
- async def execute_query(self, query, db_name):
- result = []
- conn = await self.helper.connect_to_db()
- cursor = await conn.cursor()
- await cursor.execute('USE {db_name}'.format(db_name=db_name))
- try:
- await cursor.execute(query)
- rows = await cursor.fetchall()
- for row in rows:
- result.append(list(row))
- except Exception as mysql_error:
- result = str(mysql_error)
- return result
\ No newline at end of file
+ async def execute_query(self, query, db_name):
+ result = []
+ conn = await self.helper.connect_to_db()
+ cursor = await conn.cursor()
+ await cursor.execute('USE {db_name}'.format(db_name=db_name))
+ try:
+ await cursor.execute(query)
+ rows = await cursor.fetchall()
+ for row in rows:
+ result.append(list(row))
+ except Exception as mysql_error:
+ result = str(mysql_error)
+ return result
diff --git a/tanner/emulators/php_code_injection.py b/tanner/emulators/php_code_injection.py
new file mode 100644
index 00000000..11087c86
--- /dev/null
+++ b/tanner/emulators/php_code_injection.py
@@ -0,0 +1,40 @@
+import aiohttp
+import asyncio
+import logging
+
+from tanner import config
+from tanner.utils import patterns
+
+
+class PHPCodeInjection:
+ def __init__(self, loop=None):
+ self._loop = loop if loop is not None else asyncio.get_event_loop()
+ self.logger = logging.getLogger('tanner.php_code_injecton')
+
+ async def get_injection_result(self, code):
+ code_injection_result = None
+ code = ''.format(code=code)
+ phpox_address = 'http://{host}:{port}'.format(host=config.TannerConfig.get('PHPOX', 'host'),
+ port=config.TannerConfig.get('PHPOX', 'port')
+ )
+ try:
+ async with aiohttp.ClientSession(loop=self._loop) as session:
+ async with session.post(phpox_address, data=code) as resp:
+ code_injection_result = await resp.json()
+ except aiohttp.ClientError as client_error:
+ self.logger.error('Error during connection to php sandbox %s', client_error)
+ else:
+ await session.close()
+ return code_injection_result
+
+ def scan(self, value):
+ detection = None
+ if patterns.PHP_CODE_INJECTION.match(value):
+ detection = dict(name='php_code_injection', order=3)
+ return detection
+
+ async def handle(self, attack_params, session=None):
+ result = await self.get_injection_result(attack_params[0]['value'])
+ if not result or 'stdout' not in result:
+ return dict(status_code=504)
+ return dict(value=result['stdout'], page=False)
diff --git a/tanner/emulators/rfi.py b/tanner/emulators/rfi.py
index f688cebb..71f79fd4 100644
--- a/tanner/emulators/rfi.py
+++ b/tanner/emulators/rfi.py
@@ -10,6 +10,7 @@
import aiohttp
import yarl
+from tanner import config
from tanner.utils import patterns
@@ -76,9 +77,12 @@ async def get_rfi_result(self, path):
return rfi_result
with open(os.path.join(self.script_dir, file_name), 'br') as script:
script_data = script.read()
+ phpox_address = 'http://{host}:{port}'.format(host=config.TannerConfig.get('PHPOX', 'host'),
+ port=config.TannerConfig.get('PHPOX', 'port')
+ )
try:
async with aiohttp.ClientSession(loop=self._loop) as session:
- async with session.post('http://127.0.0.1:8088/', data=script_data) as resp:
+ async with session.post(phpox_address, data=script_data) as resp:
rfi_result = await resp.json()
except aiohttp.ClientError as client_error:
self.logger.error('Error during connection to php sandbox %s', client_error)
@@ -90,12 +94,12 @@ async def get_rfi_result(self, path):
def scan(self, value):
detection = None
if patterns.RFI_ATTACK.match(value):
- detection = dict(name= 'rfi', order= 2)
+ detection = dict(name='rfi', order=2)
return detection
async def handle(self, attack_params, session=None):
result = await self.get_rfi_result(attack_params[0]['value'])
if not result or 'stdout' not in result:
- return ''
+ return dict(value='', page=True)
else:
- return result['stdout']
+ return dict(value=result['stdout'], page=False)
diff --git a/tanner/emulators/sqli.py b/tanner/emulators/sqli.py
index bb0d6a07..29b00ad9 100644
--- a/tanner/emulators/sqli.py
+++ b/tanner/emulators/sqli.py
@@ -1,12 +1,9 @@
-import os
import pylibinjection
-import sqlite3
-import urllib.parse
-from tanner.utils import sqlite_db_helper
from tanner.config import TannerConfig
from tanner.emulators import mysqli, sqlite
+
class SqliEmulator:
def __init__(self, db_name, working_dir):
if (TannerConfig.get('SQLI', 'type') == 'MySQL'):
@@ -21,7 +18,7 @@ def scan(self, value):
payload = bytes(value, 'utf-8')
sqli = pylibinjection.detect_sqli(payload)
if int(sqli['sqli']):
- detection = dict(name= 'sqli', order= 2)
+ detection = dict(name='sqli', order=2)
return detection
def map_query(self, attack_value):
@@ -30,7 +27,7 @@ def map_query(self, attack_value):
param_value = attack_value['value'].replace('\'', ' ')
tables = []
for table, columns in self.query_map.items():
- for column in columns:
+ for column in columns:
if param == column['name']:
tables.append(dict(table_name=table, column=column))
@@ -45,14 +42,15 @@ def map_query(self, attack_value):
async def get_sqli_result(self, attack_value, attacker_db):
db_query = self.map_query(attack_value)
if db_query is None:
- result = 'You have an error in your SQL syntax; check the manual\
+ error_result = 'You have an error in your SQL syntax; check the manual\
that corresponds to your MySQL server version for the\
right syntax to use near {} at line 1'.format(attack_value['id'])
+ result = dict(value=error_result, page=True)
else:
execute_result = await self.sqli_emulator.execute_query(db_query, attacker_db)
if isinstance(execute_result, list):
execute_result = ' '.join([str(x) for x in execute_result])
- result = dict(value=execute_result)
+ result = dict(value=execute_result, page=True)
return result
async def handle(self, attack_params, session):
diff --git a/tanner/emulators/sqlite.py b/tanner/emulators/sqlite.py
index 73387a8e..6bb047f6 100644
--- a/tanner/emulators/sqlite.py
+++ b/tanner/emulators/sqlite.py
@@ -1,9 +1,7 @@
-import asyncio
import os
import sqlite3
from tanner.utils import sqlite_db_helper
-from tanner import config
class SQLITEEmulator:
@@ -39,4 +37,4 @@ async def execute_query(self, query, db):
result.append(list(row))
except sqlite3.OperationalError as sqlite_error:
result = str(sqlite_error)
- return result
\ No newline at end of file
+ return result
diff --git a/tanner/emulators/xss.py b/tanner/emulators/xss.py
index d7c5638b..8537906f 100644
--- a/tanner/emulators/xss.py
+++ b/tanner/emulators/xss.py
@@ -1,16 +1,12 @@
-import mimetypes
-import re
-import urllib.parse
-
from tanner.utils import patterns
class XssEmulator:
-
+
def scan(self, value):
detection = None
if patterns.XSS_ATTACK.match(value):
- detection = dict(name= 'xss', order= 3)
+ detection = dict(name='xss', order=3)
return detection
def get_xss_result(self, session, attack_params):
@@ -18,7 +14,7 @@ def get_xss_result(self, session, attack_params):
value = ''
for param in attack_params:
value += param['value'] if not value else '\n' + param['value']
- result = dict(value=value)
+ result = dict(value=value, page=True)
return result
async def handle(self, attack_params, session):
diff --git a/tanner/redis_client.py b/tanner/redis_client.py
index 9641b6c6..854d63b5 100644
--- a/tanner/redis_client.py
+++ b/tanner/redis_client.py
@@ -23,4 +23,4 @@ async def get_redis_client(poolsize=None):
except asyncio.TimeoutError as timeout_error:
LOGGER.error('Problem with redis connection. Please, check your redis server. %s', timeout_error)
exit()
- return redis_client
\ No newline at end of file
+ return redis_client
diff --git a/tanner/reporting/log_mongodb.py b/tanner/reporting/log_mongodb.py
index a28ddd65..fb3b9db7 100644
--- a/tanner/reporting/log_mongodb.py
+++ b/tanner/reporting/log_mongodb.py
@@ -1,6 +1,6 @@
-import json
try:
import pymongo
+
MONGO = True
except ImportError:
MONGO = False
@@ -31,13 +31,11 @@ def __init__(self):
else:
print('pymongo not found. pip install pymongo')
-
def update_session(self, session_id, new_values):
session_id = ObjectId(session_id)
self.tan_sessions.update_one({'_id': session_id}, {"$set": new_values})
return True
-
def create_session(self, session_data):
session_id = self.tan_sessions.insert_one(session_data).inserted_id
- return session_id
\ No newline at end of file
+ return session_id
diff --git a/tanner/server.py b/tanner/server.py
index cf92432e..654f528b 100644
--- a/tanner/server.py
+++ b/tanner/server.py
@@ -14,7 +14,6 @@
from tanner.reporting.log_hpfeeds import Reporting as hpfeeds_report
from tanner import __version__ as tanner_version
-
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
@@ -28,7 +27,8 @@ def __init__(self):
self.base_handler = base.BaseHandler(base_dir, db_name)
self.logger = logging.getLogger(__name__)
self.redis_client = None
- if TannerConfig.get('HPFEEDS', 'enabled') == True:
+
+ if TannerConfig.get('HPFEEDS', 'enabled') is True:
self.hpf = hpfeeds_report()
self.hpf.connect()
@@ -71,19 +71,20 @@ async def handle_event(self, request):
session_data['response_msg'] = response_msg
# Log to Mongo
- if TannerConfig.get('MONGO', 'enabled') == True:
+ if TannerConfig.get('MONGO', 'enabled') is True:
db = mongo_report()
session_id = db.create_session(session_data)
self.logger.info("Writing session to DB: {}".format(session_id))
-
+
# Log to hpfeeds
- if TannerConfig.get('HPFEEDS', 'enabled') == True:
+ if TannerConfig.get('HPFEEDS', 'enabled') is True:
if self.hpf.connected():
self.hpf.create_session(session_data)
- if TannerConfig.get('LOCALLOG', 'enabled') == True:
+ if TannerConfig.get('LOCALLOG', 'enabled') is True:
lr = local_report()
lr.create_session(session_data)
+
return web.json_response(response_msg)
async def handle_dorks(self, request):
diff --git a/tanner/session.py b/tanner/session.py
index 7d1420d9..afb59883 100644
--- a/tanner/session.py
+++ b/tanner/session.py
@@ -1,7 +1,5 @@
-import asyncio
import json
import time
-import asyncio
import uuid
from tanner.config import TannerConfig
@@ -9,6 +7,7 @@
from tanner.utils.mysql_db_helper import MySQLDBHelper
from tanner.utils.sqlite_db_helper import SQLITEDBHelper
+
class Session:
KEEP_ALIVE_TIME = 75
@@ -54,7 +53,7 @@ def to_json(self):
count=self.count,
paths=self.paths,
cookies=self.cookies
- )
+ )
return json.dumps(sess)
def set_attack_type(self, path, attack_type):
@@ -66,7 +65,7 @@ def associate_db(self, db_name):
self.associated_db = db_name
async def remove_associated_db(self):
- if(TannerConfig.get('SQLI', 'type') == 'MySQL'):
+ if TannerConfig.get('SQLI', 'type') == 'MySQL':
await MySQLDBHelper().delete_db(self.associated_db)
else:
SQLITEDBHelper().delete_db(self.associated_db)
diff --git a/tanner/session_analyzer.py b/tanner/session_analyzer.py
index bd957a54..e7caf070 100644
--- a/tanner/session_analyzer.py
+++ b/tanner/session_analyzer.py
@@ -45,7 +45,7 @@ async def create_stats(self, session, redis_client):
sess_duration = session['end_time'] - session['start_time']
rps = sess_duration / session['count']
tbr, errors, hidden_links, attack_types = await self.analyze_paths(session['paths'],
- redis_client)
+ redis_client)
stats = dict(
sess_uuid=session['sess_uuid'],
@@ -76,7 +76,7 @@ async def analyze_paths(paths, redis_client):
current_path = paths[0]
dorks = await redis_client.smembers_asset(DorksManager.dorks_key)
- for i, path in enumerate(paths, start=1):
+ for _, path in enumerate(paths, start=1):
tbr.append(path['timestamp'] - current_path['timestamp'])
current_path = path
tbr_average = sum(tbr) / float(len(tbr))
@@ -84,7 +84,7 @@ async def analyze_paths(paths, redis_client):
errors = 0
hidden_links = 0
for path in paths:
- if path['response_status'] is not 200:
+ if path['response_status'] != 200:
errors += 1
if path['path'] in dorks:
hidden_links += 1
@@ -111,7 +111,7 @@ def choose_possible_owner(stats):
'IEMobile/11.0; NOKIA; Lumia 530) like Gecko (compatible; bingbot/2.0; '
'+http://www.bing.com/bingbot.htm)']
if stats['user_agent'] in bots_owner:
- hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(stats['peer_ip'])
+ hostname, _, _ = socket.gethostbyaddr(stats['peer_ip'])
if 'search.msn.com' or 'googlebot.com' in hostname:
possible_owners['crawler'] += 1
else:
diff --git a/tanner/session_manager.py b/tanner/session_manager.py
index aee9aeb0..367d3737 100644
--- a/tanner/session_manager.py
+++ b/tanner/session_manager.py
@@ -1,4 +1,3 @@
-import asyncio
import logging
import asyncio_redis
@@ -91,4 +90,4 @@ async def delete_session(self, sess, redis_client):
self.logger.error('Error connect to redis, session stay in memory. %s', redis_error)
return False
else:
- return True
\ No newline at end of file
+ return True
diff --git a/tanner/tests/test_api_server.py b/tanner/tests/test_api_server.py
index c4f56eb4..56d236cd 100644
--- a/tanner/tests/test_api_server.py
+++ b/tanner/tests/test_api_server.py
@@ -5,6 +5,7 @@
from tanner.api import server, api
+
class TestAPIServer(AioHTTPTestCase):
def setUp(self):
self.serv = server.ApiServer()
@@ -46,7 +47,8 @@ async def mock_return_snare_info(snare_uuid, count):
if snare_uuid == "8fa6aa98-4283-4085-bfb9-a1cd3a9e56e4" and count == 50:
return [{"test_sess1": "sess1_info"}, {"test_sess1": "sess2_info"}]
- assert_content = {"version": 1, "response": {"message": [{"test_sess1": "sess1_info"}, {"test_sess1": "sess2_info"}]}}
+ assert_content = {"version": 1,
+ "response": {"message": [{"test_sess1": "sess1_info"}, {"test_sess1": "sess2_info"}]}}
self.serv.api.return_snare_info = mock_return_snare_info
request = await self.client.request("GET", "/snare/8fa6aa98-4283-4085-bfb9-a1cd3a9e56e4")
assert request.status == 200
@@ -57,9 +59,12 @@ async def mock_return_snare_info(snare_uuid, count):
async def test_api_snare_stats_request(self):
async def mock_return_snare_stats(snare_uuid):
if snare_uuid == "8fa6aa98-4283-4085-bfb9-a1cd3a9e56e4":
- return {"total_sessions": 605, "total_duration": 865.560286283493, "attack_frequency": {"sqli": 0, "lfi": 0, "xss": 0, "rfi": 0, "cmd_exec": 0}}
+ return {"total_sessions": 605, "total_duration": 865.560286283493,
+ "attack_frequency": {"sqli": 0, "lfi": 0, "xss": 0, "rfi": 0, "cmd_exec": 0}}
- assert_content = {"version": 1, "response": {"message": {"total_sessions": 605, "total_duration": 865.560286283493, "attack_frequency": {"sqli": 0, "lfi": 0, "xss": 0, "rfi": 0, "cmd_exec": 0}}}}
+ assert_content = {"version": 1, "response": {
+ "message": {"total_sessions": 605, "total_duration": 865.560286283493,
+ "attack_frequency": {"sqli": 0, "lfi": 0, "xss": 0, "rfi": 0, "cmd_exec": 0}}}}
self.serv.api.return_snare_stats = mock_return_snare_stats
request = await self.client.request("GET", "/snare-stats/8fa6aa98-4283-4085-bfb9-a1cd3a9e56e4")
assert request.status == 200
@@ -70,12 +75,15 @@ async def mock_return_snare_stats(snare_uuid):
async def test_api_sessions_request(self):
async def mock_return_sessions(filters):
if type(filters) is dict and filters['peer_ip'] == "127.0.0.1" and \
- filters['start_time'] == 1497890400 and filters['user_agent'] == 'ngnix':
- return [{"sess_uuid":"f387d46eaeb1454cadf0713a4a55be49"}, {"sess_uuid":"e85ae767b0bb4b1f91b421b3a28082ef"}]
+ filters['start_time'] == 1497890400 and filters['user_agent'] == 'ngnix':
+ return [{"sess_uuid": "f387d46eaeb1454cadf0713a4a55be49"},
+ {"sess_uuid": "e85ae767b0bb4b1f91b421b3a28082ef"}]
- assert_content = {"version": 1, "response": {"message": ["f387d46eaeb1454cadf0713a4a55be49", "e85ae767b0bb4b1f91b421b3a28082ef"]}}
+ assert_content = {"version": 1, "response": {
+ "message": ["f387d46eaeb1454cadf0713a4a55be49", "e85ae767b0bb4b1f91b421b3a28082ef"]}}
self.serv.api.return_sessions = mock_return_sessions
- request = await self.client.request("GET", "/8fa6aa98-4283-4085-bfb9-a1cd3a9e56e4/sessions?filters=peer_ip:127.0.0.1 start_time:1497890400 user_agent:ngnix")
+ request = await self.client.request("GET",
+ "/8fa6aa98-4283-4085-bfb9-a1cd3a9e56e4/sessions?filters=peer_ip:127.0.0.1 start_time:1497890400 user_agent:ngnix")
assert request.status == 200
detection = await request.json()
self.assertDictEqual(detection, assert_content)
diff --git a/tanner/tests/test_base.py b/tanner/tests/test_base.py
index cd48d061..2f62a76b 100644
--- a/tanner/tests/test_base.py
+++ b/tanner/tests/test_base.py
@@ -17,19 +17,19 @@ def setUp(self):
self.handler = base.BaseHandler('/tmp/', 'test.db', self.loop)
def mock_lfi_scan(value):
- return dict(name= 'lfi', order= 0)
+ return dict(name='lfi', order=0)
self.handler.emulators['lfi'].scan = mock_lfi_scan
def test_handle_sqli(self):
- data = dict(path= '/index.html?id=1 UNION SELECT 1',
- cookies= {'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
+ data = dict(path='/index.html?id=1 UNION SELECT 1',
+ cookies={'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
async def mock_sqli_handle(path, session):
return 'sqli_test_payload'
def mock_sqli_scan(value):
- return dict(name= 'sqli', order= 2)
+ return dict(name='sqli', order=2)
self.handler.emulators['sqli'] = mock.Mock()
self.handler.emulators['sqli'].handle = mock_sqli_handle
@@ -41,14 +41,14 @@ def mock_sqli_scan(value):
self.assertDictEqual(detection, assert_detection)
def test_handle_xss(self):
- data = dict(path= '/index.html?id=',
- cookies= {'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
+ data = dict(path='/index.html?id=',
+ cookies={'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
async def mock_xss_handle(path, session):
return 'xss_test_payload'
def mock_xss_scan(value):
- return dict(name= 'xss', order= 3)
+ return dict(name='xss', order=3)
self.handler.emulators['xss'] = mock.Mock()
self.handler.emulators['xss'].handle = mock_xss_handle
@@ -60,14 +60,14 @@ def mock_xss_scan(value):
self.assertDictEqual(detection, assert_detection)
def test_handle_lfi(self):
- data = dict(path= '/index.html?file=/etc/passwd',
- cookies= {'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
+ data = dict(path='/index.html?file=/etc/passwd',
+ cookies={'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
async def mock_lfi_handle(attack_value, session):
return 'lfi_test_payload'
-
+
def mock_lfi_scan(value):
- return dict(name= 'lfi', order= 2)
+ return dict(name='lfi', order=2)
self.handler.emulators['lfi'] = mock.Mock()
self.handler.emulators['lfi'].handle = mock_lfi_handle
@@ -79,8 +79,8 @@ def mock_lfi_scan(value):
self.assertDictEqual(detection, assert_detection)
def test_handle_index(self):
- data = dict(path= '/index.html',
- cookies= {'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
+ data = dict(path='/index.html',
+ cookies={'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
detection = self.loop.run_until_complete(self.handler.handle_get(self.session, data))
@@ -88,8 +88,8 @@ def test_handle_index(self):
self.assertDictEqual(detection, assert_detection)
def test_handle_wp_content(self):
- data = dict(path= '/wp-content',
- cookies= {'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
+ data = dict(path='/wp-content',
+ cookies={'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
detection = self.loop.run_until_complete(self.handler.handle_get(self.session, data))
@@ -97,14 +97,14 @@ def test_handle_wp_content(self):
self.assertDictEqual(detection, assert_detection)
def test_handle_rfi(self):
- data = dict(path= '/index.html?file=http://attack.php',
- cookies= {'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
+ data = dict(path='/index.html?file=http://attack.php',
+ cookies={'sess_uuid': '9f82e5d0e6b64047bba996222d45e72c'})
async def mock_rfi_handle(path, session):
return 'rfi_test_payload'
def mock_rfi_scan(value):
- return dict(name= 'rfi', order= 2)
+ return dict(name='rfi', order=2)
self.handler.emulators['rfi'] = mock.Mock()
self.handler.emulators['rfi'].handle = mock_rfi_handle
@@ -123,4 +123,4 @@ def test_set_injectable_page(self):
mock_session.return_value.paths = paths
sess = session.Session(None)
injectable_page = self.handler.set_injectable_page(sess)
- self.assertEqual(injectable_page, '/python.html')
\ No newline at end of file
+ self.assertEqual(injectable_page, '/python.html')
diff --git a/tanner/tests/test_cmd_exec_emulation.py b/tanner/tests/test_cmd_exec_emulation.py
index 39c42bae..25fed880 100644
--- a/tanner/tests/test_cmd_exec_emulation.py
+++ b/tanner/tests/test_cmd_exec_emulation.py
@@ -3,6 +3,7 @@
import asyncio
from tanner.emulators import cmd_exec
+
class TestCmdExecEmulator(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
@@ -13,16 +14,16 @@ def setUp(self):
self.sess.sess_uuid.hex = 'e86d20b858224e239d3991c1a2650bc7'
def test_handle_simple_command(self):
- attack_params = [dict(id= 'foo', value= 'id')]
+ attack_params = [dict(id='foo', value='id')]
result = self.loop.run_until_complete(self.handler.handle(attack_params, self.sess))
assert_result = 'uid=0(root) gid=0(root)'
self.assertIn(assert_result, result['value'])
def test_handle_nested_commands(self):
attack_params = [
- [dict(id= 'foo1', value= 'id; uname')],
- [dict(id= 'foo2', value= 'id && uname')]
- ]
+ [dict(id='foo1', value='id; uname')],
+ [dict(id='foo2', value='id && uname')]
+ ]
assert_result = {'id': 'uid=0(root) gid=0(root)', 'uname': 'Linux'}
for attack_param in attack_params:
@@ -31,7 +32,7 @@ def test_handle_nested_commands(self):
self.assertIn(assert_result['uname'], result['value'])
def test_handle_invalid_commands(self):
- attack_params = [dict(id= 'foo', value= 'foo')]
+ attack_params = [dict(id='foo', value='foo')]
result = self.loop.run_until_complete(self.handler.handle(attack_params, self.sess))
assert_result = 'foo: not found'
- self.assertIn(assert_result, result['value'])
\ No newline at end of file
+ self.assertIn(assert_result, result['value'])
diff --git a/tanner/tests/test_config.py b/tanner/tests/test_config.py
index 9e78083d..b075c0a8 100644
--- a/tanner/tests/test_config.py
+++ b/tanner/tests/test_config.py
@@ -4,6 +4,7 @@
from tanner import config
+
class TestCongif(unittest.TestCase):
def setUp(self):
config.TannerConfig.config = None
@@ -12,17 +13,19 @@ def setUp(self):
'user_dorks': '/tmp/user_tanner/data/user_dorks.pickle'},
'TANNER': {'host': '0.0.0.0', 'port': '9000'},
'WEB': {'host': '0.0.0.0', 'port': '9001'},
- 'WEB': {'host': '0.0.0.0', 'port': '9002'},
+ 'API': {'host': '0.0.0.0', 'port': '9002'},
+ 'PHPOX': {'host': '0.0.0.0', 'port': '8088'},
'REDIS': {'host': 'localhost', 'port': '1337', 'poolsize': '40', 'timeout': '5'},
'EMULATORS': {'root_dir': '/opt/tanner'},
'EMULATOR_ENABLED': {'sqli': 'True', 'rfi': 'True', 'lfi': 'True', 'xss': 'True', 'cmd_exec': 'True'},
- 'SQLI': {'type':'SQLITE', 'db_name': 'user_tanner_db', 'host':'localhost', 'user':'user_name', 'password':'user_pass'},
+ 'SQLI': {'type': 'SQLITE', 'db_name': 'user_tanner_db', 'host': 'localhost', 'user': 'user_name',
+ 'password': 'user_pass'},
'DOCKER': {'host_image': 'test_image'},
- 'LOGGER': {'log_debug': '/opt/tanner/tanner.log', 'log_err': '/opt/tanner/tanner.err'},
+ 'LOGGER': {'log_debug': '/opt/tanner/tanner.log', 'log_err': '/opt/tanner/tanner.err'},
'MONGO': {'enabled': 'False', 'URI': 'mongodb://localhost'},
'LOCALLOG': {'enabled': 'False', 'PATH': '/tmp/user_tanner_report.json'},
'CLEANLOG': {'enabled': 'False'}
- }
+ }
self.valid_config_path = '/tmp/tanner_config'
self.cfg = configparser.ConfigParser()
@@ -51,8 +54,12 @@ def test_get_when_file_exists(self):
config.TannerConfig.config = self.cfg
for section in self.d:
for value, assertion_data in self.d[section].items():
- data = config.TannerConfig.get(section, value)
- convert_type = type(data)
+
+ convert_type = type(self.d[section][value])
+ if convert_type is bool:
+ data = config.TannerConfig.config.getboolean(section, value)
+ else:
+ data = config.TannerConfig.config.get(section, value)
self.assertEqual(data, convert_type(assertion_data))
def test_get_when_file_dont_exists(self):
@@ -62,16 +69,18 @@ def test_get_when_file_dont_exists(self):
'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'},
+ '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'},
'LOCALLOG': {'enabled': False, 'PATH': '/tmp/tanner_report.json'},
'CLEANLOG': {'enabled': False}
- }
+ }
for section in config_template:
for value, assertion_data in config_template[section].items():
diff --git a/tanner/tests/test_crlf.py b/tanner/tests/test_crlf.py
new file mode 100644
index 00000000..f7c33484
--- /dev/null
+++ b/tanner/tests/test_crlf.py
@@ -0,0 +1,22 @@
+import asyncio
+import unittest
+
+from tanner.emulators import crlf
+
+
+class TestCRLF(unittest.TestCase):
+ def setUp(self):
+ self.loop = asyncio.new_event_loop()
+ self.handler = crlf.CRLFEmulator()
+
+ def test_scan(self):
+ attack = 'foo \r\n Set-Cookie : id=0'
+ assert_result = dict(name='crlf', order=2)
+ result = self.handler.scan(attack)
+ self.assertEqual(result, assert_result)
+
+ def test_handle(self):
+ attack_params = [dict(id='foo', value='bar \r\n Set-Cookie : id=0')]
+ assert_result = {'foo': 'bar \r\n Set-Cookie : id=0'}
+ result = self.loop.run_until_complete(self.handler.handle(attack_params, None))
+ self.assertEqual(result['headers'], assert_result)
diff --git a/tanner/tests/test_lfi_emulator.py b/tanner/tests/test_lfi_emulator.py
index abf32bdc..2b1f0136 100644
--- a/tanner/tests/test_lfi_emulator.py
+++ b/tanner/tests/test_lfi_emulator.py
@@ -1,8 +1,8 @@
import unittest
-from unittest import mock
import asyncio
from tanner.emulators import lfi
+
class TestLfiEmulator(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
@@ -11,16 +11,16 @@ def setUp(self):
self.handler.helper.host_image = 'busybox:latest'
def test_handle_abspath_lfi(self):
- attack_params = [dict(id= 'foo', value= '/etc/passwd')]
+ attack_params = [dict(id='foo', value='/etc/passwd')]
result = self.loop.run_until_complete(self.handler.handle(attack_params))
- self.assertIn('root:x:0:0:root:/root:/bin/sh', result)
+ self.assertIn('root:x:0:0:root:/root:/bin/sh', result['value'])
def test_handle_relative_path_lfi(self):
- attack_params = [dict(id= 'foo', value= '../../../../../etc/passwd')]
+ attack_params = [dict(id='foo', value='../../../../../etc/passwd')]
result = self.loop.run_until_complete(self.handler.handle(attack_params))
- self.assertIn('root:x:0:0:root:/root:/bin/sh', result)
+ self.assertIn('root:x:0:0:root:/root:/bin/sh', result['value'])
def test_handle_missing_lfi(self):
- attack_params = [dict(id= 'foo', value= '../../../../../etc/bar')]
+ attack_params = [dict(id='foo', value='../../../../../etc/bar')]
result = self.loop.run_until_complete(self.handler.handle(attack_params))
- self.assertIn('No such file or directory', result)
+ self.assertIn('No such file or directory', result['value'])
diff --git a/tanner/tests/test_php_code_injetion.py b/tanner/tests/test_php_code_injetion.py
new file mode 100644
index 00000000..9e4197d9
--- /dev/null
+++ b/tanner/tests/test_php_code_injetion.py
@@ -0,0 +1,27 @@
+import asyncio
+import unittest
+
+from tanner.emulators import php_code_injection
+
+
+class TestPHPCodeInjection(unittest.TestCase):
+ def setUp(self):
+ self.loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(None)
+ self.handler = php_code_injection.PHPCodeInjection(loop=self.loop)
+
+ def test_scan(self):
+ attack = '; phpinfo();'
+ assert_result = dict(name='php_code_injection', order=3)
+ result = self.handler.scan(attack)
+ self.assertEqual(result, assert_result)
+
+ def test_handle_status_code(self):
+ async def mock_get_injection_results(code):
+ return None
+
+ self.handler.get_injection_result = mock_get_injection_results
+ attack_params = [dict(id='foo', value=';sleep(50);')]
+ assert_result = dict(status_code=504)
+ result = self.loop.run_until_complete(self.handler.handle(attack_params))
+ self.assertEqual(result, assert_result)
diff --git a/tanner/tests/test_rfi_emulation.py b/tanner/tests/test_rfi_emulation.py
index db43261e..224ed635 100644
--- a/tanner/tests/test_rfi_emulation.py
+++ b/tanner/tests/test_rfi_emulation.py
@@ -1,5 +1,4 @@
import asyncio
-import ftplib
import unittest
from tanner.emulators import rfi
@@ -32,13 +31,11 @@ def test_ftp_download_fail(self):
with self.assertLogs():
self.loop.run_until_complete(self.handler.download_file(path))
-
def test_get_result_fail(self):
data = "test data"
result = self.loop.run_until_complete(self.handler.get_rfi_result(data))
self.assertIsNone(result)
-
def test_invalid_scheme(self):
path = 'file://mirror.yandex.ru/archlinux/foobar'
data = self.loop.run_until_complete(self.handler.download_file(path))
diff --git a/tanner/tests/test_server.py b/tanner/tests/test_server.py
index c9835f7c..d747b0cf 100644
--- a/tanner/tests/test_server.py
+++ b/tanner/tests/test_server.py
@@ -1,4 +1,3 @@
-import asyncio
import uuid
from unittest import mock
@@ -8,6 +7,7 @@
from tanner.config import TannerConfig
from tanner import __version__ as tanner_version
+
class TestServer(AioHTTPTestCase):
def setUp(self):
d = dict(MONGO={'enabled': 'False', 'URI': 'mongodb://localhost'},
@@ -25,7 +25,6 @@ def setUp(self):
self.test_uuid = uuid.uuid4()
-
async def _add_or_update_mock(data, client):
sess = mock.Mock()
sess.set_attack_type = mock.Mock()
@@ -38,7 +37,7 @@ async def _delete_sessions_mock(client):
self.serv.session_manager.add_or_update_session = _add_or_update_mock
self.serv.session_manager.delete_sessions_on_shutdown = _delete_sessions_mock
-
+
async def choosed(client):
return [x for x in range(10)]
@@ -104,5 +103,3 @@ async def test_version(self):
assert request.status == 200
detection = await request.json()
self.assertDictEqual(detection, assert_content)
-
-
diff --git a/tanner/tests/test_session_analyzer.py b/tanner/tests/test_session_analyzer.py
index 114ea649..834b88e4 100644
--- a/tanner/tests/test_session_analyzer.py
+++ b/tanner/tests/test_session_analyzer.py
@@ -42,7 +42,6 @@ async def sess_get(key):
self.loop.run_until_complete(self.handler.analyze(None, redis_mock))
def test_create_stats(self):
-
async def sess_get():
return session
diff --git a/tanner/tests/test_session_manager.py b/tanner/tests/test_session_manager.py
index 99f1e9c1..9c57ba61 100644
--- a/tanner/tests/test_session_manager.py
+++ b/tanner/tests/test_session_manager.py
@@ -109,8 +109,7 @@ async def sess_sadd(key, value):
self.assertEquals([sess], self.handler.sessions)
def test_updating_session(self):
-
- async def sess_sadd(key, value):
+ async def sess_sadd(key, value):
return None
data = {
@@ -133,7 +132,6 @@ async def sess_sadd(key, value):
self.assertEqual(self.handler.sessions[0].count, 2)
def test_deleting_sessions(self):
-
async def analyze(session_key, redis_client):
return None
diff --git a/tanner/tests/test_sqli.py b/tanner/tests/test_sqli.py
index da1d7326..40c18339 100644
--- a/tanner/tests/test_sqli.py
+++ b/tanner/tests/test_sqli.py
@@ -1,5 +1,4 @@
import asyncio
-import os
import unittest
from unittest import mock
@@ -10,36 +9,36 @@ class SqliTest(unittest.TestCase):
def setUp(self):
self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(None)
-
+
query_map = {
- 'users': [{'name':'id', 'type':'INTEGER'}, {'name':'login', 'type':'text'},
- {'name':'email', 'type':'text'}, {'name':'username', 'type':'text'},
- {'name':'password', 'type':'text'}, {'name':'pass', 'type':'text'},
- {'name':'log', 'type':'text'}],
- 'comments': [{'name':'comment', 'type':'text'}]
+ 'users': [{'name': 'id', 'type': 'INTEGER'}, {'name': 'login', 'type': 'text'},
+ {'name': 'email', 'type': 'text'}, {'name': 'username', 'type': 'text'},
+ {'name': 'password', 'type': 'text'}, {'name': 'pass', 'type': 'text'},
+ {'name': 'log', 'type': 'text'}],
+ 'comments': [{'name': 'comment', 'type': 'text'}]
}
self.handler = sqli.SqliEmulator('test_db', '/tmp/')
self.handler.query_map = query_map
def test_map_query_id(self):
- attack_value = dict(id= 'id', value= '1\'UNION SELECT 1,2,3,4')
+ attack_value = dict(id='id', value='1\'UNION SELECT 1,2,3,4')
assert_result = 'SELECT * from users WHERE id=1 UNION SELECT 1,2,3,4;'
result = self.handler.map_query(attack_value)
self.assertEqual(assert_result, result)
def test_map_query_comments(self):
- attack_value = dict(id= 'comment', value= 'some_comment" UNION SELECT 1,2 AND "1"="1')
+ attack_value = dict(id='comment', value='some_comment" UNION SELECT 1,2 AND "1"="1')
assert_result = 'SELECT * from comments WHERE comment="some_comment" UNION SELECT 1,2 AND "1"="1";'
result = self.handler.map_query(attack_value)
self.assertEqual(assert_result, result)
def test_map_query_error(self):
- attack_value = dict(id= 'foo', value= 'bar\'UNION SELECT 1,2')
+ attack_value = dict(id='foo', value='bar\'UNION SELECT 1,2')
result = self.handler.map_query(attack_value)
self.assertIsNone(result)
def test_get_sqli_result(self):
- attack_value = dict(id= 'id', value= '1 UNION SELECT 1,2,3,4')
+ attack_value = dict(id='id', value='1 UNION SELECT 1,2,3,4')
async def mock_execute_query(query, db_name):
return [[1, 'name', 'email@mail.com', 'password'], [1, '2', '3', '4']]
@@ -47,14 +46,14 @@ async def mock_execute_query(query, db_name):
self.handler.sqli_emulator = mock.Mock()
self.handler.sqli_emulator.execute_query = mock_execute_query
- assert_result = dict(value="[1, 'name', 'email@mail.com', 'password'] [1, '2', '3', '4']")
+ assert_result = dict(value="[1, 'name', 'email@mail.com', 'password'] [1, '2', '3', '4']", page=True)
result = self.loop.run_until_complete(self.handler.get_sqli_result(attack_value, 'foo.db'))
self.assertEqual(assert_result, result)
def test_get_sqli_result_error(self):
- attack_value = dict(id= 'foo', value= 'bar\'UNION SELECT 1,2')
+ attack_value = dict(id='foo', value='bar\'UNION SELECT 1,2')
assert_result = 'You have an error in your SQL syntax; check the manual\
that corresponds to your MySQL server version for the\
right syntax to use near foo at line 1'
result = self.loop.run_until_complete(self.handler.get_sqli_result(attack_value, 'foo.db'))
- self.assertEqual(assert_result, result)
\ No newline at end of file
+ self.assertEqual(assert_result, result['value'])
diff --git a/tanner/tests/test_sqlite.py b/tanner/tests/test_sqlite.py
index da3244c0..dc2686ad 100644
--- a/tanner/tests/test_sqlite.py
+++ b/tanner/tests/test_sqlite.py
@@ -6,48 +6,49 @@
from tanner.emulators import sqlite
+
class SqliteTest(unittest.TestCase):
- def setUp(self):
- self.loop = asyncio.new_event_loop()
- asyncio.set_event_loop(None)
- self.filename = '/tmp/db/test_db'
- os.makedirs(os.path.dirname(self.filename), exist_ok=True)
- open('/tmp/db/test_db', 'a').close()
- # Insert some testing data
- conn = sqlite3.connect(self.filename)
- self.cursor = conn.cursor()
- self.cursor.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, username text);')
- self.cursor.execute('INSERT INTO TEST VALUES(0, "test0")')
- conn.commit()
-
- self.handler = sqlite.SQLITEEmulator('test_db', '/tmp/')
-
- def tearDown(self):
- if os.path.exists(self.filename):
- os.remove(self.filename)
-
- def test_db_copy(self):
- session = mock.Mock()
- session.sess_uuid.hex = 'd877339ec415484987b279469167af3d'
- self.loop.run_until_complete(self.handler.create_attacker_db(session))
- self.assertTrue(os.path.exists('/tmp/db/attacker_d877339ec415484987b279469167af3d'))
-
- def test_create_query_map(self):
- result = self.handler.helper.create_query_map('/tmp/db', 'test_db')
- assert_result = {'test': [{'name': 'id', 'type': 'INTEGER'}, {'name': 'username', 'type': 'text'}]}
- self.assertEqual(result, assert_result)
-
- def test_insert_dummy_data(self):
- def mock_generate_dummy_data(data_tokens):
- return [(1, 'test1'), (2, 'test2')], ['I', 'L']
-
- self.handler.helper.generate_dummy_data = mock_generate_dummy_data
-
- self.loop.run_until_complete(self.handler.helper.insert_dummy_data('test', 'I,L', self.cursor))
- assert_result = [[0, 'test0'], [1, 'test1'], [2, 'test2']]
-
- result = []
- for row in self.cursor.execute('SELECT * FROM test;'):
- result.append(list(row))
-
- self.assertEqual(result, assert_result)
\ No newline at end of file
+ def setUp(self):
+ self.loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(None)
+ self.filename = '/tmp/db/test_db'
+ os.makedirs(os.path.dirname(self.filename), exist_ok=True)
+ open('/tmp/db/test_db', 'a').close()
+ # Insert some testing data
+ conn = sqlite3.connect(self.filename)
+ self.cursor = conn.cursor()
+ self.cursor.execute('CREATE TABLE test (id INTEGER PRIMARY KEY, username TEXT)')
+ self.cursor.execute('INSERT INTO TEST VALUES(0, "test0")')
+ conn.commit()
+
+ self.handler = sqlite.SQLITEEmulator('test_db', '/tmp/')
+
+ def tearDown(self):
+ if os.path.exists(self.filename):
+ os.remove(self.filename)
+
+ def test_db_copy(self):
+ session = mock.Mock()
+ session.sess_uuid.hex = 'd877339ec415484987b279469167af3d'
+ self.loop.run_until_complete(self.handler.create_attacker_db(session))
+ self.assertTrue(os.path.exists('/tmp/db/attacker_d877339ec415484987b279469167af3d'))
+
+ def test_create_query_map(self):
+ result = self.handler.helper.create_query_map('/tmp/db', 'test_db')
+ assert_result = {'test': [{'name': 'id', 'type': 'INTEGER'}, {'name': 'username', 'type': 'TEXT'}]}
+ self.assertEqual(result, assert_result)
+
+ def test_insert_dummy_data(self):
+ def mock_generate_dummy_data(data_tokens):
+ return [(1, 'test1'), (2, 'test2')], ['I', 'L']
+
+ self.handler.helper.generate_dummy_data = mock_generate_dummy_data
+
+ self.loop.run_until_complete(self.handler.helper.insert_dummy_data('test', 'I,L', self.cursor))
+ assert_result = [[0, 'test0'], [1, 'test1'], [2, 'test2']]
+
+ result = []
+ for row in self.cursor.execute('SELECT * FROM test;'):
+ result.append(list(row))
+
+ self.assertEqual(result, assert_result)
diff --git a/tanner/tests/test_xss_emulator.py b/tanner/tests/test_xss_emulator.py
index 117d21ac..6f50a056 100644
--- a/tanner/tests/test_xss_emulator.py
+++ b/tanner/tests/test_xss_emulator.py
@@ -1,7 +1,6 @@
import asyncio
import unittest
-from unittest import mock
from tanner.emulators import xss
@@ -13,16 +12,16 @@ def setUp(self):
self.handler = xss.XssEmulator()
def test_multiple_xss(self):
- attack_params = [dict(id= 'comment', value= ''),
- dict(id= 'name', value= ''),
- dict(id= 'email', value= '')]
+ attack_params = [dict(id='comment', value=''),
+ dict(id='name', value=''),
+ dict(id='email', value='')]
xss = self.loop.run_until_complete(self.handler.handle(attack_params, None))
assert_result = ''
self.assertIn(assert_result, xss['value'])
def test_xss(self):
- attack_params = [dict(id= 'foo', value= '')]
+ attack_params = [dict(id='foo', value='')]
xss = self.loop.run_until_complete(self.handler.handle(attack_params, None))
- assert_result = dict(value=attack_params[0]['value'])
+ assert_result = dict(value=attack_params[0]['value'], page=True)
self.assertDictEqual(xss, assert_result)
diff --git a/tanner/utils/base_db_helper.py b/tanner/utils/base_db_helper.py
index 5e39643d..fd45ade8 100644
--- a/tanner/utils/base_db_helper.py
+++ b/tanner/utils/base_db_helper.py
@@ -1,9 +1,9 @@
-import asyncio
-import elizabeth
import json
import logging
import random
+import elizabeth
+
from tanner.config import TannerConfig
@@ -20,7 +20,8 @@ def read_config(self):
else:
return config
- def generate_dummy_data(self, data_tokens):
+ @staticmethod
+ def generate_dummy_data(data_tokens):
"""
Insert dummy data based on data tokens
I - integer id
@@ -50,8 +51,8 @@ def generate_dummy_data(self, data_tokens):
data = elizabeth.Personal().password()
values.append(data)
if token == 'T':
- sample_length = random.randint(1,10)
- data = elizabeth.Text().text(quantity= sample_length)
+ sample_length = random.randint(1, 10)
+ data = elizabeth.Text().text(quantity=sample_length)
values.append(data)
inserted_data.append(tuple(values))
diff --git a/tanner/utils/docker_helper.py b/tanner/utils/docker_helper.py
index df0d7130..79193a72 100644
--- a/tanner/utils/docker_helper.py
+++ b/tanner/utils/docker_helper.py
@@ -1,17 +1,18 @@
-import asyncio
-import docker
import logging
-# TODO : Replace docker with aiodocker
+import docker
+
from tanner.config import TannerConfig
+# TODO : Replace docker with aiodocker
+
class DockerHelper:
def __init__(self):
+ self.logger = logging.getLogger('tanner.docker_helper.DockerHelper')
try:
self.docker_client = docker.from_env(version='auto')
except docker.errors.APIError as docker_error:
self.logger.error('Error while connecting to docker service %s', docker_error)
self.host_image = TannerConfig.get('DOCKER', 'host_image')
- self.logger = logging.getLogger('tanner.docker_helper.DockerHelper')
async def setup_host_image(self):
try:
@@ -19,27 +20,27 @@ async def setup_host_image(self):
self.docker_client.images.pull(self.host_image)
except docker.errors.APIError as docker_error:
self.logger.error('Error while pulling %s image %s', self.host_image, docker_error)
-
+
async def get_container(self, container_name):
container = None
try:
- container_if_exists = self.docker_client.containers.list(all= True,
- filters= dict(name= container_name)
+ container_if_exists = self.docker_client.containers.list(all=True,
+ filters=dict(name=container_name)
)
if container_if_exists:
container = container_if_exists[0]
except docker.errors.APIError as server_error:
self.logger.error('Error while fetching container list %s', server_error)
return container
-
+
async def create_container(self, container_name):
await self.setup_host_image()
container = await self.get_container(container_name)
if not container:
try:
- container = self.docker_client.containers.create(image= self.host_image,
- stdin_open= True,
- name= container_name
+ container = self.docker_client.containers.create(image=self.host_image,
+ stdin_open=True,
+ name=container_name
)
except (docker.errors.APIError, docker.errors.ImageNotFound) as docker_error:
self.logger.error('Error while creating a container %s', docker_error)
@@ -49,7 +50,7 @@ async def delete_env(self, container_name):
container = await self.get_container(container_name)
try:
if container:
- container.remove(force = True)
+ container.remove(force=True)
except docker.errors.APIError as server_error:
self.logger.error('Error while removing container %s', server_error)
@@ -61,4 +62,4 @@ async def execute_cmd(self, container, cmd):
container.kill()
except docker.errors.APIError as server_error:
self.logger.error('Error while executing command %s in container %s', cmd, server_error)
- return execute_result
\ No newline at end of file
+ return execute_result
diff --git a/tanner/utils/logger.py b/tanner/utils/logger.py
index 742c04cf..54af7f71 100644
--- a/tanner/utils/logger.py
+++ b/tanner/utils/logger.py
@@ -1,17 +1,21 @@
import logging
import logging.handlers
+
from tanner.config import TannerConfig
+
class LevelFilter(logging.Filter):
- '''Filters (lets through) all messages with level < LEVEL'''
+ """Filters (lets through) all messages with level < LEVEL"""
+
def __init__(self, level):
self.level = level
def filter(self, record):
- return record.levelno < self.level # "<" instead of "<=": since logger.setLevel is inclusive, this should be exclusive
+ return record.levelno < self.level # "<" instead of "<=": since logger.setLevel is inclusive, this should be exclusive
class Logger:
+
@staticmethod
def create_logger(debug_filename, err_filename, logger_name):
if TannerConfig.get('CLEANLOG', 'enabled') == 'True':
diff --git a/tanner/utils/mysql_db_helper.py b/tanner/utils/mysql_db_helper.py
index 4ca1aabd..6bf5a13f 100644
--- a/tanner/utils/mysql_db_helper.py
+++ b/tanner/utils/mysql_db_helper.py
@@ -1,5 +1,3 @@
-import asyncio
-import json
import logging
import subprocess
import aiomysql
@@ -7,15 +5,16 @@
from tanner.config import TannerConfig
from tanner.utils.base_db_helper import BaseDBHelper
+
class MySQLDBHelper(BaseDBHelper):
def __init__(self):
super(MySQLDBHelper, self).__init__()
self.logger = logging.getLogger('tanner.db_helper.MySQLDBHelper')
async def connect_to_db(self):
- conn = await aiomysql.connect(host = TannerConfig.get('SQLI', 'host'),
- user = TannerConfig.get('SQLI', 'user'),
- password = TannerConfig.get('SQLI', 'password')
+ conn = await aiomysql.connect(host=TannerConfig.get('SQLI', 'host'),
+ user=TannerConfig.get('SQLI', 'user'),
+ password=TannerConfig.get('SQLI', 'password')
)
return conn
@@ -23,19 +22,19 @@ async def check_db_exists(self, db_name, ):
conn = await self.connect_to_db()
cursor = await conn.cursor()
check_DB_exists_query = 'SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA '
- check_DB_exists_query+= 'WHERE SCHEMA_NAME=\'{db_name}\''.format(db_name=db_name)
+ check_DB_exists_query += 'WHERE SCHEMA_NAME=\'{db_name}\''.format(db_name=db_name)
await cursor.execute(check_DB_exists_query)
result = await cursor.fetchall()
- #return 0 if no such database exists else 1
+ # return 0 if no such database exists else 1
return len(result)
-
+
async def setup_db_from_config(self, name=None):
config = self.read_config()
if name is not None:
db_name = name
else:
db_name = config['name']
-
+
conn = await self.connect_to_db()
cursor = await conn.cursor()
create_db_query = 'CREATE DATABASE {db_name}'
@@ -63,7 +62,7 @@ async def copy_db(self, user_db, attacker_db):
if db_exists:
self.logger.info('Attacker db already exists')
else:
- #create new attacker db
+ # create new attacker db
conn = await self.connect_to_db()
cursor = await conn.cursor()
await cursor.execute('CREATE DATABASE {db_name}'.format(db_name=attacker_db))
@@ -71,19 +70,19 @@ async def copy_db(self, user_db, attacker_db):
# copy user db to attacker db
dump_db_cmd = 'mysqldump -h {host} -u {user} -p{password} {db_name}'
restore_db_cmd = 'mysql -h {host} -u {user} -p{password} {db_name}'
- dump_db_cmd = dump_db_cmd.format(host = TannerConfig.get('SQLI', 'host'),
- user = TannerConfig.get('SQLI', 'user'),
- password = TannerConfig.get('SQLI', 'password'),
+ dump_db_cmd = dump_db_cmd.format(host=TannerConfig.get('SQLI', 'host'),
+ user=TannerConfig.get('SQLI', 'user'),
+ password=TannerConfig.get('SQLI', 'password'),
db_name=user_db
)
- restore_db_cmd = restore_db_cmd.format(host = TannerConfig.get('SQLI', 'host'),
- user = TannerConfig.get('SQLI', 'user'),
- password = TannerConfig.get('SQLI', 'password'),
+ restore_db_cmd = restore_db_cmd.format(host=TannerConfig.get('SQLI', 'host'),
+ user=TannerConfig.get('SQLI', 'user'),
+ password=TannerConfig.get('SQLI', 'password'),
db_name=attacker_db
)
try:
- dump_db_process = subprocess.Popen(dump_db_cmd, stdout = subprocess.PIPE, shell = True)
- restore_db_process = subprocess.Popen(restore_db_cmd, stdin = dump_db_process.stdout, shell = True)
+ dump_db_process = subprocess.Popen(dump_db_cmd, stdout=subprocess.PIPE, shell=True)
+ restore_db_process = subprocess.Popen(restore_db_cmd, stdin=dump_db_process.stdout, shell=True)
dump_db_process.stdout.close()
dump_db_process.wait()
restore_db_process.wait()
@@ -101,7 +100,7 @@ async def insert_dummy_data(self, table_name, data_tokens, cursor):
inserted_string_patt = inserted_string_patt[:-1]
await cursor.executemany("INSERT INTO " + table_name + " VALUES(" +
- inserted_string_patt + ")", inserted_data)
+ inserted_string_patt + ")", inserted_data)
async def create_query_map(self, db_name):
query_map = {}
@@ -132,6 +131,6 @@ async def create_query_map(self, db_name):
else:
columns.append(dict(name=row[3], type='TEXT'))
query_map[table] = columns
- except :
+ except:
self.logger.error('Error during query map creation')
- return query_map
\ No newline at end of file
+ return query_map
diff --git a/tanner/utils/patterns.py b/tanner/utils/patterns.py
index d801ae8d..48e1a305 100644
--- a/tanner/utils/patterns.py
+++ b/tanner/utils/patterns.py
@@ -6,8 +6,11 @@
LFI_ATTACK = re.compile('.*(\/\.\.)*(home|proc|usr|etc)\/.*')
LFI_FILEPATH = re.compile('((\.\.|\/).*)')
XSS_ATTACK = re.compile('.*<(.|\n)*?>')
-CMD_ATTACK = re.compile('.*(alias|cat|cd|cp|echo|exec|find|for|grep|ifconfig|ls|man|mkdir|netstat|ping|ps|pwd|uname|wget|touch|while).*')
+CMD_ATTACK = re.compile(
+ '.*(alias|cat|cd|cp|echo|exec|find|for|grep|ifconfig|ls|man|mkdir|netstat|ping|ps|pwd|uname|wget|touch|while).*')
+PHP_CODE_INJECTION = re.compile('.*(;)*(echo|system|print|phpinfo)(\(.*\)).*')
+CRLF_ATTACK = re.compile('.*(\r\n).*')
REMOTE_FILE_URL = re.compile('(.*(http(s){0,1}|ftp(s){0,1}):.*)')
WORD_PRESS_CONTENT = re.compile('\/wp-content\/.*')
HTML_TAGS = re.compile('.*<(.*)>.*')
-QUERY = re.compile('.*\?.*=')
\ No newline at end of file
+QUERY = re.compile('.*\?.*=')
diff --git a/tanner/utils/sqlite_db_helper.py b/tanner/utils/sqlite_db_helper.py
index 44464ff1..4d4d41d6 100644
--- a/tanner/utils/sqlite_db_helper.py
+++ b/tanner/utils/sqlite_db_helper.py
@@ -1,14 +1,11 @@
-import asyncio
-import json
import logging
import os
-import random
import shutil
import sqlite3
-from tanner.config import TannerConfig
from tanner.utils.base_db_helper import BaseDBHelper
+
class SQLITEDBHelper(BaseDBHelper):
def __init__(self):
super(SQLITEDBHelper, self).__init__()
diff --git a/tanner/web/server.py b/tanner/web/server.py
index 2c301f08..193350b6 100644
--- a/tanner/web/server.py
+++ b/tanner/web/server.py
@@ -8,6 +8,7 @@
from tanner import redis_client
from tanner.config import TannerConfig
+
class TannerWebServer:
def __init__(self):
self.logger = logging.getLogger('tanner.web.tannerwebserver')
@@ -22,14 +23,14 @@ async def handle_index(self, request):
async def handle_snares(self, request):
snares = await self.api.return_snares()
return {
- 'snares' : snares
+ 'snares': snares
}
@aiohttp_jinja2.template('snare.html')
async def handle_snare(self, request):
snare_uuid = request.match_info['snare_uuid']
- return{
- 'snare' : snare_uuid
+ return {
+ 'snare': snare_uuid
}
@aiohttp_jinja2.template('snare-stats.html')
@@ -37,7 +38,7 @@ async def handle_snare_stats(self, request):
snare_uuid = request.match_info['snare_uuid']
snare_stats = await self.api.return_snare_stats(snare_uuid)
return {
- 'snare_stats' : snare_stats
+ 'snare_stats': snare_stats
}
@aiohttp_jinja2.template('sessions.html')
@@ -59,26 +60,26 @@ async def handle_sessions(self, request):
result = 'Invalid filter definition'
else:
sessions = await self.api.return_sessions(applied_filters)
- result = sessions[15*(page_id-1):15*page_id]
+ result = sessions[15 * (page_id - 1):15 * page_id]
next_val = None
pre_val = None
- if(page_id*15 <= len(sessions)):
+ if page_id * 15 <= len(sessions):
next_val = '/{snare_uuid}/sessions/page/{page_id}'.format(snare_uuid=snare_uuid,
page_id=str(page_id + 1)
)
if len(applied_filters) > 1:
next_val += '?filters={filters}'.format(filters=params['filters'])
- if(page_id > 1):
+ if page_id > 1:
pre_val = '/{snare_uuid}/sessions/page/{page_id}'.format(snare_uuid=snare_uuid,
- page_id=str(page_id - 1)
- )
+ page_id=str(page_id - 1)
+ )
if len(applied_filters) > 1:
pre_val += '?filters={filters}'.format(filters=params['filters'])
return {
- 'sessions' : result,
- 'next_val' : next_val,
- 'pre_val' : pre_val
+ 'sessions': result,
+ 'next_val': next_val,
+ 'pre_val': pre_val
}
@aiohttp_jinja2.template('session.html')
@@ -86,7 +87,7 @@ async def handle_session_info(self, request):
sess_uuid = request.match_info['sess_uuid']
session = await self.api.return_session_info(sess_uuid)
return {
- 'session' : session
+ 'session': session
}
async def on_shutdown(self, app):
@@ -102,9 +103,9 @@ def setup_routes(self, app):
app.router.add_static('/static/', path='tanner/web/static')
def create_app(self, loop):
- app = web.Application(loop= loop)
+ app = web.Application(loop=loop)
aiohttp_jinja2.setup(app,
- loader= jinja2.FileSystemLoader('tanner/web/templates'))
+ loader=jinja2.FileSystemLoader('tanner/web/templates'))
app.on_shutdown.append(self.on_shutdown)
self.setup_routes(app)
return app