230 lines
8 KiB
Python
230 lines
8 KiB
Python
|
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())
|