From d81f3580e955f5d6b3a0432dbbba91dd7d932437 Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Thu, 10 Aug 2017 02:22:12 +0530 Subject: [PATCH 01/13] PHP Code Injection Emulator (#183) * Basic php code injection * make injection more particular * add new function * make regex hard * fix tests * minor changes --- tanner/emulators/base.py | 20 +++++++++------ tanner/emulators/php_code_injection.py | 35 ++++++++++++++++++++++++++ tanner/utils/patterns.py | 3 ++- 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 tanner/emulators/php_code_injection.py diff --git a/tanner/emulators/base.py b/tanner/emulators/base.py index b57a3c47..1a12dd4d 100644 --- a/tanner/emulators/base.py +++ b/tanner/emulators/base.py @@ -5,20 +5,24 @@ import yarl 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 from tanner.utils import patterns class BaseHandler: def __init__(self, base_dir, db_name, loop=None): + self.emulator_enabled = TannerConfig.get('EMULATORS', '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 + '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 } - self.get_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'cmd_exec'] - self.post_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'cmd_exec'] + + self.get_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'cmd_exec' ] + self.post_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'cmd_exec'] self.cookie_emulators = ['sqli'] def extract_get_data(self, path): diff --git a/tanner/emulators/php_code_injection.py b/tanner/emulators/php_code_injection.py new file mode 100644 index 00000000..8d5bc549 --- /dev/null +++ b/tanner/emulators/php_code_injection.py @@ -0,0 +1,35 @@ +import aiohttp +import asyncio +import logging + +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) + try: + async with aiohttp.ClientSession(loop=self._loop) as session: + async with session.post('http://127.0.0.1:8088/', 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 '' + return result['stdout'] diff --git a/tanner/utils/patterns.py b/tanner/utils/patterns.py index d801ae8d..d4e7b4ca 100644 --- a/tanner/utils/patterns.py +++ b/tanner/utils/patterns.py @@ -7,7 +7,8 @@ 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).*') +PHP_CODE_INJECTION = re.compile('.*(;)*(echo|system|print|phpinfo)(\(.*\)).*') 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('.*\?.*=') From f035baa47fe725901eca8dd1007c3f4871206936 Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Wed, 16 Aug 2017 11:59:40 +0530 Subject: [PATCH 02/13] Snare tanner communication (#184) * Make different type of detection * Make emulator compatible with the new structure * fix tests * fix typo --- tanner/emulators/base.py | 24 ++++++++++++++++-------- tanner/emulators/cmd_exec.py | 4 ++-- tanner/emulators/lfi.py | 3 ++- tanner/emulators/php_code_injection.py | 4 ++-- tanner/emulators/rfi.py | 4 ++-- tanner/emulators/sqli.py | 2 +- tanner/emulators/xss.py | 2 +- tanner/tests/test_lfi_emulator.py | 6 +++--- tanner/tests/test_sqli.py | 4 ++-- tanner/tests/test_xss_emulator.py | 2 +- 10 files changed, 32 insertions(+), 23 deletions(-) diff --git a/tanner/emulators/base.py b/tanner/emulators/base.py index 1a12dd4d..2a5bd6af 100644 --- a/tanner/emulators/base.py +++ b/tanner/emulators/base.py @@ -61,8 +61,9 @@ async def get_emulation_result(self, session, data, target_emulators): 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 async def handle_post(self, session, data): @@ -112,12 +113,19 @@ async def emulate(self, data, session): detection = await self.handle_post(session, data) 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 return detection diff --git a/tanner/emulators/cmd_exec.py b/tanner/emulators/cmd_exec.py index b751ef42..5a1b4f5b 100644 --- a/tanner/emulators/cmd_exec.py +++ b/tanner/emulators/cmd_exec.py @@ -17,7 +17,7 @@ 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): @@ -29,4 +29,4 @@ def scan(self, value): 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/lfi.py b/tanner/emulators/lfi.py index baba3973..89a1495e 100644 --- a/tanner/emulators/lfi.py +++ b/tanner/emulators/lfi.py @@ -35,5 +35,6 @@ 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/php_code_injection.py b/tanner/emulators/php_code_injection.py index 8d5bc549..73625ab5 100644 --- a/tanner/emulators/php_code_injection.py +++ b/tanner/emulators/php_code_injection.py @@ -31,5 +31,5 @@ def scan(self, value): 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 '' - return result['stdout'] + 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..c15822ca 100644 --- a/tanner/emulators/rfi.py +++ b/tanner/emulators/rfi.py @@ -96,6 +96,6 @@ def scan(self, value): 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..585afc09 100644 --- a/tanner/emulators/sqli.py +++ b/tanner/emulators/sqli.py @@ -52,7 +52,7 @@ async def get_sqli_result(self, attack_value, attacker_db): 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/xss.py b/tanner/emulators/xss.py index d7c5638b..680d1737 100644 --- a/tanner/emulators/xss.py +++ b/tanner/emulators/xss.py @@ -18,7 +18,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/tests/test_lfi_emulator.py b/tanner/tests/test_lfi_emulator.py index abf32bdc..d5e71284 100644 --- a/tanner/tests/test_lfi_emulator.py +++ b/tanner/tests/test_lfi_emulator.py @@ -13,14 +13,14 @@ def setUp(self): def test_handle_abspath_lfi(self): 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')] 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')] 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_sqli.py b/tanner/tests/test_sqli.py index da1d7326..cc63115d 100644 --- a/tanner/tests/test_sqli.py +++ b/tanner/tests/test_sqli.py @@ -47,7 +47,7 @@ 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) @@ -57,4 +57,4 @@ def test_get_sqli_result_error(self): 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) diff --git a/tanner/tests/test_xss_emulator.py b/tanner/tests/test_xss_emulator.py index 117d21ac..c5f702ee 100644 --- a/tanner/tests/test_xss_emulator.py +++ b/tanner/tests/test_xss_emulator.py @@ -24,5 +24,5 @@ def test_xss(self): 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) From 93e460933ca2853ba0db07a4b1993ee101da858c Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Fri, 18 Aug 2017 22:15:48 +0530 Subject: [PATCH 03/13] CRLF Emulator (#186) * add crlf pattern * basic crlf emulator model * basic crlf emulator model * add crlf to base emulator --- tanner/emulators/base.py | 11 ++++++----- tanner/emulators/crlf.py | 20 ++++++++++++++++++++ tanner/utils/patterns.py | 1 + 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 tanner/emulators/crlf.py diff --git a/tanner/emulators/base.py b/tanner/emulators/base.py index 2a5bd6af..ec1e5c26 100644 --- a/tanner/emulators/base.py +++ b/tanner/emulators/base.py @@ -5,24 +5,25 @@ import yarl from tanner.config import TannerConfig -from tanner.emulators import lfi, rfi, sqli, xss, cmd_exec, php_code_injection +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('EMULATORS', 'emulator_enabled') - + self.emulators = { '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 + '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' ] - self.post_emulators = ['sqli', 'rfi', 'lfi', 'xss', 'php_code_injection', 'cmd_exec'] + 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): diff --git a/tanner/emulators/crlf.py b/tanner/emulators/crlf.py new file mode 100644 index 00000000..6e8a0c12 --- /dev/null +++ b/tanner/emulators/crlf.py @@ -0,0 +1,20 @@ +import logging +import re + +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/utils/patterns.py b/tanner/utils/patterns.py index d4e7b4ca..04173bb4 100644 --- a/tanner/utils/patterns.py +++ b/tanner/utils/patterns.py @@ -8,6 +8,7 @@ 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).*') 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('.*<(.*)>.*') From f517c9758d881006da9472c651102c486340dff9 Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Tue, 22 Aug 2017 19:14:47 +0530 Subject: [PATCH 04/13] docs for crlf and code injection (#194) --- docs/source/emulators.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 From 27cbf9af6766ebf7e4110c30c447ae7aa4792f48 Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Tue, 22 Aug 2017 23:13:47 +0530 Subject: [PATCH 05/13] Tests for crlf and code injection (#195) --- tanner/tests/test_crlf.py | 21 +++++++++++++++++++++ tanner/tests/test_php_code_injetion.py | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tanner/tests/test_crlf.py create mode 100644 tanner/tests/test_php_code_injetion.py diff --git a/tanner/tests/test_crlf.py b/tanner/tests/test_crlf.py new file mode 100644 index 00000000..8dcb11ae --- /dev/null +++ b/tanner/tests/test_crlf.py @@ -0,0 +1,21 @@ +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_php_code_injetion.py b/tanner/tests/test_php_code_injetion.py new file mode 100644 index 00000000..ef98bf9a --- /dev/null +++ b/tanner/tests/test_php_code_injetion.py @@ -0,0 +1,25 @@ +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) From ac6b1bc6708ea870dc214316b1cfaa5b86780c0f Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Tue, 22 Aug 2017 23:15:46 +0530 Subject: [PATCH 06/13] fix sqlite problem (#193) * fix sqlite problem * fix tests --- tanner/emulators/sqli.py | 5 +++-- tanner/tests/test_sqli.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tanner/emulators/sqli.py b/tanner/emulators/sqli.py index 585afc09..53215b9a 100644 --- a/tanner/emulators/sqli.py +++ b/tanner/emulators/sqli.py @@ -30,7 +30,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,9 +45,10 @@ 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): diff --git a/tanner/tests/test_sqli.py b/tanner/tests/test_sqli.py index cc63115d..056a24de 100644 --- a/tanner/tests/test_sqli.py +++ b/tanner/tests/test_sqli.py @@ -10,7 +10,7 @@ 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'}, @@ -57,4 +57,4 @@ def test_get_sqli_result_error(self): 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) + self.assertEqual(assert_result, result['value']) From 2f18e542d28430fc578c228ee8be2bc4d8d302b0 Mon Sep 17 00:00:00 2001 From: Ravinder Nehra Date: Mon, 28 Aug 2017 14:59:21 +0530 Subject: [PATCH 07/13] Some small changes (#198) * add phpox address in config * return tanner verion to snare * fix typo --- docs/source/config.rst | 9 +++++++-- tanner/config.py | 1 + tanner/emulators/base.py | 3 ++- tanner/emulators/php_code_injection.py | 10 +++++++--- tanner/emulators/rfi.py | 6 +++++- tanner/tests/test_config.py | 2 ++ 6 files changed, 24 insertions(+), 7 deletions(-) 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/tanner/config.py b/tanner/config.py index 1b0d0ae2..a8417b41 100644 --- a/tanner/config.py +++ b/tanner/config.py @@ -9,6 +9,7 @@ '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/tanner/emulators/base.py b/tanner/emulators/base.py index ec1e5c26..655ed48a 100644 --- a/tanner/emulators/base.py +++ b/tanner/emulators/base.py @@ -4,6 +4,7 @@ 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, php_code_injection, crlf from tanner.utils import patterns @@ -127,7 +128,7 @@ async def emulate(self, data, session): 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/php_code_injection.py b/tanner/emulators/php_code_injection.py index 73625ab5..0c98049b 100644 --- a/tanner/emulators/php_code_injection.py +++ b/tanner/emulators/php_code_injection.py @@ -2,6 +2,7 @@ import asyncio import logging +from tanner import config from tanner.utils import patterns class PHPCodeInjection: @@ -9,12 +10,15 @@ 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): + 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('http://127.0.0.1:8088/', data=code) as resp: + 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) @@ -31,5 +35,5 @@ def scan(self, value): 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(status_code=504) return dict(value=result['stdout'], page=False) diff --git a/tanner/emulators/rfi.py b/tanner/emulators/rfi.py index c15822ca..1ad4185d 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) diff --git a/tanner/tests/test_config.py b/tanner/tests/test_config.py index 9e78083d..40539551 100644 --- a/tanner/tests/test_config.py +++ b/tanner/tests/test_config.py @@ -13,6 +13,7 @@ def setUp(self): 'TANNER': {'host': '0.0.0.0', 'port': '9000'}, 'WEB': {'host': '0.0.0.0', 'port': '9001'}, 'WEB': {'host': '0.0.0.0', 'port': '9002'}, + 'PHPOX': {'host': '0.0.0.0', 'port': '9078'}, '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'}, @@ -62,6 +63,7 @@ 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}, From 60162847eaf26b1fef223b7b8563e213bc91d3ce Mon Sep 17 00:00:00 2001 From: Evgeniya Tokarchuk Date: Sun, 17 Dec 2017 02:32:46 +0100 Subject: [PATCH 08/13] merge fixes --- tanner/config.py | 12 ++++++++++++ tanner/emulators/base.py | 2 +- tanner/tests/test_config.py | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tanner/config.py b/tanner/config.py index a8417b41..766abeb9 100644 --- a/tanner/config.py +++ b/tanner/config.py @@ -48,3 +48,15 @@ def get(section, value): return res else: return config_template[section][value] + + @staticmethod + def get_section(section): + if TannerConfig.config is not None: + try: + res = TannerConfig.config[section] + 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] + return res + else: + return config_template[section] diff --git a/tanner/emulators/base.py b/tanner/emulators/base.py index 655ed48a..0a2d54a7 100644 --- a/tanner/emulators/base.py +++ b/tanner/emulators/base.py @@ -11,7 +11,7 @@ class BaseHandler: def __init__(self, base_dir, db_name, loop=None): - self.emulator_enabled = TannerConfig.get('EMULATORS', 'emulator_enabled') + self.emulator_enabled = TannerConfig.get_section('EMULATOR_ENABLED') self.emulators = { 'rfi': rfi.RfiEmulator(base_dir, loop) if self.emulator_enabled['rfi'] else None, diff --git a/tanner/tests/test_config.py b/tanner/tests/test_config.py index 40539551..0906c4c0 100644 --- a/tanner/tests/test_config.py +++ b/tanner/tests/test_config.py @@ -12,8 +12,8 @@ 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'}, - 'PHPOX': {'host': '0.0.0.0', 'port': '9078'}, + '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'}, From d6fa2f331bd3716aaba09107ba769f37a6ad3eb6 Mon Sep 17 00:00:00 2001 From: Evgeniya Date: Sat, 20 Jan 2018 23:35:34 +0100 Subject: [PATCH 09/13] fix config --- tanner/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tanner/config.py b/tanner/config.py index 766abeb9..78ac33e9 100644 --- a/tanner/config.py +++ b/tanner/config.py @@ -12,7 +12,7 @@ '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}, + 'EMULATOR_ENABLED': {'sqli': True, 'rfi': True, 'lfi': True, 'xss': True, 'cmd_exec': True, 'php_code_injection': 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'}, From c90164469436e34237a2a3d872ce89fabe9d0985 Mon Sep 17 00:00:00 2001 From: Evgeniya Date: Sat, 20 Jan 2018 23:40:31 +0100 Subject: [PATCH 10/13] add crlf --- tanner/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tanner/config.py b/tanner/config.py index 78ac33e9..718d3ab7 100644 --- a/tanner/config.py +++ b/tanner/config.py @@ -12,7 +12,7 @@ '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, 'php_code_injection': True}, + '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'}, From ba2f2adf614a0d093f7e9b86067ac511d753d208 Mon Sep 17 00:00:00 2001 From: Evgeniia Date: Tue, 30 Jan 2018 17:16:33 +0100 Subject: [PATCH 11/13] Linting (#223) * reformat all the code according to pep8, fix small issues * proper check for config values, sqlite test fixes --- bin/tanner | 3 +- bin/tannerapi | 5 +- bin/tannerweb | 4 +- tanner/__init__.py | 2 +- tanner/api/api.py | 34 +++++----- tanner/api/server.py | 6 +- tanner/config.py | 3 +- tanner/emulators/base.py | 33 ++++----- tanner/emulators/cmd_exec.py | 10 ++- tanner/emulators/crlf.py | 4 +- tanner/emulators/lfi.py | 9 ++- tanner/emulators/mysqli.py | 62 ++++++++--------- tanner/emulators/php_code_injection.py | 1 + tanner/emulators/rfi.py | 2 +- tanner/emulators/sqli.py | 7 +- tanner/emulators/sqlite.py | 4 +- tanner/emulators/xss.py | 8 +-- tanner/redis_client.py | 2 +- tanner/reporting/log_mongodb.py | 6 +- tanner/server.py | 13 ++-- tanner/session.py | 7 +- tanner/session_analyzer.py | 8 +-- tanner/session_manager.py | 3 +- tanner/tests/test_api_server.py | 22 ++++-- tanner/tests/test_base.py | 38 +++++------ tanner/tests/test_cmd_exec_emulation.py | 13 ++-- tanner/tests/test_config.py | 13 ++-- tanner/tests/test_crlf.py | 3 +- tanner/tests/test_lfi_emulator.py | 8 +-- tanner/tests/test_php_code_injetion.py | 4 +- tanner/tests/test_rfi_emulation.py | 3 - tanner/tests/test_server.py | 7 +- tanner/tests/test_session_analyzer.py | 1 - tanner/tests/test_session_manager.py | 4 +- tanner/tests/test_sqli.py | 21 +++--- tanner/tests/test_sqlite.py | 89 +++++++++++++------------ tanner/tests/test_xss_emulator.py | 9 ++- tanner/utils/base_db_helper.py | 11 +-- tanner/utils/docker_helper.py | 27 ++++---- tanner/utils/logger.py | 8 ++- tanner/utils/mysql_db_helper.py | 41 ++++++------ tanner/utils/patterns.py | 3 +- tanner/utils/sqlite_db_helper.py | 5 +- tanner/web/server.py | 31 ++++----- 44 files changed, 297 insertions(+), 300 deletions(-) diff --git a/bin/tanner b/bin/tanner index a47aea7e..802d1a90 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 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/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 718d3ab7..b33a2b72 100644 --- a/tanner/config.py +++ b/tanner/config.py @@ -4,6 +4,7 @@ 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}, @@ -55,7 +56,7 @@ def get_section(section): try: res = TannerConfig.config[section] except (configparser.NoOptionError, configparser.NoSectionError): - LOGGER.warning("Error in config, default value will be used. Section: %s Value: %s", section, value) + LOGGER.warning("Error in config, default value will be used. Section: %s Value: %s", section) res = config_template[section] return res else: diff --git a/tanner/emulators/base.py b/tanner/emulators/base.py index 0a2d54a7..243e93dd 100644 --- a/tanner/emulators/base.py +++ b/tanner/emulators/base.py @@ -1,4 +1,3 @@ -import asyncio import mimetypes import re import urllib.parse @@ -9,19 +8,21 @@ 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 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 - } + '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'] @@ -53,19 +54,19 @@ 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) if emulation_result: detection['payload'] = emulation_result - + return detection async def handle_post(self, session, data): @@ -91,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 @@ -115,7 +116,7 @@ async def emulate(self, data, session): detection = await self.handle_post(session, data) else: detection = await self.handle_get(session, data) - + if 'payload' not in detection: detection['type'] = 1 elif 'payload' in detection: diff --git a/tanner/emulators/cmd_exec.py b/tanner/emulators/cmd_exec.py index 5a1b4f5b..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) @@ -23,10 +21,10 @@ async def get_cmd_exec_results(self, container, cmd): 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 diff --git a/tanner/emulators/crlf.py b/tanner/emulators/crlf.py index 6e8a0c12..3e12111c 100644 --- a/tanner/emulators/crlf.py +++ b/tanner/emulators/crlf.py @@ -1,8 +1,6 @@ -import logging -import re - from tanner.utils import patterns + class CRLFEmulator: def scan(self, value): diff --git a/tanner/emulators/lfi.py b/tanner/emulators/lfi.py index 89a1495e..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,7 +27,7 @@ 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): 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 index 0c98049b..11087c86 100644 --- a/tanner/emulators/php_code_injection.py +++ b/tanner/emulators/php_code_injection.py @@ -5,6 +5,7 @@ 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() diff --git a/tanner/emulators/rfi.py b/tanner/emulators/rfi.py index 1ad4185d..71f79fd4 100644 --- a/tanner/emulators/rfi.py +++ b/tanner/emulators/rfi.py @@ -94,7 +94,7 @@ 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): diff --git a/tanner/emulators/sqli.py b/tanner/emulators/sqli.py index 53215b9a..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): 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 680d1737..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): 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 0906c4c0..5378ec00 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 @@ -17,13 +18,14 @@ def setUp(self): '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() @@ -67,13 +69,14 @@ def test_get_when_file_dont_exists(self): '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 index 8dcb11ae..f7c33484 100644 --- a/tanner/tests/test_crlf.py +++ b/tanner/tests/test_crlf.py @@ -3,6 +3,7 @@ from tanner.emulators import crlf + class TestCRLF(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() @@ -16,6 +17,6 @@ def test_scan(self): 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'} + 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 d5e71284..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['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['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['value']) diff --git a/tanner/tests/test_php_code_injetion.py b/tanner/tests/test_php_code_injetion.py index ef98bf9a..9e4197d9 100644 --- a/tanner/tests/test_php_code_injetion.py +++ b/tanner/tests/test_php_code_injetion.py @@ -3,6 +3,7 @@ from tanner.emulators import php_code_injection + class TestPHPCodeInjection(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() @@ -18,8 +19,9 @@ def test_scan(self): 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) + 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 056a24de..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 @@ -12,34 +11,34 @@ def setUp(self): 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']] @@ -52,7 +51,7 @@ async def mock_execute_query(query, db_name): 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' 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 c5f702ee..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,15 +12,15 @@ 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'], page=True) 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 04173bb4..48e1a305 100644 --- a/tanner/utils/patterns.py +++ b/tanner/utils/patterns.py @@ -6,7 +6,8 @@ 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}):.*)') 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 From 29d8a34bf876e6ac594a80923535c5b0e8c4b77b Mon Sep 17 00:00:00 2001 From: Evgeniia Date: Tue, 30 Jan 2018 17:16:43 +0100 Subject: [PATCH 12/13] get boolean values in a proper way (#224) * get boolean values in a proper way * fix tests --- bin/tanner | 2 +- tanner/config.py | 37 +++++++++++++++++++++++++++---------- tanner/tests/test_config.py | 8 ++++++-- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/bin/tanner b/bin/tanner index 802d1a90..cfd33344 100644 --- a/bin/tanner +++ b/bin/tanner @@ -25,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/tanner/config.py b/tanner/config.py index b33a2b72..e69db282 100644 --- a/tanner/config.py +++ b/tanner/config.py @@ -13,12 +13,15 @@ '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, 'php_code_injection': True, "crlf":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} } @@ -39,25 +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: - return config_template[section][value] + res = config_template[section][value] + return res @staticmethod def get_section(section): + res = {} if TannerConfig.config is not None: try: - res = TannerConfig.config[section] + 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] - return res + else: - return config_template[section] + res = config_template[section] + + return res diff --git a/tanner/tests/test_config.py b/tanner/tests/test_config.py index 5378ec00..b075c0a8 100644 --- a/tanner/tests/test_config.py +++ b/tanner/tests/test_config.py @@ -54,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): From 0f28fb496a9568ad48c0e081ee372bfde06dda4a Mon Sep 17 00:00:00 2001 From: Evgeniya Date: Tue, 30 Jan 2018 23:28:35 +0100 Subject: [PATCH 13/13] change version in setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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',