Metasploit Framework is an open-source penetration testing and exploitation framework written in Ruby. It provides infrastructure for developing, testing, and executing exploit code against remote targets.
modules/— Metasploit modules (exploits, auxiliary, post, payloads, encoders, evasion, nops)lib/msf/— Core framework library codelib/rex/— Rex (Ruby Exploitation) librarylib/metasploit/— Metasploit namespace librariesdata/— Data files used by modules (wordlists, templates, binaries)spec/— RSpec test suitetools/— Developer and operational toolsplugins/— msfconsole pluginsscripts/— Example automation scriptsdocumentation/modules/— Markdown documentation for Metasploit modules
- Ruby (see
.ruby-versionfor the current version). Minimum supported: 3.2+ - Follow the project's
.rubocop.ymlconfiguration — runrubocopon changed files before submitting - Run
ruby tools/dev/msftidy.rb <module_file_path>to catch common module issues # frozen_string_literal: true— add to new library files (lib/); useString.newwhere a mutable string is needed. Do NOT add to module files or spec files (the framework extensively mutates string buffers via instance variables, and the RuboCop copStyle/FrozenStringLiteralCommentis disabled project-wide). Existing files that already have it are fine to leave- No enforced line length limit, but keep code readable
- Use
%q{}for long multi-line strings (curly braces preferred for module descriptions) - Multiline block comments are acceptable for embedded code snippets/payloads
- Don't use
get_/set_prefixes for accessor methods in new code - Method parameter names must be at least 2 characters (exception for well-known crypto abbreviations)
New exploit modules should follow this canonical structure and ordering:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
# 1. Protocol mixins first
include Msf::Exploit::Remote::HttpClient
# 2. Utility/feature mixins second
include Msf::Exploit::FileDropper
# 3. Reporting mixins (if needed)
# include Msf::Auxiliary::Report
# 4. AutoCheck ALWAYS LAST — must be prepend, not include
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Vendor Product Vulnerability Type',
'Description' => %q{
Description of the vulnerability and what this module does.
},
'Author' => [
'Discoverer Name', # Vulnerability discovery
'Module Author' # Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2024-XXXXX'],
['URL', 'https://example.com/advisory']
],
'Targets' => [
[
'Automatic',
{
'Platform' => ['linux'], # or 'win', 'osx', 'unix', 'php', 'python', 'java'
'Arch' => [ARCH_CMD], # or ARCH_X86, ARCH_X64, ARCH_PHP, ARCH_JAVA, ARCH_PYTHON, ARCH_ARMLE, ARCH_AARCH64, ARCH_MIPSLE — see rex-arch gem for full list
'Type' => :cmd # or :dropper, :psh_stager — determines payload delivery
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2024-01-01',
'Notes' => {
'Stability' => [], # e.g. CRASH_SAFE, CRASH_SERVICE_RESTARTS
'SideEffects' => [], # e.g. IOC_IN_LOGS, ARTIFACTS_ON_DISK
'Reliability' => [] # e.g. REPEATABLE_SESSION
}
)
)
end
def check
# Always return CheckCode with a reason string
CheckCode::Safe('Target is not vulnerable')
end
def exploit
# Exploitation logic
end
endAuxiliary modules use def run (not exploit) and inherit from Msf::Auxiliary:
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Vendor Product Scanner/Gatherer',
'Description' => %q{
Description of what this module discovers or does.
},
'Author' => ['Author Name'],
'License' => MSF_LICENSE,
'References' => [['CVE', '2024-XXXXX']],
'Notes' => {
'Stability' => [], # e.g. CRASH_SAFE
'SideEffects' => [], # e.g. IOC_IN_LOGS
'Reliability' => [] # e.g. REPEATABLE_SESSION
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end
def check
CheckCode::Safe('Target is not affected')
end
def run
# Main logic — use report_service, report_vuln, print_good, etc.
end
endPost modules inherit from Msf::Post, require a session, and declare compatible session types:
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Post::Linux::System
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Platform Subsystem Gather/Action',
'Description' => %q{
Description of what this post module does on the target.
},
'Author' => ['Author Name'],
'License' => MSF_LICENSE,
'Platform' => ['linux'], # or 'win', 'osx', 'unix', 'bsd', 'solaris'
'SessionTypes' => ['meterpreter', 'shell'], # or just ['meterpreter'] if shell won't work
'Notes' => {
'Stability' => [], # e.g. CRASH_SAFE
'SideEffects' => [], # e.g. ARTIFACTS_ON_DISK, CONFIG_CHANGES
'Reliability' => []
}
)
)
end
def run
# Use create_process, file_exist?, read_file, etc.
# Access session via `session` method
end
endThe Notes hash declares the module's operational characteristics:
| Key | Values | Meaning |
|---|---|---|
Stability |
CRASH_SAFE, CRASH_SERVICE_RESTARTS, CRASH_SERVICE_DOWN, CRASH_OS_RESTARTS, CRASH_OS_DOWN |
Impact on target stability |
SideEffects |
IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES, ACCOUNT_LOCKOUTS, SCREEN_EFFECTS, AUDIO_EFFECTS, PHYSICAL_EFFECTS |
Observable traces left on target |
Reliability |
REPEATABLE_SESSION, FIRST_ATTEMPT_FAIL, UNRELIABLE_SESSION, EVENT_DEPENDENT |
How reliably the module succeeds |
See also: lib/msf/core/constants.rb for the full list of valid values with descriptions.
Which module types require Notes:
| Module Type | Notes Required? | Enforced By |
|---|---|---|
| Exploit | Yes | msftidy + rubocop (Lint/ModuleEnforceNotes) |
| Auxiliary | Yes | rubocop (Lint/ModuleEnforceNotes) |
| Post | Yes | rubocop (Lint/ModuleEnforceNotes) |
| Evasion | No | — |
| Payload | No | — |
| Encoder | No | — |
| Nop | No | — |
The same Stability, SideEffects, and Reliability constants apply uniformly — there are no type-specific values. Payloads, encoders, and nops don't use Notes because they don't independently interact with targets.
The inline comments in the templates above list common values but are not exhaustive. Consult these source files for the full set:
| Field | Source File | Notes |
|---|---|---|
| Platform | lib/msf/core/module/platform.rb |
Class hierarchy — use the lowercase short name (e.g. 'linux', 'win', 'osx') |
| Arch | rex-arch gem |
Constants like ARCH_CMD, ARCH_X86, ARCH_X64, ARCH_PHP etc. |
| Stability / SideEffects / Reliability | lib/msf/core/constants.rb |
All valid Notes hash values with descriptions |
| Rank | lib/msf/core/constants.rb |
ManualRanking through ExcellentRanking |
| CheckCode | lib/msf/core/exploit.rb (line ~52) |
Vulnerable, Appears, Safe, Detected, Unknown, Unsupported |
Follow this order for includes and prepends in module classes:
- Protocol mixins —
Msf::Exploit::Remote::HttpClient,RubySMB,Msf::Exploit::Remote::Udp, etc. - Utility/feature mixins —
Msf::Exploit::FileDropper,Msf::Exploit::CmdStager,Msf::Exploit::EXE, etc. - Reporting mixins —
Msf::Auxiliary::Report - Post mixins (if needed) —
Msf::Post::File,Msf::Post::Linux::Priv, etc. prepend Msf::Exploit::Remote::AutoCheck— always last, after all includes
AutoCheck must use prepend, not include (the module raises NotImplementedError if included). It wraps the exploit/run method to automatically call check before exploitation.
- Prefer writing modules in Ruby. Go and Python modules are accepted, but their external runtimes don't support the full framework API (e.g. network pivoting). Ruby modules do not have this limitation
- Prefer using hash over an array for return values, and use kwargs for reusable APIs for future extensions
- Before writing a new module, check that there is not an existing module or open pull request that already covers the same functionality
- Each module should be in its own file under the appropriate
modules/subdirectory. In some scenarios adding module actions or targets is preferred - Exploits require a
DisclosureDatefield - Exploits, auxiliary, and post modules require
NoteswithStability,SideEffects, andReliability - License new code with
MSF_LICENSE(the project default, defined inlib/msf/core/constants.rb) - Module descriptions or documentation should list the range of vulnerable versions and the fixed version of the affected software, when known
- Module descriptions should only use ASCII characters
- New modules require an associated markdown file in the
documentation/modulesfolder with the same structure, including steps to set up the vulnerable environment for testing. If a Dockerfile or docker-compose file is used for the test environment, include the setup commands in the markdown rather than committing separate Docker files. The Scenarios section must be filled out by a human at all times. Followdocumentation/modules/module_doc_template.mdas a template - If there's only one
ACTIONin the exploit, it can likely be omitted
- When possible don't set a default payload (
DefaultOptionswith'PAYLOAD') in modules — let the framework choose the most appropriate payload automatically - Define bad characters instead of explicitly base-64 encoding payloads
- Don't check the number of sessions at the end of an exploit and report success based on that — not all payloads open sessions
- Don't submit any kind of opaque binary blob — everything must include source code and build instructions
Payload selection guidance:
| Scenario | Approach |
|---|---|
| Only command execution available (no file write) | Use ARCH_CMD payloads |
| Only HTTP(S) outbound (curl/wget available) | Use fetch payload (Msf::Exploit::Remote::HttpServer + fetch handler) |
| File write possible on target | Use dropper/EXE payload (Msf::Exploit::EXE) |
| Full command stager needed (multi-step upload) | Use Msf::Exploit::CmdStager — but prefer fetch when only download mechanisms are available |
- When overriding
cleanup, always callsuperto ensure the parent mixin chain cleans up connections and sessions properly - When opening a file, make sure the file exists first
- Don't print host information like
#{ip}:#{port}because it doesn't handle IPv6 addresses — use#{Rex::Socket.to_authority(ip, port)} - Use the TEST-NET-1 range for example / non-routeable IP addresses in unit tests and spec files:
192.0.2.0. Local/private IPs are fine in module documentation scenarios
- All
print_*calls should start with a capital letter - Call
report_servicewhen a service can be reported - Call
report_vulnwhen a vulnerability can be reported - When creating a fake account / username use the
Fakergem (e.g.Faker::Internet.username) notRex::Text.rand_text_alphanumeric
- Use
create_process(executable, args: [], time_out: 15, opts: {})instead of the deprecatedcmd_execwith separate arguments - Use
Msf::OptionalSessionfor modules that work both with and without an existing session (e.g. local exploits that can also run standalone) - Use the module mixin APIs — don't reinvent the wheel
- When checking for a string in a response — will it always be in English?
- Ensure hardcoded strings being regex'ed will be consistent across multiple versions
checkmethods must only returnCheckCodevalues (e.g.CheckCode::Vulnerable,CheckCode::Safe) — never raise exceptions or callfail_with- When writing a
checkmethod, verify it does not produce false positives when run against unrelated software or services - Prefer using
Rex::Versionfor version checks - Use
fail_with(Failure::UnexpectedReply, '...')(and otherFailure::*constants) to bail out ofexploit/runmethods — don't useraiseor barereturnfor error conditions get_versionmethods should return a REX versionCheckCode::Vulnerableis only used when the vulnerability has been exploitedCheckCode::Appearsis only used when the application's version has been checked- Always provide a human-readable reason string when returning a CheckCode, e.g.
CheckCode::Safe("Target is running patched version #{version}")— never return a bare constant or empty call - Use specific regular expressions or
res.get_html_documentfor version extraction with CSS selectors. Don't use generic selectors likehref .*to grab the version — be more precise - Catch exceptions that may be raised and ensure a valid CheckCode is returned
- Research and determine a minimum version where the application is vulnerable; mark prior versions as safe
- Check helper methods used by both
#checkand#exploit(or#run) — ensure there is no condition (exception, return, etc.) where#checkcould return something other than a CheckCode - Prefer
prepend Msf::Exploit::Remote::AutoCheckover manually callingcheckinsideexploit— this lets the framework handle check-before-exploit automatically
When writing or modifying code in lib/:
- Use specific error classes (
Rex::RuntimeError,Rex::ConnectionError,ArgumentError,Rex::TimeoutError) — neverraise "bare string"which makes targeted rescue impossible - Use
rescue StandardError => eor a more specific class — never barerescue(it discards the exception object, making debugging impossible) and neverrescue Exception(it catchesSignalExceptionandSystemExit, hiding Ctrl-C and kill signals) - Propagate errors with context:
raise Rex::ConnectionError, "Failed to connect to #{host}: #{e.message}"
- Add YARD
@paramand@returntags to all public methods - Add
# frozen_string_literal: trueto new library files - Avoid
get_/set_prefixes for accessor-style methods in new code (Ruby convention: use the attribute name directly, e.g.def versionnotdef get_version) - Link to the specification or RFC when implementing binary/protocol parsers
- Write RSpec tests for any library changes — tests live in
spec/mirroring thelib/structure - Follow Better Specs conventions
- Keep PRs focused — small fixes are easier to review
- Any new hash cracking implementations require adding a test hash to
tools/dev/hash_cracker_validator.rband ensuring that passes without error
- Tests live in
spec/mirroring thelib/structure - Run a single spec file:
bundle exec rspec spec/path/to/spec.rb - Run a single example by line:
bundle exec rspec spec/path/to/spec.rb:42 - Run the full suite:
bundle exec rake spec(slow — prefer targeted runs during development) - Module functional tests live under
spec/modules/and test end-to-end behaviour - Always run specs relevant to your change before submitting
- Use the
RubySMBlibrary for SMB modules - Use
Rex::Stopwatch.elapsed_timeto track elapsed time - Use the
Rex::MIME::Messageclass for MIME messages instead of hardcoding XML - When creating random variable names prefer
Rex::RandomIdentifier::Generatorand specify the runtime language used. This avoids generating language keywords that would break the script - Use
Msf::Exploit::SQLiwhen exploiting SQL injection vulnerabilities
register_options([
OptString.new('TARGETURI', [true, 'Base path to the application', '/']),
OptInt.new('TIMEOUT', [true, 'Request timeout in seconds', 10]),
OptBool.new('SSL', [false, 'Use SSL/TLS', false])
])
register_advanced_options([
OptString.new('UserAgent', [false, 'Custom User-Agent header'])
])- Use
SCREAMING_SNAKE_CASEfor standard option names andCamelCasefor advanced option names - Access options via
datastore['OPTION_NAME']
- Use
print_status,print_good,print_error,print_warningfor console output - Use
vprint_*variants for verbose-only output (shown when user setsVERBOSE true) - Do not prefix messages with
#{peer},#{rhost}:#{rport}, or#{Rex::Socket.to_authority(rhost, rport)}— the framework auto-prepends host:port viaprint_prefixwhen theTcpmixin (orHttpClient) is included
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api', 'version')
)
fail_with(Failure::Unreachable, 'Target did not respond') unless res
fail_with(Failure::UnexpectedReply, "Unexpected status: #{res.code}") unless res.code == 200
json = res.get_json_document
fail_with(Failure::UnexpectedReply, 'Response is not valid JSON') if json.empty?
# For HTML parsing:
html = res.get_html_document
version = html.at_css('meta[name="version"]')&.[]('content')- Always use
res.get_json_document— neverJSON.parse(res.body) - Use
res.get_html_documentwith CSS selectors for HTML parsing - Check
resfor nil (target didn't respond) before accessing.codeor.body - Use
fail_with(Failure::*, 'reason')for error conditions inexploit/run
- Use
send_request_cgifor HTTP requests in modules - Use
connect/disconnectfor TCP socket operations - Use the
srvhostmethod to access the server host — don't usedatastore['SRVHOST']directly (enforced byLint/DatastoreSrvhostUsagecop)
These patterns exist in older code but should not be used in new modules or library code. When touching existing code that uses these patterns, prefer modernizing it:
| Legacy Pattern | Modern Replacement | Notes |
|---|---|---|
print_status("#{peer} - message") |
print_status("message") |
The framework auto-prepends host:port via print_prefix; also applies to #{rhost}:#{rport}, #{ip}:#{rport}, #{Rex::Socket.to_authority(...)} at message start. Enforced by Lint/RedundantPeerInPrint cop |
HttpFingerprint = { :pattern => [...] } |
Implement a check method + prepend AutoCheck |
HttpFingerprint is a passive fingerprinting mechanism that predates the check API |
cmd_exec("command #{user_input}") |
create_process("command", args: [user_input]) |
String interpolation in cmd_exec is a command injection risk; create_process separates executable from arguments by design |
cmd_exec(cmd, args_string, timeout) |
create_process(cmd, args: args_array, time_out: timeout) |
Enforced by Lint/DetectOutdatedCmdExecApi rubocop cop |
DefaultOptions => { 'PAYLOAD' => '...' } |
Remove — let the framework choose automatically | Only acceptable when the module genuinely only works with a single specific payload |
include Msf::Exploit::Remote::AutoCheck |
prepend Msf::Exploit::Remote::AutoCheck |
Include raises NotImplementedError; prepend is required |
Bare rescue in library code |
rescue StandardError => e |
Bare rescue discards the exception object; rescue Exception is worse — it catches signals/exits |
raise "error message" in library code |
raise Rex::RuntimeError, "message" |
Specific classes enable targeted error handling |
Manual check call inside exploit |
prepend AutoCheck + separate check method |
Let the framework handle check-before-exploit |
When updating an existing module, the lowest-effort improvement is adding AutoCheck:
# If the module already has a `def check` method, just add this line
# after the other includes:
prepend Msf::Exploit::Remote::AutoCheckThis single addition gives users the ability to verify vulnerability before exploitation, with automatic abort if the target is not vulnerable (overridable with set ForceExploit true).
- Work on a topic branch — don't commit directly to
master - Follow the 50/72 rule for Git commit messages (50 char subject, 72 char body wrap)
- Ensure
rubocopandmsftidypass on any changed files with no new offenses - Ensure
ruby tools/dev/msftidy_docs.rb <documentation_file>passes on any changed documentation markdown docs with no new offenses - Include console output (especially
msfconsoledemonstrations) in your pull request when the changes have observable effects - Include verification steps so reviewers can test your changes
- Reference associated issues in your pull request description (e.g.,
See #1234)
- Don't submit untested code — all code must be manually verified
- Don't include sensitive information (IPs, credentials, API keys, hashes of credentials) in code or docs
- Don't include more than one module per pull request
- Don't add new scripts to
scripts/— use post modules instead - Don't use
pack/unpackwith invalid directives (enforced by linter)