Permalink
Browse files

MySQLI emulator (#133)

* Add basic function for setting a new MySQL database

* fix

* Implement creation of attacker db

* Use config data for connecting to DB

* fix

* fix connection to DB using config

* Replace pymysql with aiomysql

* Abstract DBHelper

* fix db_helper

* Abstract sqli emulator

* Remove unnecessary test

* Add dependency aiomysql

* Fix query map

* Change name of default db

* Implement delete db after session expiry

* Port to async-wait syntax

* Remove unnecessary parameter

* fix tests
  • Loading branch information...
rnehra01 authored and afeena committed May 14, 2017
1 parent a127e64 commit d79e1b6a34906d2527214ed19364c8d7f8edddc3
View
@@ -1,4 +1,5 @@
aiohttp<2.0
aiomysql
elizabeth
yarl
redis
View
@@ -10,7 +10,7 @@
'TANNER': {'host': '0.0.0.0', 'port': 8090},
'REDIS': {'host': 'localhost', 'port': 6379, 'poolsize': 80, 'timeout': 1},
'EMULATORS': {'root_dir': '/opt/tanner'},
'SQLI': {'db_name': 'tanner.db'},
'SQLI': {'type':'MySQL', 'db_name': 'tanner_db', 'host':'localhost', 'user':'root', 'password':'user_pass'},
'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'},
View
@@ -30,7 +30,7 @@ def handle_post(self, session, data):
if xss_result:
detection = {'name': 'xss', 'order': 2, 'payload': xss_result}
else:
sqli_data = yield from self.emulators['sqli'].check_post_data(data)
sqli_data = self.emulators['sqli'].check_post_data(data)
if sqli_data:
sqli_result = yield from self.emulators['sqli'].handle(sqli_data, session, 1)
detection = {'name': 'sqli', 'order': 2, 'payload': sqli_result}
@@ -56,7 +56,7 @@ def handle_get(self, session, path):
attack_value = value
if detection['order'] <= 1:
sqli = yield from self.emulators['sqli'].check_get_data(path)
sqli = self.emulators['sqli'].check_get_data(path)
if sqli:
detection = {'name': 'sqli', 'order': 2}
attack_value = path
View
@@ -0,0 +1,39 @@
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()
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 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
View
@@ -5,34 +5,25 @@
import pylibinjection
from asyncio.subprocess import PIPE
from tanner.utils import db_helper
from tanner import config
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):
self.db_name = db_name
self.working_dir = os.path.join(working_dir, 'db/')
self.helper = db_helper.DBHelper()
self.query_map = None
if (TannerConfig.get('SQLI', 'type') == 'MySQL'):
self.sqli_emulator = mysqli.MySQLIEmulator(db_name)
else:
self.sqli_emulator = sqlite.SQLITEEmulator(db_name, working_dir)
@asyncio.coroutine
def setup_db(self):
if not os.path.exists(self.working_dir):
os.makedirs(self.working_dir)
db = os.path.join(self.working_dir, self.db_name)
if not os.path.exists(db):
yield from self.helper.setup_db_from_config(self.working_dir, self.db_name)
if self.query_map is None:
self.query_map = yield from self.helper.create_query_map(self.working_dir, self.db_name)
self.query_map = None
@staticmethod
def check_sqli(path):
payload = bytes(path, 'utf-8')
sqli = pylibinjection.detect_sqli(payload)
return int(sqli['sqli'])
@asyncio.coroutine
def check_post_data(self, data):
sqli_data = []
for (param, value) in data['post_data'].items():
@@ -41,31 +32,19 @@ def check_post_data(self, data):
sqli_data.append((param, value))
return sqli_data
@asyncio.coroutine
def check_get_data(self, path):
request_query = urllib.parse.urlparse(path).query
parsed_queries = urllib.parse.parse_qsl(request_query)
for query in parsed_queries:
sqli = self.check_sqli(query[1])
return sqli
@asyncio.coroutine
def create_attacker_db(self, session):
attacker_db_name = session.sess_uuid.hex + '.db'
attacker_db = yield from self.helper.copy_db(self.db_name,
attacker_db_name,
self.working_dir
)
session.associate_db(attacker_db)
return attacker_db
@staticmethod
def prepare_get_query(path):
query = urllib.parse.urlparse(path).query
parsed_query = urllib.parse.parse_qsl(query)
return parsed_query
@asyncio.coroutine
def map_query(self, query):
db_query = None
param = query[0][0]
@@ -84,37 +63,24 @@ def map_query(self, query):
return db_query
@staticmethod
def execute_query(query, db):
result = []
conn = sqlite3.connect(db)
cursor = conn.cursor()
try:
for row in cursor.execute(query):
result.append(list(row))
except sqlite3.OperationalError as sqlite_error:
result = str(sqlite_error)
return result
@asyncio.coroutine
def get_sqli_result(self, query, attacker_db):
db_query = yield from self.map_query(query)
async def get_sqli_result(self, query, attacker_db):
db_query = self.map_query(query)
if db_query is None:
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(query[0][0])
else:
execute_result = self.execute_query(db_query, 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, page='/index.html')
return result
@asyncio.coroutine
def handle(self, path, session, post_request=0):
yield from self.setup_db()
async def handle(self, path, session, post_request=0):
if self.query_map is None:
self.query_map = await self.sqli_emulator.setup_db(self.query_map)
if not post_request:
path = self.prepare_get_query(path)
attacker_db = yield from self.create_attacker_db(session)
result = yield from self.get_sqli_result(path, attacker_db)
attacker_db = await self.sqli_emulator.create_attacker_db(session)
result = await self.get_sqli_result(path, attacker_db)
return result
View
@@ -0,0 +1,42 @@
import asyncio
import os
import sqlite3
from tanner.utils import sqlite_db_helper
from tanner import config
class SQLITEEmulator:
def __init__(self, db_name, working_dir):
self.db_name = db_name
self.working_dir = os.path.join(working_dir, 'db/')
self.helper = sqlite_db_helper.SQLITEDBHelper()
async def setup_db(self, query_map):
if not os.path.exists(self.working_dir):
os.makedirs(self.working_dir)
db = os.path.join(self.working_dir, self.db_name)
if not os.path.exists(db):
await self.helper.setup_db_from_config(self.working_dir, self.db_name)
query_map = self.helper.create_query_map(self.working_dir, self.db_name)
return query_map
async def create_attacker_db(self, session):
attacker_db_name = 'attacker_' + session.sess_uuid.hex
attacker_db = self.helper.copy_db(self.db_name,
attacker_db_name,
self.working_dir
)
session.associate_db(attacker_db)
return attacker_db
async def execute_query(self, query, db):
result = []
conn = sqlite3.connect(db)
cursor = conn.cursor()
try:
for row in cursor.execute(query):
result.append(list(row))
except sqlite3.OperationalError as sqlite_error:
result = str(sqlite_error)
return result
View
@@ -1,8 +1,11 @@
import json
import time
import os
import asyncio
import uuid
from tanner.config import TannerConfig
from tanner.utils.mysql_db_helper import MySQLDBHelper
from tanner.utils.sqlite_db_helper import SQLITEDBHelper
class Session:
KEEP_ALIVE_TIME = 75
@@ -59,9 +62,11 @@ def set_attack_type(self, path, attack_type):
def associate_db(self, db_name):
self.associated_db = db_name
def remove_associated_db(self):
if self.associated_db is not None and os.path.exists(self.associated_db):
os.remove(self.associated_db)
async def remove_associated_db(self):
if(TannerConfig.get('SQLI', 'type') == 'MySQL'):
await MySQLDBHelper().delete_db(self.associated_db)
else:
SQLITEDBHelper().delete_db(self.associated_db)
def get_uuid(self):
return str(self.sess_uuid)
@@ -72,7 +72,7 @@ def delete_old_sessions(self, redis_client):
for sess in self.sessions:
if not sess.is_expired():
continue
sess.remove_associated_db()
yield from sess.remove_associated_db()
self.sessions.remove(sess)
try:
yield from redis_client.set(sess.get_uuid(), sess.to_json())
@@ -15,7 +15,7 @@ def setUp(self):
'TANNER': {'host': '0.0.0.0', 'port': '9000'},
'REDIS': {'host': 'localhost', 'port': '1337', 'poolsize': '40', 'timeout': '5'},
'EMULATORS': {'root_dir': '/tmp/user_tanner'},
'SQLI': {'db_name': 'user_tanner.db'},
'SQLI': {'type':'MySQL', 'db_name': 'user_tanner_db', 'host':'localhost', 'user':'user_name', 'password':'user_pass'},
'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'}
@@ -59,7 +59,7 @@ def test_get_when_file_dont_exists(self):
'TANNER': {'host': '0.0.0.0', 'port': 8090},
'REDIS': {'host': 'localhost', 'port': 6379, 'poolsize': 80, 'timeout': 1},
'EMULATORS': {'root_dir': '/opt/tanner'},
'SQLI': {'db_name': 'tanner.db'},
'SQLI': {'type':'MySQL', 'db_name': 'tanner_db', 'host':'localhost', 'user':'root', 'password':'user_pass'},
'LOGGER': {'log_file': '/opt/tanner/tanner.log'},
'MONGO': {'enabled': 'False', 'URI': 'mongodb://localhost'},
'LOCALLOG': {'enabled': 'False', 'PATH': '/tmp/tanner_report.json'}
View
@@ -24,22 +24,16 @@ def setUp(self):
self.handler = sqli.SqliEmulator('test.db', '/tmp/')
self.handler.query_map = query_map
def test_db_copy(self):
session = mock.Mock()
session.sess_uuid.hex = 'ad16014d-9b4a-451d-a6d1-fc8681566458'
self.loop.run_until_complete(self.handler.create_attacker_db(session))
self.assertTrue(os.path.exists('/tmp/db/ad16014d-9b4a-451d-a6d1-fc8681566458.db'))
def test_map_query_id(self):
query = [('id', '1\'UNION SELECT 1,2,3,4')]
assert_result = 'SELECT * from users WHERE id=1 UNION SELECT 1,2,3,4;'
result = self.loop.run_until_complete(self.handler.map_query(query))
result = self.handler.map_query(query)
self.assertEqual(assert_result, result)
def test_map_query_comments(self):
query = [('comment', '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.loop.run_until_complete(self.handler.map_query(query))
result = self.handler.map_query(query)
self.assertEqual(assert_result, result)
def test_map_query_error(self):
@@ -0,0 +1,58 @@
import asyncio
import elizabeth
import json
import logging
import random
from tanner.config import TannerConfig
class BaseDBHelper:
def __init__(self):
self.logger = logging.getLogger('tanner.base_db_helper.BaseDBHelper')
def read_config(self):
with open(TannerConfig.get('DATA', 'db_config')) as db_config:
try:
config = json.load(db_config)
except json.JSONDecodeError as json_error:
self.logger.info('Failed to load json: %s', json_error)
else:
return config
def generate_dummy_data(self, data_tokens):
"""
Insert dummy data based on data tokens
I - integer id
L - login/username
E - email
P - password
T - piece of text
:return:
"""
token_list = data_tokens.split(',')
samples_count = random.randint(100, 1000)
inserted_data = []
for i in range(samples_count):
values = []
for token in token_list:
if token == 'I':
values.append(i)
if token == 'L':
data = elizabeth.Personal().username()
values.append(data)
if token == 'E':
data = elizabeth.Personal().email()
values.append(data)
if token == 'P':
data = elizabeth.Personal().password()
values.append(data)
if token == 'T':
sample_length = random.randint(1,10)
data = elizabeth.Text().text(quantity= sample_length)
values.append(data)
inserted_data.append(tuple(values))
return inserted_data, token_list
Oops, something went wrong.

0 comments on commit d79e1b6

Please sign in to comment.