Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions lib/msf/core/post/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -879,12 +879,15 @@ def _read_file_meterpreter(file_name)
fd = session.fs.file.new(file_name, 'rb')

data = ''.b
data << fd.read
data << fd.read until fd.eof?
loop do
chunk = fd.read
break if chunk.nil? || chunk.empty?

data << chunk
end

data
rescue EOFError
# Sometimes fd isn't marked EOF in time?
data
rescue ::Rex::Post::Meterpreter::RequestError => e
print_error("Failed to open file: #{file_name}: #{e}")
Expand Down
10 changes: 7 additions & 3 deletions spec/acceptance/meterpreter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def initialize(path)
end

console.sendline payload.handler_command(default_module_datastore: default_module_datastore)
console.recvuntil(/Started reverse TCP handler[^\n]*\n/)
console.recvuntil(/Started (?:reverse TCP|HTTPS? reverse) handler[^\n]*\n/)
payload_process = executed_payload
session_id = nil

Expand Down Expand Up @@ -332,8 +332,10 @@ def get_file_attachment_contents(path)

meterpreter_config[:module_tests].each do |module_test|
describe module_test[:name].to_s, focus: module_test[:focus] do
c2_profile = payload_config.dig(:datastore, :module, :MALLEABLEC2)
c2_suffix = c2_profile ? " (malleable c2: #{File.basename(c2_profile)})" : ''
it(
"#{Acceptance::Session.current_platform}/#{meterpreter_runtime_name} meterpreter successfully opens a session for the #{payload_config[:name].inspect} payload and passes the #{module_test[:name].inspect} tests",
"#{Acceptance::Session.current_platform}/#{meterpreter_runtime_name} meterpreter successfully opens a session for the #{payload_config[:name].inspect} payload and passes the #{module_test[:name].inspect} tests#{c2_suffix}",
if: (
# Run if ENV['SESSION'] = 'java php' etc
Acceptance::Session.run_meterpreter?(meterpreter_config) &&
Expand All @@ -343,7 +345,9 @@ def get_file_attachment_contents(path)
Acceptance::Session.supported_platform?(payload_config) &&
Acceptance::Session.supported_platform?(module_test) &&
# Skip tests that are explicitly skipped, or won't pass in the current environment
!Acceptance::Session.skipped_module_test?(module_test, allure_test_environment)
!Acceptance::Session.skipped_module_test?(module_test, allure_test_environment) &&
# Skip module tests that the payload has explicitly opted out of
!Array(payload_config[:skip_module_tests]).include?(module_test[:name])
),
# test metadata - will appear in allure report
module_test: module_test[:name]
Expand Down
54 changes: 54 additions & 0 deletions spec/file_fixtures/malleable_c2/base64_transforms.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# base64_transforms.profile
# Purpose: exercises base64 encoding on server->client GET responses and
# prepend/append transforms on client->server POST bodies.
# Used by acceptance tests to confirm wrap/unwrap encoding round-trips correctly.

set useragent "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";

http-get {
set uri "/updates/check";

client {
header "Accept" "application/json";

metadata {
parameter "v";
}
}

server {
header "Content-Type" "application/octet-stream";

output {
base64;
prepend "START_";
append "_END";
print;
}
}
}

http-post {
set uri "/updates/report";

client {
header "Content-Type" "application/octet-stream";

id {
parameter "uid";
}

output {
base64;
print;
}
}

server {
header "Content-Type" "text/plain";

output {
print;
}
}
}
52 changes: 52 additions & 0 deletions spec/file_fixtures/malleable_c2/minimal_uris_headers.profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# minimal_uris_headers.profile
# Purpose: exercises custom URI routing + response headers; no encoding directives.
# Used by acceptance tests to confirm the handler registers profile URIs and that
# the payload reaches a session over those URIs.

set useragent "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)";

http-get {
set uri "/jquery-3.3.1.min.js";

client {
header "Accept" "text/javascript, application/javascript";
header "Referer" "https://www.example.com/";

metadata {
parameter "callback";
}
}

server {
header "Content-Type" "application/javascript; charset=utf-8";
header "Cache-Control" "max-age=604800";

output {
print;
}
}
}

http-post {
set uri "/jquery-3.3.1.min.js/save";

client {
header "Content-Type" "application/octet-stream";

id {
parameter "id";
}

output {
print;
}
}

server {
header "Content-Type" "text/plain; charset=utf-8";

output {
print;
}
}
}
45 changes: 45 additions & 0 deletions spec/lib/msf/core/payload/malleable_c2_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Msf::Payload::MalleableC2 do
let(:fixture_path) { File.join(Msf::Config.install_root, 'spec', 'file_fixtures', 'malleable_c2') }

describe Msf::Payload::MalleableC2::Parser do
subject(:parser) { described_class.new }

describe '#parse' do
context 'with minimal_uris_headers.profile' do
it 'returns a ParsedProfile without raising' do
path = File.join(fixture_path, 'minimal_uris_headers.profile')
result = parser.parse(path)
expect(result).to be_a(Msf::Payload::MalleableC2::ParsedProfile)
end
end

context 'with base64_transforms.profile' do
it 'returns a ParsedProfile without raising' do
path = File.join(fixture_path, 'base64_transforms.profile')
result = parser.parse(path)
expect(result).to be_a(Msf::Payload::MalleableC2::ParsedProfile)
end
end

context 'with a non-existent path' do
it 'raises an exception' do
expect {
parser.parse('/nonexistent/path.profile')
}.to raise_error(Errno::ENOENT)
end
end
end

describe 'ParsedProfile#uris' do
it 'returns the URIs declared in minimal_uris_headers.profile' do
path = File.join(fixture_path, 'minimal_uris_headers.profile')
profile = parser.parse(path)
expect(profile.uris).to contain_exactly('/jquery-3.3.1.min.js', '/jquery-3.3.1.min.js/save')
end
end
end
end
68 changes: 68 additions & 0 deletions spec/support/acceptance/session/java.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require_relative './shared'

module Acceptance::Session::Java
MALLEABLE_C2_FIXTURE_PATH = File.expand_path('../../../../../spec/file_fixtures/malleable_c2', __FILE__)

JAVA_METERPRETER = {
payloads: [
{
Expand All @@ -17,6 +19,72 @@ module Acceptance::Session::Java
spawn: 0
}
}
},
{
name: "java/meterpreter_reverse_http",
skip_module_tests: ['post/test/socket_channels'],
extension: ".jar",
platforms: [:osx, :linux, :windows],
execute_cmd: ["java", "-jar", "${payload_path}"],
generate_options: {
'-f': "jar"
},
datastore: {
global: {},
module: {
MALLEABLEC2: File.join(MALLEABLE_C2_FIXTURE_PATH, 'minimal_uris_headers.profile')
}
}
},
{
name: "java/meterpreter_reverse_http",
skip_module_tests: ['post/test/socket_channels'],
extension: ".jar",
platforms: [:osx, :linux, :windows],
execute_cmd: ["java", "-jar", "${payload_path}"],
generate_options: {
'-f': "jar"
},
datastore: {
global: {},
module: {
MALLEABLEC2: File.join(MALLEABLE_C2_FIXTURE_PATH, 'base64_transforms.profile')
}
}
},
{
name: "java/meterpreter_reverse_https",
skip_module_tests: ['post/test/socket_channels'],
extension: ".jar",
# TODO: HTTPS payloads broken on Windows environments
platforms: [:osx, :linux],
execute_cmd: ["java", "-jar", "${payload_path}"],
generate_options: {
'-f': "jar"
},
datastore: {
global: {},
module: {
MALLEABLEC2: File.join(MALLEABLE_C2_FIXTURE_PATH, 'minimal_uris_headers.profile')
}
}
},
{
name: "java/meterpreter_reverse_https",
skip_module_tests: ['post/test/socket_channels'],
extension: ".jar",
# TODO: HTTPS payloads broken on Windows environments
platforms: [:osx, :linux],
execute_cmd: ["java", "-jar", "${payload_path}"],
generate_options: {
'-f': "jar"
},
datastore: {
global: {},
module: {
MALLEABLEC2: File.join(MALLEABLE_C2_FIXTURE_PATH, 'base64_transforms.profile')
}
}
}
],
module_tests: [
Expand Down
Loading
Loading