EAHonepot/main.py

230 lines
8 KiB
Python
Raw Permalink Normal View History

2024-11-19 19:25:45 +00:00
import asyncio
import paramiko
import socket
import aiosqlite
from aiosqlite import connect
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.types import FSInputFile
import threading
import time
import matplotlib.pyplot as plt
import os
async def load_config():
"""async def that loads config from a database
"""
async with aiosqlite.connect("config.db") as conn:
async with conn.execute("SELECT tg_bot_token, chat_id, cb, ma, delay, port FROM config LIMIT 1") as cursor:
config = await cursor.fetchone()
if not config:
raise ValueError("No configuration")
return config
async def send_msg(token, chat_id, message):
"""Fynction that let alerts about a login attempts in telegram chat exist
Args:
token (int): telegram bot token
chat_id (int): telegram chat id with alerts in botapi standart
message (int): message, that sending to chat when someone is trying to brute-force this honeypot
"""
bot = Bot(token=token)
await bot.send_message(chat_id=chat_id, text=message)
await bot.session.close()
async def log_db(username, password, client_ip):
"""Async fynction wich logging all login attempts in database
Args:
username (str): username entered by attackers
password (str): password entered by attackers
client_ip (str): attackers ip
"""
async with aiosqlite.connect("honeypot_logs.db") as conn:
await conn.execute('''CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password TEXT,
ip TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)''')
await conn.execute('INSERT INTO logs (username, password, ip) VALUES (?, ?, ?)',
(username, password, client_ip))
await conn.commit()
class FakeSSHServer(paramiko.ServerInterface):
"""Class wich raises ssh server using paramiko
"""
def __init__(self, client_ip, send_message_func, log_func, ma, delay):
super().__init__()
self.client_ip = client_ip
self.send_message_func = send_message_func
self.log_func = log_func
self.auth_attempts = {}
self.ma = ma
self.delay = delay
self.completion_event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == "session":
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
asyncio.run(self.log_func(username, password, self.client_ip))
asyncio.run(self.send_message_func(
f"Login attempt from {self.client_ip}\nUsername: {username}\nPassword: {password}"))
if username not in self.auth_attempts:
self.auth_attempts[username] = 0
self.auth_attempts[username] += 1
if self.auth_attempts[username] >= self.ma:
asyncio.run(self.send_message_func(
f"Failed login attempts exceeded for {self.client_ip}"
))
time.sleep(self.delay / 1000.0)
return paramiko.AUTH_FAILED
def handle_client(client_socket, client_address, tg_bot_token, chat_id, cb, ma, delay):
"""Fuction wich handles ssh clients
Args:
client_address (str): client ip adress
tg_bot_token (str): telegram bot token
chat_id (str): telegram chat id
cb (str): banner, wich paramiko sends to clients
ma (int): max attempts from a client
delay (int): delay in answer
"""
transport = paramiko.Transport(client_socket)
try:
server_key = paramiko.RSAKey(filename="server_key.pem")
transport.add_server_key(server_key)
server = FakeSSHServer(
client_ip=client_address[0],
send_message_func=lambda msg: send_msg(tg_bot_token, chat_id, msg),
log_func=log_db,
ma=ma,
delay=delay
)
transport.local_version = cb
transport.start_server(server=server)
channel = transport.accept(20)
if channel:
channel.close()
finally:
transport.close()
client_socket.close()
async def start_server(tg_bot_token, chat_id, cb, ma, delay, port):
"""Starts a paramiko ssh server
Args:
tg_bot_token (str): telegram bot token
chat_id (str): telegram chat id
cb (str): banner, wich paramiko sends to clients
ma (int): max attempts from a client
delay (int): delay in answer to client
port (ште): port on which the ssh server is raised
"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("", port))
server_socket.listen(100)
print(f"Listening for connections on port {port}...")
server_socket.setblocking(False)
loop = asyncio.get_event_loop()
while True:
client_socket, client_address = await loop.sock_accept(server_socket)
threading.Thread(target=handle_client, args=(
client_socket, client_address, tg_bot_token, chat_id, cb, ma, delay)).start()
async def tg_bot(tg_bot_token, chat_id):
"""Async function with a telegram bot
Args:
tg_bot_token (str): variable with a telegram bot token
chat_id (int): variable with a telegram chat id
"""
bot = Bot(token=tg_bot_token)
dp = Dispatcher()
@dp.message(Command('start'))
async def cmd_start(message: types.Message):
if str(message.chat.id) == chat_id:
await message.answer("90")
@dp.message(Command("stat_ip"))
async def statip(message: types.Message):
if str(message.chat.id) == chat_id:
async with connect("honeypot_logs.db") as conn:
async with conn.execute("SELECT ip, COUNT(*) AS attempts FROM logs GROUP BY ip ORDER BY attempts DESC") as cursor:
rows = await cursor.fetchall()
stats = "\n".join([f"{ip}: {count}" for ip, count in rows])
await message.answer(f"IP Stats:\n{stats}")
@dp.message(Command("stat_ip_chart"))
async def statip_chart(message: types.Message):
if str(message.chat.id) == chat_id:
async with connect("honeypot_logs.db") as conn:
async with conn.execute("SELECT ip, COUNT(*) AS attempts FROM logs GROUP BY ip ORDER BY attempts DESC") as cursor:
rows = await cursor.fetchall()
if not rows:
await message.answer("No data")
return
labels = [row[0] for row in rows]
sizes = [row[1] for row in rows]
plt.figure(figsize=(8, 8))
plt.pie(
sizes,
labels=None,
autopct='%1.1f%%',
colors=plt.cm.tab20.colors[:len(sizes)]
)
plt.legend(labels=[f"{ip}: {count}" for ip, count in rows],
loc="lower left", bbox_to_anchor=(1, 0))
plt.title("Shares of IP addresses from the total array")
plt.savefig("temp.png", bbox_inches="tight")
plt.close()
png_path = os.path.join(os.path.dirname(__file__), 'temp.png')
await message.answer(f'{png_path}')
png = FSInputFile(png_path)
await message.answer_photo(photo=png, caption='Ip statistic graph')
os.remove(png_path)
await dp.start_polling(bot)
async def main():
"""loads all other functions
"""
tg_bot_token, chat_id, cb, ma, delay, port = await load_config()
chat_id = str(chat_id)
await asyncio.gather(
start_server(tg_bot_token, chat_id, cb, ma, delay, port),
tg_bot(tg_bot_token, chat_id)
)
if __name__ == "__main__":
asyncio.run(main())