├── .coveragerc ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── bin ├── __init__.py └── hornet ├── hornet-supervisord.conf ├── hornet ├── __init__.py ├── common │ ├── __init__.py │ ├── config.py │ └── helpers.py ├── core │ ├── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── ifconfig_command.py │ │ ├── ls_command.py │ │ ├── ping_command.py │ │ └── wget_command.py │ ├── consumer.py │ ├── db │ │ ├── __init__.py │ │ ├── handler.py │ │ └── models.py │ ├── fs_wrapper.py │ ├── handler.py │ ├── host.py │ ├── session.py │ └── shell.py ├── data │ ├── commands │ │ ├── ifconfig │ │ │ ├── help │ │ │ ├── output_template │ │ │ └── version │ │ ├── ls │ │ │ ├── help │ │ │ └── version │ │ ├── ping │ │ │ └── help │ │ ├── uname │ │ │ ├── help │ │ │ └── version │ │ └── wget │ │ │ ├── help │ │ │ ├── no_param │ │ │ └── version │ ├── default_config.json │ └── linux_fs_list.txt ├── main.py └── tests │ ├── __init__.py │ ├── commands │ ├── __init__.py │ ├── base.py │ ├── test_cd.py │ ├── test_echo.py │ ├── test_ifconfig.py │ ├── test_logout.py │ ├── test_ls.py │ ├── test_ping.py │ ├── test_pwd.py │ ├── test_ssh.py │ ├── test_uname.py │ └── test_wget.py │ ├── test_fs_wrapper.py │ ├── test_helpers.py │ ├── test_hornet.py │ └── test_host.py ├── requirements.txt ├── scripts ├── docker-run.sh └── run.sh └── setup.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | */tests* 4 | 5 | [report] 6 | exclude_lines= 7 | pragma: no cover 8 | except 9 | raise 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | .idea/ 3 | 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | .eggs/ 8 | dist 9 | build 10 | eggs 11 | parts 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | .coverage 19 | 20 | # htmlcov 21 | htmlcov/ 22 | 23 | # coverage 24 | cover/ 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - 2.7 5 | addons: 6 | apt: 7 | packages: 8 | - libmysqlclient-dev 9 | install: 10 | - pip install -e . 11 | - pip install coveralls 12 | - pip install --upgrade git+https://github.com/ianepperson/telnetsrvlib.git#egg=telnetsrv-0.4.1 13 | before_script: 14 | - mysql -e 'create database hornet;' 15 | script: 16 | - nosetests --cover-branches --with-coverage --cover-erase --cover-package=hornet --cover-html 17 | after_success: 18 | - coveralls 19 | before_deploy: 20 | - TRAVIS_COMMIT_MSG=\"$(git log --format=%B --no-merges -n 1)\" 21 | deploy: 22 | provider: pypi 23 | user: shouldntwork 24 | password: 25 | secure: IO6XHe8aoeev3stQh4TEz+251RXta4Qzya0rDuMCTXcDyxbffyyk0ppE7y454On28LqX3oDf5OLB3g4EFLr7hgx1Ckf5wiCkpV8vantLZPR8X1Uf0uTkLsJwHVmnoAOdravmlvLyw1wWbBsJminJLI4MlCXYRSjf0Gwyv1xo+rU= 26 | on: 27 | tags: true 28 | repo: czardoz/hornet 29 | condition: $TRAVIS_COMMIT_MSG =~ "[release]" 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | MAINTAINER Aniket Panse 4 | 5 | RUN apt-get clean && \ 6 | apt-get upgrade -y && \ 7 | apt-get update -y --fix-missing && \ 8 | apt-get install -y libmysqlclient-dev python-pip vim less git; 9 | 10 | ENV MYSQL_DATA_DIR=/var/lib/mysql \ 11 | MYSQL_RUN_DIR=/run/mysqld \ 12 | MYSQL_LOG_DIR=/var/log/mysql; 13 | 14 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server; 15 | 16 | RUN pip install supervisor && echo_supervisord_conf; 17 | 18 | RUN mkdir /opt/hornet; 19 | COPY . /opt/hornet 20 | WORKDIR /opt/hornet 21 | RUN pip install . && pip install --upgrade git+https://github.com/ianepperson/telnetsrvlib.git#egg=telnetsrv-0.4.1; 22 | 23 | ENTRYPOINT ["scripts/run.sh"] 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include hornet * 2 | include README.rst 3 | include requirements.txt 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | rm -rf *.pyc 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Hornet 3 | ======================= 4 | 5 | |travis| |coverage| 6 | 7 | .. |coverage| image:: https://coveralls.io/repos/czardoz/hornet/badge.png?branch=master 8 | :target: https://coveralls.io/r/czardoz/hornet?branch=master 9 | 10 | .. |travis| image:: https://travis-ci.org/czardoz/hornet.png?branch=master 11 | :target: https://travis-ci.org/czardoz/hornet 12 | 13 | Overview 14 | ========= 15 | 16 | Hornet is aimed to be a medium interaction SSH Honeypot, that supports multiple virtual 17 | hosts. Each virtual host is configured independently, and gets its own sandboxed filesystem. 18 | Hornet allows interactions across hosts, meaning that the attacker may login to one host 19 | from another (using the ssh command). A Hornet instance *must* contain a default host, 20 | which serves as a launchpad to log into the other hosts. Any configured host can be set 21 | to default with a simple configuration change. 22 | 23 | At a high level, Hornet can be visualized to be working according to the following diagram: 24 | 25 | .. code-block:: 26 | 27 | +-------------+ 28 | | VirtualHost | 29 | +----------------> | | 30 | | | One | 31 | | +------+------+ 32 | | ^ 33 | | | 34 | | | 35 | v v 36 | +--------+----+ +------+------+ 37 | | Default | | VirtualHost | 38 | Attacker+--------> | | <---------> | | 39 | | VirtualHost | | One | 40 | +--------+----+ +------+------+ 41 | ^ ^ 42 | | | 43 | | | 44 | | v 45 | | +------+------+ 46 | | | VirtualHost | 47 | +----------------> | | 48 | | One | 49 | +-------------+ 50 | 51 | The double ended arrows signify possible interaction, through commands 52 | such as ``ssh``, ``ping``, etc. 53 | 54 | Each VirtualHost has the following configurable attributes: 55 | 56 | * Hostname 57 | * IP Address 58 | * Sandboxed Filesystem 59 | * User Pool 60 | * DNS server (common across all VirtualHosts) 61 | * Shell environment parameters (such as ``$PATH``) 62 | 63 | 64 | Supported Commands 65 | ==================== 66 | 67 | Hornet currently supports the following commands: 68 | 69 | * ``cd`` 70 | * ``ls`` 71 | * ``echo`` 72 | * ``ssh`` 73 | * ``logout`` 74 | * ``pwd`` 75 | * ``ifconfig`` 76 | * ``ping`` 77 | * ``uname`` 78 | 79 | Installation 80 | ============== 81 | 82 | Installing is simple, 83 | 84 | Install MySQL client libraries 85 | 86 | .. code-block:: 87 | 88 | $ sudo apt-get install libmysqlclient-dev 89 | 90 | Then install hornet using `pip` 91 | 92 | .. code-block:: 93 | 94 | $ pip install git+https://github.com/czardoz/hornet.git 95 | 96 | And since the latest version of telnetsrvlib on GitHub is super cool 97 | 98 | .. code-block:: 99 | 100 | $ pip install --upgrade git+https://github.com/ianepperson/telnetsrvlib.git#egg=telnetsrv-0.4.1 101 | 102 | Usage 103 | ======= 104 | 105 | Create a directory anywhere 106 | 107 | .. code-block:: 108 | 109 | $ mkdir ~/honeypot 110 | 111 | Initialize Hornet 112 | 113 | .. code-block:: 114 | 115 | $ cd honeypot 116 | $ hornet -v 117 | 118 | You should see something like this (ignore the errors): 119 | 120 | .. code-block:: 121 | 122 | 2015-01-31 19:34:19,624 [INFO] (root) Starting Hornet, version: 0.0.1 123 | 2015-01-31 19:34:19,624 [INFO] (hornet.main) Config file /tmp/honeypot/config.json not found, copying default 124 | 2015-01-31 19:34:19,625 [DEBUG] (hornet.common.config) Default host set to: test02 125 | 2015-01-31 19:34:19,625 [INFO] (hornet.main) Creating directory /tmp/honeypot/vhosts for virtual host filesystems 126 | 2015-01-31 19:34:19,628 [ERROR] (hornet.core.host) IP address for test01 is not specified in the config file (or is "null") 127 | 2015-01-31 19:34:19,628 [INFO] (hornet.core.host) Assigned random IP 192.168.0.103 to host test01 128 | 2015-01-31 19:34:19,633 [ERROR] (hornet.core.host) IP Address 192.168.0.443 for test03 is not valid for the specified network 129 | 2015-01-31 19:34:19,633 [INFO] (hornet.core.host) Assigned random IP 192.168.0.27 to host test03 130 | 2015-01-31 19:34:19,640 [INFO] (hornet.main) SSH server listening on 127.0.0.1:59866 131 | 132 | Once you get it working, you can set about configuring it. Hit `Ctrl+C` to stop the honeypot. 133 | 134 | .. code-block:: 135 | 136 | ... 137 | 2015-01-31 19:34:19,640 [INFO] (hornet.main) SSH server listening on 127.0.0.1:59866 138 | ^CKeyboardInterrupt 139 | 2015-01-31 19:40:58,419 [INFO] (root) Quitting 140 | 2015-01-31 19:40:58,419 [DEBUG] (root) Stopping the server 141 | 142 | Now, you'll see a ``config.json`` created in the current directory. 143 | 144 | .. code-block:: 145 | 146 | $ cat config.json 147 | { 148 | "port": 0, 149 | "host": "127.0.0.1", 150 | "key_file": "test_server.key", 151 | "network": { 152 | "network_ip": "192.168.0.0/24", 153 | "dns_server": "192.168.0.2", 154 | "gateway": "192.168.0.1" 155 | }, 156 | "virtual_hosts": [ 157 | { 158 | "hostname": "test02", 159 | "valid_logins": { 160 | "mango": "apple", 161 | "vstfpd": "1q2w3e4r", 162 | "testuser": "testpassword" 163 | }, 164 | "env": { 165 | "BROWSER": "firefox", 166 | "EDITOR": "gedit", 167 | "SHELL": "/bin/bash", 168 | "PAGER": "less" 169 | }, 170 | "default": true, 171 | "ip_address": "192.168.0.232" 172 | }, 173 | { 174 | "hostname": "test03", 175 | ... 176 | "ip_address": "192.168.0.443" 177 | } 178 | ] 179 | } 180 | 181 | Edit it according to your wish. You'll also see a ``vhosts/`` directory. 182 | Inside it are the sandbox filesystems for each VirtualHost (as defined in 183 | the config file). These filesystems can be populated with any files you 184 | wish. 185 | 186 | You can now restart the honeypot: 187 | 188 | .. code-block:: 189 | 190 | $ hornet -v 191 | 192 | 193 | Careful! 194 | ============ 195 | 196 | Hornet is under development, and should not be used for production purposes 197 | yet. There are a fair amount of bugs, and perhaps security risks. Know what 198 | you're doing! 199 | -------------------------------------------------------------------------------- /bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/bin/__init__.py -------------------------------------------------------------------------------- /bin/hornet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent 21 | import gevent.monkey 22 | gevent.monkey.patch_all() 23 | 24 | 25 | import logging 26 | import argparse 27 | import os 28 | 29 | from hornet.main import Hornet 30 | 31 | __version_tuple__ = (0, 0, 1) 32 | __version__ = '.'.join(map(str, __version_tuple__)) 33 | 34 | 35 | class LogFilter(logging.Filter): 36 | 37 | def filter(self, record): 38 | if record.name.startswith('paramiko') or record.name.startswith('telnetsrv'): 39 | return False 40 | else: 41 | return True 42 | 43 | 44 | def setup_logging(args): 45 | root_logger = logging.getLogger() 46 | root_logger.setLevel(logging.DEBUG) 47 | console_handler = logging.StreamHandler() 48 | if args.verbose or args.extremely_verbose: 49 | log_level = logging.DEBUG 50 | else: 51 | log_level = logging.WARNING 52 | 53 | formatter = logging.Formatter('%(asctime)s [%(levelname)s] (%(name)s) %(message)s') 54 | console_handler.setFormatter(formatter) 55 | if not args.extremely_verbose: 56 | console_handler.addFilter(LogFilter()) 57 | console_handler.setLevel(log_level) 58 | root_logger.addHandler(console_handler) 59 | 60 | 61 | if __name__ == '__main__': 62 | 63 | # Command line arguments 64 | parser = argparse.ArgumentParser() 65 | parser.add_argument('-v', '--verbose', action='store_true', default=False, help='More detailed logging.') 66 | parser.add_argument('-V', '--extremely-verbose', action='store_true', default=False, 67 | help='Logs from 3rd party libs are enabled as well.') 68 | args = parser.parse_args() 69 | setup_logging(args) 70 | 71 | logging.info('Starting Hornet, version: %s', __version__) 72 | working_directory = os.getcwd() 73 | honeypot = Hornet(working_directory, vhost_create_fs=True) 74 | greenlets = honeypot.start() 75 | 76 | try: 77 | gevent.joinall(greenlets) 78 | except KeyboardInterrupt: 79 | logging.info('Quitting') 80 | honeypot.stop() 81 | -------------------------------------------------------------------------------- /hornet-supervisord.conf: -------------------------------------------------------------------------------- 1 | ; Supervisor configuration for Hornet 2 | 3 | ; Configuration for supervisord itself. 4 | [supervisord] 5 | logfile=/var/log/supervisord/supervisord.log 6 | logfile_maxbytes=50MB 7 | logfile_backups=10 8 | loglevel=error 9 | pidfile=/var/run/supervisord.pid 10 | ;nodaemon=false 11 | minfds=1024 12 | minprocs=200 13 | user=root 14 | childlogdir=/var/log/supervisord/ 15 | ; Configuration for hornet. 16 | [program:hornet] 17 | command=hornet -v 18 | directory=/opt/vfs/ 19 | autorestart=true 20 | redirect_stderr=true 21 | stdout_logfile=/var/log/hornet.log 22 | -------------------------------------------------------------------------------- /hornet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/__init__.py -------------------------------------------------------------------------------- /hornet/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/common/__init__.py -------------------------------------------------------------------------------- /hornet/common/config.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import netaddr 21 | import logging 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | class Network(netaddr.IPNetwork): 27 | def __init__(self, addr, dns_server, gateway): 28 | self.dns_server = dns_server 29 | self.gateway = gateway 30 | super(Network, self).__init__(addr) 31 | 32 | 33 | class Config(object): 34 | 35 | def __init__(self, cdict): 36 | self.port = cdict['port'] 37 | self.host = cdict['host'] 38 | self.database = cdict['database'] 39 | self.network = Network(cdict['network']['network_ip'], 40 | cdict['network']['dns_server'], cdict['network']['gateway']) 41 | self.num_vhosts = len(cdict['virtual_hosts']) 42 | self.vhost_params = cdict['virtual_hosts'] 43 | self.key_file = cdict['key_file'] 44 | 45 | self.default_hostname = None 46 | for p in self.vhost_params: 47 | if p.get('default', False): 48 | logger.debug('Default host set to: %s', p['hostname']) 49 | self.default_hostname = p['hostname'] 50 | if self.default_hostname is None: 51 | logger.info('Default host not found, setting %s to default.', self.vhost_params[0]['hostname']) 52 | self.default_hostname = self.vhost_params[0]['hostname'] 53 | -------------------------------------------------------------------------------- /hornet/common/helpers.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | import random 22 | 23 | from paramiko import RSAKey 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | 28 | def get_rsa_key_file(filename, password=None): 29 | try: 30 | key = RSAKey(filename=filename, password=password) 31 | except IOError: 32 | logger.info('RSA Key file not found, generating a new one: %s', filename) 33 | key = RSAKey.generate(1024) 34 | key.write_private_key_file(filename, password=password) 35 | return key 36 | 37 | 38 | def get_random_item(collection): 39 | if isinstance(collection, dict): 40 | all_keys = list(collection.keys()) 41 | r = random.choice(all_keys) 42 | return collection[r] 43 | elif isinstance(collection, list): 44 | return random.choice(collection) 45 | 46 | 47 | # http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size 48 | # By Fred Cicera 49 | def human_readable(num, suffix=''): 50 | for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: 51 | if abs(num) < 1024.0: 52 | return "%3.1f%s%s" % (num, unit, suffix) 53 | num /= 1024.0 54 | return "%.1f%s%s" % (num, 'Yi', suffix) 55 | -------------------------------------------------------------------------------- /hornet/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/core/__init__.py -------------------------------------------------------------------------------- /hornet/core/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/core/commands/__init__.py -------------------------------------------------------------------------------- /hornet/core/commands/ifconfig_command.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | from string import Template 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | class _IfconfigTemplate(object): 27 | 28 | def __init__(self, template_path): 29 | self.interface_data = {} 30 | with open(template_path, 'r') as templatefile: 31 | self.all_template = templatefile.read() 32 | interfaces = self.all_template.split('\n\n') 33 | for interface in interfaces: 34 | first_line = interface.split('\n')[0] 35 | interface_name = first_line.split()[0] 36 | self.interface_data[interface_name] = interface 37 | 38 | def interface_exists(self, interface): 39 | return interface in self.interface_data 40 | 41 | def render(self, iface, network, ip_address): 42 | mapping = { 43 | 'ip_addr': ip_address, 44 | 'broadcast_addr': network.broadcast, 45 | 'subnet_mask': network.netmask 46 | } 47 | template_string = Template(self.interface_data[iface]) 48 | return template_string.safe_substitute(mapping) 49 | 50 | def render_all(self, network, ip_address): 51 | mapping = { 52 | 'ip_addr': ip_address, 53 | 'broadcast_addr': network.broadcast, 54 | 'subnet_mask': network.netmask 55 | } 56 | template_string = Template(self.all_template) 57 | return template_string.safe_substitute(mapping) 58 | 59 | 60 | class IfconfigCommand(object): 61 | 62 | def __init__(self, params, template_path, ip_address, network): 63 | self.params = params 64 | self.ip_address = ip_address 65 | self.network = network 66 | self.template_path = template_path 67 | self.template = None 68 | 69 | def process(self): 70 | self.template = _IfconfigTemplate(self.template_path) 71 | if self.params: 72 | interface_name = self.params[0] 73 | if not self.template.interface_exists(interface_name): 74 | return '{}: error fetching interface information: Device not found'.format(interface_name) 75 | else: 76 | return self.template.render(interface_name, self.network, self.ip_address) 77 | else: 78 | return self.template.render_all(self.network, self.ip_address) 79 | -------------------------------------------------------------------------------- /hornet/core/commands/ls_command.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | import os 22 | import time 23 | 24 | from tarfile import filemode # Coverts file/directory mode into the ls format (e.g drwxr-xr-x) 25 | from fs.errors import IllegalBackReference 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | 30 | class _PathInfo(object): 31 | 32 | def __init__(self, path, total, output, path_exists, is_dir): 33 | self.path = path 34 | self.total = total / 2 35 | self.path_output = output 36 | self.path_exists = path_exists 37 | self.is_dir = is_dir 38 | 39 | 40 | class LsCommand(object): 41 | def __init__(self, args, paths, filesystem, working_path): 42 | self.args = args 43 | self.paths = sorted(paths) 44 | self.filesystem = filesystem 45 | self.working_path = working_path 46 | self.output = {} 47 | 48 | def process(self): 49 | for count, p in enumerate(self.paths): 50 | try: 51 | normalized_path = os.path.normpath(os.path.join(self.working_path, p)) 52 | self._process_path(normalized_path, key_path=p) 53 | except IllegalBackReference as e: 54 | logger.warn('Access to the external file system was attempted.') 55 | new_path = os.path.join(self.working_path, p.lstrip('../')) 56 | self._process_path(new_path, key_path=p) 57 | result = '' 58 | if len(self.paths) == 1: 59 | path = self.paths[0] 60 | current_path_info = self.output[path] 61 | if self.args.l: 62 | if not self.args.directory: 63 | if current_path_info.is_dir: 64 | result += 'total {}\n'.format(current_path_info.total) 65 | if self.args.all: 66 | result += self._get_hidden_dirs(current_path_info.path) 67 | 68 | result += '\n'.join(current_path_info.path_output) 69 | else: 70 | if self.args.all: 71 | if not self.args.directory: 72 | if not current_path_info.is_dir: 73 | dirs = current_path_info.path_output 74 | else: 75 | dirs = ['.', '..'] + current_path_info.path_output 76 | else: 77 | dirs = current_path_info.path_output 78 | else: 79 | dirs = current_path_info.path_output 80 | result += ' '.join(dirs) 81 | else: 82 | for path in self.paths: 83 | current_path_info = self.output[path] 84 | if current_path_info.is_dir: 85 | if not self.args.directory: 86 | result += '{}:\n'.format(path) 87 | if self.args.l: 88 | if not self.args.directory and current_path_info.is_dir: 89 | result += 'total {}\n'.format(current_path_info.total) 90 | if self.args.all: 91 | result += self._get_hidden_dirs(current_path_info.path) 92 | result += '\n'.join(current_path_info.path_output) 93 | else: 94 | if self.args.all: 95 | dirs = ['.', '..'] + current_path_info.path_output 96 | else: 97 | dirs = current_path_info.path_output 98 | result += ' '.join(dirs) 99 | if not self.args.directory: 100 | result += '\n\n' 101 | else: 102 | result += '\n' 103 | return result.strip() # remove the last newline, because shell.writeline() will introduce it later. 104 | 105 | def _stat_path(self, path): 106 | hidden = False 107 | base_name = path.split('/')[-1] 108 | if base_name.startswith('.'): 109 | hidden = True 110 | stat_result = os.stat(self.filesystem.getsyspath(path)) 111 | try: 112 | last_modified = time.strftime("%b %d %H:%M", time.localtime(stat_result.st_mtime)) 113 | except ValueError: 114 | last_modified = time.strftime("%b %d %H:%M") 115 | name = os.path.basename(path) or '.' 116 | total = stat_result.st_blocks 117 | if self.args.l: 118 | path_string = "%s %2s %s %s %6s %s %s" % ( 119 | filemode(stat_result.st_mode), 120 | stat_result.st_nlink, 121 | 'ftp', 122 | 'ftp', 123 | stat_result.st_size, 124 | last_modified, 125 | name 126 | ) 127 | else: 128 | path_string = name 129 | 130 | return {'path_string': path_string, 'total': total, 'hidden': hidden} 131 | 132 | def _process_path(self, path, key_path=None): 133 | path_output = [] 134 | is_directory = False 135 | logger.debug('Processing path: {}'.format(path)) 136 | if self.args.directory: 137 | if self.filesystem.isfile(path) or self.filesystem.isdir(path): 138 | exists = True 139 | stat = self._stat_path(path) 140 | if stat['hidden']: 141 | if self.args.all: 142 | path_output.append(stat['path_string']) 143 | else: 144 | path_output.append(stat['path_string']) 145 | total = stat['total'] 146 | if self.filesystem.isdir(path): 147 | is_directory = True 148 | else: 149 | exists = False 150 | total = 0 151 | path_output.append('ls: cannot access {}: No such file or directory'.format(path.lstrip('/'))) 152 | path_info = _PathInfo(path, total, path_output, exists, is_directory) 153 | self._add_path_output(path_info, key_path) 154 | else: 155 | if self.filesystem.isdir(unicode(path)): 156 | # Process all files one by one, adding to the output list 157 | exists = True 158 | total = 0 159 | is_directory = True 160 | for file_ in sorted(self.filesystem.listdir(path)): 161 | file_path = os.path.join(path, file_) 162 | stat = self._stat_path(file_path) 163 | if stat['hidden']: 164 | if self.args.all: 165 | path_output.append(stat['path_string']) 166 | else: 167 | path_output.append(stat['path_string']) 168 | total += stat['total'] 169 | elif self.filesystem.isfile(path): 170 | exists = True 171 | stat = self._stat_path(path) 172 | if stat['hidden']: 173 | if self.args.all: 174 | path_output.append(stat['path_string']) 175 | else: 176 | path_output.append(stat['path_string']) 177 | total = stat['total'] 178 | else: 179 | exists = False 180 | total = 0 181 | path_output.append('ls: cannot access {}: No such file or directory'.format(path.lstrip('/'))) 182 | path_info = _PathInfo(path, total, path_output, exists, is_directory) 183 | self._add_path_output(path_info, key_path) 184 | 185 | def _add_path_output(self, path_info, key_path): 186 | if key_path is None: 187 | self.output[path_info.path] = path_info 188 | else: 189 | self.output[key_path] = path_info 190 | 191 | def _get_hidden_dirs(self, path): 192 | path = self.filesystem.getsyspath(path) 193 | parent_path = os.path.abspath(os.path.join(path, os.pardir)) 194 | return '{}\n{}\n'.format(self._stat_relative_dirs(path, name='.'), 195 | self._stat_relative_dirs(parent_path, name='..')) 196 | 197 | def _stat_relative_dirs(self, path, name=None): 198 | stat_result = os.stat(path) 199 | try: 200 | last_modified = time.strftime("%b %d %H:%M", time.localtime(stat_result.st_mtime)) 201 | except ValueError: 202 | last_modified = time.strftime("%b %d %H:%M") 203 | return "%s %2s %s %s %6s %s %s" % ( 204 | filemode(stat_result.st_mode), 205 | stat_result.st_nlink, 206 | 'ftp', 207 | 'ftp', 208 | stat_result.st_size, 209 | last_modified, 210 | name 211 | ) 212 | -------------------------------------------------------------------------------- /hornet/core/commands/ping_command.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import re 21 | import random 22 | import gevent 23 | import logging 24 | 25 | IP_ADDRESS_REGEX = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." \ 26 | "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." \ 27 | "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." \ 28 | "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$" 29 | 30 | logger = logging.getLogger(__name__) 31 | 32 | 33 | class PingCommand(object): 34 | 35 | def __init__(self, host, shell): 36 | self.user_provided_host = host 37 | self.shell = shell 38 | 39 | self.host = None 40 | self.ip = None 41 | 42 | # These record the stats to show at the end 43 | self.total_count = 1 44 | self.success_count = 0 45 | self.times = [] 46 | 47 | # These are used to randomize pings 48 | self.mean = random.randint(13, 140) 49 | self.standard_deviation = 3 50 | self.success_probability = 0.93 51 | 52 | def process(self): 53 | 54 | self._resolve_hostname() 55 | if not self.ip: 56 | self.shell.writeline('ping: unknown host {}'.format(self.user_provided_host)) 57 | return 58 | 59 | self.shell.writeline('PING {} ({}) 56(84) bytes of data.'.format(self.host, self.ip)) 60 | 61 | while not self.shell.interrupt: 62 | if random.uniform(0, 1) < self.success_probability: 63 | time = random.normalvariate(self.mean, self.standard_deviation) 64 | line = '64 bytes from {} ({}): icmp_seq={} ttl=53 time={:.1f} ms'.format( 65 | self.host, 66 | self.ip, 67 | self.total_count, 68 | time 69 | ) 70 | self.success_count += 1 71 | self.times.append(time) 72 | self.shell.writeline(line) 73 | self.total_count += 1 74 | gevent.sleep(1) 75 | 76 | self.shell.writeline('^C') 77 | self.shell.writeline('--- {} ping statistics ---'.format(self.user_provided_host)) 78 | self.shell.writeline('{} packets transmitted, {} received, {} packet loss, time {:.2f}ms'.format( 79 | self.total_count, self.success_count, self._get_percentage_packet_loss(), sum(self.times) 80 | )) 81 | if self.times: # Only show the average if ctrl + C was not pressed before a second was up 82 | self.shell.writeline('rtt min/avg/max/mdev = {:.3f}/{:.3f}/{:.3f}/{:.3f} ms'.format( 83 | min(self.times), 84 | sum(self.times) / self.total_count, 85 | max(self.times), 86 | self._get_std_deviation() 87 | )) 88 | 89 | def _resolve_hostname(self): 90 | if re.match(IP_ADDRESS_REGEX, self.user_provided_host): 91 | target_host = self._reverse_hostname_lookup(self.user_provided_host) 92 | if target_host: 93 | self.host = target_host.hostname 94 | self.ip = target_host.ip_address 95 | else: 96 | self.ip = self.user_provided_host # Able to ping any IP 97 | self.host = self.user_provided_host 98 | else: 99 | if self.user_provided_host in self.shell.vhosts: # Only ping hosts in our honeypot 100 | self.ip = self.shell.vhosts[self.user_provided_host].ip_address 101 | 102 | def _get_percentage_packet_loss(self): 103 | return '{:.2%}'.format(1 - float(self.success_count)/self.total_count) 104 | 105 | def _get_std_deviation(self): 106 | variance = sum((t - self.mean)**2 for t in self.times) 107 | return (variance / self.total_count) ** 0.5 108 | 109 | def _reverse_hostname_lookup(self, ip_addr): 110 | for h in self.shell.vhosts: 111 | if self.shell.vhosts[h].ip_address == ip_addr: 112 | return self.shell.vhosts[h] 113 | -------------------------------------------------------------------------------- /hornet/core/commands/wget_command.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | import os 22 | import random 23 | import urlparse 24 | import gevent 25 | import requests 26 | import requests.exceptions 27 | import time 28 | import socket 29 | 30 | from contextlib import closing 31 | from hornet.common.helpers import human_readable 32 | 33 | logger = logging.getLogger(__name__) 34 | 35 | 36 | class WgetCommand(object): 37 | 38 | PROGRESS_BAR = '{:.0%}[{}>{}] {:,} {}' 39 | 40 | def __init__(self, url, working_path, filesystem, args, shell): 41 | self.shell = shell 42 | self.url = url 43 | self.parsed_url = None 44 | self.args = args 45 | self.working_path = working_path 46 | self.filesystem = filesystem 47 | self.outputfile = None 48 | self.fail_flag = False 49 | self.session = requests.session() 50 | 51 | self.ip_address = None 52 | self.port = None 53 | self.content_type = None 54 | 55 | # Used to render the progressbar 56 | self.progressbar_size = 50 57 | 58 | # Used to calculate download progress and speed 59 | self.total_size = 0 60 | self.currently_downloaded = 0 61 | self.start_time = None 62 | 63 | if self.args.output_document: 64 | self.outputfile = self.args.output_document 65 | else: 66 | self.outputfile = url.split('/')[-1] 67 | if not self.outputfile: 68 | self.outputfile = 'index.html' 69 | 70 | def process(self): 71 | self._parse_url() 72 | self._get_total_size() 73 | 74 | if self.fail_flag: 75 | self._write_info_line() 76 | self._write_dns_resolution_failed() 77 | self.shell.writeline('wget: unable to resolve host address \'{}\''.format(self.parsed_url.hostname)) 78 | return 79 | 80 | self._write_info_line() 81 | self._write_dns_resolution_successful() 82 | self._write_connection_info() 83 | 84 | output_path = os.path.join(self.working_path, self.outputfile) 85 | with open(self.filesystem.getsyspath(output_path), 'w') as output: 86 | self.start_time = time.clock() 87 | with closing(self.session.get(self.url, stream=True)) as response: 88 | progress_greenlet = gevent.spawn(self._render_progressbar) 89 | logger.debug('Progress greenlet started') 90 | for data in response.iter_content(128): 91 | if data: 92 | self.currently_downloaded += len(data) 93 | output.write(data) 94 | gevent.sleep(0) 95 | logger.debug('Download complete') 96 | gevent.joinall([progress_greenlet]) 97 | self._write_conclusion() 98 | 99 | def _get_total_size(self): 100 | try: 101 | resp = self.session.get(self.url) 102 | except requests.exceptions.RequestException: 103 | self.fail_flag = True 104 | return 105 | if not resp.status_code == 200: 106 | logger.debug('Response for url: {} is "{}"'.format(self.url, resp.status_code)) 107 | self.fail_flag = True 108 | return 109 | content_length = resp.headers.get('content-length', None) 110 | if content_length: 111 | try: 112 | self.total_size = int(content_length) 113 | except ValueError: 114 | logger.error('Invalid content-length received ' 115 | 'for url ({}): {}'.format(self.url, content_length)) 116 | self.fail_flag = True 117 | elif len(resp.content): 118 | self.total_size = len(resp.content) 119 | else: 120 | self.fail_flag = True 121 | self.content_type = resp.headers.get('content-type', None) 122 | if not self.content_type: 123 | self.content_type = 'text/plain' 124 | logger.debug('Total size set to: {}'.format(self.total_size)) 125 | 126 | def _write_info_line(self): 127 | self.shell.writeline('--{}-- {}'.format(time.strftime('%Y-%m-%d %H:%M:%S'), self.url)) 128 | 129 | def _write_dns_resolution_failed(self): 130 | self.shell.writeline('Resolving {} ({})... failed: Name or service ' 131 | 'not known.'.format(self.parsed_url.hostname, self.parsed_url.hostname)) 132 | 133 | def _write_dns_resolution_successful(self): 134 | try: 135 | # This will generally be successful. If the hostname was really not 136 | # resolved, `_get_total_size()` would have failed. 137 | self.ip_address = socket.gethostbyname(self.parsed_url.hostname) 138 | except socket.error: 139 | self.ip_address = '.'.join(''.format(random.randint(1, 254)) for i in range(4)) 140 | self.shell.writeline('Resolving {0} ({0})... {1}'.format( 141 | self.parsed_url.hostname, 142 | self.ip_address, 143 | )) 144 | 145 | def _write_connection_info(self): 146 | self.shell.writeline('Connecting to {0} ({0})|{1}|:{2}... connected.'.format( 147 | self.parsed_url.hostname, 148 | self.ip_address, 149 | self.port 150 | )) 151 | self.shell.writeline('HTTP request sent, awaiting response... 200 OK') 152 | self.shell.writeline('Length: {} ({}) [{}]'.format( 153 | self.total_size, 154 | human_readable(self.total_size), 155 | self.content_type 156 | )) 157 | self.shell.writeline('Saving to:\'{}\''.format(self.outputfile)) 158 | self.shell.writeline('') 159 | 160 | def _render_progressbar(self): 161 | while not self.currently_downloaded == self.total_size: 162 | self.shell.updateline(self._get_progressbar()) 163 | gevent.sleep(0.3) 164 | # Update one last time to show 100% progress 165 | self.shell.updateline('{} in {:.2f}s'.format(self._get_progressbar(), time.clock()-self.start_time)) 166 | self.shell.writeline('') 167 | 168 | def _get_progressbar(self): 169 | percent = self.currently_downloaded / float(self.total_size) 170 | done = int(percent * self.progressbar_size) 171 | not_done = self.progressbar_size - done 172 | 173 | elapsed_time = time.clock() - self.start_time 174 | speed = human_readable(self.currently_downloaded / elapsed_time, suffix='B/s') 175 | return self.PROGRESS_BAR.format( 176 | percent, 177 | (done - 1) * '=', 178 | not_done * ' ', 179 | self.total_size, 180 | speed 181 | ) 182 | 183 | def _parse_url(self): 184 | self.parsed_url = urlparse.urlparse(self.url) 185 | if not self.parsed_url.scheme.startswith('http'): 186 | self.fail_flag = True 187 | return 188 | self.port = self.parsed_url.port 189 | if not self.port: 190 | if self.parsed_url.scheme == 'http': 191 | self.port = 80 192 | else: 193 | self.port = 443 194 | 195 | def _write_conclusion(self): 196 | self.shell.writeline('{} - \'{}\' saved [{}/{}]'.format( 197 | time.strftime('%Y-%m-%d %H:%M:%S'), 198 | self.outputfile, 199 | self.currently_downloaded, 200 | self.total_size 201 | )) 202 | -------------------------------------------------------------------------------- /hornet/core/consumer.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURzPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | import gevent 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | class SessionConsumer(object): 26 | 27 | def __init__(self, session_q): 28 | self.session_q = session_q 29 | self.run_greenlet = None 30 | 31 | def _process_session(self, session): 32 | logger.debug('Persisting session %s', session.id) 33 | 34 | def _start_processing(self): 35 | for session in self.session_q: 36 | self._process_session(session) 37 | 38 | def start(self): 39 | self.run_greenlet = gevent.spawn(self._start_processing) 40 | return self.run_greenlet 41 | 42 | def stop(self): 43 | logger.info('Consumer stopping, no further sessions will be processed.') 44 | self.run_greenlet.kill() 45 | -------------------------------------------------------------------------------- /hornet/core/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/core/db/__init__.py -------------------------------------------------------------------------------- /hornet/core/db/handler.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | from contextlib import contextmanager 22 | from sqlalchemy import create_engine 23 | from sqlalchemy.orm import sessionmaker 24 | 25 | from models import AttackCommand, AttackSession, Base 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | Session = sessionmaker() 30 | 31 | 32 | class DatabaseHandler(object): 33 | 34 | def __init__(self, config): 35 | self.config = config 36 | logger.debug('Found database configuration: %s', config.database) 37 | self.engine = create_engine(config.database, echo=False) 38 | Session.configure(bind=self.engine) 39 | Base.metadata.create_all(self.engine) 40 | 41 | def create_attack_session(self, session): 42 | logger.debug('Creating new attack session, %s - start-time: %s remote-addr: %s', 43 | session.id, session.start_time, session.client_address) 44 | with self.session_context() as dbsession: 45 | attack_session = AttackSession(start_time=session.start_time, id=session.id, 46 | source_ip=session.client_address[0], source_port=session.client_address[1]) 47 | dbsession.add(attack_session) 48 | 49 | def create_attack_command(self, attack_session_id, command, host): 50 | logger.debug('Adding a new attack command (%s) to session %s.', command, attack_session_id) 51 | with self.session_context() as dbsession: 52 | attack_session = dbsession.query(AttackSession).filter_by(id=attack_session_id).one() 53 | attack_command = AttackCommand(command=command, host=host.hostname, session_id=attack_session.id) 54 | attack_session.commands.append(attack_command) 55 | 56 | @contextmanager 57 | def session_context(self): 58 | session = Session() 59 | try: 60 | yield session 61 | session.commit() 62 | except: 63 | session.rollback() 64 | raise 65 | finally: 66 | session.close() 67 | -------------------------------------------------------------------------------- /hornet/core/db/models.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import arrow 21 | 22 | from sqlalchemy import Column, String, UnicodeText, ForeignKey, Integer 23 | from sqlalchemy.ext.declarative import declarative_base 24 | from sqlalchemy.orm import relationship 25 | 26 | Base = declarative_base() 27 | 28 | class AttackSession(Base): 29 | 30 | __tablename__ = 'attacksession' 31 | 32 | id = Column(String(50), primary_key=True) 33 | start_time = Column(Integer) 34 | source_ip = Column(String(16)) 35 | source_port = Column(Integer) 36 | end_time = Column(Integer) 37 | commands = relationship('AttackCommand', backref='session', order_by='AttackCommand.time', 38 | cascade="all, delete-orphan") 39 | 40 | class AttackCommand(Base): 41 | 42 | __tablename__ = 'attackcommand' 43 | 44 | id = Column(Integer, autoincrement=True, primary_key=True) 45 | time = Column(Integer, default=lambda: arrow.now().timestamp) 46 | command = Column(String(2048)) 47 | host = Column(String(2048)) 48 | output = Column(UnicodeText) 49 | session_id = Column(String(50), ForeignKey('attacksession.id')) 50 | -------------------------------------------------------------------------------- /hornet/core/fs_wrapper.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2014 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import os 21 | import logging 22 | 23 | import fs.errors 24 | 25 | import hornet 26 | 27 | from fs.osfs import OSFS 28 | 29 | 30 | directories = [] 31 | 32 | with open(os.path.join(os.path.dirname(hornet.__file__), 'data', 33 | 'linux_fs_list.txt')) as fslist: 34 | for line in fslist: 35 | line = line.strip() 36 | directories.append(unicode(line)) 37 | 38 | 39 | class SandboxedFS(OSFS): 40 | 41 | def __init__(self, *args, **kwargs): 42 | create_fs = kwargs.pop('create_fs') if 'create_fs' in kwargs else False 43 | super(SandboxedFS, self).__init__(*args, **kwargs) 44 | if create_fs: 45 | for each in directories: 46 | try: 47 | self.makedirs(each) 48 | except fs.errors.DirectoryExists as e: 49 | logging.debug('Directory creation skipped for: %s', each) 50 | 51 | def isfile(self, path): 52 | if not isinstance(path, unicode): 53 | path = unicode(path) 54 | return super(SandboxedFS, self).isfile(path) 55 | 56 | def open(self, 57 | path, 58 | mode="r", 59 | buffering=-1, 60 | encoding=None, 61 | errors=None, 62 | newline='', 63 | line_buffering=False, 64 | **options): 65 | if not isinstance(path, unicode): 66 | path = unicode(path) 67 | logging.debug('Opening a new path: {}, mode={}'.format(path, mode)) 68 | return super(SandboxedFS, self).open( 69 | path, mode, buffering, encoding, errors, newline, **options 70 | ) 71 | 72 | def exists(self, path): 73 | if not isinstance(path, unicode): 74 | path = unicode(path) 75 | return super(SandboxedFS, self).exists(path) 76 | 77 | def makedir(self, path, permissions=None, recreate=False): 78 | if not isinstance(path, unicode): 79 | path = unicode(path) 80 | return super(SandboxedFS, self).makedir(path, permissions, recreate) 81 | 82 | def create(self, path, wipe=False): 83 | if not isinstance(path, unicode): 84 | path = unicode(path) 85 | return super(SandboxedFS, self).create(path, wipe) 86 | 87 | def listdir(self, path='/'): 88 | if not isinstance(path, unicode): 89 | path = unicode(path) 90 | return super(SandboxedFS, self).listdir(path) 91 | 92 | def isdir(self, path): 93 | if not isinstance(path, unicode): 94 | path = unicode(path) 95 | return super(SandboxedFS, self).isdir(path) 96 | -------------------------------------------------------------------------------- /hornet/core/handler.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import logging 21 | import os 22 | 23 | from paramiko import SSHException 24 | from telnetsrv.paramiko_ssh import SSHHandler 25 | 26 | from hornet.core.session import Session 27 | from hornet.core.shell import Shell 28 | from hornet.common.helpers import get_rsa_key_file 29 | 30 | logger = logging.getLogger(__name__) 31 | 32 | 33 | class SSHWrapper(object): 34 | 35 | """ Helper class to pass the client socket to _SSHHandler """ 36 | 37 | def __init__(self, vhosts, session_q, config, working_directory, db_handler): 38 | self.vhosts = vhosts 39 | self.session_q = session_q 40 | self.config = config 41 | self.db_handler = db_handler 42 | self.working_directory = working_directory 43 | 44 | def handle_session(self, client_socket, client_address): 45 | current_session = Session(client_address, self.session_q) 46 | logger.info('Connection from %s, %s', client_address, client_socket) 47 | 48 | # Set the host_key attribute on our _SSHHandler class 49 | key_file_path = os.path.join(self.working_directory, self.config.key_file) 50 | _SSHHandler.host_key = get_rsa_key_file(key_file_path) 51 | 52 | try: 53 | _SSHHandler(current_session, client_socket, client_address, self.vhosts, self.config, self.db_handler) 54 | except (SSHException, EOFError): 55 | logging.error('SSH Session %s ended unexpectedly', current_session.id) 56 | 57 | 58 | class _SSHHandler(SSHHandler): 59 | 60 | telnet_handler = Shell 61 | 62 | def __init__(self, session, socket, client_address, vhosts, config, db_handler): 63 | self.session = session 64 | self.vhosts = vhosts 65 | self.config = config 66 | self.db_handler = db_handler 67 | request = _SSHHandler.dummy_request() 68 | request._sock = socket 69 | super(_SSHHandler, self).__init__(request, client_address, None) 70 | 71 | def authCallbackUsername(self, username): 72 | raise Exception() # Disable username based logins. 73 | 74 | def authCallback(self, username, password): 75 | default = None 76 | for hostname, host in self.vhosts.iteritems(): 77 | if host.default: 78 | default = host 79 | if default.authenticate(username, password): 80 | return True 81 | else: 82 | raise Exception('Bad username/password') 83 | 84 | def setup(self): 85 | 86 | self.transport.load_server_moduli() 87 | self.transport.add_server_key(self.host_key) 88 | self.transport.start_server(server=self) 89 | 90 | while True: # pragma: no cover 91 | channel = self.transport.accept(20) 92 | if channel is None: 93 | # check to see if any thread is running 94 | any_running = False 95 | for c, thread in self.channels.items(): 96 | if thread.is_alive(): 97 | any_running = True 98 | break 99 | if not any_running: 100 | break 101 | 102 | def start_pty_request(self, channel, term, modes): # pragma: no cover 103 | """ Start a PTY - intended to run it a (green)thread. """ 104 | request = self.dummy_request() 105 | request._sock = channel 106 | request.modes = modes 107 | request.term = term 108 | request.username = self.username 109 | 110 | # This should block until the user quits the pty 111 | self.pty_handler(request, self.client_address, self.tcp_server, 112 | self.session, self.vhosts, self.config, self.db_handler) 113 | 114 | # Shutdown the entire session 115 | self.transport.close() 116 | -------------------------------------------------------------------------------- /hornet/core/session.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import uuid 21 | import arrow 22 | import gevent 23 | import logging 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | 28 | class Session(object): 29 | 30 | def __init__(self, client_address, session_q): 31 | self.id = uuid.uuid4() 32 | self.start_time = arrow.now().timestamp 33 | self.end_time = None 34 | self.client_address = client_address 35 | self.session_q = session_q 36 | self.last_activity = arrow.now().timestamp 37 | self.watcher_greenlet = gevent.spawn(self.watch) 38 | 39 | def watch(self, max_diff=60): 40 | logger.debug('Started watching %s', self) 41 | while True: 42 | diff = arrow.now().timestamp - self.last_activity 43 | if diff > max_diff: 44 | self.finish() 45 | break 46 | gevent.sleep(5) 47 | 48 | def finish(self): 49 | logger.debug('Detected inactive session: %s', self.id) 50 | self.end_time = self.last_activity 51 | self.session_q.put(self) 52 | 53 | def __repr__(self): 54 | return ''.format( 55 | self.last_activity, 56 | self.id, 57 | self.client_address 58 | ) 59 | -------------------------------------------------------------------------------- /hornet/core/shell.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import argparse 21 | import curses 22 | 23 | import logging 24 | import socket 25 | import arrow 26 | 27 | from telnetsrv.green import TelnetHandler 28 | 29 | logger = logging.getLogger(__name__) 30 | 31 | 32 | class Shell(TelnetHandler): 33 | """ 34 | This class implements the shell functionality. It handles the various VirtualHosts and 35 | functions as the point of communication for a Session. 36 | """ 37 | PROMPT = '' 38 | WELCOME = '' 39 | 40 | def __init__(self, request, client_address, server, session, vhosts, config, db_handler): 41 | self.session = session 42 | self.vhosts = vhosts 43 | self.login_stack = [] 44 | self.logging = logger 45 | self.current_host = None 46 | self.config = config 47 | self.raw_input = None 48 | self.input = None 49 | self.command_greenlet = None 50 | self.interrupt = False 51 | self.db_handler = db_handler 52 | self.db_handler.create_attack_session(self.session) 53 | 54 | TelnetHandler.__init__(self, request, client_address, server) 55 | 56 | def set_host(self, host, default=False): 57 | 58 | self.current_host = host 59 | if default: 60 | self.current_host.login(self.username) 61 | self.writeline(self.current_host.welcome) 62 | self.PROMPT = self.current_host.prompt 63 | self.WELCOME = self.current_host.welcome 64 | 65 | def handle(self): # pragma: no cover 66 | 67 | if not self.authentication_ok(): 68 | return 69 | 70 | default_host = self.vhosts[self.config.default_hostname] 71 | self.login_stack.append(default_host) 72 | self.set_host(default_host, default=True) 73 | self.session_start() 74 | while self.RUNSHELL: 75 | try: 76 | raw_input_ = self.readline(prompt=self.PROMPT).strip() 77 | self.input = self.input_reader(self, raw_input_) 78 | self.raw_input = self.input.raw 79 | if self.input.cmd: 80 | 81 | # Clear the interrupt flag 82 | self.interrupt = False 83 | 84 | cmd = self.input.cmd 85 | params = self.input.params 86 | try: 87 | if cmd == 'QUIT': # Handle Ctrl+D 88 | cmd = 'logout' 89 | if cmd in {'ssh', 'logout'}: # These are handled by the Shell itself. 90 | command = getattr(self, 'run_' + cmd) 91 | command(params) 92 | else: # The rest of the commands are handled by the VirtualHosts 93 | command = getattr(self.current_host, 'run_' + cmd) 94 | command(params, self) 95 | except AttributeError: 96 | # User entered something we have not implemented. 97 | logger.exception('AttributeError occured while running ' 98 | 'command "%s" with params "%s"', cmd, params) 99 | self.writeerror("{}: command not found".format(cmd)) 100 | except: 101 | logger.exception('Unknown exception has occured') 102 | self.writeerror("{}: command not found".format(cmd)) 103 | finally: 104 | self.PROMPT = self.current_host.prompt 105 | self.db_handler.create_attack_command( 106 | self.session.id, cmd, self.current_host 107 | ) 108 | except socket.error: 109 | break 110 | self.logging.debug("Exiting handler") 111 | 112 | def run_ssh(self, params): 113 | parser = argparse.ArgumentParser() 114 | parser.add_argument('-p', dest='port', default=22, type=int) 115 | parser.add_argument('-l', dest='username') 116 | parser.add_argument('host_string') 117 | args = parser.parse_args(params) 118 | username = args.username 119 | if username is None: 120 | try: 121 | username, _ = args.host_string.split('@') 122 | except ValueError: 123 | username = self.current_host.current_user 124 | if '@' in args.host_string: 125 | _, hostname = args.host_string.split('@') 126 | else: 127 | hostname = args.host_string 128 | if hostname not in self.vhosts: 129 | self.writeline('ssh: Could not resolve hostname {}: Name or service not known'.format(hostname)) 130 | return 131 | 132 | # Hostname is valid. Now get the password. 133 | new_host = self.vhosts[hostname] 134 | password = self.readline(echo=False, prompt=self.PROMPT_PASS, use_history=False) 135 | if new_host.authenticate(username, password): 136 | self.login_stack.append(new_host) 137 | new_host.login(username) 138 | self.set_host(new_host) 139 | self.writeline(new_host.welcome) 140 | 141 | def run_logout(self, _): # Don't care about the params 142 | if len(self.login_stack) == 1: 143 | self.RUNSHELL = False 144 | self.login_stack = [] 145 | return 146 | current_host = self.login_stack.pop() 147 | current_host.logout() 148 | prev_host = self.login_stack[-1] 149 | self.set_host(prev_host) 150 | 151 | def setterm(self, term): 152 | """ Set the curses structures for this terminal """ 153 | logger.debug("Setting termtype to %s" % (term, )) 154 | try: 155 | curses.setupterm(term) # This will raise if the termtype is not supported 156 | except TypeError: 157 | file_ = open('/dev/null', 'w') 158 | dummyfd = file_.fileno() 159 | curses.setupterm(term, fd=dummyfd) 160 | 161 | self.TERM = term 162 | self.ESCSEQ = {} 163 | for k in self.KEYS.keys(): 164 | str_ = curses.tigetstr(curses.has_key._capability_names[k]) 165 | if str_: 166 | self.ESCSEQ[str_] = k 167 | # Create a copy to prevent altering the class 168 | self.CODES = self.CODES.copy() 169 | self.CODES['DEOL'] = curses.tigetstr('el') 170 | self.CODES['DEL'] = curses.tigetstr('dch1') 171 | self.CODES['INS'] = curses.tigetstr('ich1') 172 | self.CODES['CSRLEFT'] = curses.tigetstr('cub1') 173 | self.CODES['CSRRIGHT'] = curses.tigetstr('cuf1') 174 | 175 | def inputcooker_store_queue(self, char): # pragma: no cover 176 | """Put the cooked data in the input queue (no locking needed)""" 177 | if type(char) in [type(()), type([]), type("")]: 178 | for v in char: 179 | if v == chr(3): 180 | self.interrupt = True 181 | self.cookedq.put(v) 182 | else: 183 | if char == chr(3): 184 | self.interrupt = True 185 | self.cookedq.put(char) 186 | 187 | def updateline(self, data): 188 | self.write('\r') 189 | self.write(self.CODES['DEOL']) 190 | self.write(data) 191 | 192 | def writecooked(self, text): 193 | TelnetHandler.writecooked(self, text) 194 | self.session.last_activity = arrow.now().timestamp 195 | -------------------------------------------------------------------------------- /hornet/data/commands/ifconfig/help: -------------------------------------------------------------------------------- 1 | Usage: 2 | ifconfig [-a] [-v] [-s] [[]
] 3 | [add
[/]] 4 | [del
[/]] 5 | [[-]broadcast [
]] [[-]pointopoint [
]] 6 | [netmask
] [dstaddr
] [tunnel
] 7 | [outfill ] [keepalive ] 8 | [hw
] [metric ] [mtu ] 9 | [[-]trailers] [[-]arp] [[-]allmulti] 10 | [multicast] [[-]promisc] 11 | [mem_start ] [io_addr ] [irq ] [media ] 12 | [txqueuelen ] 13 | [[-]dynamic] 14 | [up|down] ... 15 | 16 | =Hardware Type. 17 | List of possible hardware types: 18 | loop (Local Loopback) slip (Serial Line IP) cslip (VJ Serial Line IP) 19 | slip6 (6-bit Serial Line IP) cslip6 (VJ 6-bit Serial Line IP) adaptive (Adaptive Serial Line IP) 20 | ash (Ash) ether (Ethernet) ax25 (AMPR AX.25) 21 | netrom (AMPR NET/ROM) rose (AMPR ROSE) tunnel (IPIP Tunnel) 22 | ppp (Point-to-Point Protocol) hdlc ((Cisco)-HDLC) lapb (LAPB) 23 | arcnet (ARCnet) dlci (Frame Relay DLCI) frad (Frame Relay Access Device) 24 | sit (IPv6-in-IPv4) fddi (Fiber Distributed Data Interface) hippi (HIPPI) 25 | irda (IrLAP) ec (Econet) x25 (generic X.25) 26 | eui64 (Generic EUI-64) 27 | =Address family. Default: inet 28 | List of possible address families: 29 | unix (UNIX Domain) inet (DARPA Internet) inet6 (IPv6) 30 | ax25 (AMPR AX.25) netrom (AMPR NET/ROM) rose (AMPR ROSE) 31 | ipx (Novell IPX) ddp (Appletalk DDP) ec (Econet) 32 | ash (Ash) x25 (CCITT X.25) 33 | -------------------------------------------------------------------------------- /hornet/data/commands/ifconfig/output_template: -------------------------------------------------------------------------------- 1 | eth0 Link encap:Ethernet HWaddr 00:16:3e:76:35:d1 2 | inet addr:${ip_addr} Bcast:${broadcast_addr} Mask:${subnet_mask} 3 | UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 4 | RX packets:326315 errors:0 dropped:0 overruns:0 frame:3713742 5 | TX packets:220255 errors:152 dropped:0 overruns:0 carrier:0 6 | collisions:0 txqueuelen:1000 7 | RX bytes:423979889 (423.9 MB) TX bytes:26847799 (968.2 MB) 8 | Interrupt:18 9 | 10 | lo Link encap:Local Loopback 11 | inet addr:127.0.0.1 Mask:255.0.0.0 12 | inet6 addr: ::1/128 Scope:Host 13 | UP LOOPBACK RUNNING MTU:65536 Metric:1 14 | RX packets:71580 errors:0 dropped:0 overruns:0 frame:0 15 | TX packets:71580 errors:0 dropped:0 overruns:0 carrier:0 16 | collisions:0 txqueuelen:0 17 | RX bytes:97211487 (97.2 MB) TX bytes:97211487 (97.2 MB) 18 | -------------------------------------------------------------------------------- /hornet/data/commands/ifconfig/version: -------------------------------------------------------------------------------- 1 | net-tools 1.60 2 | ifconfig 1.42 (2001-04-13) 3 | -------------------------------------------------------------------------------- /hornet/data/commands/ls/help: -------------------------------------------------------------------------------- 1 | Usage: ls [OPTION]... [FILE]... 2 | List information about the FILEs (the current directory by default). 3 | Sort entries alphabetically if none of -cftuvSUX nor --sort is specified. 4 | 5 | Mandatory arguments to long options are mandatory for short options too. 6 | -a, --all do not ignore entries starting with . 7 | -A, --almost-all do not list implied . and .. 8 | --author with -l, print the author of each file 9 | -b, --escape print C-style escapes for nongraphic characters 10 | --block-size=SIZE scale sizes by SIZE before printing them. E.g., 11 | '--block-size=M' prints sizes in units of 12 | 1,048,576 bytes. See SIZE format below. 13 | -B, --ignore-backups do not list implied entries ending with ~ 14 | -c with -lt: sort by, and show, ctime (time of last 15 | modification of file status information) 16 | with -l: show ctime and sort by name 17 | otherwise: sort by ctime, newest first 18 | -C list entries by columns 19 | --color[=WHEN] colorize the output. WHEN defaults to 'always' 20 | or can be 'never' or 'auto'. More info below 21 | -d, --directory list directory entries instead of contents, 22 | and do not dereference symbolic links 23 | -D, --dired generate output designed for Emacs' dired mode 24 | -f do not sort, enable -aU, disable -ls --color 25 | -F, --classify append indicator (one of */=>@|) to entries 26 | --file-type likewise, except do not append '*' 27 | --format=WORD across -x, commas -m, horizontal -x, long -l, 28 | single-column -1, verbose -l, vertical -C 29 | --full-time like -l --time-style=full-iso 30 | -g like -l, but do not list owner 31 | --group-directories-first 32 | group directories before files. 33 | augment with a --sort option, but any 34 | use of --sort=none (-U) disables grouping 35 | -G, --no-group in a long listing, don't print group names 36 | -h, --human-readable with -l, print sizes in human readable format 37 | (e.g., 1K 234M 2G) 38 | --si likewise, but use powers of 1000 not 1024 39 | -H, --dereference-command-line 40 | follow symbolic links listed on the command line 41 | --dereference-command-line-symlink-to-dir 42 | follow each command line symbolic link 43 | that points to a directory 44 | --hide=PATTERN do not list implied entries matching shell PATTERN 45 | (overridden by -a or -A) 46 | --indicator-style=WORD append indicator with style WORD to entry names: 47 | none (default), slash (-p), 48 | file-type (--file-type), classify (-F) 49 | -i, --inode print the index number of each file 50 | -I, --ignore=PATTERN do not list implied entries matching shell PATTERN 51 | -k, --kibibytes use 1024-byte blocks 52 | -l use a long listing format 53 | -L, --dereference when showing file information for a symbolic 54 | link, show information for the file the link 55 | references rather than for the link itself 56 | -m fill width with a comma separated list of entries 57 | -n, --numeric-uid-gid like -l, but list numeric user and group IDs 58 | -N, --literal print raw entry names (don't treat e.g. control 59 | characters specially) 60 | -o like -l, but do not list group information 61 | -p, --indicator-style=slash 62 | append / indicator to directories 63 | -q, --hide-control-chars print ? instead of non graphic characters 64 | --show-control-chars show non graphic characters as-is (default 65 | unless program is 'ls' and output is a terminal) 66 | -Q, --quote-name enclose entry names in double quotes 67 | --quoting-style=WORD use quoting style WORD for entry names: 68 | literal, locale, shell, shell-always, c, escape 69 | -r, --reverse reverse order while sorting 70 | -R, --recursive list subdirectories recursively 71 | -s, --size print the allocated size of each file, in blocks 72 | -S sort by file size 73 | --sort=WORD sort by WORD instead of name: none -U, 74 | extension -X, size -S, time -t, version -v 75 | --time=WORD with -l, show time as WORD instead of modification 76 | time: atime -u, access -u, use -u, ctime -c, 77 | or status -c; use specified time as sort key 78 | if --sort=time 79 | --time-style=STYLE with -l, show times using style STYLE: 80 | full-iso, long-iso, iso, locale, +FORMAT. 81 | FORMAT is interpreted like 'date'; if FORMAT is 82 | FORMAT1FORMAT2, FORMAT1 applies to 83 | non-recent files and FORMAT2 to recent files; 84 | if STYLE is prefixed with 'posix-', STYLE 85 | takes effect only outside the POSIX locale 86 | -t sort by modification time, newest first 87 | -T, --tabsize=COLS assume tab stops at each COLS instead of 8 88 | -u with -lt: sort by, and show, access time 89 | with -l: show access time and sort by name 90 | otherwise: sort by access time 91 | -U do not sort; list entries in directory order 92 | -v natural sort of (version) numbers within text 93 | -w, --width=COLS assume screen width instead of current value 94 | -x list entries by lines instead of by columns 95 | -X sort alphabetically by entry extension 96 | -Z, --context print any SELinux security context of each file 97 | -1 list one file per line 98 | --help display this help and exit 99 | --version output version information and exit 100 | 101 | SIZE is an integer and optional unit (example: 10M is 10*1024*1024). Units 102 | are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB, ... (powers of 1000). 103 | 104 | Using color to distinguish file types is disabled both by default and 105 | with --color=never. With --color=auto, ls emits color codes only when 106 | standard output is connected to a terminal. The LS_COLORS environment 107 | variable can change the settings. Use the dircolors command to set it. 108 | 109 | Exit status: 110 | 0 if OK, 111 | 1 if minor problems (e.g., cannot access subdirectory), 112 | 2 if serious trouble (e.g., cannot access command-line argument). 113 | 114 | Report ls bugs to bug-coreutils@gnu.org 115 | GNU coreutils home page: 116 | General help using GNU software: 117 | For complete documentation, run: info coreutils 'ls invocation' 118 | -------------------------------------------------------------------------------- /hornet/data/commands/ls/version: -------------------------------------------------------------------------------- 1 | ls (GNU coreutils) 8.21 2 | Copyright (C) 2013 Free Software Foundation, Inc. 3 | License GPLv3+: GNU GPL version 3 or later . 4 | This is free software: you are free to change and redistribute it. 5 | There is NO WARRANTY, to the extent permitted by law. 6 | 7 | Written by Richard M. Stallman and David MacKenzie. 8 | -------------------------------------------------------------------------------- /hornet/data/commands/ping/help: -------------------------------------------------------------------------------- 1 | Usage: ping [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface] 2 | [-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos] 3 | [-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option] 4 | [-w deadline] [-W timeout] [hop1 ...] destination 5 | -------------------------------------------------------------------------------- /hornet/data/commands/uname/help: -------------------------------------------------------------------------------- 1 | Usage: uname [OPTION]... 2 | Print certain system information. With no OPTION, same as -s. 3 | 4 | -a, --all print all information, in the following order, 5 | except omit -p and -i if unknown: 6 | -s, --kernel-name print the kernel name 7 | -n, --nodename print the network node hostname 8 | -r, --kernel-release print the kernel release 9 | -v, --kernel-version print the kernel version 10 | -m, --machine print the machine hardware name 11 | -p, --processor print the processor type or "unknown" 12 | -i, --hardware-platform print the hardware platform or "unknown" 13 | -o, --operating-system print the operating system 14 | --help display this help and exit 15 | --version output version information and exit 16 | 17 | Report uname bugs to bug-coreutils@gnu.org 18 | GNU coreutils home page: 19 | General help using GNU software: 20 | For complete documentation, run: info coreutils 'uname invocation' 21 | -------------------------------------------------------------------------------- /hornet/data/commands/uname/version: -------------------------------------------------------------------------------- 1 | uname (GNU coreutils) 8.21 2 | Copyright (C) 2013 Free Software Foundation, Inc. 3 | License GPLv3+: GNU GPL version 3 or later . 4 | This is free software: you are free to change and redistribute it. 5 | There is NO WARRANTY, to the extent permitted by law. 6 | 7 | Written by David MacKenzie. 8 | -------------------------------------------------------------------------------- /hornet/data/commands/wget/help: -------------------------------------------------------------------------------- 1 | GNU Wget 1.15, a non-interactive network retriever. 2 | Usage: wget [OPTION]... [URL]... 3 | 4 | Mandatory arguments to long options are mandatory for short options too. 5 | 6 | Startup: 7 | -V, --version display the version of Wget and exit. 8 | -h, --help print this help. 9 | -b, --background go to background after startup. 10 | -e, --execute=COMMAND execute a `.wgetrc'-style command. 11 | 12 | Logging and input file: 13 | -o, --output-file=FILE log messages to FILE. 14 | -a, --append-output=FILE append messages to FILE. 15 | -d, --debug print lots of debugging information. 16 | -q, --quiet quiet (no output). 17 | -v, --verbose be verbose (this is the default). 18 | -nv, --no-verbose turn off verboseness, without being quiet. 19 | --report-speed=TYPE Output bandwidth as TYPE. TYPE can be bits. 20 | -i, --input-file=FILE download URLs found in local or external FILE. 21 | -F, --force-html treat input file as HTML. 22 | -B, --base=URL resolves HTML input-file links (-i -F) 23 | relative to URL. 24 | --config=FILE Specify config file to use. 25 | 26 | Download: 27 | -t, --tries=NUMBER set number of retries to NUMBER (0 unlimits). 28 | --retry-connrefused retry even if connection is refused. 29 | -O, --output-document=FILE write documents to FILE. 30 | -nc, --no-clobber skip downloads that would download to 31 | existing files (overwriting them). 32 | -c, --continue resume getting a partially-downloaded file. 33 | --progress=TYPE select progress gauge type. 34 | -N, --timestamping don't re-retrieve files unless newer than 35 | local. 36 | --no-use-server-timestamps don't set the local file's timestamp by 37 | the one on the server. 38 | -S, --server-response print server response. 39 | --spider don't download anything. 40 | -T, --timeout=SECONDS set all timeout values to SECONDS. 41 | --dns-timeout=SECS set the DNS lookup timeout to SECS. 42 | --connect-timeout=SECS set the connect timeout to SECS. 43 | --read-timeout=SECS set the read timeout to SECS. 44 | -w, --wait=SECONDS wait SECONDS between retrievals. 45 | --waitretry=SECONDS wait 1..SECONDS between retries of a retrieval. 46 | --random-wait wait from 0.5*WAIT...1.5*WAIT secs between retrievals. 47 | --no-proxy explicitly turn off proxy. 48 | -Q, --quota=NUMBER set retrieval quota to NUMBER. 49 | --bind-address=ADDRESS bind to ADDRESS (hostname or IP) on local host. 50 | --limit-rate=RATE limit download rate to RATE. 51 | --no-dns-cache disable caching DNS lookups. 52 | --restrict-file-names=OS restrict chars in file names to ones OS allows. 53 | --ignore-case ignore case when matching files/directories. 54 | -4, --inet4-only connect only to IPv4 addresses. 55 | -6, --inet6-only connect only to IPv6 addresses. 56 | --prefer-family=FAMILY connect first to addresses of specified family, 57 | one of IPv6, IPv4, or none. 58 | --user=USER set both ftp and http user to USER. 59 | --password=PASS set both ftp and http password to PASS. 60 | --ask-password prompt for passwords. 61 | --no-iri turn off IRI support. 62 | --local-encoding=ENC use ENC as the local encoding for IRIs. 63 | --remote-encoding=ENC use ENC as the default remote encoding. 64 | --unlink remove file before clobber. 65 | 66 | Directories: 67 | -nd, --no-directories don't create directories. 68 | -x, --force-directories force creation of directories. 69 | -nH, --no-host-directories don't create host directories. 70 | --protocol-directories use protocol name in directories. 71 | -P, --directory-prefix=PREFIX save files to PREFIX/... 72 | --cut-dirs=NUMBER ignore NUMBER remote directory components. 73 | 74 | HTTP options: 75 | --http-user=USER set http user to USER. 76 | --http-password=PASS set http password to PASS. 77 | --no-cache disallow server-cached data. 78 | --default-page=NAME Change the default page name (normally 79 | this is `index.html'.). 80 | -E, --adjust-extension save HTML/CSS documents with proper extensions. 81 | --ignore-length ignore `Content-Length' header field. 82 | --header=STRING insert STRING among the headers. 83 | --max-redirect maximum redirections allowed per page. 84 | --proxy-user=USER set USER as proxy username. 85 | --proxy-password=PASS set PASS as proxy password. 86 | --referer=URL include `Referer: URL' header in HTTP request. 87 | --save-headers save the HTTP headers to file. 88 | -U, --user-agent=AGENT identify as AGENT instead of Wget/VERSION. 89 | --no-http-keep-alive disable HTTP keep-alive (persistent connections). 90 | --no-cookies don't use cookies. 91 | --load-cookies=FILE load cookies from FILE before session. 92 | --save-cookies=FILE save cookies to FILE after session. 93 | --keep-session-cookies load and save session (non-permanent) cookies. 94 | --post-data=STRING use the POST method; send STRING as the data. 95 | --post-file=FILE use the POST method; send contents of FILE. 96 | --method=HTTPMethod use method "HTTPMethod" in the header. 97 | --body-data=STRING Send STRING as data. --method MUST be set. 98 | --body-file=FILE Send contents of FILE. --method MUST be set. 99 | --content-disposition honor the Content-Disposition header when 100 | choosing local file names (EXPERIMENTAL). 101 | --content-on-error output the received content on server errors. 102 | --auth-no-challenge send Basic HTTP authentication information 103 | without first waiting for the server's 104 | challenge. 105 | 106 | HTTPS (SSL/TLS) options: 107 | --secure-protocol=PR choose secure protocol, one of auto, SSLv2, 108 | SSLv3, TLSv1 and PFS. 109 | --https-only only follow secure HTTPS links 110 | --no-check-certificate don't validate the server's certificate. 111 | --certificate=FILE client certificate file. 112 | --certificate-type=TYPE client certificate type, PEM or DER. 113 | --private-key=FILE private key file. 114 | --private-key-type=TYPE private key type, PEM or DER. 115 | --ca-certificate=FILE file with the bundle of CA's. 116 | --ca-directory=DIR directory where hash list of CA's is stored. 117 | --random-file=FILE file with random data for seeding the SSL PRNG. 118 | --egd-file=FILE file naming the EGD socket with random data. 119 | 120 | FTP options: 121 | --ftp-user=USER set ftp user to USER. 122 | --ftp-password=PASS set ftp password to PASS. 123 | --no-remove-listing don't remove `.listing' files. 124 | --no-glob turn off FTP file name globbing. 125 | --no-passive-ftp disable the "passive" transfer mode. 126 | --preserve-permissions preserve remote file permissions. 127 | --retr-symlinks when recursing, get linked-to files (not dir). 128 | 129 | WARC options: 130 | --warc-file=FILENAME save request/response data to a .warc.gz file. 131 | --warc-header=STRING insert STRING into the warcinfo record. 132 | --warc-max-size=NUMBER set maximum size of WARC files to NUMBER. 133 | --warc-cdx write CDX index files. 134 | --warc-dedup=FILENAME do not store records listed in this CDX file. 135 | --no-warc-compression do not compress WARC files with GZIP. 136 | --no-warc-digests do not calculate SHA1 digests. 137 | --no-warc-keep-log do not store the log file in a WARC record. 138 | --warc-tempdir=DIRECTORY location for temporary files created by the 139 | WARC writer. 140 | 141 | Recursive download: 142 | -r, --recursive specify recursive download. 143 | -l, --level=NUMBER maximum recursion depth (inf or 0 for infinite). 144 | --delete-after delete files locally after downloading them. 145 | -k, --convert-links make links in downloaded HTML or CSS point to 146 | local files. 147 | --backups=N before writing file X, rotate up to N backup files. 148 | -K, --backup-converted before converting file X, back up as X.orig. 149 | -m, --mirror shortcut for -N -r -l inf --no-remove-listing. 150 | -p, --page-requisites get all images, etc. needed to display HTML page. 151 | --strict-comments turn on strict (SGML) handling of HTML comments. 152 | 153 | Recursive accept/reject: 154 | -A, --accept=LIST comma-separated list of accepted extensions. 155 | -R, --reject=LIST comma-separated list of rejected extensions. 156 | --accept-regex=REGEX regex matching accepted URLs. 157 | --reject-regex=REGEX regex matching rejected URLs. 158 | --regex-type=TYPE regex type (posix). 159 | -D, --domains=LIST comma-separated list of accepted domains. 160 | --exclude-domains=LIST comma-separated list of rejected domains. 161 | --follow-ftp follow FTP links from HTML documents. 162 | --follow-tags=LIST comma-separated list of followed HTML tags. 163 | --ignore-tags=LIST comma-separated list of ignored HTML tags. 164 | -H, --span-hosts go to foreign hosts when recursive. 165 | -L, --relative follow relative links only. 166 | -I, --include-directories=LIST list of allowed directories. 167 | --trust-server-names use the name specified by the redirection 168 | url last component. 169 | -X, --exclude-directories=LIST list of excluded directories. 170 | -np, --no-parent don't ascend to the parent directory. 171 | 172 | Mail bug reports and suggestions to . 173 | -------------------------------------------------------------------------------- /hornet/data/commands/wget/no_param: -------------------------------------------------------------------------------- 1 | wget: missing URL 2 | Usage: wget [OPTION]... [URL]... 3 | 4 | Try 'wget --help' for more options. -------------------------------------------------------------------------------- /hornet/data/commands/wget/version: -------------------------------------------------------------------------------- 1 | GNU Wget 1.15 built on linux-gnu. 2 | 3 | +digest +https +ipv6 +iri +large-file +nls +ntlm +opie +ssl/openssl 4 | 5 | Wgetrc: 6 | /etc/wgetrc (system) 7 | Locale: 8 | /usr/share/locale 9 | Compile: 10 | gcc -DHAVE_CONFIG_H -DSYSTEM_WGETRC="/etc/wgetrc" 11 | -DLOCALEDIR="/usr/share/locale" -I. -I../../src -I../lib 12 | -I../../lib -D_FORTIFY_SOURCE=2 -I/usr/include -g -O2 13 | -fstack-protector --param=ssp-buffer-size=4 -Wformat 14 | -Werror=format-security -DNO_SSLv2 -D_FILE_OFFSET_BITS=64 -g -Wall 15 | Link: 16 | gcc -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat 17 | -Werror=format-security -DNO_SSLv2 -D_FILE_OFFSET_BITS=64 -g -Wall 18 | -Wl,-Bsymbolic-functions -Wl,-z,relro -L/usr/lib -lssl -lcrypto 19 | -ldl -lz -lidn -luuid ftp-opie.o openssl.o http-ntlm.o 20 | ../lib/libgnu.a 21 | 22 | Copyright (C) 2011 Free Software Foundation, Inc. 23 | License GPLv3+: GNU GPL version 3 or later 24 | . 25 | This is free software: you are free to change and redistribute it. 26 | There is NO WARRANTY, to the extent permitted by law. 27 | 28 | Originally written by Hrvoje Niksic . 29 | Please send bug reports and questions to . 30 | -------------------------------------------------------------------------------- /hornet/data/default_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 0, 3 | "host": "127.0.0.1", 4 | "key_file": "test_server.key", 5 | "network": { 6 | "network_ip": "192.168.0.0/24", 7 | "dns_server": "192.168.0.2", 8 | "gateway": "192.168.0.1" 9 | }, 10 | "database": "mysql+mysqldb://travis@127.0.0.1/hornet", 11 | "virtual_hosts": [ 12 | { 13 | "hostname": "test01", 14 | "valid_logins": { 15 | "admin": "admin123", 16 | "root": "toor", 17 | "testuser": "passtest" 18 | }, 19 | "env": { 20 | "BROWSER": "firefox", 21 | "EDITOR": "gedit", 22 | "SHELL": "/bin/bash", 23 | "PAGER": "less" 24 | }, 25 | "ip_address": null 26 | }, 27 | { 28 | "hostname": "test02", 29 | "valid_logins": { 30 | "mango": "apple", 31 | "vstfpd": "1q2w3e4r", 32 | "testuser": "testpassword" 33 | }, 34 | "env": { 35 | "BROWSER": "firefox", 36 | "EDITOR": "gedit", 37 | "SHELL": "/bin/bash", 38 | "PAGER": "less" 39 | }, 40 | "default": true, 41 | "ip_address": "192.168.0.232" 42 | }, 43 | { 44 | "hostname": "test03", 45 | "valid_logins": { 46 | "administrator": "qwerty", 47 | "httpd": "httpd", 48 | "root": "s3cr3t" 49 | }, 50 | "env": { 51 | "BROWSER": "firefox", 52 | "EDITOR": "gedit", 53 | "SHELL": "/bin/bash", 54 | "PAGER": "less" 55 | }, 56 | "ip_address": "192.168.0.443" 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /hornet/data/linux_fs_list.txt: -------------------------------------------------------------------------------- 1 | /bin 2 | /home 3 | /tmp 4 | /dev 5 | /dev/v4l 6 | /dev/vboxusb 7 | /dev/snd 8 | /dev/vfio 9 | /dev/hugepages 10 | /dev/mqueue 11 | /dev/shm 12 | /dev/usb 13 | /dev/disk 14 | /dev/dri 15 | /dev/block 16 | /dev/char 17 | /dev/pts 18 | /dev/cpu 19 | /dev/mapper 20 | /dev/input 21 | /dev/bus 22 | /dev/net 23 | /dev/lightnvm 24 | /sys 25 | /sys/fs 26 | /sys/bus 27 | /sys/dev 28 | /sys/devices 29 | /sys/block 30 | /sys/class 31 | /sys/power 32 | /sys/firmware 33 | /sys/kernel 34 | /sys/module 35 | /sys/hypervisor 36 | /etc 37 | /etc/python2.7 38 | /etc/dictionaries-common 39 | /etc/ufw 40 | /etc/cron.daily 41 | /etc/gss 42 | /etc/java-8-openjdk 43 | /etc/gtk-3.0 44 | /etc/calendar 45 | /etc/icedtea-web 46 | /etc/logrotate.d 47 | /etc/tex4ht 48 | /etc/sane.d 49 | /etc/vmware-vix 50 | /etc/gdb 51 | /etc/apparmor.d 52 | /etc/default 53 | /etc/bonobo-activation 54 | /etc/firefox 55 | /etc/lvm 56 | /etc/selinux 57 | /etc/rc1.d 58 | /etc/dbus-1 59 | /etc/texmf 60 | /etc/gnome-app-install 61 | /etc/timidity 62 | /etc/usb_modeswitch.d 63 | /etc/X11 64 | /etc/brltty 65 | /etc/ldap 66 | /etc/menu 67 | /etc/ODBCDataSources 68 | /etc/python3.5 69 | /etc/signon-ui 70 | /etc/avahi 71 | /etc/gnome-vfs-2.0 72 | /etc/wpa_supplicant 73 | /etc/wireshark 74 | /etc/grub.d 75 | /etc/ifplugd 76 | /etc/gimp 77 | /etc/modprobe.d 78 | /etc/bluetooth 79 | /etc/dconf 80 | /etc/polkit-1 81 | /etc/UPower 82 | /etc/mysql 83 | /etc/doc-base 84 | /etc/alternatives 85 | /etc/bash_completion.d 86 | /etc/.java 87 | /etc/ca-certificates 88 | /etc/systemd 89 | /etc/zsh 90 | /etc/sound 91 | /etc/udisks2 92 | /etc/libpaper.d 93 | /etc/compizconfig 94 | /etc/gconf 95 | /etc/sysctl.d 96 | /etc/initramfs-tools 97 | /etc/ppp 98 | /etc/rcS.d 99 | /etc/tmpfiles.d 100 | /etc/rsyslog.d 101 | /etc/sudoers.d 102 | /etc/emacs 103 | /etc/cupshelpers 104 | /etc/openvpn 105 | /etc/apport 106 | /etc/ssh 107 | /etc/groff 108 | /etc/menu-methods 109 | /etc/pulse 110 | /etc/kernel 111 | /etc/insserv.conf.d 112 | /etc/samba 113 | /etc/lightdm 114 | /etc/smartmontools 115 | /etc/fonts 116 | /etc/console-setup 117 | /etc/perl 118 | /etc/apparmor 119 | /etc/binfmt.d 120 | /etc/aptdaemon 121 | /etc/docker 122 | /etc/guest-session 123 | /etc/hp 124 | /etc/sgml 125 | /etc/speech-dispatcher 126 | /etc/request-key.d 127 | /etc/rc0.d 128 | /etc/gnome 129 | /etc/apt 130 | /etc/update-manager 131 | /etc/insserv 132 | /etc/vim 133 | /etc/network 134 | /etc/update-motd.d 135 | /etc/openal 136 | /etc/cron.hourly 137 | /etc/vmware 138 | /etc/rc3.d 139 | /etc/iproute2 140 | /etc/gtk-2.0 141 | /etc/modules-load.d 142 | /etc/thnuclnt 143 | /etc/acpi 144 | /etc/NetworkManager 145 | /etc/libreoffice 146 | /etc/depmod.d 147 | /etc/apm 148 | /etc/openmpi 149 | /etc/maven 150 | /etc/cups 151 | /etc/profile.d 152 | /etc/udev 153 | /etc/pam.d 154 | /etc/ImageMagick-6 155 | /etc/mono 156 | /etc/lighttpd 157 | /etc/pcmcia 158 | /etc/security 159 | /etc/pki 160 | /etc/cron.weekly 161 | /etc/python 162 | /etc/rc5.d 163 | /etc/logcheck 164 | /etc/rc6.d 165 | /etc/cron.monthly 166 | /etc/xdg 167 | /etc/cron.d 168 | /etc/terminfo 169 | /etc/ssl 170 | /etc/dpkg 171 | /etc/chatscripts 172 | /etc/xml 173 | /etc/ghostscript 174 | /etc/update-notifier 175 | /etc/sensors.d 176 | /etc/libnl-3 177 | /etc/dhcp 178 | /etc/game-data-packager 179 | /etc/vmware-installer 180 | /etc/python3 181 | /etc/kbd 182 | /etc/ld.so.conf.d 183 | /etc/resolvconf 184 | /etc/opt 185 | /etc/dkms 186 | /etc/cracklib 187 | /etc/newt 188 | /etc/rc4.d 189 | /etc/skel 190 | /etc/armagetronad 191 | /etc/init 192 | /etc/pm 193 | /etc/java 194 | /etc/apache2 195 | /etc/dnsmasq.d 196 | /etc/rc2.d 197 | /etc/thermald 198 | /etc/at-spi2 199 | /etc/init.d 200 | /boot 201 | /boot/grub 202 | /boot/efi 203 | /srv 204 | /lib 205 | /lib/ufw 206 | /lib/firmware 207 | /lib/brltty 208 | /lib/hdparm 209 | /lib/lsb 210 | /lib/recovery-mode 211 | /lib/modprobe.d 212 | /lib/systemd 213 | /lib/xtables 214 | /lib/apparmor 215 | /lib/modules 216 | /lib/ifupdown 217 | /lib/crda 218 | /lib/udev 219 | /lib/security 220 | /lib/x86_64-linux-gnu 221 | /lib/terminfo 222 | /lib/sysvinit 223 | /lib/i386-linux-gnu 224 | /lib/resolvconf 225 | /lib/init 226 | /lib/linux-sound-base 227 | /lib64 228 | /root 229 | /usr 230 | /usr/bin 231 | /usr/include 232 | /usr/lib 233 | /usr/locale 234 | /usr/local 235 | /usr/games 236 | /usr/share 237 | /usr/lib32 238 | /usr/src 239 | /usr/sbin 240 | /cdrom 241 | /media 242 | /media/apt 243 | /snap 244 | /snap/bin 245 | /snap/core 246 | /snap/stellarium-plars 247 | /var 248 | /var/tmp 249 | /var/log 250 | /var/lib 251 | /var/snap 252 | /var/local 253 | /var/crash 254 | /var/backups 255 | /var/spool 256 | /var/opt 257 | /var/metrics 258 | /var/cache 259 | /var/mail 260 | /lib32 261 | /sbin 262 | /run 263 | /run/udisks2 264 | /run/wpa_supplicant 265 | /run/tlp 266 | /run/docker 267 | /run/vmblock-fuse 268 | /run/lightdm 269 | /run/vmware 270 | /run/NetworkManager 271 | /run/thermald 272 | /run/network 273 | /run/uuidd 274 | /run/cups 275 | /run/avahi-daemon 276 | /run/dbus 277 | /run/resolvconf 278 | /run/user 279 | /run/sudo 280 | /run/samba 281 | /run/openvpn 282 | /run/sendsigs.omit.d 283 | /run/pppconfig 284 | /run/log 285 | /run/tmpfiles.d 286 | /run/mount 287 | /run/systemd 288 | /run/lock 289 | /run/blkid 290 | /run/plymouth 291 | /run/udev 292 | /run/initramfs 293 | /mnt 294 | /opt 295 | /opt/zoom 296 | /opt/google 297 | /opt/extras.ubuntu.com 298 | /opt/vagrant 299 | /lost+found 300 | -------------------------------------------------------------------------------- /hornet/main.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import logging 22 | import os 23 | import shutil 24 | import gevent.server 25 | import gevent.queue 26 | 27 | import hornet 28 | from hornet.core.handler import SSHWrapper 29 | from hornet.common.config import Config 30 | from hornet.core.host import VirtualHost 31 | from hornet.core.consumer import SessionConsumer 32 | from hornet.core.db.handler import DatabaseHandler 33 | 34 | logger = logging.getLogger(__name__) 35 | 36 | 37 | class Hornet(object): 38 | 39 | def __init__(self, working_directory, vhost_create_fs=False): 40 | self.server = None 41 | self.handler = None 42 | self.server_greenlet = None 43 | self.session_q = gevent.queue.Queue() 44 | self.consumer = SessionConsumer(self.session_q) 45 | self.consumer_greenlet = None 46 | self.working_directory = working_directory 47 | self.config = self._load_config() 48 | self._vhost_create_fs = vhost_create_fs 49 | try: 50 | self.db_handler = DatabaseHandler(self.config) 51 | except Exception: 52 | logger.exception('Could not initialize database: %s', self.config.database) 53 | 54 | # Create virtual hosts 55 | self.vhosts = self._create_vhosts() 56 | 57 | def _load_config(self): 58 | config_path = os.path.join(self.working_directory, 'config.json') 59 | if not os.path.isfile(config_path): 60 | source = os.path.join(os.path.dirname(hornet.__file__), 'data', 'default_config.json') 61 | destination = config_path 62 | logger.info('Config file {} not found, copying default'.format(destination)) 63 | shutil.copyfile(src=source, dst=destination) 64 | with open(config_path, 'r') as config_fp: 65 | config_params = json.load(config_fp) 66 | return Config(config_params) 67 | 68 | def _create_vhosts(self): 69 | 70 | # Create a directory for virtual filesystems, if it doesn't exist 71 | vhosts_path = os.path.join(self.working_directory, 'vhosts') 72 | if not os.path.isdir(vhosts_path): 73 | logger.info('Creating directory {} for virtual host filesystems'.format(vhosts_path)) 74 | os.mkdir(vhosts_path) 75 | 76 | hosts = {} 77 | for host_params in self.config.vhost_params: 78 | h = VirtualHost(host_params, self.config.network, vhosts_path, create_fs=self._vhost_create_fs) 79 | hosts[h.hostname] = h 80 | return hosts 81 | 82 | def start(self): 83 | self.handler = SSHWrapper(self.vhosts, self.session_q, self.config, self.working_directory, self.db_handler) 84 | self.server = gevent.server.StreamServer((self.config.host, self.config.port), 85 | handle=self.handler.handle_session) 86 | self.server_greenlet = gevent.spawn(self.server.serve_forever) 87 | while self.server.server_port == 0: 88 | gevent.sleep(0) # Bad way of waiting, but can't think of anything right now. 89 | logger.info('SSH server listening on {}:{}'.format(self.server.server_host, self.server.server_port)) 90 | 91 | self.consumer_greenlet = self.consumer.start() 92 | return [self.server_greenlet, self.consumer_greenlet] 93 | 94 | def stop(self): 95 | logging.debug('Stopping the server') 96 | self.server.stop() 97 | self.consumer.stop() 98 | -------------------------------------------------------------------------------- /hornet/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/tests/__init__.py -------------------------------------------------------------------------------- /hornet/tests/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czardoz/hornet/b16ed6b3b7b5e7519932084353ddd2e7eb4d4350/hornet/tests/commands/__init__.py -------------------------------------------------------------------------------- /hornet/tests/commands/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import unittest 4 | import shutil 5 | import hornet 6 | 7 | 8 | class BaseTestClass(unittest.TestCase): 9 | 10 | def setUp(self): 11 | self.working_dir = tempfile.mkdtemp() 12 | test_config = os.path.join(os.path.dirname(hornet.__file__), 'data', 'default_config.json') 13 | shutil.copyfile(test_config, os.path.join(self.working_dir, 'config.json')) 14 | 15 | def tearDown(self): 16 | shutil.rmtree(self.working_dir) 17 | 18 | def create_filesystem(self, honeypot): 19 | default_host = honeypot.vhosts[honeypot.config.default_hostname] 20 | default_host.filesystem.makedir('/etc', recreate=True) 21 | default_host.filesystem.makedir('/var', recreate=True) 22 | default_host.filesystem.makedir('/bin', recreate=True) 23 | default_host.filesystem.makedir('/.hidden', recreate=True) 24 | default_host.filesystem.makedir('/etc/init.d', recreate=True) 25 | default_host.filesystem.create('/etc/passwd') 26 | default_host.filesystem.create('/etc/.config') 27 | default_host.filesystem.create('/etc/sysctl.conf') 28 | default_host.filesystem.create('/.hidden/.rcconf') 29 | default_host.filesystem.create('/initrd.img') 30 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_cd.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | gevent.monkey.patch_all() 22 | 23 | import paramiko 24 | from hornet.main import Hornet 25 | from hornet.tests.commands.base import BaseTestClass 26 | 27 | 28 | class HornetTests(BaseTestClass): 29 | 30 | def test_cd(self): 31 | """ Tests if cd command works """ 32 | 33 | honeypot = Hornet(self.working_dir) 34 | honeypot.start() 35 | self.create_filesystem(honeypot) 36 | 37 | while honeypot.server.server_port == 0: # wait until the server is ready 38 | gevent.sleep(0) 39 | port = honeypot.server.server_port 40 | client = paramiko.SSHClient() 41 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 42 | # If we log in properly, this should raise no errors 43 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 44 | channel = client.invoke_shell() 45 | 46 | while not channel.recv_ready(): 47 | gevent.sleep(0) # :-( 48 | 49 | welcome = '' 50 | while channel.recv_ready(): 51 | welcome += channel.recv(1) 52 | lines = welcome.split('\r\n') 53 | prompt = lines[-1] 54 | self.assertTrue(prompt.endswith('$ ')) 55 | 56 | # Now send the cd command 57 | cd_command = 'cd /etc' 58 | channel.send(cd_command + '\r\n') 59 | 60 | while not channel.recv_ready(): 61 | gevent.sleep(0) # :-( 62 | 63 | output = '' 64 | while not output.endswith('$ '): 65 | output += channel.recv(1) 66 | 67 | lines = output.split('\r\n') 68 | command = lines[0] 69 | command_output = '\r\n'.join(lines[1:-1]) 70 | next_prompt = lines[-1] 71 | 72 | self.assertEquals(command, cd_command) 73 | self.assertEquals(command_output, '') # cd should normally give no output 74 | self.assertTrue(next_prompt.endswith('$ ')) 75 | self.assertTrue('/etc' in next_prompt) 76 | 77 | pwd_command = 'pwd' # Make sure the cd command worked 78 | channel.send(pwd_command + '\r\n') 79 | 80 | while not channel.recv_ready(): 81 | gevent.sleep(0) # :-( 82 | 83 | output = '' 84 | while not output.endswith('$ '): 85 | output += channel.recv(1) 86 | 87 | lines = output.split('\r\n') 88 | command = lines[0] 89 | command_output = '\r\n'.join(lines[1:-1]) 90 | next_prompt = lines[-1] 91 | 92 | self.assertEquals(command, pwd_command) 93 | self.assertEquals(command_output, '/etc') 94 | self.assertTrue(next_prompt.endswith('$ ')) 95 | honeypot.stop() 96 | 97 | def test_cd_with_backref(self): 98 | """ Tests if cd command works with backref (cd ../..) """ 99 | 100 | honeypot = Hornet(self.working_dir) 101 | honeypot.start() 102 | self.create_filesystem(honeypot) 103 | 104 | while honeypot.server.server_port == 0: # wait until the server is ready 105 | gevent.sleep(0) 106 | port = honeypot.server.server_port 107 | client = paramiko.SSHClient() 108 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 109 | # If we log in properly, this should raise no errors 110 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 111 | channel = client.invoke_shell() 112 | 113 | while not channel.recv_ready(): 114 | gevent.sleep(0) # :-( 115 | 116 | welcome = '' 117 | while channel.recv_ready(): 118 | welcome += channel.recv(1) 119 | lines = welcome.split('\r\n') 120 | prompt = lines[-1] 121 | self.assertTrue(prompt.endswith('$ ')) 122 | 123 | # Now send the cd command 124 | cd_command = 'cd /etc/init.d' 125 | channel.send(cd_command + '\r\n') 126 | 127 | while not channel.recv_ready(): 128 | gevent.sleep(0) # :-( 129 | 130 | output = '' 131 | while not output.endswith('$ '): 132 | output += channel.recv(1) 133 | 134 | lines = output.split('\r\n') 135 | command = lines[0] 136 | command_output = '\r\n'.join(lines[1:-1]) 137 | next_prompt = lines[-1] 138 | 139 | self.assertEquals(command, cd_command) 140 | self.assertEquals(command_output, '') # cd should normally give no output 141 | self.assertTrue(next_prompt.endswith('$ ')) 142 | self.assertTrue('/etc/init.d' in next_prompt) 143 | 144 | # Now send the cd command 145 | cd_command = 'cd ../..' 146 | channel.send(cd_command + '\r\n') 147 | 148 | while not channel.recv_ready(): 149 | gevent.sleep(0) # :-( 150 | 151 | output = '' 152 | while not output.endswith('$ '): 153 | output += channel.recv(1) 154 | 155 | lines = output.split('\r\n') 156 | command = lines[0] 157 | command_output = '\r\n'.join(lines[1:-1]) 158 | next_prompt = lines[-1] 159 | 160 | self.assertEquals(command, cd_command) 161 | self.assertEquals(command_output, '') # cd should normally give no output 162 | self.assertTrue(next_prompt.endswith(':/$ ')) 163 | honeypot.stop() 164 | 165 | def test_cd_with_backref_overflow(self): 166 | """ Tests if cd command works with backref overflow (cd ../../../..) """ 167 | 168 | honeypot = Hornet(self.working_dir) 169 | honeypot.start() 170 | self.create_filesystem(honeypot) 171 | 172 | while honeypot.server.server_port == 0: # wait until the server is ready 173 | gevent.sleep(0) 174 | port = honeypot.server.server_port 175 | client = paramiko.SSHClient() 176 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 177 | # If we log in properly, this should raise no errors 178 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 179 | channel = client.invoke_shell() 180 | 181 | while not channel.recv_ready(): 182 | gevent.sleep(0) # :-( 183 | 184 | welcome = '' 185 | while channel.recv_ready(): 186 | welcome += channel.recv(1) 187 | lines = welcome.split('\r\n') 188 | prompt = lines[-1] 189 | self.assertTrue(prompt.endswith('$ ')) 190 | 191 | # Now send the cd command 192 | cd_command = 'cd /etc/init.d' 193 | channel.send(cd_command + '\r\n') 194 | 195 | while not channel.recv_ready(): 196 | gevent.sleep(0) # :-( 197 | 198 | output = '' 199 | while not output.endswith('$ '): 200 | output += channel.recv(1) 201 | 202 | lines = output.split('\r\n') 203 | command = lines[0] 204 | command_output = '\r\n'.join(lines[1:-1]) 205 | next_prompt = lines[-1] 206 | 207 | self.assertEquals(command, cd_command) 208 | self.assertEquals(command_output, '') # cd should normally give no output 209 | self.assertTrue(next_prompt.endswith('$ ')) 210 | self.assertTrue('/etc/init.d' in next_prompt) 211 | 212 | # Now send the cd command 213 | cd_command = 'cd ../../../..' 214 | channel.send(cd_command + '\r\n') 215 | 216 | while not channel.recv_ready(): 217 | gevent.sleep(0) # :-( 218 | 219 | output = '' 220 | while not output.endswith('$ '): 221 | output += channel.recv(1) 222 | 223 | lines = output.split('\r\n') 224 | command = lines[0] 225 | command_output = '\r\n'.join(lines[1:-1]) 226 | next_prompt = lines[-1] 227 | 228 | self.assertEquals(command, cd_command) 229 | self.assertEquals(command_output, '') # cd should normally give no output 230 | self.assertTrue(next_prompt.endswith(':/$ ')) 231 | honeypot.stop() 232 | 233 | def test_cd_no_args(self): 234 | """ Tests if cd command works without any arguments """ 235 | 236 | honeypot = Hornet(self.working_dir) 237 | honeypot.start() 238 | self.create_filesystem(honeypot) 239 | 240 | while honeypot.server.server_port == 0: # wait until the server is ready 241 | gevent.sleep(0) 242 | port = honeypot.server.server_port 243 | client = paramiko.SSHClient() 244 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 245 | # If we log in properly, this should raise no errors 246 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 247 | channel = client.invoke_shell() 248 | 249 | while not channel.recv_ready(): 250 | gevent.sleep(0) # :-( 251 | 252 | welcome = '' 253 | while channel.recv_ready(): 254 | welcome += channel.recv(1) 255 | lines = welcome.split('\r\n') 256 | prompt = lines[-1] 257 | self.assertTrue(prompt.endswith('$ ')) 258 | 259 | # Now send the cd command 260 | cd_command = 'cd /etc/init.d' 261 | channel.send(cd_command + '\r\n') 262 | 263 | while not channel.recv_ready(): 264 | gevent.sleep(0) # :-( 265 | 266 | output = '' 267 | while not output.endswith('$ '): 268 | output += channel.recv(1) 269 | 270 | lines = output.split('\r\n') 271 | command = lines[0] 272 | command_output = '\r\n'.join(lines[1:-1]) 273 | next_prompt = lines[-1] 274 | 275 | self.assertEquals(command, cd_command) 276 | self.assertEquals(command_output, '') # cd should normally give no output 277 | self.assertTrue(next_prompt.endswith('$ ')) 278 | self.assertTrue('/etc/init.d' in next_prompt) 279 | 280 | # Now send the cd command 281 | cd_command = 'cd' 282 | channel.send(cd_command + '\r\n') 283 | 284 | while not channel.recv_ready(): 285 | gevent.sleep(0) # :-( 286 | 287 | output = '' 288 | while not output.endswith('$ '): 289 | output += channel.recv(1) 290 | 291 | lines = output.split('\r\n') 292 | command = lines[0] 293 | command_output = '\r\n'.join(lines[1:-1]) 294 | next_prompt = lines[-1] 295 | 296 | self.assertEquals(command, cd_command) 297 | self.assertEquals(command_output, '') # cd should normally give no output 298 | self.assertTrue(next_prompt.endswith(':/$ ')) 299 | honeypot.stop() 300 | 301 | def test_cd_invalid_args(self): 302 | """ Tests if cd command works with invalid arguments """ 303 | 304 | honeypot = Hornet(self.working_dir) 305 | honeypot.start() 306 | self.create_filesystem(honeypot) 307 | 308 | while honeypot.server.server_port == 0: # wait until the server is ready 309 | gevent.sleep(0) 310 | port = honeypot.server.server_port 311 | client = paramiko.SSHClient() 312 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 313 | # If we log in properly, this should raise no errors 314 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 315 | channel = client.invoke_shell() 316 | 317 | while not channel.recv_ready(): 318 | gevent.sleep(0) # :-( 319 | 320 | welcome = '' 321 | while channel.recv_ready(): 322 | welcome += channel.recv(1) 323 | lines = welcome.split('\r\n') 324 | prompt = lines[-1] 325 | self.assertTrue(prompt.endswith('$ ')) 326 | 327 | # Now send the cd command 328 | cd_command = 'cd /etc/invalidlocation' 329 | channel.send(cd_command + '\r\n') 330 | 331 | while not channel.recv_ready(): 332 | gevent.sleep(0) # :-( 333 | 334 | output = '' 335 | while not output.endswith('$ '): 336 | output += channel.recv(1) 337 | 338 | lines = output.split('\r\n') 339 | command = lines[0] 340 | command_output = '\r\n'.join(lines[1:-1]) 341 | next_prompt = lines[-1] 342 | 343 | self.assertEquals(command, cd_command) 344 | self.assertEquals(command_output, 'cd: /etc/invalidlocation: No such file or directory') 345 | self.assertTrue(next_prompt.endswith('$ ')) 346 | 347 | honeypot.stop() 348 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_echo.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | gevent.monkey.patch_all() 22 | 23 | import paramiko 24 | from hornet.main import Hornet 25 | from hornet.tests.commands.base import BaseTestClass 26 | 27 | 28 | class HornetTests(BaseTestClass): 29 | 30 | def test_echo_params(self): 31 | """ Tests if echo command works when parameters are specified """ 32 | 33 | honeypot = Hornet(self.working_dir) 34 | honeypot.start() 35 | while honeypot.server.server_port == 0: # wait until the server is ready 36 | gevent.sleep(0) 37 | port = honeypot.server.server_port 38 | client = paramiko.SSHClient() 39 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 40 | # If we log in properly, this should raise no errors 41 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 42 | channel = client.invoke_shell() 43 | 44 | while not channel.recv_ready(): 45 | gevent.sleep(0) # :-( 46 | 47 | welcome = '' 48 | while channel.recv_ready(): 49 | welcome += channel.recv(1) 50 | lines = welcome.split('\r\n') 51 | prompt = lines[-1] 52 | self.assertTrue(prompt.endswith('$ ')) 53 | 54 | # Now send the echo command 55 | channel.send('echo this is a test\r\n') 56 | 57 | while not channel.recv_ready(): 58 | gevent.sleep(0) # :-( 59 | 60 | output = '' 61 | while not output.endswith('$ '): 62 | output += channel.recv(1) 63 | 64 | lines = output.split('\r\n') 65 | command = lines[0] 66 | command_output = '\r\n'.join(lines[1:-1]) 67 | next_prompt = lines[-1] 68 | self.assertEquals('echo this is a test', command) 69 | self.assertEquals('this is a test', command_output) 70 | self.assertTrue(next_prompt.endswith('$ ')) 71 | honeypot.stop() 72 | 73 | def test_echo_no_params(self): 74 | """ Tests if echo command works when no parameters are specified """ 75 | 76 | honeypot = Hornet(self.working_dir) 77 | honeypot.start() 78 | while honeypot.server.server_port == 0: # wait until the server is ready 79 | gevent.sleep(0) 80 | port = honeypot.server.server_port 81 | client = paramiko.SSHClient() 82 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 83 | # If we log in properly, this should raise no errors 84 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 85 | channel = client.invoke_shell() 86 | 87 | while not channel.recv_ready(): 88 | gevent.sleep(0) # :-( 89 | 90 | welcome = '' 91 | while channel.recv_ready(): 92 | welcome += channel.recv(1) 93 | lines = welcome.split('\r\n') 94 | prompt = lines[-1] 95 | self.assertTrue(prompt.endswith('$ ')) 96 | 97 | # Now send the echo command 98 | channel.send('echo\r\n') 99 | 100 | while not channel.recv_ready(): 101 | gevent.sleep(0) # :-( 102 | 103 | output = '' 104 | while not output.endswith('$ '): 105 | output += channel.recv(1) 106 | 107 | lines = output.split('\r\n') 108 | command = lines[0] 109 | command_output = '\r\n'.join(lines[1:-1]) 110 | next_prompt = lines[-1] 111 | self.assertEquals('echo', command) 112 | self.assertEquals('', command_output) 113 | self.assertTrue(next_prompt.endswith('$ ')) 114 | honeypot.stop() 115 | 116 | def test_echo_env_var(self): 117 | """ Tests if echo command works when environment variables as specified in the 118 | config are specified """ 119 | 120 | honeypot = Hornet(self.working_dir) 121 | honeypot.start() 122 | while honeypot.server.server_port == 0: # wait until the server is ready 123 | gevent.sleep(0) 124 | port = honeypot.server.server_port 125 | client = paramiko.SSHClient() 126 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 127 | # If we log in properly, this should raise no errors 128 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 129 | channel = client.invoke_shell() 130 | 131 | while not channel.recv_ready(): 132 | gevent.sleep(0) # :-( 133 | 134 | welcome = '' 135 | while channel.recv_ready(): 136 | welcome += channel.recv(1) 137 | lines = welcome.split('\r\n') 138 | prompt = lines[-1] 139 | self.assertTrue(prompt.endswith('$ ')) 140 | 141 | # Now send the echo command 142 | channel.send('echo $BROWSER\r\n') 143 | 144 | while not channel.recv_ready(): 145 | gevent.sleep(0) # :-( 146 | 147 | output = '' 148 | while not output.endswith('$ '): 149 | output += channel.recv(1) 150 | 151 | lines = output.split('\r\n') 152 | command = lines[0] 153 | command_output = '\r\n'.join(lines[1:-1]) 154 | next_prompt = lines[-1] 155 | self.assertEquals('echo $BROWSER', command) 156 | self.assertEquals('firefox', command_output) 157 | self.assertTrue(next_prompt.endswith('$ ')) 158 | honeypot.stop() 159 | 160 | def test_echo_star(self): 161 | """ Tests if echo command works when '*' exists in the params """ 162 | 163 | honeypot = Hornet(self.working_dir) 164 | honeypot.start() 165 | default_host = honeypot.vhosts[honeypot.config.default_hostname] 166 | default_host.filesystem.makedir('/etc') 167 | default_host.filesystem.makedir('/var') 168 | default_host.filesystem.makedir('/opt') 169 | 170 | while honeypot.server.server_port == 0: # wait until the server is ready 171 | gevent.sleep(0) 172 | port = honeypot.server.server_port 173 | client = paramiko.SSHClient() 174 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 175 | # If we log in properly, this should raise no errors 176 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 177 | channel = client.invoke_shell() 178 | 179 | while not channel.recv_ready(): 180 | gevent.sleep(0) # :-( 181 | 182 | welcome = '' 183 | while channel.recv_ready(): 184 | welcome += channel.recv(1) 185 | lines = welcome.split('\r\n') 186 | prompt = lines[-1] 187 | self.assertTrue(prompt.endswith('$ ')) 188 | 189 | # Now send the echo command 190 | channel.send('echo *\r\n') 191 | 192 | while not channel.recv_ready(): 193 | gevent.sleep(0) # :-( 194 | 195 | output = '' 196 | while not output.endswith('$ '): 197 | output += channel.recv(1) 198 | 199 | lines = output.split('\r\n') 200 | command = lines[0] 201 | command_output = '\r\n'.join(lines[1:-1]) 202 | next_prompt = lines[-1] 203 | self.assertEquals('echo *', command) 204 | self.assertTrue('var' in command_output) 205 | self.assertTrue('etc' in command_output) 206 | self.assertTrue('opt' in command_output) 207 | self.assertTrue(next_prompt.endswith('$ ')) 208 | honeypot.stop() 209 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_ifconfig.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | import os 20 | 21 | import gevent.monkey 22 | import hornet 23 | 24 | gevent.monkey.patch_all() 25 | 26 | import paramiko 27 | from hornet.main import Hornet 28 | from hornet.tests.commands.base import BaseTestClass 29 | 30 | 31 | class HornetTests(BaseTestClass): 32 | 33 | def test_ifconfig(self): 34 | """ Tests if ifconfig command works """ 35 | 36 | honeypot = Hornet(self.working_dir) 37 | honeypot.start() 38 | self.create_filesystem(honeypot) 39 | 40 | while honeypot.server.server_port == 0: # wait until the server is ready 41 | gevent.sleep(0) 42 | port = honeypot.server.server_port 43 | client = paramiko.SSHClient() 44 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 45 | # If we log in properly, this should raise no errors 46 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 47 | channel = client.invoke_shell() 48 | 49 | while not channel.recv_ready(): 50 | gevent.sleep(0) # :-( 51 | 52 | welcome = '' 53 | while channel.recv_ready(): 54 | welcome += channel.recv(1) 55 | lines = welcome.split('\r\n') 56 | prompt = lines[-1] 57 | self.assertTrue(prompt.endswith('$ ')) 58 | 59 | # Now send the cd command 60 | cd_command = 'ifconfig' 61 | channel.send(cd_command + '\r\n') 62 | 63 | while not channel.recv_ready(): 64 | gevent.sleep(0) # :-( 65 | 66 | output = '' 67 | while not output.endswith('$ '): 68 | output += channel.recv(1) 69 | 70 | lines = output.split('\r\n') 71 | command = lines[0] 72 | command_output = '\r\n'.join(lines[1:-1]) 73 | next_prompt = lines[-1] 74 | 75 | self.assertEquals(command, cd_command) 76 | interfaces = sorted(command_output.split('\r\n\r\n')) 77 | self.assertTrue(interfaces[0].startswith('eth0 ')) 78 | self.assertTrue('HWaddr 00:16:3e:76:35:d1' in interfaces[0]) 79 | self.assertTrue('inet addr:192.168.0.232 ' in interfaces[0]) 80 | self.assertTrue('Bcast:192.168.0.255 ' in interfaces[0]) 81 | self.assertTrue('Mask:255.255.255.0' in interfaces[0]) 82 | 83 | self.assertTrue(interfaces[1].startswith('lo ')) 84 | self.assertTrue('inet addr:127.0.0.1 ' in interfaces[1]) 85 | self.assertTrue('Mask:255.0.0.0' in interfaces[1]) 86 | 87 | self.assertTrue(next_prompt.endswith('$ ')) 88 | 89 | honeypot.stop() 90 | 91 | def test_ifconfig_bad_input_multiple_params(self): 92 | """ Tests if 'ifconfig asd up' works """ 93 | 94 | honeypot = Hornet(self.working_dir) 95 | honeypot.start() 96 | self.create_filesystem(honeypot) 97 | 98 | while honeypot.server.server_port == 0: # wait until the server is ready 99 | gevent.sleep(0) 100 | port = honeypot.server.server_port 101 | client = paramiko.SSHClient() 102 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 103 | # If we log in properly, this should raise no errors 104 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 105 | channel = client.invoke_shell() 106 | 107 | while not channel.recv_ready(): 108 | gevent.sleep(0) # :-( 109 | 110 | welcome = '' 111 | while channel.recv_ready(): 112 | welcome += channel.recv(1) 113 | lines = welcome.split('\r\n') 114 | prompt = lines[-1] 115 | self.assertTrue(prompt.endswith('$ ')) 116 | 117 | # Now send the cd command 118 | cd_command = 'ifconfig asd lol' 119 | channel.send(cd_command + '\r\n') 120 | 121 | while not channel.recv_ready(): 122 | gevent.sleep(0) # :-( 123 | 124 | output = '' 125 | while not output.endswith('$ '): 126 | output += channel.recv(1) 127 | 128 | lines = output.split('\r\n') 129 | command = lines[0] 130 | command_output = '\r\n'.join(lines[1:-1]) 131 | next_prompt = lines[-1] 132 | 133 | self.assertEquals(command, cd_command) 134 | self.assertEquals(command_output, 'SIOCSIFFLAGS: Operation not permitted') 135 | self.assertTrue(next_prompt.endswith('$ ')) 136 | 137 | honeypot.stop() 138 | 139 | def test_ifconfig_bad_input(self): 140 | """ Tests if 'ifconfig asd' works """ 141 | 142 | honeypot = Hornet(self.working_dir) 143 | honeypot.start() 144 | self.create_filesystem(honeypot) 145 | 146 | while honeypot.server.server_port == 0: # wait until the server is ready 147 | gevent.sleep(0) 148 | port = honeypot.server.server_port 149 | client = paramiko.SSHClient() 150 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 151 | # If we log in properly, this should raise no errors 152 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 153 | channel = client.invoke_shell() 154 | 155 | while not channel.recv_ready(): 156 | gevent.sleep(0) # :-( 157 | 158 | welcome = '' 159 | while channel.recv_ready(): 160 | welcome += channel.recv(1) 161 | lines = welcome.split('\r\n') 162 | prompt = lines[-1] 163 | self.assertTrue(prompt.endswith('$ ')) 164 | 165 | # Now send the cd command 166 | cd_command = 'ifconfig asd' 167 | channel.send(cd_command + '\r\n') 168 | 169 | while not channel.recv_ready(): 170 | gevent.sleep(0) # :-( 171 | 172 | output = '' 173 | while not output.endswith('$ '): 174 | output += channel.recv(1) 175 | 176 | lines = output.split('\r\n') 177 | command = lines[0] 178 | command_output = '\r\n'.join(lines[1:-1]) 179 | next_prompt = lines[-1] 180 | 181 | self.assertEquals(command, cd_command) 182 | self.assertEquals(command_output, 'asd: error fetching interface information: Device not found') 183 | self.assertTrue(next_prompt.endswith('$ ')) 184 | 185 | honeypot.stop() 186 | 187 | def test_ifconfig_interface_param(self): 188 | """ Tests if 'ifconfig eth0' works """ 189 | 190 | honeypot = Hornet(self.working_dir) 191 | honeypot.start() 192 | self.create_filesystem(honeypot) 193 | 194 | while honeypot.server.server_port == 0: # wait until the server is ready 195 | gevent.sleep(0) 196 | port = honeypot.server.server_port 197 | client = paramiko.SSHClient() 198 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 199 | # If we log in properly, this should raise no errors 200 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 201 | channel = client.invoke_shell() 202 | 203 | while not channel.recv_ready(): 204 | gevent.sleep(0) # :-( 205 | 206 | welcome = '' 207 | while channel.recv_ready(): 208 | welcome += channel.recv(1) 209 | lines = welcome.split('\r\n') 210 | prompt = lines[-1] 211 | self.assertTrue(prompt.endswith('$ ')) 212 | 213 | # Now send the cd command 214 | cd_command = 'ifconfig eth0' 215 | channel.send(cd_command + '\r\n') 216 | 217 | while not channel.recv_ready(): 218 | gevent.sleep(0) # :-( 219 | 220 | output = '' 221 | while not output.endswith('$ '): 222 | output += channel.recv(1) 223 | 224 | lines = output.split('\r\n') 225 | command = lines[0] 226 | command_output = '\r\n'.join(lines[1:-1]) 227 | next_prompt = lines[-1] 228 | 229 | self.assertEquals(command, cd_command) 230 | self.assertTrue(command_output.startswith('eth0 ')) 231 | self.assertTrue('HWaddr 00:16:3e:76:35:d1' in command_output) 232 | self.assertTrue('inet addr:192.168.0.232 ' in command_output) 233 | self.assertTrue('Bcast:192.168.0.255 ' in command_output) 234 | self.assertTrue('Mask:255.255.255.0' in command_output) 235 | self.assertFalse('lo ' in command_output) 236 | self.assertFalse('127.0.0.1' in command_output) 237 | self.assertTrue(next_prompt.endswith('$ ')) 238 | 239 | honeypot.stop() 240 | 241 | def test_ifconfig_loopback_param(self): 242 | """ Tests if 'ifconfig lo' works """ 243 | 244 | honeypot = Hornet(self.working_dir) 245 | honeypot.start() 246 | self.create_filesystem(honeypot) 247 | 248 | while honeypot.server.server_port == 0: # wait until the server is ready 249 | gevent.sleep(0) 250 | port = honeypot.server.server_port 251 | client = paramiko.SSHClient() 252 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 253 | # If we log in properly, this should raise no errors 254 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 255 | channel = client.invoke_shell() 256 | 257 | while not channel.recv_ready(): 258 | gevent.sleep(0) # :-( 259 | 260 | welcome = '' 261 | while channel.recv_ready(): 262 | welcome += channel.recv(1) 263 | lines = welcome.split('\r\n') 264 | prompt = lines[-1] 265 | self.assertTrue(prompt.endswith('$ ')) 266 | 267 | # Now send the cd command 268 | cd_command = 'ifconfig lo' 269 | channel.send(cd_command + '\r\n') 270 | 271 | while not channel.recv_ready(): 272 | gevent.sleep(0) # :-( 273 | 274 | output = '' 275 | while not output.endswith('$ '): 276 | output += channel.recv(1) 277 | 278 | lines = output.split('\r\n') 279 | command = lines[0] 280 | command_output = '\r\n'.join(lines[1:-1]) 281 | next_prompt = lines[-1] 282 | 283 | self.assertEquals(command, cd_command) 284 | self.assertTrue(command_output.startswith('lo ')) 285 | self.assertFalse('HWaddr 00:16:3e:76:35:d1' in command_output) 286 | self.assertFalse('inet addr:192.168.0.232 ' in command_output) 287 | self.assertTrue('lo ' in command_output) 288 | self.assertTrue('127.0.0.1' in command_output) 289 | self.assertTrue(next_prompt.endswith('$ ')) 290 | 291 | honeypot.stop() 292 | 293 | def test_ifconfig_version_param(self): 294 | """ Tests if 'ifconfig --version' works """ 295 | 296 | honeypot = Hornet(self.working_dir) 297 | honeypot.start() 298 | self.create_filesystem(honeypot) 299 | 300 | while honeypot.server.server_port == 0: # wait until the server is ready 301 | gevent.sleep(0) 302 | port = honeypot.server.server_port 303 | client = paramiko.SSHClient() 304 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 305 | # If we log in properly, this should raise no errors 306 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 307 | channel = client.invoke_shell() 308 | 309 | while not channel.recv_ready(): 310 | gevent.sleep(0) # :-( 311 | 312 | welcome = '' 313 | while channel.recv_ready(): 314 | welcome += channel.recv(1) 315 | lines = welcome.split('\r\n') 316 | prompt = lines[-1] 317 | self.assertTrue(prompt.endswith('$ ')) 318 | 319 | # Now send the ifconfig command 320 | ifconfig_command = 'ifconfig --version' 321 | channel.send(ifconfig_command + '\r\n') 322 | 323 | while not channel.recv_ready(): 324 | gevent.sleep(0) # :-( 325 | 326 | output = '' 327 | while not output.endswith('$ '): 328 | output += channel.recv(1) 329 | 330 | lines = output.split('\r\n') 331 | command = lines[0] 332 | command_output = '\r\n'.join(lines[1:-1]) 333 | next_prompt = lines[-1] 334 | 335 | expected_output = [] 336 | version_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 337 | 'commands', 'ifconfig', 'version') 338 | with open(version_file_path) as version_file: 339 | for line in version_file: 340 | line = line.strip() 341 | expected_output.append(line) 342 | 343 | self.assertEquals(command, ifconfig_command) 344 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 345 | self.assertTrue(next_prompt.endswith('$ ')) 346 | 347 | honeypot.stop() 348 | 349 | def test_ifconfig_help_param(self): 350 | """ Tests if 'ifconfig --help' works """ 351 | 352 | honeypot = Hornet(self.working_dir) 353 | honeypot.start() 354 | self.create_filesystem(honeypot) 355 | 356 | while honeypot.server.server_port == 0: # wait until the server is ready 357 | gevent.sleep(0) 358 | port = honeypot.server.server_port 359 | client = paramiko.SSHClient() 360 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 361 | # If we log in properly, this should raise no errors 362 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 363 | channel = client.invoke_shell() 364 | 365 | while not channel.recv_ready(): 366 | gevent.sleep(0) # :-( 367 | 368 | welcome = '' 369 | while channel.recv_ready(): 370 | welcome += channel.recv(1) 371 | lines = welcome.split('\r\n') 372 | prompt = lines[-1] 373 | self.assertTrue(prompt.endswith('$ ')) 374 | 375 | # Now send the ifconfig command 376 | ifconfig_command = 'ifconfig --help' 377 | channel.send(ifconfig_command + '\r\n') 378 | 379 | while not channel.recv_ready(): 380 | gevent.sleep(0) # :-( 381 | 382 | output = '' 383 | while not output.endswith('$ '): 384 | output += channel.recv(1) 385 | 386 | lines = output.split('\r\n') 387 | command = lines[0] 388 | command_output = '\r\n'.join(lines[1:-1]) 389 | next_prompt = lines[-1] 390 | 391 | expected_output = [] 392 | help_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 393 | 'commands', 'ifconfig', 'help') 394 | with open(help_file_path) as help_file: 395 | for line in help_file: 396 | line = line.strip() 397 | expected_output.append(line) 398 | 399 | self.assertEquals(command, ifconfig_command) 400 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 401 | self.assertTrue(next_prompt.endswith('$ ')) 402 | 403 | honeypot.stop() 404 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_logout.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | gevent.monkey.patch_all() 22 | 23 | import paramiko 24 | from hornet.main import Hornet 25 | from hornet.tests.commands.base import BaseTestClass 26 | 27 | 28 | class HornetTests(BaseTestClass): 29 | 30 | def test_logout(self): 31 | """ Tests logout command 32 | eg: $ logout 33 | """ 34 | 35 | honeypot = Hornet(self.working_dir) 36 | honeypot.start() 37 | 38 | while honeypot.server.server_port == 0: # wait until the server is ready 39 | gevent.sleep(0) 40 | port = honeypot.server.server_port 41 | client = paramiko.SSHClient() 42 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 43 | # If we log in properly, this should raise no errors 44 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 45 | channel = client.invoke_shell() 46 | 47 | while not channel.recv_ready(): 48 | gevent.sleep(0) # :-( 49 | 50 | welcome = '' 51 | while channel.recv_ready(): 52 | welcome += channel.recv(1) 53 | lines = welcome.split('\r\n') 54 | prompt = lines[-1] 55 | self.assertTrue(prompt.endswith('$ ')) 56 | 57 | # Now send the ssh command 58 | channel.send('ssh test01\r\n') 59 | while not channel.recv_ready(): 60 | gevent.sleep(0) # :-( 61 | output = '' 62 | while not output.endswith('Password:'): 63 | output += channel.recv(1) 64 | 65 | # Now send the password 66 | channel.send('passtest\r\n') 67 | 68 | output = '' 69 | while not output.endswith('$ '): 70 | output += channel.recv(1) 71 | self.assertTrue('Welcome to test01 server' in output) 72 | self.assertTrue(output.endswith('$ ')) 73 | 74 | # Now send the logout command 75 | channel.send('logout\r\n') 76 | while not channel.recv_ready(): 77 | gevent.sleep(0) # :-( 78 | output = '' 79 | while not output.endswith('$ '): 80 | output += channel.recv(1) 81 | self.assertTrue('testuser@test02' in output) 82 | self.assertTrue(output.endswith('$ ')) 83 | 84 | honeypot.stop() 85 | 86 | def test_logout_close(self): 87 | """ Tests logout command when only logged in to the default VirtualHost 88 | eg: $ logout 89 | """ 90 | 91 | honeypot = Hornet(self.working_dir) 92 | honeypot.start() 93 | 94 | while honeypot.server.server_port == 0: # wait until the server is ready 95 | gevent.sleep(0) 96 | port = honeypot.server.server_port 97 | client = paramiko.SSHClient() 98 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 99 | # If we log in properly, this should raise no errors 100 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 101 | channel = client.invoke_shell() 102 | 103 | while not channel.recv_ready(): 104 | gevent.sleep(0) # :-( 105 | 106 | welcome = '' 107 | while channel.recv_ready(): 108 | welcome += channel.recv(1) 109 | lines = welcome.split('\r\n') 110 | prompt = lines[-1] 111 | self.assertTrue(prompt.endswith('$ ')) 112 | 113 | # Now send the logout command 114 | channel.send('logout\r\n') 115 | 116 | honeypot.stop() 117 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_ping.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | 22 | gevent.monkey.patch_all() 23 | 24 | import paramiko 25 | import os 26 | import hornet 27 | 28 | from hornet.main import Hornet 29 | from hornet.tests.commands.base import BaseTestClass 30 | 31 | 32 | class HornetTests(BaseTestClass): 33 | 34 | def test_ping_help(self): 35 | """ Tests basic 'ping -h' """ 36 | 37 | honeypot = Hornet(self.working_dir) 38 | honeypot.start() 39 | 40 | while honeypot.server.server_port == 0: # wait until the server is ready 41 | gevent.sleep(0) 42 | port = honeypot.server.server_port 43 | client = paramiko.SSHClient() 44 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 45 | # If we log in properly, this should raise no errors 46 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 47 | channel = client.invoke_shell() 48 | 49 | while not channel.recv_ready(): 50 | gevent.sleep(0) # :-( 51 | 52 | welcome = '' 53 | while channel.recv_ready(): 54 | welcome += channel.recv(1) 55 | lines = welcome.split('\r\n') 56 | prompt = lines[-1] 57 | self.assertTrue(prompt.endswith('$ ')) 58 | 59 | # Now send the ping command 60 | ping_command = 'ping -h' 61 | channel.send(ping_command + '\r\n') 62 | 63 | while not channel.recv_ready(): 64 | gevent.sleep(0) # :-( 65 | 66 | output = '' 67 | while not output.endswith('$ '): 68 | output += channel.recv(1) 69 | 70 | lines = output.split('\r\n') 71 | command = lines[0] 72 | command_output = '\r\n'.join(lines[1:-1]) 73 | next_prompt = lines[-1] 74 | 75 | expected_output = [] 76 | help_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 77 | 'commands', 'ping', 'help') 78 | with open(help_file_path) as help_file: 79 | for line in help_file: 80 | line = line.strip() 81 | expected_output.append(line) 82 | 83 | self.assertEquals(command, ping_command) 84 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 85 | self.assertTrue(next_prompt.endswith('$ ')) 86 | 87 | honeypot.stop() 88 | 89 | def test_ping_unknown_host(self): 90 | """ Tests basic 'ping lolwakaka' """ 91 | 92 | honeypot = Hornet(self.working_dir) 93 | honeypot.start() 94 | 95 | while honeypot.server.server_port == 0: # wait until the server is ready 96 | gevent.sleep(0) 97 | port = honeypot.server.server_port 98 | client = paramiko.SSHClient() 99 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 100 | # If we log in properly, this should raise no errors 101 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 102 | channel = client.invoke_shell() 103 | 104 | while not channel.recv_ready(): 105 | gevent.sleep(0) # :-( 106 | 107 | welcome = '' 108 | while channel.recv_ready(): 109 | welcome += channel.recv(1) 110 | lines = welcome.split('\r\n') 111 | prompt = lines[-1] 112 | self.assertTrue(prompt.endswith('$ ')) 113 | 114 | # Now send the ping command 115 | ping_command = 'ping lolwakaka' 116 | channel.send(ping_command + '\r\n') 117 | 118 | while not channel.recv_ready(): 119 | gevent.sleep(0) # :-( 120 | 121 | output = '' 122 | while not output.endswith('$ '): 123 | output += channel.recv(1) 124 | 125 | lines = output.split('\r\n') 126 | command = lines[0] 127 | command_output = '\r\n'.join(lines[1:-1]) 128 | next_prompt = lines[-1] 129 | 130 | self.assertEquals(command, ping_command) 131 | self.assertEquals(command_output, 'ping: unknown host lolwakaka') 132 | self.assertTrue(next_prompt.endswith('$ ')) 133 | 134 | honeypot.stop() 135 | 136 | def test_ping_multiple_hosts(self): 137 | """ Tests basic 'ping lolwakaka awasd' 138 | Makes sure the last param is picked up as the host to ping. 139 | """ 140 | 141 | honeypot = Hornet(self.working_dir) 142 | honeypot.start() 143 | 144 | while honeypot.server.server_port == 0: # wait until the server is ready 145 | gevent.sleep(0) 146 | port = honeypot.server.server_port 147 | client = paramiko.SSHClient() 148 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 149 | # If we log in properly, this should raise no errors 150 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 151 | channel = client.invoke_shell() 152 | 153 | while not channel.recv_ready(): 154 | gevent.sleep(0) # :-( 155 | 156 | welcome = '' 157 | while channel.recv_ready(): 158 | welcome += channel.recv(1) 159 | lines = welcome.split('\r\n') 160 | prompt = lines[-1] 161 | self.assertTrue(prompt.endswith('$ ')) 162 | 163 | # Now send the ping command 164 | ping_command = 'ping lolwakaka awasd' 165 | channel.send(ping_command + '\r\n') 166 | 167 | while not channel.recv_ready(): 168 | gevent.sleep(0) # :-( 169 | 170 | output = '' 171 | while not output.endswith('$ '): 172 | output += channel.recv(1) 173 | 174 | lines = output.split('\r\n') 175 | command = lines[0] 176 | command_output = '\r\n'.join(lines[1:-1]) 177 | next_prompt = lines[-1] 178 | 179 | self.assertEquals(command, ping_command) 180 | self.assertEquals(command_output, 'ping: unknown host awasd') 181 | self.assertTrue(next_prompt.endswith('$ ')) 182 | 183 | honeypot.stop() 184 | 185 | def test_ping_ctrl_c(self): 186 | """ Tests basic 'ping test01' 187 | Makes sure the last param is picked up as the host to ping. 188 | """ 189 | 190 | honeypot = Hornet(self.working_dir) 191 | honeypot.start() 192 | 193 | while honeypot.server.server_port == 0: # wait until the server is ready 194 | gevent.sleep(0) 195 | port = honeypot.server.server_port 196 | client = paramiko.SSHClient() 197 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 198 | # If we log in properly, this should raise no errors 199 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 200 | channel = client.invoke_shell() 201 | 202 | while not channel.recv_ready(): 203 | gevent.sleep(0) # :-( 204 | 205 | welcome = '' 206 | while channel.recv_ready(): 207 | welcome += channel.recv(1) 208 | lines = welcome.split('\r\n') 209 | prompt = lines[-1] 210 | self.assertTrue(prompt.endswith('$ ')) 211 | 212 | # Now send the ping command 213 | ping_command = 'ping test01' 214 | channel.send(ping_command + '\r\n') 215 | 216 | while not channel.recv_ready(): 217 | gevent.sleep(0) # :-( 218 | 219 | lines = [] 220 | for i in range(2): 221 | line = '' 222 | while not line.endswith('\r\n'): 223 | line += channel.recv(1) 224 | lines.append(line.strip('\r\n')) 225 | channel.send(chr(3)) 226 | 227 | output = '' 228 | while not output.endswith('$ '): 229 | data = channel.recv(1) 230 | output += data 231 | 232 | lines = output.split('\r\n') 233 | lines = [l for l in lines if not l.startswith('64 bytes from')] # Skip the ping response lines 234 | 235 | self.assertEquals('^C', lines[0]) 236 | self.assertEquals('--- test01 ping statistics ---', lines[1]) 237 | 238 | self.assertTrue('packets transmitted, ' in lines[2]) 239 | self.assertTrue('received, ' in lines[2]) 240 | self.assertTrue('% packet loss, time' in lines[2]) 241 | self.assertTrue(lines[2].endswith('ms')) 242 | 243 | self.assertTrue(lines[3].startswith('rtt min/avg/max/mdev = ')) 244 | self.assertTrue(lines[3].endswith('ms')) 245 | 246 | next_prompt = lines[-1] 247 | self.assertTrue(next_prompt.endswith('$ ')) 248 | 249 | honeypot.stop() 250 | 251 | def test_ping_ctrl_c_ip_addr(self): 252 | """ Tests basic 'ping 192.168.0.232' 253 | Makes sure the last param is picked up as the host to ping. 254 | """ 255 | 256 | honeypot = Hornet(self.working_dir) 257 | honeypot.start() 258 | 259 | while honeypot.server.server_port == 0: # wait until the server is ready 260 | gevent.sleep(0) 261 | port = honeypot.server.server_port 262 | client = paramiko.SSHClient() 263 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 264 | # If we log in properly, this should raise no errors 265 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 266 | channel = client.invoke_shell() 267 | 268 | while not channel.recv_ready(): 269 | gevent.sleep(0) # :-( 270 | 271 | welcome = '' 272 | while channel.recv_ready(): 273 | welcome += channel.recv(1) 274 | lines = welcome.split('\r\n') 275 | prompt = lines[-1] 276 | self.assertTrue(prompt.endswith('$ ')) 277 | 278 | # Now send the ping command 279 | ping_command = 'ping 192.168.0.232' 280 | channel.send(ping_command + '\r\n') 281 | 282 | while not channel.recv_ready(): 283 | gevent.sleep(0) # :-( 284 | 285 | lines = [] 286 | for i in range(2): 287 | line = '' 288 | while not line.endswith('\r\n'): 289 | line += channel.recv(1) 290 | line = line.strip('\r\n') 291 | if line.startswith('64 bytes from'): 292 | self.assertTrue('test02 (192.168.0.232)' in line) 293 | lines.append(line) 294 | channel.send(chr(3)) 295 | 296 | output = '' 297 | while not output.endswith('$ '): 298 | data = channel.recv(1) 299 | output += data 300 | 301 | lines = output.split('\r\n') 302 | lines = [l for l in lines if not l.startswith('64 bytes from')] # Skip the ping response lines 303 | 304 | self.assertEquals('^C', lines[0]) 305 | self.assertEquals('--- 192.168.0.232 ping statistics ---', lines[1]) 306 | 307 | self.assertTrue('packets transmitted, ' in lines[2]) 308 | self.assertTrue('received, ' in lines[2]) 309 | self.assertTrue('% packet loss, time' in lines[2]) 310 | self.assertTrue(lines[2].endswith('ms')) 311 | 312 | self.assertTrue(lines[3].startswith('rtt min/avg/max/mdev = ')) 313 | self.assertTrue(lines[3].endswith('ms')) 314 | 315 | next_prompt = lines[-1] 316 | self.assertTrue(next_prompt.endswith('$ ')) 317 | 318 | honeypot.stop() -------------------------------------------------------------------------------- /hornet/tests/commands/test_pwd.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | gevent.monkey.patch_all() 22 | 23 | import paramiko 24 | from hornet.main import Hornet 25 | from hornet.tests.commands.base import BaseTestClass 26 | 27 | 28 | class HornetTests(BaseTestClass): 29 | 30 | def test_pwd(self): 31 | """ Tests if pwd command works """ 32 | 33 | honeypot = Hornet(self.working_dir) 34 | honeypot.start() 35 | 36 | while honeypot.server.server_port == 0: # wait until the server is ready 37 | gevent.sleep(0) 38 | port = honeypot.server.server_port 39 | client = paramiko.SSHClient() 40 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 41 | # If we log in properly, this should raise no errors 42 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 43 | channel = client.invoke_shell() 44 | 45 | while not channel.recv_ready(): 46 | gevent.sleep(0) # :-( 47 | 48 | welcome = '' 49 | while channel.recv_ready(): 50 | welcome += channel.recv(1) 51 | lines = welcome.split('\r\n') 52 | prompt = lines[-1] 53 | self.assertTrue(prompt.endswith('$ ')) 54 | 55 | # Now send the pwd command 56 | pwd_command = 'pwd /something/ any kind of param 123' 57 | channel.send(pwd_command + '\r\n') 58 | 59 | while not channel.recv_ready(): 60 | gevent.sleep(0) # :-( 61 | 62 | output = '' 63 | while not output.endswith('$ '): 64 | output += channel.recv(1) 65 | 66 | lines = output.split('\r\n') 67 | command = lines[0] 68 | command_output = '\r\n'.join(lines[1:-1]) 69 | next_prompt = lines[-1] 70 | 71 | self.assertEquals(command, pwd_command) 72 | self.assertEquals(command_output, 'pwd: too many arguments') 73 | self.assertTrue(next_prompt.endswith('$ ')) 74 | 75 | pwd_command = 'pwd' 76 | channel.send(pwd_command + '\r\n') 77 | 78 | while not channel.recv_ready(): 79 | gevent.sleep(0) # :-( 80 | 81 | output = '' 82 | while not output.endswith('$ '): 83 | output += channel.recv(1) 84 | 85 | lines = output.split('\r\n') 86 | command = lines[0] 87 | command_output = '\r\n'.join(lines[1:-1]) 88 | next_prompt = lines[-1] 89 | 90 | self.assertEquals(command, pwd_command) 91 | self.assertEquals(command_output, '/') 92 | self.assertTrue(next_prompt.endswith('$ ')) 93 | honeypot.stop() 94 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_ssh.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | gevent.monkey.patch_all() 22 | 23 | import paramiko 24 | from hornet.main import Hornet 25 | from hornet.tests.commands.base import BaseTestClass 26 | 27 | 28 | class HornetTests(BaseTestClass): 29 | 30 | def test_ssh_no_username(self): 31 | """ Tests if ssh command works when no username is provided in host string 32 | eg: $ ssh test01 33 | """ 34 | 35 | honeypot = Hornet(self.working_dir) 36 | honeypot.start() 37 | 38 | while honeypot.server.server_port == 0: # wait until the server is ready 39 | gevent.sleep(0) 40 | port = honeypot.server.server_port 41 | client = paramiko.SSHClient() 42 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 43 | # If we log in properly, this should raise no errors 44 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 45 | channel = client.invoke_shell() 46 | 47 | while not channel.recv_ready(): 48 | gevent.sleep(0) # :-( 49 | 50 | welcome = '' 51 | while channel.recv_ready(): 52 | welcome += channel.recv(1) 53 | lines = welcome.split('\r\n') 54 | prompt = lines[-1] 55 | self.assertTrue(prompt.endswith('$ ')) 56 | 57 | # Now send the ssh command 58 | channel.send('ssh test01\r\n') 59 | while not channel.recv_ready(): 60 | gevent.sleep(0) # :-( 61 | output = '' 62 | while not output.endswith('Password:'): 63 | output += channel.recv(1) 64 | 65 | # Now send the password 66 | channel.send('passtest\r\n') 67 | 68 | output = '' 69 | while not output.endswith('$ '): 70 | output += channel.recv(1) 71 | self.assertTrue('Welcome to test01 server' in output) 72 | self.assertTrue(output.endswith('$ ')) 73 | honeypot.stop() 74 | 75 | def test_ssh_with_username(self): 76 | """ Tests if ssh command works when username is provided in host string 77 | eg: $ ssh mango@test01 78 | """ 79 | 80 | honeypot = Hornet(self.working_dir) 81 | honeypot.start() 82 | 83 | while honeypot.server.server_port == 0: # wait until the server is ready 84 | gevent.sleep(0) 85 | port = honeypot.server.server_port 86 | client = paramiko.SSHClient() 87 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 88 | # If we log in properly, this should raise no errors 89 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 90 | channel = client.invoke_shell() 91 | 92 | while not channel.recv_ready(): 93 | gevent.sleep(0) # :-( 94 | 95 | welcome = '' 96 | while channel.recv_ready(): 97 | welcome += channel.recv(1) 98 | lines = welcome.split('\r\n') 99 | prompt = lines[-1] 100 | self.assertTrue(prompt.endswith('$ ')) 101 | 102 | # Now send the ssh command 103 | channel.send('ssh root@test01\r\n') 104 | while not channel.recv_ready(): 105 | gevent.sleep(0) # :-( 106 | output = '' 107 | while not output.endswith('Password:'): 108 | output += channel.recv(1) 109 | 110 | # Now send the password 111 | channel.send('toor\r\n') 112 | 113 | output = '' 114 | while not output.endswith('$ '): 115 | output += channel.recv(1) 116 | self.assertTrue('Welcome to test01 server' in output) 117 | self.assertTrue(output.endswith('$ ')) 118 | honeypot.stop() 119 | 120 | def test_ssh_with_username_param(self): 121 | """ Tests if ssh command works when username is provided as a parameter 122 | eg: $ ssh test01 -l mango 123 | """ 124 | 125 | honeypot = Hornet(self.working_dir) 126 | honeypot.start() 127 | 128 | while honeypot.server.server_port == 0: # wait until the server is ready 129 | gevent.sleep(0) 130 | port = honeypot.server.server_port 131 | client = paramiko.SSHClient() 132 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 133 | # If we log in properly, this should raise no errors 134 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 135 | channel = client.invoke_shell() 136 | 137 | while not channel.recv_ready(): 138 | gevent.sleep(0) # :-( 139 | 140 | welcome = '' 141 | while channel.recv_ready(): 142 | welcome += channel.recv(1) 143 | lines = welcome.split('\r\n') 144 | prompt = lines[-1] 145 | self.assertTrue(prompt.endswith('$ ')) 146 | 147 | # Now send the ssh command 148 | channel.send('ssh test01 -l root\r\n') 149 | while not channel.recv_ready(): 150 | gevent.sleep(0) # :-( 151 | output = '' 152 | while not output.endswith('Password:'): 153 | output += channel.recv(1) 154 | 155 | # Now send the password 156 | channel.send('toor\r\n') 157 | 158 | output = '' 159 | while not output.endswith('$ '): 160 | output += channel.recv(1) 161 | self.assertTrue('Welcome to test01 server' in output) 162 | self.assertTrue(output.endswith('$ ')) 163 | honeypot.stop() 164 | 165 | def test_ssh_bad_hostname(self): 166 | """ Tests if ssh command returns correct string if host doesn't exist 167 | eg: $ ssh test01 -l mango 168 | """ 169 | 170 | honeypot = Hornet(self.working_dir) 171 | honeypot.start() 172 | 173 | while honeypot.server.server_port == 0: # wait until the server is ready 174 | gevent.sleep(0) 175 | port = honeypot.server.server_port 176 | client = paramiko.SSHClient() 177 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 178 | # If we log in properly, this should raise no errors 179 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 180 | channel = client.invoke_shell() 181 | 182 | while not channel.recv_ready(): 183 | gevent.sleep(0) # :-( 184 | 185 | welcome = '' 186 | while channel.recv_ready(): 187 | welcome += channel.recv(1) 188 | lines = welcome.split('\r\n') 189 | prompt = lines[-1] 190 | self.assertTrue(prompt.endswith('$ ')) 191 | 192 | # Now send the ssh command 193 | channel.send('ssh blahblah\r\n') 194 | while not channel.recv_ready(): 195 | gevent.sleep(0) # :-( 196 | output = '' 197 | while not output.endswith('$ '): 198 | output += channel.recv(1) 199 | self.assertTrue('Name or service not known' in output) 200 | self.assertTrue(output.endswith('$ ')) 201 | honeypot.stop() 202 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_uname.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | import os 20 | 21 | import gevent.monkey 22 | import hornet 23 | 24 | gevent.monkey.patch_all() 25 | 26 | import paramiko 27 | from hornet.main import Hornet 28 | from hornet.tests.commands.base import BaseTestClass 29 | 30 | 31 | class HornetTests(BaseTestClass): 32 | 33 | def test_uname(self): 34 | """ Tests if uname command works """ 35 | 36 | honeypot = Hornet(self.working_dir) 37 | honeypot.start() 38 | self.create_filesystem(honeypot) 39 | 40 | while honeypot.server.server_port == 0: # wait until the server is ready 41 | gevent.sleep(0) 42 | port = honeypot.server.server_port 43 | client = paramiko.SSHClient() 44 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 45 | # If we log in properly, this should raise no errors 46 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 47 | channel = client.invoke_shell() 48 | 49 | while not channel.recv_ready(): 50 | gevent.sleep(0) # :-( 51 | 52 | welcome = '' 53 | while channel.recv_ready(): 54 | welcome += channel.recv(1) 55 | lines = welcome.split('\r\n') 56 | prompt = lines[-1] 57 | self.assertTrue(prompt.endswith('$ ')) 58 | 59 | # Now send the uname command 60 | uname_command = 'uname' 61 | channel.send(uname_command + '\r\n') 62 | 63 | while not channel.recv_ready(): 64 | gevent.sleep(0) # :-( 65 | 66 | output = '' 67 | while not output.endswith('$ '): 68 | output += channel.recv(1) 69 | 70 | lines = output.split('\r\n') 71 | command = lines[0] 72 | command_output = '\r\n'.join(lines[1:-1]) 73 | next_prompt = lines[-1] 74 | 75 | self.assertEquals(command, uname_command) 76 | self.assertEquals(command_output, 'Linux') 77 | self.assertTrue(next_prompt.endswith('$ ')) 78 | 79 | honeypot.stop() 80 | 81 | def test_uname_with_params(self): 82 | """ Tests if 'uname -a' command works """ 83 | 84 | honeypot = Hornet(self.working_dir) 85 | honeypot.start() 86 | self.create_filesystem(honeypot) 87 | 88 | while honeypot.server.server_port == 0: # wait until the server is ready 89 | gevent.sleep(0) 90 | port = honeypot.server.server_port 91 | client = paramiko.SSHClient() 92 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 93 | # If we log in properly, this should raise no errors 94 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 95 | channel = client.invoke_shell() 96 | 97 | while not channel.recv_ready(): 98 | gevent.sleep(0) # :-( 99 | 100 | welcome = '' 101 | while channel.recv_ready(): 102 | welcome += channel.recv(1) 103 | lines = welcome.split('\r\n') 104 | prompt = lines[-1] 105 | self.assertTrue(prompt.endswith('$ ')) 106 | 107 | # Now send the cd command 108 | uname_command = 'uname -a' 109 | channel.send(uname_command + '\r\n') 110 | 111 | while not channel.recv_ready(): 112 | gevent.sleep(0) # :-( 113 | 114 | output = '' 115 | while not output.endswith('$ '): 116 | output += channel.recv(1) 117 | 118 | lines = output.split('\r\n') 119 | command = lines[0] 120 | command_output = '\r\n'.join(lines[1:-1]) 121 | next_prompt = lines[-1] 122 | 123 | expected_info = ['Linux', honeypot.config.default_hostname, '3.13.0-37-generic', 124 | '#64-Ubuntu SMP Mon Sep 22 21:30:01 UTC 2014', 'i686', 125 | 'i686', 'i686', 'GNU/Linux'] 126 | 127 | self.assertEquals(command, uname_command) 128 | self.assertEquals(command_output, ' '.join(expected_info)) 129 | self.assertTrue(next_prompt.endswith('$ ')) 130 | 131 | honeypot.stop() 132 | 133 | def test_uname_other_params(self): 134 | """ Tests if 'uname -snrvmpio' works """ 135 | 136 | honeypot = Hornet(self.working_dir) 137 | honeypot.start() 138 | self.create_filesystem(honeypot) 139 | 140 | while honeypot.server.server_port == 0: # wait until the server is ready 141 | gevent.sleep(0) 142 | port = honeypot.server.server_port 143 | client = paramiko.SSHClient() 144 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 145 | # If we log in properly, this should raise no errors 146 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 147 | channel = client.invoke_shell() 148 | 149 | while not channel.recv_ready(): 150 | gevent.sleep(0) # :-( 151 | 152 | welcome = '' 153 | while channel.recv_ready(): 154 | welcome += channel.recv(1) 155 | lines = welcome.split('\r\n') 156 | prompt = lines[-1] 157 | self.assertTrue(prompt.endswith('$ ')) 158 | 159 | # Now send the uname command 160 | uname_command = 'uname -snrvmpio' 161 | channel.send(uname_command + '\r\n') 162 | 163 | while not channel.recv_ready(): 164 | gevent.sleep(0) # :-( 165 | 166 | output = '' 167 | while not output.endswith('$ '): 168 | output += channel.recv(1) 169 | 170 | lines = output.split('\r\n') 171 | command = lines[0] 172 | command_output = '\r\n'.join(lines[1:-1]) 173 | next_prompt = lines[-1] 174 | 175 | expected_info = ['Linux', honeypot.config.default_hostname, '3.13.0-37-generic', 176 | '#64-Ubuntu SMP Mon Sep 22 21:30:01 UTC 2014', 'i686', 177 | 'i686', 'i686', 'GNU/Linux'] 178 | 179 | self.assertEquals(command, uname_command) 180 | self.assertEquals(command_output, ' '.join(expected_info)) 181 | self.assertTrue(next_prompt.endswith('$ ')) 182 | 183 | honeypot.stop() 184 | 185 | def test_uname_version_param(self): 186 | """ Tests if 'uname --version' works """ 187 | 188 | honeypot = Hornet(self.working_dir) 189 | honeypot.start() 190 | self.create_filesystem(honeypot) 191 | 192 | while honeypot.server.server_port == 0: # wait until the server is ready 193 | gevent.sleep(0) 194 | port = honeypot.server.server_port 195 | client = paramiko.SSHClient() 196 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 197 | # If we log in properly, this should raise no errors 198 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 199 | channel = client.invoke_shell() 200 | 201 | while not channel.recv_ready(): 202 | gevent.sleep(0) # :-( 203 | 204 | welcome = '' 205 | while channel.recv_ready(): 206 | welcome += channel.recv(1) 207 | lines = welcome.split('\r\n') 208 | prompt = lines[-1] 209 | self.assertTrue(prompt.endswith('$ ')) 210 | 211 | # Now send the uname command 212 | uname_command = 'uname --version' 213 | channel.send(uname_command + '\r\n') 214 | 215 | while not channel.recv_ready(): 216 | gevent.sleep(0) # :-( 217 | 218 | output = '' 219 | while not output.endswith('$ '): 220 | output += channel.recv(1) 221 | 222 | lines = output.split('\r\n') 223 | command = lines[0] 224 | command_output = '\r\n'.join(lines[1:-1]) 225 | next_prompt = lines[-1] 226 | 227 | expected_output = [] 228 | version_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 229 | 'commands', 'uname', 'version') 230 | with open(version_file_path) as version_file: 231 | for line in version_file: 232 | line = line.strip() 233 | expected_output.append(line) 234 | 235 | self.assertEquals(command, uname_command) 236 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 237 | self.assertTrue(next_prompt.endswith('$ ')) 238 | 239 | honeypot.stop() 240 | 241 | def test_uname_help_param(self): 242 | """ Tests if 'uname --help' works """ 243 | 244 | honeypot = Hornet(self.working_dir) 245 | honeypot.start() 246 | self.create_filesystem(honeypot) 247 | 248 | while honeypot.server.server_port == 0: # wait until the server is ready 249 | gevent.sleep(0) 250 | port = honeypot.server.server_port 251 | client = paramiko.SSHClient() 252 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 253 | # If we log in properly, this should raise no errors 254 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 255 | channel = client.invoke_shell() 256 | 257 | while not channel.recv_ready(): 258 | gevent.sleep(0) # :-( 259 | 260 | welcome = '' 261 | while channel.recv_ready(): 262 | welcome += channel.recv(1) 263 | lines = welcome.split('\r\n') 264 | prompt = lines[-1] 265 | self.assertTrue(prompt.endswith('$ ')) 266 | 267 | # Now send the uname command 268 | uname_command = 'uname --help' 269 | channel.send(uname_command + '\r\n') 270 | 271 | while not channel.recv_ready(): 272 | gevent.sleep(0) # :-( 273 | 274 | output = '' 275 | while not output.endswith('$ '): 276 | output += channel.recv(1) 277 | 278 | lines = output.split('\r\n') 279 | command = lines[0] 280 | command_output = '\r\n'.join(lines[1:-1]) 281 | next_prompt = lines[-1] 282 | 283 | expected_output = [] 284 | version_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 285 | 'commands', 'uname', 'help') 286 | with open(version_file_path) as version_file: 287 | for line in version_file: 288 | line = line.strip() 289 | expected_output.append(line) 290 | 291 | self.assertEquals(command, uname_command) 292 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 293 | self.assertTrue(next_prompt.endswith('$ ')) 294 | 295 | honeypot.stop() 296 | -------------------------------------------------------------------------------- /hornet/tests/commands/test_wget.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | import gevent.monkey 20 | gevent.monkey.patch_all() 21 | 22 | import os 23 | import unittest 24 | import paramiko 25 | import hornet 26 | 27 | from hornet.main import Hornet 28 | from hornet.tests.commands.base import BaseTestClass 29 | 30 | 31 | class HornetTests(BaseTestClass): 32 | 33 | def test_wget_bad_hostname(self): 34 | """ Tests if 'wget http://asdjkhaskdh/index.html' works (bad hostname case) """ 35 | 36 | honeypot = Hornet(self.working_dir) 37 | honeypot.start() 38 | 39 | while honeypot.server.server_port == 0: # wait until the server is ready 40 | gevent.sleep(0) 41 | port = honeypot.server.server_port 42 | client = paramiko.SSHClient() 43 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 44 | # If we log in properly, this should raise no errors 45 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 46 | channel = client.invoke_shell() 47 | 48 | while not channel.recv_ready(): 49 | gevent.sleep(0) # :-( 50 | 51 | welcome = '' 52 | while channel.recv_ready(): 53 | welcome += channel.recv(1) 54 | lines = welcome.split('\r\n') 55 | prompt = lines[-1] 56 | self.assertTrue(prompt.endswith('$ ')) 57 | 58 | # Now send the wget command 59 | wget_command = 'wget http://asdjkhaskdh/index.html' 60 | channel.send(wget_command + '\r\n') 61 | 62 | while not channel.recv_ready(): 63 | gevent.sleep(0) # :-( 64 | 65 | output = '' 66 | while not output.endswith('$ '): 67 | output += channel.recv(1) 68 | 69 | lines = output.split('\r\n') 70 | command = lines[0] 71 | next_prompt = lines[-1] 72 | 73 | self.assertEquals(command, wget_command) 74 | 75 | self.assertTrue(lines[1].startswith('--')) 76 | self.assertTrue('http://asdjkhaskdh/index.html' in lines[1]) 77 | self.assertEquals('Resolving asdjkhaskdh (asdjkhaskdh)... ' 78 | 'failed: Name or service not known.', lines[2]) 79 | self.assertEquals('wget: unable to resolve host address \'asdjkhaskdh\'', lines[3]) 80 | self.assertTrue(next_prompt.endswith('$ ')) 81 | 82 | honeypot.stop() 83 | 84 | def test_wget_help_param(self): 85 | """ Tests if 'wget --help' works """ 86 | 87 | honeypot = Hornet(self.working_dir) 88 | honeypot.start() 89 | self.create_filesystem(honeypot) 90 | 91 | while honeypot.server.server_port == 0: # wait until the server is ready 92 | gevent.sleep(0) 93 | port = honeypot.server.server_port 94 | client = paramiko.SSHClient() 95 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 96 | # If we log in properly, this should raise no errors 97 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 98 | channel = client.invoke_shell() 99 | 100 | while not channel.recv_ready(): 101 | gevent.sleep(0) # :-( 102 | 103 | welcome = '' 104 | while channel.recv_ready(): 105 | welcome += channel.recv(1) 106 | lines = welcome.split('\r\n') 107 | prompt = lines[-1] 108 | self.assertTrue(prompt.endswith('$ ')) 109 | 110 | # Now send the wget command 111 | wget_command = 'wget --help' 112 | channel.send(wget_command + '\r\n') 113 | 114 | while not channel.recv_ready(): 115 | gevent.sleep(0) # :-( 116 | 117 | output = '' 118 | while not output.endswith('$ '): 119 | output += channel.recv(1) 120 | 121 | lines = output.split('\r\n') 122 | command = lines[0] 123 | command_output = '\r\n'.join(lines[1:-1]) 124 | next_prompt = lines[-1] 125 | 126 | expected_output = [] 127 | help_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 128 | 'commands', 'wget', 'help') 129 | with open(help_file_path) as help_file: 130 | for line in help_file: 131 | line = line.strip() 132 | expected_output.append(line) 133 | 134 | self.assertEquals(command, wget_command) 135 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 136 | self.assertTrue(next_prompt.endswith('$ ')) 137 | 138 | honeypot.stop() 139 | 140 | def test_wget_version_param(self): 141 | """ Tests if 'wget --version' works """ 142 | 143 | honeypot = Hornet(self.working_dir) 144 | honeypot.start() 145 | self.create_filesystem(honeypot) 146 | 147 | while honeypot.server.server_port == 0: # wait until the server is ready 148 | gevent.sleep(0) 149 | port = honeypot.server.server_port 150 | client = paramiko.SSHClient() 151 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 152 | # If we log in properly, this should raise no errors 153 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 154 | channel = client.invoke_shell() 155 | 156 | while not channel.recv_ready(): 157 | gevent.sleep(0) # :-( 158 | 159 | welcome = '' 160 | while channel.recv_ready(): 161 | welcome += channel.recv(1) 162 | lines = welcome.split('\r\n') 163 | prompt = lines[-1] 164 | self.assertTrue(prompt.endswith('$ ')) 165 | 166 | # Now send the wget command 167 | wget_command = 'wget --version' 168 | channel.send(wget_command + '\r\n') 169 | 170 | while not channel.recv_ready(): 171 | gevent.sleep(0) # :-( 172 | 173 | output = '' 174 | while not output.endswith('$ '): 175 | output += channel.recv(1) 176 | 177 | lines = output.split('\r\n') 178 | command = lines[0] 179 | command_output = '\r\n'.join(lines[1:-1]) 180 | next_prompt = lines[-1] 181 | 182 | expected_output = [] 183 | version_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 184 | 'commands', 'wget', 'version') 185 | with open(version_file_path) as version_file: 186 | for line in version_file: 187 | line = line.strip() 188 | expected_output.append(line) 189 | 190 | self.assertEquals(command, wget_command) 191 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 192 | self.assertTrue(next_prompt.endswith('$ ')) 193 | 194 | honeypot.stop() 195 | 196 | def test_wget_bad_returncode(self): 197 | """ Tests if 'wget http://httpbin.org/status/500' shows an error resolving """ 198 | 199 | honeypot = Hornet(self.working_dir) 200 | honeypot.start() 201 | 202 | while honeypot.server.server_port == 0: # wait until the server is ready 203 | gevent.sleep(0) 204 | port = honeypot.server.server_port 205 | client = paramiko.SSHClient() 206 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 207 | # If we log in properly, this should raise no errors 208 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 209 | channel = client.invoke_shell() 210 | 211 | while not channel.recv_ready(): 212 | gevent.sleep(0) # :-( 213 | 214 | welcome = '' 215 | while channel.recv_ready(): 216 | welcome += channel.recv(1) 217 | lines = welcome.split('\r\n') 218 | prompt = lines[-1] 219 | self.assertTrue(prompt.endswith('$ ')) 220 | 221 | # Now send the wget command 222 | wget_command = 'wget http://httpbin.org/status/500' 223 | channel.send(wget_command + '\r\n') 224 | 225 | while not channel.recv_ready(): 226 | gevent.sleep(0) # :-( 227 | 228 | output = '' 229 | while not output.endswith('$ '): 230 | output += channel.recv(1) 231 | 232 | lines = output.split('\r\n') 233 | command = lines[0] 234 | next_prompt = lines[-1] 235 | 236 | self.assertEquals(command, wget_command) 237 | 238 | self.assertTrue(lines[1].startswith('--')) 239 | self.assertTrue('http://httpbin.org/status/500' in lines[1]) 240 | self.assertEquals('Resolving httpbin.org (httpbin.org)... ' 241 | 'failed: Name or service not known.', lines[2]) 242 | self.assertEquals('wget: unable to resolve host address \'httpbin.org\'', lines[3]) 243 | self.assertTrue(next_prompt.endswith('$ ')) 244 | 245 | honeypot.stop() 246 | 247 | def test_wget_no_content_length(self): 248 | """ Tests if 'wget http://pathod.net/response_preview?spec=200:r' shows an error resolving """ 249 | 250 | honeypot = Hornet(self.working_dir) 251 | honeypot.start() 252 | 253 | while honeypot.server.server_port == 0: # wait until the server is ready 254 | gevent.sleep(0) 255 | port = honeypot.server.server_port 256 | client = paramiko.SSHClient() 257 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 258 | # If we log in properly, this should raise no errors 259 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 260 | channel = client.invoke_shell() 261 | 262 | while not channel.recv_ready(): 263 | gevent.sleep(0) # :-( 264 | 265 | welcome = '' 266 | while channel.recv_ready(): 267 | welcome += channel.recv(1) 268 | lines = welcome.split('\r\n') 269 | prompt = lines[-1] 270 | self.assertTrue(prompt.endswith('$ ')) 271 | 272 | # Now send the wget command 273 | wget_command = 'wget http://pathod.net/response_preview?spec=200:r' 274 | channel.send(wget_command + '\r\n') 275 | 276 | while not channel.recv_ready(): 277 | gevent.sleep(0) # :-( 278 | 279 | output = '' 280 | while not output.endswith('$ '): 281 | output += channel.recv(1) 282 | 283 | lines = output.split('\r\n') 284 | command = lines[0] 285 | next_prompt = lines[-1] 286 | 287 | self.assertEquals(command, wget_command) 288 | 289 | self.assertTrue(lines[1].startswith('--')) 290 | self.assertTrue('http://pathod.net/response_preview?spec=200:r' in lines[1]) 291 | self.assertEquals('Resolving pathod.net (pathod.net)... ' 292 | 'failed: Name or service not known.', lines[2]) 293 | self.assertEquals('wget: unable to resolve host address \'pathod.net\'', lines[3]) 294 | self.assertTrue(next_prompt.endswith('$ ')) 295 | 296 | honeypot.stop() 297 | 298 | def test_wget_bad_content_length(self): 299 | """ Tests if 'wget http://pathod.net/response_preview?spec=200%3Ar%3Ah%22Content-Length 300 | %22%3D%22%27unparsable%22' shows an error resolving """ 301 | 302 | honeypot = Hornet(self.working_dir) 303 | honeypot.start() 304 | 305 | while honeypot.server.server_port == 0: # wait until the server is ready 306 | gevent.sleep(0) 307 | port = honeypot.server.server_port 308 | client = paramiko.SSHClient() 309 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 310 | # If we log in properly, this should raise no errors 311 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 312 | channel = client.invoke_shell() 313 | 314 | while not channel.recv_ready(): 315 | gevent.sleep(0) # :-( 316 | 317 | welcome = '' 318 | while channel.recv_ready(): 319 | welcome += channel.recv(1) 320 | lines = welcome.split('\r\n') 321 | prompt = lines[-1] 322 | self.assertTrue(prompt.endswith('$ ')) 323 | 324 | # Now send the wget command 325 | wget_command = 'wget http://pathod.net/response_preview?spec=200%3Ar%3Ah%22' \ 326 | 'Content-Length%22%3D%22%27unparsable%22' 327 | channel.send(wget_command + '\r\n') 328 | 329 | while not channel.recv_ready(): 330 | gevent.sleep(0) # :-( 331 | 332 | output = '' 333 | while not output.endswith('$ '): 334 | output += channel.recv(1) 335 | 336 | lines = output.split('\r\n') 337 | command = lines[0] 338 | next_prompt = lines[-1] 339 | 340 | self.assertEquals(command, wget_command) 341 | 342 | self.assertTrue(lines[1].startswith('--')) 343 | self.assertTrue('http://pathod.net/response_preview?spec=200%3Ar%3Ah%22' 344 | 'Content-Length%22%3D%22%27unparsable%22' in lines[1]) 345 | self.assertEquals('Resolving pathod.net (pathod.net)... ' 346 | 'failed: Name or service not known.', lines[2]) 347 | self.assertEquals('wget: unable to resolve host address \'pathod.net\'', lines[3]) 348 | self.assertTrue(next_prompt.endswith('$ ')) 349 | honeypot.stop() 350 | 351 | def test_wget_download_url(self): 352 | """ Tests if 'wget http://httpbin.org/drip?numbytes=2048&duration=3&code=200' downloads the file properly """ 353 | 354 | honeypot = Hornet(self.working_dir) 355 | honeypot.start() 356 | 357 | while honeypot.server.server_port == 0: # wait until the server is ready 358 | gevent.sleep(0) 359 | port = honeypot.server.server_port 360 | client = paramiko.SSHClient() 361 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 362 | # If we log in properly, this should raise no errors 363 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 364 | channel = client.invoke_shell() 365 | 366 | while not channel.recv_ready(): 367 | gevent.sleep(0) # :-( 368 | 369 | welcome = '' 370 | while channel.recv_ready(): 371 | welcome += channel.recv(1) 372 | lines = welcome.split('\r\n') 373 | prompt = lines[-1] 374 | self.assertTrue(prompt.endswith('$ ')) 375 | 376 | # Now send the wget command 377 | wget_command = 'wget http://httpbin.org/drip?numbytes=2048&duration=3&code=200 -O drip' 378 | channel.send(wget_command + '\r\n') 379 | 380 | while not channel.recv_ready(): 381 | gevent.sleep(0) # :-( 382 | 383 | output = '' 384 | while not output.endswith('$ '): 385 | output += channel.recv(1) 386 | 387 | lines = output.split('\r\n') 388 | command = lines[0] 389 | command_output = lines[1:-1] 390 | next_prompt = lines[-1] 391 | 392 | self.assertEquals(command, wget_command) 393 | self.assertTrue(lines[1].startswith('--')) 394 | 395 | default_host = honeypot.vhosts[honeypot.config.default_hostname] 396 | file_ = default_host.filesystem.getsyspath('drip') 397 | with open(file_, 'r') as downloaded_file: 398 | data = downloaded_file.read() 399 | self.assertEquals(len(data), 2048) 400 | 401 | self.assertTrue(command_output[0].startswith('--')) 402 | self.assertTrue(command_output[0].endswith( 403 | 'http://httpbin.org/drip?numbytes=2048&duration=3&code=200' 404 | )) 405 | 406 | self.assertTrue(command_output[1].startswith('Resolving httpbin.org (httpbin.org)...')) 407 | self.assertTrue(command_output[2].startswith('Connecting to httpbin.org (httpbin.org)')) 408 | self.assertTrue(command_output[2].endswith('|:80... connected.')) 409 | self.assertEquals(command_output[3], 'HTTP request sent, awaiting response... 200 OK') 410 | self.assertEquals(command_output[4], 'Length: 2048 (2.0K) [application/octet-stream]') 411 | self.assertEquals(command_output[5], 'Saving to:\'drip\'') 412 | self.assertTrue(command_output[-1].endswith('\'drip\' saved [2048/2048]')) 413 | 414 | self.assertTrue(next_prompt.endswith('$ ')) 415 | honeypot.stop() 416 | 417 | def test_wget_no_params(self): 418 | """ Tests if 'wget' works """ 419 | 420 | honeypot = Hornet(self.working_dir) 421 | honeypot.start() 422 | self.create_filesystem(honeypot) 423 | 424 | while honeypot.server.server_port == 0: # wait until the server is ready 425 | gevent.sleep(0) 426 | port = honeypot.server.server_port 427 | client = paramiko.SSHClient() 428 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 429 | # If we log in properly, this should raise no errors 430 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 431 | channel = client.invoke_shell() 432 | 433 | while not channel.recv_ready(): 434 | gevent.sleep(0) # :-( 435 | 436 | welcome = '' 437 | while channel.recv_ready(): 438 | welcome += channel.recv(1) 439 | lines = welcome.split('\r\n') 440 | prompt = lines[-1] 441 | self.assertTrue(prompt.endswith('$ ')) 442 | 443 | # Now send the wget command 444 | wget_command = 'wget' 445 | channel.send(wget_command + '\r\n') 446 | 447 | while not channel.recv_ready(): 448 | gevent.sleep(0) # :-( 449 | 450 | output = '' 451 | while not output.endswith('$ '): 452 | output += channel.recv(1) 453 | 454 | lines = output.split('\r\n') 455 | command = lines[0] 456 | command_output = '\r\n'.join(lines[1:-1]) 457 | next_prompt = lines[-1] 458 | 459 | expected_output = [] 460 | noparam_file_path = os.path.join(os.path.dirname(hornet.__file__), 'data', 461 | 'commands', 'wget', 'no_param') 462 | with open(noparam_file_path) as help_file: 463 | for line in help_file: 464 | line = line.strip() 465 | expected_output.append(line) 466 | 467 | self.assertEquals(command, wget_command) 468 | self.assertEquals(command_output, '\r\n'.join(expected_output)) 469 | self.assertTrue(next_prompt.endswith('$ ')) 470 | 471 | honeypot.stop() 472 | 473 | if __name__ == '__main__': 474 | unittest.main() 475 | -------------------------------------------------------------------------------- /hornet/tests/test_fs_wrapper.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | import gevent.monkey 20 | 21 | gevent.monkey.patch_all() 22 | 23 | import random 24 | import string 25 | import unittest 26 | import tempfile 27 | import os 28 | 29 | from hornet.core.fs_wrapper import SandboxedFS 30 | 31 | 32 | class HornetTests(unittest.TestCase): 33 | 34 | def test_create(self): 35 | """ Test basic 'ls' """ 36 | 37 | testfs = self.create_filesystem() 38 | new_filename = self.random_name() 39 | 40 | res = testfs.create(new_filename) 41 | 42 | self.assertTrue(res) 43 | 44 | # ensure file exists on disk 45 | self.assertTrue(os.path.isfile(os.path.join(testfs.root_path, new_filename))) 46 | 47 | def test_makedir(self): 48 | testfs = self.create_filesystem() 49 | new_dirname = self.random_name() 50 | 51 | res = testfs.makedir(new_dirname) 52 | 53 | self.assertIsNotNone(res) 54 | 55 | # ensure directory exists on disk 56 | self.assertTrue(os.path.isdir(os.path.join(testfs.root_path, new_dirname))) 57 | 58 | def create_filesystem(self): 59 | temp_dir = tempfile.mkdtemp(prefix='test_hornet_') 60 | return SandboxedFS(temp_dir) 61 | 62 | def random_name(self): 63 | return ''.join( 64 | random.choice(string.ascii_uppercase + string.digits) for _ in range(6) 65 | ) 66 | -------------------------------------------------------------------------------- /hornet/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | 22 | gevent.monkey.patch_all() 23 | 24 | import os 25 | import shutil 26 | import unittest 27 | import tempfile 28 | 29 | import hornet 30 | from hornet.common.helpers import get_random_item 31 | 32 | 33 | class HornetTests(unittest.TestCase): 34 | 35 | def setUp(self): 36 | self.working_dir = tempfile.mkdtemp() 37 | test_config = os.path.join(os.path.dirname(hornet.__file__), 'data', 'default_config.json') 38 | shutil.copyfile(test_config, os.path.join(self.working_dir, 'config.json')) 39 | 40 | def tearDown(self): 41 | shutil.rmtree(self.working_dir) 42 | 43 | def test_random_choice_dict(self): 44 | test_dict = { 45 | 'a': 1, 46 | 'b': 2, 47 | 'c': 3, 48 | 'd': 4 49 | } 50 | x = get_random_item(test_dict) 51 | self.assertTrue(x in test_dict.values()) 52 | 53 | def test_random_choice_list(self): 54 | test_list = range(4, 99) 55 | x = get_random_item(test_list) 56 | self.assertTrue(4 <= x <= 99) 57 | -------------------------------------------------------------------------------- /hornet/tests/test_hornet.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | gevent.monkey.patch_all() 22 | 23 | import os 24 | import shutil 25 | import unittest 26 | import tempfile 27 | import paramiko 28 | 29 | import hornet 30 | from hornet.main import Hornet 31 | 32 | 33 | class HornetTests(unittest.TestCase): 34 | 35 | def setUp(self): 36 | self.working_dir = tempfile.mkdtemp() 37 | test_config = os.path.join(os.path.dirname(hornet.__file__), 'data', 'default_config.json') 38 | shutil.copyfile(test_config, os.path.join(self.working_dir, 'config.json')) 39 | 40 | def tearDown(self): 41 | shutil.rmtree(self.working_dir) 42 | 43 | def test_config_loading(self): 44 | """ Tests whether Hornet can properly load a configuration file""" 45 | 46 | honeypot = Hornet(self.working_dir) 47 | self.assertEquals(honeypot.config.host, '127.0.0.1') 48 | self.assertEquals(honeypot.config.port, 0) 49 | self.assertEquals(honeypot.config.default_hostname, 'test02') 50 | self.assertEquals(len(honeypot.config.vhost_params), 3) 51 | 52 | def test_vfs_creation(self): 53 | """ Tests whether virtual file systems for each host are created """ 54 | 55 | honeypot = Hornet(self.working_dir) 56 | honeypot.start() 57 | vfs_dir = os.path.join(self.working_dir, 'vhosts') 58 | self.assertTrue(os.path.isdir(vfs_dir)) 59 | for item in os.listdir(vfs_dir): 60 | self.assertTrue(item.startswith('test')) 61 | honeypot.stop() 62 | 63 | def test_key_creation(self): 64 | """ Tests if key file is generated on run """ 65 | 66 | honeypot = Hornet(self.working_dir) 67 | honeypot.start() 68 | while honeypot.server.server_port == 0: # wait until the server is ready 69 | gevent.sleep(0) 70 | port = honeypot.server.server_port 71 | client = paramiko.SSHClient() 72 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 73 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 74 | # Add a sleep here if this test fails for no reason... the server needs time to write the key file 75 | # gevent.sleep(1) 76 | self.assertTrue(os.path.isfile(os.path.join(self.working_dir, 'test_server.key'))) 77 | honeypot.stop() 78 | 79 | def test_login_success(self): 80 | """ Tests whether an SSH client can login to the Honeypot """ 81 | 82 | honeypot = Hornet(self.working_dir) 83 | honeypot.start() 84 | while honeypot.server.server_port == 0: # wait until the server is ready 85 | gevent.sleep(0) 86 | port = honeypot.server.server_port 87 | client = paramiko.SSHClient() 88 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 89 | # If we log in properly, this should raise no errors 90 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 91 | gevent.sleep(1) 92 | honeypot.stop() 93 | 94 | def test_login_failure(self): 95 | """ Tests whether an SSH client login fails on bad credentials """ 96 | 97 | honeypot = Hornet(self.working_dir) 98 | honeypot.start() 99 | while honeypot.server.server_port == 0: # wait until the server is ready 100 | gevent.sleep(0) 101 | port = honeypot.server.server_port 102 | client = paramiko.SSHClient() 103 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 104 | with self.assertRaises(paramiko.AuthenticationException): 105 | client.connect('127.0.0.1', port=port, username='aksjd', password='asjdhkasd') 106 | client.close() 107 | gevent.sleep(1) 108 | honeypot.stop() 109 | 110 | def test_vhost_creation(self): 111 | """ Tests whether virtual hosts are created properly """ 112 | 113 | honeypot = Hornet(self.working_dir) 114 | self.assertEquals(len(honeypot.vhosts), 3) 115 | default = None 116 | for hostname, host in honeypot.vhosts.iteritems(): 117 | if host.default: 118 | default = host 119 | self.assertFalse(default is None) 120 | 121 | def test_config_copying(self): 122 | """ Tests whether Hornet can copy the default configuration file""" 123 | 124 | os.remove(os.path.join(self.working_dir, 'config.json')) 125 | self.assertFalse(os.path.isfile(os.path.join(self.working_dir, 'config.json'))) 126 | honeypot = Hornet(self.working_dir) 127 | self.assertTrue(os.path.isfile(os.path.join(self.working_dir, 'config.json'))) 128 | -------------------------------------------------------------------------------- /hornet/tests/test_host.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # 3 | # Hornet - SSH Honeypot 4 | # 5 | # Copyright (C) 2015 Aniket Panse 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import gevent.monkey 21 | import paramiko 22 | 23 | gevent.monkey.patch_all() 24 | 25 | import os 26 | import shutil 27 | import unittest 28 | import tempfile 29 | 30 | import hornet 31 | from hornet.main import Hornet 32 | from hornet.common.helpers import get_random_item 33 | 34 | 35 | class HornetTests(unittest.TestCase): 36 | def setUp(self): 37 | self.working_dir = tempfile.mkdtemp() 38 | test_config = os.path.join(os.path.dirname(hornet.__file__), 'data', 'default_config.json') 39 | shutil.copyfile(test_config, os.path.join(self.working_dir, 'config.json')) 40 | 41 | def tearDown(self): 42 | shutil.rmtree(self.working_dir) 43 | 44 | def test_ip_assignment(self): 45 | """ 46 | Tests whether IP addresses are assigned to each host. 47 | """ 48 | 49 | def check_ipv4(value): 50 | parts = value.split('.') 51 | if len(parts) == 4 and all(x.isdigit() for x in parts): 52 | numbers = list(int(x) for x in parts) 53 | return all(0 <= num < 256 for num in numbers) 54 | return False 55 | 56 | honeypot = Hornet(self.working_dir) 57 | 58 | for hostname, host in honeypot.vhosts.iteritems(): 59 | self.assertTrue(check_ipv4(host.ip_address)) 60 | 61 | def test_default_welcome_message(self): 62 | """ 63 | Tests whether a virtual host loads a default welcome message 64 | """ 65 | honeypot = Hornet(self.working_dir) 66 | for ip, host in honeypot.vhosts.iteritems(): 67 | self.assertTrue(host.welcome.startswith('Welcome to ')) 68 | 69 | def test_custom_welcome_message(self): 70 | 71 | honeypot = Hornet(self.working_dir) 72 | random_host = get_random_item(honeypot.vhosts) 73 | random_host.filesystem.makedir('/etc') 74 | with random_host.filesystem.open('/etc/motd', 'w') as motd_file: 75 | motd_file.write(u'TestingCustomWelcomeMessage') 76 | self.assertEquals(random_host.welcome, u'TestingCustomWelcomeMessage') 77 | 78 | def test_shell_set_host(self): 79 | """ Tests if host related attributes are set on the shell properly """ 80 | 81 | honeypot = Hornet(self.working_dir) 82 | honeypot.start() 83 | while honeypot.server.server_port == 0: # wait until the server is ready 84 | gevent.sleep(0) 85 | port = honeypot.server.server_port 86 | client = paramiko.SSHClient() 87 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 88 | # If we log in properly, this should raise no errors 89 | client.connect('127.0.0.1', port=port, username='testuser', password='testpassword') 90 | channel = client.invoke_shell() 91 | 92 | while not channel.recv_ready(): 93 | gevent.sleep(0) # :-( 94 | 95 | welcome = '' 96 | while channel.recv_ready(): 97 | welcome += channel.recv(1) 98 | lines = welcome.split('\r\n') 99 | prompt = lines[-1] 100 | username, remaining = prompt.split('@') 101 | self.assertEquals(username, 'testuser') 102 | hostname, remaining = remaining.split(':') 103 | self.assertEquals(hostname, 'test02') 104 | self.assertTrue(prompt.endswith('$ ')) 105 | honeypot.stop() 106 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.18.4 2 | telnetsrv==0.4 3 | gevent==1.2.2 4 | paramiko==2.4.0 5 | netaddr==0.7.19 6 | fs==2.0.20 7 | nose==1.3.7 8 | arrow==0.12.1 9 | SQLAlchemy==1.2.1 10 | mysqlclient==1.3.12 11 | -------------------------------------------------------------------------------- /scripts/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | docker run -td --name hornet aniket/hornet:1.0 3 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sed -i 's/127.0.0.1/0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf 4 | /etc/init.d/mysql restart; 5 | mysql -u root -v -e 'CREATE DATABASE IF NOT EXISTS hornet;'; 6 | mysql -u root -v -e "CREATE USER 'hornetservice'@'%';"; 7 | mysql -u root -v -e "GRANT ALL PRIVILEGES ON *.* TO 'hornetservice'@'%';"; 8 | 9 | mkdir /opt/vfs/; 10 | 11 | sed 's/\/\/travis\@/\/\/hornetservice\@/' ./hornet/data/default_config.json > /opt/vfs/config.json; 12 | sed -i 's/port": 0,/port":\ 2222,/' /opt/vfs/config.json; 13 | sed -i 's/"host": "127.0.0.1",/"host": "0.0.0.0",/' /opt/vfs/config.json; 14 | 15 | mkdir -p /var/log/supervisord/; 16 | supervisord -n -c ./hornet-supervisord.conf; 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Need to import multiprocessing to work around a 2 | # bug (http://bugs.python.org/issue15881#msg170215) 3 | 4 | import multiprocessing 5 | from setuptools import setup, find_packages 6 | 7 | setup( 8 | name='hornet', 9 | version='0.0.1', 10 | description='SSH Honeypot', 11 | url='https://github.com/czardoz/hornet', 12 | author='Aniket Panse', 13 | author_email='aniketpanse@gmail.com', 14 | license='GPLv3', 15 | packages=find_packages(), 16 | include_package_data=True, 17 | zip_safe=False, 18 | install_requires=open('requirements.txt').readlines(), 19 | test_suite='nose.collector', 20 | long_description=open('README.rst').read(), 21 | tests_require=['nose'], 22 | scripts=['bin/hornet'], 23 | dependency_links=['git+https://github.com/ianepperson/telnetsrvlib.git#egg=telnetsrv-0.4.1'] 24 | ) 25 | --------------------------------------------------------------------------------