Permalink
Browse files

Command exec emulator (#144)

* Add CMD identifier pattern

* CmdExec emulator

* update DOCKER in requirements

* add DOCKER service to travis

* Use host-image from config

* fix when image is unavailable

* change default host_image

* add logging and error catching

* fix tests

* change calling function location

* Catch docker service exception

* Add TODO

* Support nested commands

* add get & post support using different approach

* fix merge conflicts

* change indentation to spaces

* remove unnecessary files
  • Loading branch information...
rnehra01 authored and afeena committed Jun 4, 2017
1 parent 369618a commit 6beb6275c5a10954ff5402e1ff04941213ffe42e
View
@@ -1,3 +1,6 @@
sudo: required
services:
- docker
language: python
python:
- "3.5"
View
@@ -1,6 +1,7 @@
aiohttp>=2.0
aiomysql
elizabeth
docker
elizabeth==0.3.27
yarl
redis
asyncio_redis
View
@@ -11,6 +11,7 @@
'REDIS': {'host': 'localhost', 'port': 6379, 'poolsize': 80, 'timeout': 1},
'EMULATORS': {'root_dir': '/opt/tanner'},
'SQLI': {'type':'SQLITE', 'db_name': 'tanner_db', 'host':'localhost', 'user':'root', 'password':'user_pass'},
'CMD_EXEC': {'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'},
View
@@ -3,7 +3,7 @@
import urllib.parse
import yarl
from tanner.emulators import lfi, rfi, sqli, xss
from tanner.emulators import lfi, rfi, sqli, xss, cmd_exec
from tanner.utils import patterns
@@ -20,7 +20,8 @@ def __init__(self, base_dir, db_name, loop=None):
'rfi': rfi.RfiEmulator(base_dir, loop),
'lfi': lfi.LfiEmulator(base_dir),
'xss': xss.XssEmulator(),
'sqli': sqli.SqliEmulator(db_name, base_dir)
'sqli': sqli.SqliEmulator(db_name, base_dir),
'cmd_exec': cmd_exec.CmdExecEmulator()
}
async def handle_post(self, session, data):
@@ -33,6 +34,12 @@ def __init__(self, base_dir, db_name, loop=None):
if sqli_data:
sqli_result = await self.emulators['sqli'].handle(sqli_data, session, 1)
detection = {'name': 'sqli', 'order': 2, 'payload': sqli_result}
else:
cmd_exec_data = await self.emulators['cmd_exec'].check_post_data(data)
if cmd_exec_data:
cmd_exec_results = await self.emulators['cmd_exec'].handle(cmd_exec_data[0][1], session)
detection = {'name': 'cmd_exec', 'order': 3, 'payload': cmd_exec_results}
return detection
async def handle_get(self, session, path):
@@ -54,10 +61,15 @@ def __init__(self, base_dir, db_name, loop=None):
attack_value = value
if detection['order'] <= 1:
sqli = self.emulators['sqli'].check_get_data(path)
if sqli:
detection = {'name': 'sqli', 'order': 2}
attack_value = path
cmd_exec = await self.emulators['cmd_exec'].check_get_data(path)
if cmd_exec:
detection = {'name': 'cmd_exec', 'order': 3}
attack_value = cmd_exec[0][1]
else:
sqli = self.emulators['sqli'].check_get_data(path)
if sqli:
detection = {'name': 'sqli', 'order': 2}
attack_value = path
if detection['name'] in self.emulators:
emulation_result = await self.emulators[detection['name']].handle(attack_value, session)
@@ -0,0 +1,93 @@
import asyncio
import docker
import yarl
# TODO : Replace docker with aiodocker
import logging
from tanner.config import TannerConfig
from tanner.utils import patterns
class CmdExecEmulator:
def __init__(self):
try:
self.docker_client = docker.from_env(version='auto')
except docker.errors as docker_error:
self.logger.error('Error while connecting to docker service %s', docker_error)
self.host_image = TannerConfig.get('CMD_EXEC', 'host_image')
self.logger = logging.getLogger('tanner.cmd_exec_emulator.CmdExecEmulator')
async def setup_host_image(self):
try:
if not self.docker_client.images.list(self.host_image):
self.docker_client.images.pull(self.host_image)
except docker.errors 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)
)
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_attacker_env(self, session):
await self.setup_host_image()
container_name = 'attacker_' + session.sess_uuid.hex
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
)
session.associate_env(container_name)
except docker.errors as docker_error:
self.logger.error('Error while creating a container %s', docker_error)
return container
async def get_cmd_exec_results(self, container, cmd):
execute_result = None
try:
container.start()
execute_result = container.exec_run(['sh', '-c', cmd]).decode('utf-8')
container.kill()
except docker.errors.APIError as server_error:
self.logger.error('Error while executing command %s in container %s', cmd, server_error)
result = dict(value= execute_result, page= '/index.html')
return result
async def delete_env(self, container_name):
container = await self.get_container(container_name)
try:
if container:
container.remove(force = True)
except docker.errors.APIError as server_error:
self.logger.error('Error while removing container %s', server_error)
async def check_post_data(self, data):
cmd_data = []
for (param_id, param_value) in data['post_data'].items():
if patterns.CMD_ATTACK.match(param_value):
cmd_data.append((param_id, param_value))
return cmd_data
async def check_get_data(self, path):
cmd_data = []
query = yarl.URL(path).query_string
params = query.split('&')
for param in params:
if len(param.split('=')) == 2:
param_id, param_value = param.split('=')
if patterns.CMD_ATTACK.match(param_value):
cmd_data.append((param_id, param_value))
return cmd_data
async def handle(self, value, session= None):
container = await self.create_attacker_env(session)
result = await self.get_cmd_exec_results(container, value)
return result
View
@@ -1,9 +1,11 @@
import asyncio
import json
import time
import asyncio
import uuid
from tanner.config import TannerConfig
from tanner.emulators import cmd_exec
from tanner.utils.mysql_db_helper import MySQLDBHelper
from tanner.utils.sqlite_db_helper import SQLITEDBHelper
@@ -20,6 +22,7 @@ def __init__(self, data):
'response_status': data['status']}]
self.cookies = data['cookies']
self.associated_db = None
self.associated_env = None
except KeyError:
raise
@@ -68,5 +71,11 @@ def associate_db(self, db_name):
else:
SQLITEDBHelper().delete_db(self.associated_db)
def associate_env(self, env):
self.associated_env = env
async def remove_associated_env(self):
await cmd_exec.CmdExecEmulator().delete_env(self.associated_env)
def get_uuid(self):
return str(self.sess_uuid)
@@ -71,6 +71,8 @@ def get_session(self, data):
if not sess.is_expired():
continue
await sess.remove_associated_db()
sess.remove_associated_db()
await sess.remove_associated_env()
self.sessions.remove(sess)
try:
await redis_client.set(sess.get_uuid(), sess.to_json())
@@ -4,7 +4,6 @@
from tanner import config
class TestCongif(unittest.TestCase):
def setUp(self):
config.TannerConfig.config = None
@@ -16,6 +15,7 @@ def setUp(self):
'REDIS': {'host': 'localhost', 'port': '1337', 'poolsize': '40', 'timeout': '5'},
'EMULATORS': {'root_dir': '/tmp/user_tanner'},
'SQLI': {'type':'SQLITE', 'db_name': 'user_tanner_db', 'host':'localhost', 'user':'user_name', 'password':'user_pass'},
'CMD_EXEC': {'host_image': 'test_image'},
'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'}
@@ -60,6 +60,7 @@ def test_get_when_file_dont_exists(self):
'REDIS': {'host': 'localhost', 'port': 6379, 'poolsize': 80, 'timeout': 1},
'EMULATORS': {'root_dir': '/opt/tanner'},
'SQLI': {'type':'SQLITE', 'db_name': 'tanner_db', 'host':'localhost', 'user':'root', 'password':'user_pass'},
'CMD_EXEC': {'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'}
View
@@ -6,6 +6,7 @@
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).*')
REMOTE_FILE_URL = re.compile('(.*(http(s){0,1}|ftp(s){0,1}):.*)')
WORD_PRESS_CONTENT = re.compile('\/wp-content\/.*')
HTML_TAGS = re.compile('.*<(.*)>.*')

0 comments on commit 6beb627

Please sign in to comment.