diff --git a/docker/tanner/docker-compose.yml b/docker/tanner/docker-compose.yml new file mode 100644 index 00000000..9a650126 --- /dev/null +++ b/docker/tanner/docker-compose.yml @@ -0,0 +1,86 @@ +version: '2.3' + +networks: + tanner_local: + +services: + +# Tanner Redis Service + tanner_redis: + container_name: tanner_redis + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/redis:1804" + +# PHP Sandbox service + tanner_phpox: + build: ./phpox + container_name: tanner_phpox + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/phpox:1804" + +# Tanner API Service + tanner_api: + build: ./tanner + container_name: tanner_api + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/tanner:1804" + command: tannerapi + depends_on: + - tanner_redis + +# Tanner WEB Service + tanner_web: + build: ./tanner + container_name: tanner_web + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/tanner:1804" + command: tannerweb + depends_on: + - tanner_redis + +# Tanner Service + tanner: + build: ./tanner + container_name: tanner + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/tanner:1804" + command: tanner + depends_on: + - tanner_api + - tanner_web + - tanner_phpox + +# Snare Service + snare: + build: ./snare + container_name: snare + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + ports: + - "80:80" + image: "dtagdevsec/snare:1804" + depends_on: + - tanner diff --git a/docker/tanner/phpox/Dockerfile b/docker/tanner/phpox/Dockerfile new file mode 100644 index 00000000..8accf709 --- /dev/null +++ b/docker/tanner/phpox/Dockerfile @@ -0,0 +1,50 @@ +FROM alpine + +# Include dist +ADD dist/ /root/dist/ + +# Install packages +RUN apk -U --no-cache add \ + build-base \ + file \ + git \ + make \ + php7 \ + php7-dev \ + python3 \ + python3-dev \ + re2c && \ + pip3 install --no-cache-dir --upgrade pip && \ + +# Install bfr sandbox from git + git clone https://github.com/mushorg/BFR /opt/BFR && \ + cd /opt/BFR && \ + git checkout 508729202428a35bcc6bb27dd97b831f7e5009b5 && \ + phpize7 && \ + ./configure \ + --with-php-config=/usr/bin/php-config7 \ + --enable-bfr && \ + make && \ + make install && \ + cd / && \ + rm -rf /opt/BFR /tmp/* /var/tmp/* && \ + echo "zend_extension = "$(find /usr -name bfr.so) >> /etc/php7/php.ini && \ + +# Install PHP Sandbox + git clone https://github.com/mushorg/phpox /opt/phpox && \ + cd /opt/phpox && \ + cp /root/dist/sandbox.py . && \ + pip3 install -r requirements.txt && \ + make && \ + +# Clean up + apk del --purge build-base \ + git \ + php7-dev \ + python3-dev && \ + rm -rf /root/* && \ + rm -rf /var/cache/apk/* + +# Set workdir and start phpsandbox +WORKDIR /opt/phpox +CMD python3.6 sandbox.py diff --git a/docker/tanner/phpox/dist/sandbox.py b/docker/tanner/phpox/dist/sandbox.py new file mode 100644 index 00000000..8b5d363e --- /dev/null +++ b/docker/tanner/phpox/dist/sandbox.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2016 Lukas Rist +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import os +import tempfile +import json +import asyncio +import hashlib +import argparse + +from aiohttp import web +from asyncio.subprocess import PIPE + +from pprint import pprint + +class PHPSandbox(object): + @classmethod + def php_tag_check(cls, script): + with open(script, "r+") as check_file: + file_content = check_file.read() + if "" not in file_content: + file_content += "?>" + check_file.write(file_content) + return script + + @asyncio.coroutine + def read_process(self): + while True: + line = yield from self.proc.stdout.readline() + if not line: + break + else: + self.stdout_value += line + b'\n' + + @asyncio.coroutine + def sandbox(self, script, phpbin="php7.0"): + if not os.path.isfile(script): + raise Exception("Sample not found: {0}".format(script)) + + try: + cmd = [phpbin, "sandbox.php", script] + self.proc = yield from asyncio.create_subprocess_exec(*cmd, stdout=PIPE) + self.stdout_value = b'' + yield from asyncio.wait_for(self.read_process(), timeout=3) + except Exception as e: + try: + self.proc.kill() + except Exception: + pass + print("Error executing the sandbox: {}".format(e)) + # raise e + return {'stdout': self.stdout_value.decode('utf-8')} + + +class EchoServer(asyncio.Protocol): + def connection_made(self, transport): + # peername = transport.get_extra_info('peername') + # print('connection from {}'.format(peername)) + self.transport = transport + + def data_received(self, data): + # print('data received: {}'.format(data.decode())) + self.transport.write(data) + + +@asyncio.coroutine +def api(request): + data = yield from request.read() + file_md5 = hashlib.md5(data).hexdigest() + with tempfile.NamedTemporaryFile(suffix='.php') as f: + f.write(data) + f.seek(0) + sb = PHPSandbox() + try: + server = yield from loop.create_server(EchoServer, '127.0.0.1', 1234) + ret = yield from asyncio.wait_for(sb.sandbox(f.name, phpbin), timeout=10) + server.close() + except KeyboardInterrupt: + pass + ret['file_md5'] = file_md5 + return web.Response(body=json.dumps(ret, sort_keys=True, indent=4).encode('utf-8')) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("--phpbin", help="PHP binary, ex: php7.0", default="php7.0") + args = parser.parse_args() + phpbin = args.phpbin + + app = web.Application() + app.router.add_route('POST', '/', api) + + loop = asyncio.get_event_loop() + handler = app.make_handler() + f = loop.create_server(handler, '0.0.0.0', 8088) + srv = loop.run_until_complete(f) + print('serving on', srv.sockets[0].getsockname()) + try: + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + loop.run_until_complete(handler.finish_connections(1.0)) + srv.close() + loop.run_until_complete(srv.wait_closed()) + loop.run_until_complete(app.finish()) + loop.close() diff --git a/docker/tanner/phpox/docker-compose.yml b/docker/tanner/phpox/docker-compose.yml new file mode 100644 index 00000000..f1802258 --- /dev/null +++ b/docker/tanner/phpox/docker-compose.yml @@ -0,0 +1,17 @@ +version: '2.3' + +networks: + phpox_local: + +services: + +# PHP Sandbox service + phpox: + build: . + container_name: phpox + restart: always + stop_signal: SIGKILL + tty: true + networks: + - phpox_local + image: "dtagdevsec/phpox:1804" diff --git a/docker/tanner/redis/Dockerfile b/docker/tanner/redis/Dockerfile new file mode 100644 index 00000000..065d9c43 --- /dev/null +++ b/docker/tanner/redis/Dockerfile @@ -0,0 +1,18 @@ +FROM redis:alpine + +# Include dist +ADD dist/ /root/dist/ + +# Setup apt +RUN apk -U --no-cache add \ + redis && \ + + cp /root/dist/redis.conf /etc && \ + +# Clean up + rm -rf /root/* && \ + rm -rf /tmp/* /var/tmp/* && \ + rm -rf /var/cache/apk/* + +# Start conpot +CMD redis-server /etc/redis.conf diff --git a/docker/tanner/redis/dist/redis.conf b/docker/tanner/redis/dist/redis.conf new file mode 100644 index 00000000..edfe45da --- /dev/null +++ b/docker/tanner/redis/dist/redis.conf @@ -0,0 +1,2 @@ +bind 0.0.0.0 +protected-mode no diff --git a/docker/tanner/snare/Dockerfile b/docker/tanner/snare/Dockerfile new file mode 100644 index 00000000..d5ae290e --- /dev/null +++ b/docker/tanner/snare/Dockerfile @@ -0,0 +1,39 @@ +FROM alpine + +# Include dist +#ADD dist/ /root/dist/ + +# Setup apt +RUN apk -U --no-cache add \ + build-base \ + git \ + libcap \ + linux-headers \ + python3 \ + python3-dev && \ + +# Setup ConPot + git clone https://github.com/mushorg/snare /opt/snare && \ + cd /opt/snare/ && \ + pip3 install --no-cache-dir --upgrade pip setuptools && \ + pip3 install --no-cache-dir -r requirements.txt && \ + python3.6 clone.py --target http://example.com && \ + cd / && \ + #setcap cap_net_bind_service=+ep /usr/bin/python3.6 && \ + +# Get wireshark manuf db for scapy, setup configs, user, groups + addgroup -g 2000 snare && \ + adduser -S -s /bin/ash -u 2000 -D -g 2000 snare && \ + +# Clean up + apk del --purge \ + build-base \ + linux-headers \ + python3-dev && \ + rm -rf /root/* && \ + rm -rf /tmp/* /var/tmp/* && \ + rm -rf /var/cache/apk/* + +# Start snare +WORKDIR /opt/snare +CMD /usr/bin/python3.6 /opt/snare/snare.py --tanner tanner --debug true --no-dorks --auto-update false --host-ip 0.0.0.0 --port 80 --page-dir example.com diff --git a/docker/tanner/snare/docker-compose.yml b/docker/tanner/snare/docker-compose.yml new file mode 100644 index 00000000..98cb1156 --- /dev/null +++ b/docker/tanner/snare/docker-compose.yml @@ -0,0 +1,19 @@ +version: '2.3' + +networks: + snare_local: + +services: + +# Snare service + snare: + build: . + container_name: snare + restart: always + stop_signal: SIGKILL + tty: true + networks: + - snare_local + ports: + - "80:80" + image: "dtagdevsec/snare:1804" diff --git a/docker/tanner/tanner/Dockerfile b/docker/tanner/tanner/Dockerfile new file mode 100644 index 00000000..55e14032 --- /dev/null +++ b/docker/tanner/tanner/Dockerfile @@ -0,0 +1,41 @@ +FROM alpine + +# Include dist +ADD dist/ /root/dist/ + +# Setup apt +RUN apk -U --no-cache add \ + build-base \ + git \ + libcap \ + linux-headers \ + py3-yarl \ + python3 \ + python3-dev && \ + +# Setup ConPot + git clone https://github.com/mushorg/tanner /opt/tanner && \ + cp /root/dist/config.py /opt/tanner/tanner/ && \ + cp /root/dist/requirements.txt /opt/tanner/ && \ + cd /opt/tanner/ && \ + pip3 install --no-cache-dir --upgrade pip setuptools && \ + pip3 install --no-cache-dir -r requirements.txt && \ + python3 setup.py install && \ + cd / && \ + +# Get wireshark manuf db for scapy, setup configs, user, groups + addgroup -g 2000 tanner && \ + adduser -S -s /bin/ash -u 2000 -D -g 2000 tanner && \ + +# Clean up + apk del --purge \ + build-base \ + linux-headers \ + python3-dev && \ + rm -rf /root/* && \ + rm -rf /tmp/* /var/tmp/* && \ + rm -rf /var/cache/apk/* + +# Start conpot +WORKDIR /opt/tanner +CMD tanner diff --git a/docker/tanner/tanner/dist/config.py b/docker/tanner/tanner/dist/config.py new file mode 100644 index 00000000..7c913a79 --- /dev/null +++ b/docker/tanner/tanner/dist/config.py @@ -0,0 +1,80 @@ +import configparser +import logging +import os +import sys + +LOGGER = logging.getLogger(__name__) + +config_template = {'DATA': {'db_config': '/opt/tanner/db/db_config.json', 'dorks': '/opt/tanner/data/dorks.pickle', + 'user_dorks': '/opt/tanner/data/user_dorks.pickle'}, + 'TANNER': {'host': '0.0.0.0', 'port': 8090}, + 'WEB': {'host': '0.0.0.0', 'port': 8091}, + 'API': {'host': '0.0.0.0', 'port': 8092}, + 'PHPOX': {'host': '0.0.0.0', 'port': 8088}, + 'REDIS': {'host': 'tanner_redis', 'port': 6379, 'poolsize': 80, 'timeout': 1}, + 'EMULATORS': {'root_dir': '/opt/tanner'}, + 'EMULATOR_ENABLED': {'sqli': True, 'rfi': True, 'lfi': False, 'xss': True, 'cmd_exec': False, + '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'}, + 'LOCALLOG': {'enabled': True, 'PATH': '/tmp/tanner_report.json'}, + 'CLEANLOG': {'enabled': False} + } + + +class TannerConfig(): + config = None + + @staticmethod + def set_config(config_path): + cfg = configparser.ConfigParser() + if not os.path.exists(config_path): + print("Config file {} doesn't exist. Check the config path or use default".format(config_path)) + sys.exit(1) + + cfg.read(config_path) + TannerConfig.config = cfg + + @staticmethod + def get(section, value): + res = None + if TannerConfig.config is not None: + try: + 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] + + else: + res = config_template[section][value] + return res + + @staticmethod + def get_section(section): + res = {} + if TannerConfig.config is not None: + try: + sec = TannerConfig.config[section] + for k, v in sec.items(): + convert_type = type(config_template[section][k]) + if convert_type is bool: + res[k] = TannerConfig.config[section].getboolean(k) + else: + res[k] = convert_type(v) + except (configparser.NoOptionError, configparser.NoSectionError): + LOGGER.warning("Error in config, default value will be used. Section: %s Value: %s", section) + res = config_template[section] + + else: + res = config_template[section] + + return res diff --git a/docker/tanner/tanner/dist/requirements.txt b/docker/tanner/tanner/dist/requirements.txt new file mode 100644 index 00000000..deb861e1 --- /dev/null +++ b/docker/tanner/tanner/dist/requirements.txt @@ -0,0 +1,13 @@ +aiohttp==2.2 +aiomysql +aiohttp_jinja2==0.14.0 +docker<2.6 +elizabeth==0.3.27 +yarl<0.11 +redis +asyncio_redis +uvloop +pymongo +pylibinjection +jinja2 +pycodestyle diff --git a/docker/tanner/tanner/docker-compose.yml b/docker/tanner/tanner/docker-compose.yml new file mode 100644 index 00000000..c35b89f9 --- /dev/null +++ b/docker/tanner/tanner/docker-compose.yml @@ -0,0 +1,59 @@ +version: '2.3' + +networks: + tanner_local: + +services: + +# Tanner Redis Service + tanner_redis: + container_name: tanner_redis + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/redis:1804" + +# Tanner API Service + tanner_api: + build: . + container_name: tanner_api + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/tanner:1804" + command: tannerapi + depends_on: + - redis + +# Tanner WEB Service + tanner_web: + build: . + container_name: tanner_web + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/tanner:1804" + command: tannerweb + depends_on: + - redis + +# Tanner Service + tanner: + build: . + container_name: tanner + restart: always + stop_signal: SIGKILL + tty: true + networks: + - tanner_local + image: "dtagdevsec/tanner:1804" + command: tanner + depends_on: + - tanner_api + - tanner_web