├── .gitignore
├── README.md
├── __pycache__
├── baseline_http_server.cpython-310.pyc
├── dashboard_data_parser.cpython-310.pyc
├── honeypy.cpython-310.pyc
├── ssh_honeypot.cpython-310.pyc
├── ssh_honeypy_web_app.cpython-310.pyc
├── ssh_honeypy_web_app.cpython-38.pyc
├── web_app.cpython-310.pyc
├── web_honeypot.cpython-310.pyc
└── web_honeypot.cpython-38.pyc
├── assets
└── images
│ ├── Dashboard.PNG
│ ├── honeypy-favicon.ico
│ ├── honeypy-logo-black-text.png
│ └── honeypy-logo-white.png
├── dashboard_data_parser.py
├── honeypy.py
├── log_files
├── baseline_ssh.py
├── cmd_audits.log
├── creds_audits.log
└── http_audit.log
├── public.env
├── requirements.txt
├── ssh_honeypot.py
├── systemd
└── honeypy.service
├── templates
├── dashboard.html
├── index.html
└── wp-admin.html
├── web_app.py
└── web_honeypot.py
/.gitignore:
--------------------------------------------------------------------------------
1 | static/server.key
2 | static/server.key.pub
3 | assets/images/top10cmd.png
4 | assets/images/top10creds.png
5 | Docker/
6 | __pycache__
7 | requirements1.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | A modular, graphic-based honeypot to capture IP Adresses, usernames, passwords, and commands from various protocols (SSH & HTTP supported right now). Written in Python.
4 |
5 | # Install
6 |
7 | **1) Clone repository.**
8 | `git clone https://github.com/collinsmc23/ssh_honeypy.git`
9 |
10 | **2) Permissions.**
11 | Move into `ssh_honeypy` folder.
12 |
13 | Ensure `main.py` has proper permisions. (`chmod 755 honeypy.py`)
14 |
15 | **3) Keygen.**
16 |
17 | Create a new folder `static`.
18 |
19 | `mkdir static`
20 |
21 | Move into directory.
22 |
23 | `cd static`
24 |
25 | An RSA key must be generated for the SSH server host key. The SSH host key provides proper identification for the SSH server. Ensure the key is titled `server.key` and resides in the same relative directory to the main program.
26 |
27 | `ssh-keygen -t rsa -b 2048 -f server.key`
28 |
29 | # Usage
30 |
31 | To provision a new instance of HONEYPY, use the `honeypy.py` file. This is the main file to interface with for HONEYPY.
32 |
33 | HONEYPY requires a bind IP address (`-a`) and network port to listen on (`-p`). Use `0.0.0.0` to listen on all network interfaces. The protocol type must also be defined.
34 |
35 | ```
36 | -a / --address: Bind address.
37 | -p / --port: Port.
38 | -s / --ssh OR -wh / --http: Declare honeypot type.
39 | ```
40 |
41 | Example: `python3 honeypy.py -a 0.0.0.0 -p 22 --ssh`
42 |
43 | 💡 If HONEPY is set up to listen on a privileged port (22), the program must be run with `sudo` or root privileges. No other services should be using the specified port.
44 |
45 | If port 22 is being used as the listener, the default SSH port must be changed. Refer to Hostinger's "[How to Change the SSH Port](https://www.hostinger.com/tutorials/how-to-change-ssh-port-vps)" guide.
46 |
47 | ❗ To run with `sudo`, the `root` account must have access to all Python libraries used in this project (libraries defined in `requirements.txt`). Install by switching to the root account, then supply:
48 |
49 | `root@my_host# pip install -r requirements`
50 |
51 | This will install all the packages for the root user, but it will affect the global environment and isn't considered the "safeest" way to do this.
52 |
53 | **Optional Arguments**
54 |
55 | A username (`-u`) and password (`-w`) can be specified to authenticate the SSH server. The default configuration will accept all usernames and passwords.
56 |
57 | ```
58 | -u / --username: Username.
59 | -w / --password: Password.
60 | -t / --tarpit: For SSH-based honeypots, -t can be used to trap sessions inside the shell, by sending a 'endless' SSH banner.
61 | ```
62 |
63 | Example: `python3 main.py -a 0.0.0.0 -p 22 --ssh -u admin -w admin --tarpit`
64 |
65 | # Logging Files
66 |
67 | HONEYPY has three loggers configured. Loggers will route to either `cmd_audits.log`, `creds_audits.log` (for SSH), and `http_audit.log` (for HTTP) log files for information capture.
68 |
69 | `cmd_audits.log`: Captures IP address, username, password, and all commands supplied.
70 |
71 | `creds_audits.log`: Captures IP address, username, and password, comma seperated. Used to see how many hosts attempt to connect to SSH_HONEYPY.
72 |
73 | `http_audit.log`: Captures IP address, username, password.
74 |
75 | The log files will be located in `../ssh_honeypy/log_files/..`
76 |
77 | # Honeypot Types
78 | This honeypot was written with modularity in mind to support future honeypot types (Telnet, HTTPS, SMTP, etc). As of right now there are two honeypot types supported.
79 |
80 | ## SSH
81 | The project started out with only supported SSH. Use the following instructions above to provision an SSH-based honeypot which emulates a basic shell.
82 |
83 | 💡 `-t / --tarpit`: A tarpit is a security mechanism designed to slow down or delay the attempts of an attacker trying to brute-force login credentials. Leveraging Python's time module, a very long SSH-banner is sent to a connecting shell session. The only way to get out of the session is by closing the terminal.
84 |
85 | ## HTTP
86 | Using Python Flask as a basic template to provision a simple web service, HONEYPY impersonates a default WordPress `wp-admin` login page. Username / password pairs are collected.
87 |
88 | There are default credentials accepted, `admin` and `deeboodah`, which will proceed to a Rick Roll gif. Username and password can be changed using the `-u / --username: Username.
89 | -w / --password: Password` arguments.
90 |
91 | The web-based honeypot runs on port 5000 by default. This can be changed using the `-p / --port` flag option.
92 |
93 | 💡 There is currently not a dashboard panel supported for HTTP-based results. This will be a future addition.
94 |
95 | # Dashboard
96 |
97 | HONEYPY comes packaged with a `web_app.py` file. This can be run in a seperate terminal session on localhost to view statistics such as top 10 IP addresses, usernames, passwords, commands, and all data in tabular format. As of right now, the dashboards do not dynamically update as new entries or information are added to the log files. The dashboard must be run every time to re-populate to the most up-to-date information.
98 |
99 | Run `python3 web_app.py` on localhost. Default port for Python Dash is `8050`. `http://127.0.0.1:8050`. Go to your browser of choice to view dashboard metrics.
100 |
101 | 💡 The dashboard data includes a country code lookup that uses the IP address to determine the two-digit country code. To get the IP address, the [ipinfo() CleanTalk API](https://cleantalk.org/help/api-ip-info-country-code) is used. Due to rate limiting contraints, CleanTalk can only lookup 1000 IP addresses per 60 seconds.
102 | - By default, the country code lookup is set to `False`, as this will have impact on how long it takes to provision the honeypot (pandas has to pivot on dataframes, which takes time). Set the `COUNTRY` environment variable to `True` if you would like to get the country code lookup dashboard panel.
103 | - If receiving rate limiting errors, change the `COUNTRY` environment variable in `public.env` to `False` again.
104 |
105 | HONEPY leverages Python Dash to populate the bar charts, Dash Bootstrap Components for dark-theme and style of charts, and Pandas for data parsing.
106 |
107 |
108 |
109 | # VPS Hosting (General Tips)
110 | To host on VPS, follow the general tips.
111 |
112 | To gather logging information, it's advised to use a Virtual Private Server (VPS). VPS's are cloud-based hosts with Internet access. This provides a safe, isolated way to gather real-time information without having to configure and isolate infrastructure on your home network.
113 |
114 | You can get 10% off Hostinger VPS with this code (not sponsored on this GitHub project): https://www.hostinger.com/grantcollins
115 |
116 | A majority of VPS hosting providers will provide a Virtual Firewall where you can open ports. Ensure to open ports used by HONEYPY.
117 | - `Port 80`, `Port 5000`, `Port 2223` (Whichever port you configure to listen on real SSH connection), `Port 8050`.
118 |
119 | When working on Linux-based distributions, also open the ports with IP Tables or Unfiltered Firewall (UFW).
120 | - `ufw enable`
121 | - `ufw allow [port]`
122 |
123 | # Running in Background With Systemd
124 | To run HONEPY in the background, you can use Systemd for popular Linux-based distributions.
125 |
126 | There is a template included under the systemd folder in this repo.
127 |
128 | Supply the required arguments after the `honeypy.py` to run with your desired configuration. Use your favorite text editor to change the configuration.
129 | - `ExecStart=/usr/bin/python3 /honeypy.py -a 127.0.0.1 -p 22 --ssh`
130 |
131 | Copy `honeypy.service` template file into `/etc/systemd/system`. `cp honeypy.service /etc/systemd/system`.
132 |
133 | Reload systemd with the new configuration added, `systemctl daemon-reload`.
134 |
135 | Enable the `honeypy.service` file with `systemctl enable honeypy.service`.
136 |
137 | Start the `honepy.service` file with `systemctl start honepy.service`.
138 |
139 | # Video Overview
140 |
141 | [](https://youtu.be/tyKyLhcKgNo)
142 |
143 | # Future Features
144 |
145 | - Write additional support for common protocols:
146 | - Telnet
147 | - HTTP ✅
148 | - HTTP(S)
149 | - SMTP
150 | - RDP
151 | - DNS
152 | - Telnet
153 | - Custom DNS support.
154 | - Docker support for host-based isolation and code deployment.
155 | - Systemd support to run Python script in background. ✅
156 | - Create a basic overview Dashboard. ✅
157 | - Dynamic Dashboard Updates.
158 | - Dashboard hosted on seperate host to get results independent on honeypot host.
159 | - Add SSH Banner Tarpit to trap SSH sessions ✅ (`-t, --tarpit`)
160 |
161 | # Helpful Resources
162 |
163 | Resources and guides used while developing project.
164 |
165 | - https://securehoney.net/blog/how-to-build-an-ssh-honeypot-in-python-and-docker-part-1.html
166 | - https://medium.com/@abdulsamie488/deceptive-defense-building-a-python-honeypot-to-thwart-cyber-attackers-2a9d2ced2760
167 | - https://gist.github.com/cschwede/3e2c025408ab4af531651098331cce45
168 | - https://www.hostinger.com/tutorials/how-to-change-ssh-port-vps
--------------------------------------------------------------------------------
/__pycache__/baseline_http_server.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/baseline_http_server.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/dashboard_data_parser.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/dashboard_data_parser.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/honeypy.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/honeypy.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/ssh_honeypot.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/ssh_honeypot.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/ssh_honeypy_web_app.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/ssh_honeypy_web_app.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/ssh_honeypy_web_app.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/ssh_honeypy_web_app.cpython-38.pyc
--------------------------------------------------------------------------------
/__pycache__/web_app.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/web_app.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/web_honeypot.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/web_honeypot.cpython-310.pyc
--------------------------------------------------------------------------------
/__pycache__/web_honeypot.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/__pycache__/web_honeypot.cpython-38.pyc
--------------------------------------------------------------------------------
/assets/images/Dashboard.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/assets/images/Dashboard.PNG
--------------------------------------------------------------------------------
/assets/images/honeypy-favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/assets/images/honeypy-favicon.ico
--------------------------------------------------------------------------------
/assets/images/honeypy-logo-black-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/assets/images/honeypy-logo-black-text.png
--------------------------------------------------------------------------------
/assets/images/honeypy-logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/assets/images/honeypy-logo-white.png
--------------------------------------------------------------------------------
/dashboard_data_parser.py:
--------------------------------------------------------------------------------
1 | # Import library dependencies.
2 | import pandas as pd
3 | import re
4 | import requests
5 |
6 | # This file parses the various log files. The log files have different "formats" or information provided, so needed to create unique parsers for each.
7 | # Each of these parsers takes the log file, gathers the specific information provided in the log, then returns the data in columns/rows Pandas dataframe type.
8 |
9 | # Parser for the creds file. Returns IP Address, Username, Password.
10 | def parse_creds_audits_log(creds_audits_log_file):
11 | data = []
12 |
13 | with open(creds_audits_log_file, 'r') as file:
14 | for line in file:
15 | parts = line.strip().split(', ')
16 | ip_address = parts[0]
17 | username = parts[1]
18 | password = parts[2]
19 | data.append([ip_address, username, password])
20 |
21 | df = pd.DataFrame(data, columns=["ip_address", "username", "password"])
22 | return df
23 |
24 | # Parser for commands entered during SSH session.
25 | def parse_cmd_audits_log(cmd_audits_log_file):
26 |
27 | data = []
28 |
29 | with open(cmd_audits_log_file, 'r') as file:
30 | for line in file:
31 | lines = line.strip().split('\n')
32 |
33 | # Regular expression to extract IP address and command
34 | pattern = re.compile(r"Command b'([^']*)'executed by (\d+\.\d+\.\d+\.\d+)")
35 |
36 | for line in lines:
37 | match = pattern.search(line)
38 | if match:
39 | command, ip = match.groups()
40 | data.append({'IP Address': ip, 'Command': command})
41 |
42 | df = pd.DataFrame(data)
43 |
44 | return df
45 |
46 | # Calculator to generate top 10 values from a dataframe. Supply a column name, counts how often each value occurs, stores in "count" column, then return dataframe with value/count.
47 | def top_10_calculator(dataframe, column):
48 |
49 | for col in dataframe.columns:
50 | if col == column:
51 | top_10_df = dataframe[column].value_counts().reset_index().head(10)
52 | top_10_df.columns = [column, "count"]
53 |
54 | return top_10_df
55 |
56 | # Takes an IP address as string type, uses the Cleantalk API to look up IP Geolocation.
57 | def get_country_code(ip):
58 |
59 | data_list = []
60 | # According to the CleanTalk API docs, API calls are rate limited to 1000 per 60 seconds.
61 | url = f"https://api.cleantalk.org/?method_name=ip_info&ip={ip}"
62 | try:
63 | response = requests.get(url)
64 | api_data = response.json()
65 | if response.status_code == 200:
66 | data = response.json()
67 | ip_data = data.get('data', {})
68 | country_info = ip_data.get(ip, {})
69 | data_list.append({'IP Address': ip, 'Country_Code': country_info.get('country_code')})
70 | elif response.status_code == 429:
71 | print(api_data["error_message"])
72 | print(f"[!] CleanTalk IP->Geolocation Rate Limited Exceeded.\n Please wait 60 seconds or turn Country=False (default).\n {response.status_code}")
73 | else:
74 | print(f"[!] Error: Unable to retrieve data for IP {ip}. Status code: {response.status_code}")
75 | except requests.RequestException as e:
76 | print(f"[!] Request failed: {e}")
77 |
78 | return data_list
79 |
80 | # Takes a dataframe with the IP addresses, converts each IP address to country geolocation code.
81 | def ip_to_country_code(dataframe):
82 |
83 | data = []
84 |
85 | for ip in dataframe['ip_address']:
86 | get_country = get_country_code(ip)
87 | parse_get_country = get_country[0]["Country_Code"]
88 | data.append({"IP Address": ip, "Country_Code": parse_get_country})
89 |
90 | df = pd.DataFrame(data)
91 | return df
--------------------------------------------------------------------------------
/honeypy.py:
--------------------------------------------------------------------------------
1 | # Import library dependencies.
2 | import argparse
3 | # Import project python file dependencies. This is the main file to interface with the honeypot with.
4 | from ssh_honeypot import *
5 | from web_honeypot import *
6 | from dashboard_data_parser import *
7 | from web_app import *
8 |
9 | if __name__ == "__main__":
10 | # Create parser and add arguments.
11 | parser = argparse.ArgumentParser()
12 | parser.add_argument('-a','--address', type=str, required=True)
13 | parser.add_argument('-p','--port', type=int, required=True)
14 | parser.add_argument('-u', '--username', type=str)
15 | parser.add_argument('-w', '--password', type=str)
16 | parser.add_argument('-s', '--ssh', action="store_true")
17 | parser.add_argument('-t', '--tarpit', action="store_true")
18 | parser.add_argument('-wh', '--http', action="store_true")
19 |
20 | args = parser.parse_args()
21 |
22 | # Parse the arguments based on user-supplied argument.
23 | try:
24 | if args.ssh:
25 | print("[-] Running SSH Honeypot...")
26 | honeypot(args.address, args.port, args.username, args.password, args.tarpit)
27 |
28 | elif args.http:
29 | print('[-] Running HTTP Wordpress Honeypot...')
30 | #if args.nocountry:
31 | #pass_country_status(True)
32 | if not args.username:
33 | args.username = "admin"
34 | print("[-] Running with default username of admin...")
35 | if not args.password:
36 | args.password = "deeboodah"
37 | print("[-] Running with default password of deeboodah...")
38 | print(f"Port: {args.port} Username: {args.username} Password: {args.password}")
39 | run_app(args.port, args.username, args.password)
40 | else:
41 | print("[!] You can only choose SSH (-s) (-ssh) or HTTP (-h) (-http) when running script.")
42 | except KeyboardInterrupt:
43 | print("\nProgram exited.")
44 |
--------------------------------------------------------------------------------
/log_files/baseline_ssh.py:
--------------------------------------------------------------------------------
1 | import paramiko
2 | import socket
3 | import threading
4 | import time
5 |
6 | # Define a simple server interface
7 | class SimpleSSHServer(paramiko.ServerInterface):
8 | def check_channel_request(self, kind, chanid):
9 | if kind == 'session':
10 | return paramiko.OPEN_SUCCEEDED
11 | return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
12 |
13 | def check_auth_password(self, username, password):
14 | if username == 'user' and password == 'pass':
15 | return paramiko.AUTH_SUCCESSFUL
16 | return paramiko.AUTH_FAILED
17 |
18 | def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight, modes):
19 | return True
20 |
21 | def check_channel_shell_request(self, channel):
22 | return True
23 |
24 | # Function to handle the SSH session
25 | def handle_client(client_socket):
26 | transport = paramiko.Transport(client_socket)
27 | transport.add_server_key(host_key)
28 | server = SimpleSSHServer()
29 |
30 | try:
31 | transport.start_server(server=server)
32 | chan = transport.accept(20)
33 | if chan is None:
34 | print("No channel")
35 | return
36 | standard = "Welcome to the simple SSH server!\n"
37 | multi = standard * 2
38 | for char in multi:
39 | chan.send(char)
40 | time.sleep(0.5)
41 | #chan.send("Welcome to the simple SSH server!\n")
42 | while True:
43 | command = chan.recv(1024).decode('utf-8')
44 | if command.lower() == 'exit':
45 | chan.send("Goodbye!\n")
46 | break
47 | chan.send(f"Received: {command}\n")
48 | chan.close()
49 | except Exception as e:
50 | print(f"Exception: {e}")
51 | finally:
52 | transport.close()
53 |
54 | # Function to start the SSH server
55 | def start_server(host='0.0.0.0', port=2223):
56 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
57 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
58 | server_socket.bind((host, port))
59 | server_socket.listen(100)
60 |
61 | print(f"[*] Listening on {host}:{port}")
62 |
63 | while True:
64 | client_socket, addr = server_socket.accept()
65 | print(f"[*] Accepted connection from {addr[0]}:{addr[1]}")
66 | client_handler = threading.Thread(target=handle_client, args=(client_socket,))
67 | client_handler.start()
68 |
69 | if __name__ == '__main__':
70 | # Generate an RSA key for the SSH server
71 | host_key = paramiko.RSAKey(filename="server.key")
72 | start_server(port=2222) # Use a non-privileged port for testing
73 |
--------------------------------------------------------------------------------
/log_files/cmd_audits.log:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/log_files/creds_audits.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/collinsmc23/ssh_honeypy/2f3c195e66e58ee8123589a33d26a858abd7589a/log_files/creds_audits.log
--------------------------------------------------------------------------------
/log_files/http_audit.log:
--------------------------------------------------------------------------------
1 | 2024-07-26 16:19:54,954 Username: hi, Password: hi
2 | 2024-07-26 16:21:35,336 Client with IP Address: 127.0.0.1 entered
3 | Username: hi, Password: s
4 | 2024-07-26 16:38:56,854 Client with IP Address: 127.0.0.1 entered
5 | Username: hi, Password: deebddasdf
6 | 2024-07-27 18:00:01,566 Client 127.0.0.1 attempted connection with username: user, password: hi
7 | 2024-07-27 18:57:30,746 Client with IP Address: 127.0.0.1 entered
8 | Username: admin, Password: dee
9 | 2024-07-27 18:59:11,755 Client with IP Address: 127.0.0.1 entered
10 | Username: admin, Password: b
11 | 2024-07-27 19:00:55,269 Client with IP Address: 127.0.0.1 entered
12 | Username: admin, Password: d
13 | 2024-07-27 19:00:59,503 Client with IP Address: 127.0.0.1 entered
14 | Username: admin, Password: deeboodah
15 | 2024-07-27 19:03:15,182 Client with IP Address: 127.0.0.1 entered
16 | Username: admin, Password: deeboodah
17 | 2024-07-28 11:33:00,604 Client with IP Address: 127.0.0.1 entered
18 | Username: hi, Password: hi
19 | 2024-07-31 12:09:15,992 Client with IP Address: 127.0.0.1 entered
20 | Username: admin, Password: admin
21 | 2024-07-31 12:09:25,726 Client with IP Address: 127.0.0.1 entered
22 | Username: admin, Password: deeboodah
23 |
--------------------------------------------------------------------------------
/public.env:
--------------------------------------------------------------------------------
1 | COUNTRY=True
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | dash==2.17.1
2 | dash_bootstrap_components==1.6.0
3 | dash_bootstrap_templates==1.2.3
4 | Flask==3.0.3
5 | pandas==2.2.2
6 | paramiko==3.4.0
7 | plotly==5.23.0
8 | python-dotenv==1.0.1
9 | Requests==2.32.3
10 |
--------------------------------------------------------------------------------
/ssh_honeypot.py:
--------------------------------------------------------------------------------
1 | # Import library dependencies.
2 | import logging
3 | from logging.handlers import RotatingFileHandler
4 | import paramiko
5 | import threading
6 | import socket
7 | import time
8 | from pathlib import Path
9 | # Constants.
10 | SSH_BANNER = "SSH-2.0-MySSHServer_1.0"
11 |
12 | # Constants.
13 | # Get base directory of where user is running honeypy from.
14 | base_dir = base_dir = Path(__file__).parent.parent
15 | # Source creds_audits.log & cmd_audits.log file path.
16 | server_key = base_dir / 'ssh_honeypy' / 'static' / 'server.key'
17 |
18 | creds_audits_log_local_file_path = base_dir / 'ssh_honeypy' / 'log_files' / 'creds_audits.log'
19 | cmd_audits_log_local_file_path = base_dir / 'ssh_honeypy' / 'log_files' / 'cmd_audits.log'
20 |
21 | # SSH Server Host Key.
22 | host_key = paramiko.RSAKey(filename=server_key)
23 |
24 | # Logging Format.
25 | logging_format = logging.Formatter('%(message)s')
26 |
27 | # Funnel (catch all) Logger.
28 | funnel_logger = logging.getLogger('FunnelLogger')
29 | funnel_logger.setLevel(logging.INFO)
30 | funnel_handler = RotatingFileHandler(cmd_audits_log_local_file_path, maxBytes=2000, backupCount=5)
31 | funnel_handler.setFormatter(logging_format)
32 | funnel_logger.addHandler(funnel_handler)
33 |
34 | # Credentials Logger. Captures IP Address, Username, Password.
35 | creds_logger = logging.getLogger('CredsLogger')
36 | creds_logger.setLevel(logging.INFO)
37 | creds_handler = RotatingFileHandler(creds_audits_log_local_file_path, maxBytes=2000, backupCount=5)
38 | creds_handler.setFormatter(logging_format)
39 | creds_logger.addHandler(creds_handler)
40 |
41 |
42 | # SSH Server Class. This establishes the options for the SSH server.
43 | class Server(paramiko.ServerInterface):
44 |
45 | def __init__(self, client_ip, input_username=None, input_password=None):
46 | self.event = threading.Event()
47 | self.client_ip = client_ip
48 | self.input_username = input_username
49 | self.input_password = input_password
50 |
51 | def check_channel_request(self, kind, chanid):
52 | if kind == 'session':
53 | return paramiko.OPEN_SUCCEEDED
54 |
55 | def get_allowed_auths(self, username):
56 | return "password"
57 |
58 | def check_auth_password(self, username, password):
59 | funnel_logger.info(f'Client {self.client_ip} attempted connection with ' + f'username: {username}, ' + f'password: {password}')
60 | creds_logger.info(f'{self.client_ip}, {username}, {password}')
61 | if self.input_username is not None and self.input_password is not None:
62 | if username == self.input_username and password == self.input_password:
63 | return paramiko.AUTH_SUCCESSFUL
64 | else:
65 | return paramiko.AUTH_FAILED
66 | else:
67 | return paramiko.AUTH_SUCCESSFUL
68 |
69 | def check_channel_shell_request(self, channel):
70 | self.event.set()
71 | return True
72 |
73 | def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight, modes):
74 | return True
75 |
76 | def check_channel_exec_request(self, channel, command):
77 | command = str(command)
78 | return True
79 |
80 | def emulated_shell(channel, client_ip):
81 | channel.send(b"corporate-jumpbox2$ ")
82 | command = b""
83 | while True:
84 | char = channel.recv(1)
85 | channel.send(char)
86 | if not char:
87 | channel.close()
88 |
89 | command += char
90 | # Emulate common shell commands.
91 | if char == b"\r":
92 | if command.strip() == b'exit':
93 | response = b"\n Goodbye!\n"
94 | channel.close()
95 | elif command.strip() == b'pwd':
96 | response = b"\n" + b"\\usr\\local" + b"\r\n"
97 | funnel_logger.info(f'Command {command.strip()}' + "executed by " f'{client_ip}')
98 | elif command.strip() == b'whoami':
99 | response = b"\n" + b"corpuser1" + b"\r\n"
100 | funnel_logger.info(f'Command {command.strip()}' + "executed by " f'{client_ip}')
101 | elif command.strip() == b'ls':
102 | response = b"\n" + b"jumpbox1.conf" + b"\r\n"
103 | funnel_logger.info(f'Command {command.strip()}' + "executed by " f'{client_ip}')
104 | elif command.strip() == b'cat jumpbox1.conf':
105 | response = b"\n" + b"Go to deeboodah.com" + b"\r\n"
106 | funnel_logger.info(f'Command {command.strip()}' + "executed by " f'{client_ip}')
107 | else:
108 | response = b"\n" + bytes(command.strip()) + b"\r\n"
109 | funnel_logger.info(f'Command {command.strip()}' + "executed by " f'{client_ip}')
110 | channel.send(response)
111 | channel.send(b"corporate-jumpbox2$ ")
112 | command = b""
113 |
114 | def client_handle(client, addr, username, password, tarpit=False):
115 | client_ip = addr[0]
116 | print(f"{client_ip} connected to server.")
117 | try:
118 |
119 | # Initlizes a Transport object using the socket connection from client.
120 | transport = paramiko.Transport(client)
121 | transport.local_version = SSH_BANNER
122 |
123 | # Creates an instance of the SSH server, adds the host key to prove its identity, starts SSH server.
124 | server = Server(client_ip=client_ip, input_username=username, input_password=password)
125 | transport.add_server_key(host_key)
126 | transport.start_server(server=server)
127 |
128 | # Establishes an encrypted tunnel for bidirectional communication between the client and server.
129 | channel = transport.accept(100)
130 |
131 | if channel is None:
132 | print("No channel was opened.")
133 |
134 | standard_banner = "Welcome to Ubuntu 22.04 LTS (Jammy Jellyfish)!\r\n\r\n"
135 |
136 | try:
137 | # Endless Banner: If tarpit option is passed, then send 'endless' ssh banner.
138 | if tarpit:
139 | endless_banner = standard_banner * 100
140 | for char in endless_banner:
141 | channel.send(char)
142 | time.sleep(8)
143 | # Standard Banner: Send generic welcome banner to impersonate server.
144 | else:
145 | channel.send(standard_banner)
146 | # Send channel connection to emulated shell for interpretation.
147 | emulated_shell(channel, client_ip=client_ip)
148 |
149 | except Exception as error:
150 | print(error)
151 | # Generic catch all exception error code.
152 | except Exception as error:
153 | print(error)
154 | print("!!! Exception !!!")
155 |
156 | # Once session has completed, close the transport connection.
157 | finally:
158 | try:
159 | transport.close()
160 | except Exception:
161 | pass
162 |
163 | client.close()
164 |
165 | def honeypot(address, port, username, password, tarpit=False):
166 |
167 | # Open a new socket using TCP, bind to port.
168 | socks = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
169 | socks.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
170 | socks.bind((address, port))
171 |
172 | # Can handle 100 concurrent connections.
173 | socks.listen(100)
174 | print(f"SSH server is listening on port {port}.")
175 |
176 | while True:
177 | try:
178 | # Accept connection from client and address.
179 | client, addr = socks.accept()
180 | # Start a new thread to handle the client connection.
181 | ssh_honeypot_thread = threading.Thread(target=client_handle, args=(client, addr, username, password, tarpit))
182 | ssh_honeypot_thread.start()
183 |
184 | except Exception as error:
185 | # Generic catch all exception error code.
186 | print("!!! Exception - Could not open new client connection !!!")
187 | print(error)
--------------------------------------------------------------------------------
/systemd/honeypy.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=HONEPY.
3 |
4 | [Service]
5 | ExecStart=/usr/bin/python3 /honeypy.py
6 | Restart=always
7 |
8 | [Install]
9 | WantedBy=multi-user.target
--------------------------------------------------------------------------------
/templates/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |