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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
macos_sandbox — macOS 앱 샌드박스, codesign을 통한 entitlements, 그리고 launchd, LaunchAgents 및 quarantine 속성을 사용한 샌드박스 탈출 기법을 탐구하는 블로그 게시물. | Kitploit
도구/GitHubGitHub/yo-yo-yo-jbo/macos_sandbox
Persistence MechanismsVulnerability AnalysisExploitationPost-ExploitationLearning & EducationRed Teaming
GitHubyo-yo-yo-jbo/macos_sandbox

macos_sandbox

macOS 앱 샌드박스, codesign을 통한 entitlements, 그리고 launchd, LaunchAgents 및 quarantine 속성을 사용한 샌드박스 탈출 기법을 탐구하는 블로그 게시물.

저장소 보기
3221개월 전Kitploit 검토 완료

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

macOS 소개 - App 샌드박스

macOS로 전환하는 블로그포스트 시리즈를 계속하면서, macOS App 샌드박스에 대해 조금 이야기해보려고 합니다.
macOS App 구조 블로그포스트를 먼저 읽어볼 것을 강력히 권장합니다 - 독자가 App과 프로세스(태스크)의 차이를 알고, launchd와 App 실행과의 관계에 대해 조금 알고 있다고 가정하겠습니다.

예제로 보는 샌드박스

macOS 샌드박스에 대해 처음 배웠을 때, 저는 순진하게도 악성 Word 매크로를 만들어보려고 했습니다.
이것은 (여전히) Windows 생태계에서 매우 흔한 진입 벡터이므로, 프로세스를 실행하고 전반적으로 아수라장을 만들 수 있는지 확인하고 싶었습니다.
음, macOS에서는 상황이 그렇게 쉽지 않았습니다 - 예를 들어 프로세스를 실행할 수는 있었지만, 그들이 별로 할 수 있는 것이 없는 것처럼 보였습니다.
파일을 드롭하려고 하면 항상 난해한 Operation not permitted 오류가 발생했습니다 - 도대체 무슨 일이 벌어지고 있는 걸까요?
macOS와 Word에 대해 조금 알아보기 시작했고, Adam Chester(MDSec에서 근무)의 훌륭한 블로그포스트를 발견했습니다. 이 블로그포스트를 읽을 것을 강력히 권장하지만, 여기서는 그 발견 사항을 요약하겠습니다:

  • App은 App 샌드박스라는 기술을 사용할 수 있습니다.
  • 일단 설정되면, OS는 App에 구성 가능한 규칙을 적용합니다. 예를 들어 생성할 수 있는 파일 이름, 네트워크 기능 사용 여부 등입니다.

macOS에는 한때 샌드박스에서 명령을 실행하는 sandbox-exec라는 제대로 작동하는 유틸리티가 있었습니다. 비록 더 이상 사용되지 않지만, 상당히 많은 것을 드러낼 수 있었습니다. 매뉴얼 페이지를 보면 profile을 받는다는 것을 알 수 있는데, 이를 통해 샌드박스 규칙이 프로파일로 유지된다는 결론을 내릴 수 있습니다. 이러한 프로파일은 파일, 사전 정의된 이름, 또는 심지어 리터럴 문자열 등 다양한 형태로 제공될 수 있습니다.
매뉴얼 페이지는 또한 개발자가 App 샌드박스 기능을 사용해야 한다고 명시합니다. 더 자세히 읽어보니 샌드박스 규칙이 바이너리에 내장(baked-in)되어 있다는 것을 이해했습니다. 우리의 경우 /Application/Microsoft Word.app/Contents/MacOS/Microsoft Word에 있습니다 (이것이 낯설게 느껴진다면, 제 macOS App 구조 블로그포스트를 확인해보세요).
수동으로 쉽게 추출할 수 있지만, 도구를 사용하는 것이 가장 좋습니다: codesign:

root@kitploit:~
```jbo@McJbo ~ % codesign -dv --entitlements - /Applications/Microsoft\ Word.app/Contents/MacOS/Microsoft\ Word
Executable=/Applications/Microsoft Word.app/Contents/MacOS/Microsoft Word
Identifier=com.microsoft.Word
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=351454 flags=0x10000(runtime) hashes=10972+7 location=embedded
Signature size=8980
Timestamp=Apr 10, 2023 at 8:09:50 AM
Info.plist entries=52
TeamIdentifier=UBF8T346G9
Runtime Version=13.1.0
Sealed Resources version=2 rules=13 files=28766
Internal requirements count=1 size=180
[Dict]
	[Key] com.apple.application-identifier
	[Value]
		[String] UBF8T346G9.com.microsoft.Word
	[Key] com.apple.developer.aps-environment
	[Value]
		[String] production
	[Key] com.apple.developer.team-identifier
	[Value]
		[String] UBF8T346G9
	[Key] com.apple.security.app-sandbox
	[Value]
		[Bool] true
    
...

	[Key] com.apple.security.temporary-exception.files.absolute-path.read-only
	[Value]
		[Array]
			[String] /Library/Preferences/com.microsoft.office.licensingV2.plist
			[String] /Library/Application Support/Microsoft/
      
...

	[Key] com.apple.security.temporary-exception.sbpl
	[Value]
		[Array]
			[String] (allow file-read* file-write* (require-all (vnode-type REGULAR-FILE) (regex #"(^|/)~\$[^/]+$")) )
			[String] (deny file-write* (subpath (string-append (param "_HOME") "/Library/Application Scripts")) (subpath (string-append (param "_HOME") "/Library/LaunchAgents")) )
	[Key] com.apple.security.temporary-exception.shared-preference.read-only
	[Value]
		[Array]
			[String] com.ThomsonResearchSoft.EndNote
			
...

이것은 풀어내야 할 것이 많으므로, 몇 가지 높은 수준의 메모를 해보겠습니다:

  • 먼저, 명령줄에서 display와 verbose를 의미하는 -dv 플래그를 사용했습니다. 그런 다음, --entitlements는 App 또는 바이너리와 연관된 *엔타이틀먼트(entitlements)*를 표시합니다 (네, codesign은 둘 다에 대해 작동할 수 있습니다). 엔타이틀먼트에 대해서는 다른 블로그포스트에서 자세히 다루겠지만, 지금은 이들이 App의 기능을 반영하며, 그중 하나가 App이 샌드박스 처리되었음을 나타낸다고 말해두겠습니다 (com.apple.security.app-sandbox가 Boolean 값 True를 가집니다).
  • 출력의 처음 몇 줄은 바이너리, 해시 및 기타 많은 흥미로운 것들에 대한 일반 정보입니다. 이 블로그포스트의 목적에는 범위를 벗어나지만, 꽤 흥미롭습니다!
  • 다음으로 큰 딕셔너리가 나옵니다. 제가 plist에 대해 열변을 토했던 것을 기억하는 분들은 (다시 말하지만, 제 [macOS App 구조 블로그포스트]에서) 키-값 딕셔너리가 어떤 속성 목록(property list)의 표현일 것이라고 의심할 수 있는데, 그 추측이 맞습니다.
  • 샌드박스 규칙은 딕셔너리 키 중 일부에 명시되어 있습니다. 예를 들어, com.apple.security.temporary-exception.files.absolute-path.read-only는 App이 읽을 수 있는 절대 경로의 배열을 언급합니다.
  • com.apple.security.temporary-exception.sbpl 아래에 있는 그 (악)명성 높은 정규 표현식도 여기에 있습니다 - Word가 그토록 좋아하는 그 악명 높은 ~$whatever.docx 임시 파일을 생성하기 위한 것입니다.

launchd를 이용한 샌드박스 탈출

앞서 언급한 2018년 MDSec 블로그포스트에서는 com.apple.security.temporary-exception.sbpl 아래의 deny file-write* 부분이 존재하지 않아서, 매크로가 /Library/LaunchAgents/~$evil.plist와 같은 임의의 내용을 가진 파일을 생성할 수 있었습니다. 이것이 왜 샌드박스를 탈출하는 것일까요?
LaunchAgents와 LaunchDaemons는 macOS에서 잘 알려진 (합법적인) 지속성 메커니즘입니다. 이전에도 언급했지만, (Windows 세계에서 오셨다면) 서비스(Services)로 생각하면 됩니다 - LaunchDaemons는 OS가 부팅될 때 시작되고 (따라서 사용자 세션 밖에 존재하며), LaunchAgents는 사용자가 로그인할 때 시작됩니다.
흥미롭게도, 둘 다 간단한 plist 파일로 설명됩니다. 제 OneDrive 업데이터의 예시는 다음과 같습니다:

root@kitploit:~
jbo@McJbo ~ % plutil -p /Library/LaunchAgents/com.microsoft.OneDriveStandaloneUpdater.plist
{
  "Label" => "com.microsoft.OneDriveStandaloneUpdater"
  "Program" => "/Applications/OneDrive.app/Contents/StandaloneUpdater.app/Contents/MacOS/OneDriveStandaloneUpdater"
  "ProgramArguments" => [
  ]
  "RunAtLoad" => 1
  "StartInterval" => 86400
}

이러한 LaunchAgents와 LaunchDaemons는 launchd (그 프로세스를 기억하시나요?)에 의해 실행되며, 따라서 샌드박스를 탈출합니다. launchd는 plist가 샌드박스 처리된 프로세스에서 드롭되었는지 여부를 알지 못하기 때문입니다 (설령 안다 해도 - 어떤 샌드박스 규칙을 적용해야 하는지 어떻게 알겠습니까?).

launchd를 사용하여 macOS 샌드박스를 탈출하는 이 개념은 광범위하게 사용되었으며, 실제로 저도 과거에 이를 사용한 적이 있습니다.
몇 번의 클릭을 아껴드리자면, 아이디어는 다음과 같습니다:

  • 기억하시겠지만, launchd는 macOS App을 실행합니다. 이러한 App은 더블 클릭이나 다른 방법으로 실행될 수 있습니다 - 예를 들어, zip 파일을 클릭하면 zip 파일과 연결되어 있으므로 Archive Utility가 사용됩니다.
  • launchd를 통해 App을 실행하는 또 다른 방법은 open 명령을 사용하는 것입니다.
  • open 명령은 기능이 풍부합니다 - App 선택, 열 파일 이름 선택, 또는 전체 명령줄 인수 제공과 같은 흥미로운 기능 중 일부를 사용할 수 있습니다.
  • 저는 특히 내장된 Python App (새로운 macOS 기본 설치에는 더 이상 존재하지 않음)을 사용하여 stdin 인수로 Python을 실행했는데, 이는 본질적으로 제가 드롭한 파일 (Word의 제약으로 인해 ~$evil.py였던 파일)에서 표준 입력을 리디렉션합니다.
  • 우리의 경우, launchd는 샌드박스 처리되지 않은 Python App 인스턴스를 실행했고, 이 인스턴스는 임의의 Python 명령을 담은 ~$evil.py에서 읽기 시작하여 본질적으로 샌드박스를 탈출했습니다.

다른 공개 자료에서도 유사한 아이디어가 있었습니다 (여기에 좋은 예가 있습니다) 하지만 아이디어는 동일합니다. 눈에 보이는 곳에 더 많은 것이 있다고 확신합니다!
한 가지 특별히 언급할 만한 것은 Wojciech Regula의 훌륭한 블로그포스트입니다 - 이번에는 Terminal App과 환경 변수 조작에 초점을 맞추고 있습니다. 한번 읽어보시길 권합니다!

수정 사항과 격리 xattr

MDSec이 발견한 한 가지 문제는 Office에 특화된 것이었고, 더 엄격한 규칙으로 수정되었습니다.
LaunchServices (이것은 launchd로 App을 실행하기 위한 프레임워크 이름입니다)를 남용하는 것들은 더 일반적이었고, 따라서 Apple이 이를 수정해야 했습니다.
제가 주목한 것 중 하나는 Word가 드롭한 파일이 이제 com.apple.quarantine 확장 속성으로 생성된다는 것입니다. 네, 제 Gatekeeper 소개 블로그포스트에서 언급한 바로 그 속성입니다.
알고 보니, 이 격리 속성은 특정 공격에 대한 일종의 강화 조치입니다 - 예를 들어, Terminal App은 해당 속성으로 생성된 셸 스크립트 실행을 거부했습니다. 그건 그렇고, 이것이 제가 Python에 대해 --stdin 옵션을 사용해야 했던 이유입니다.

추가 수정 사항

Gergely Kalman이 지적했듯이, Apple은 이러한 종류의 익스플로잇을 강화하기 위해 open 바이너리에 추가 검사를 추가했습니다. 호출 프로세스가 샌드박스 처리된 경우 --stdin, --args 및 기타 명령줄 플래그가 무시되는 것으로 보입니다. 그러나 open은 단순히 IPC로 LaunchServices (launchd에서)를 호출하며, 편리하게도 이를 위한 API가 있습니다, 예: LSOpenURLsWithRole.
저는 LaunchServices 자체도 강화되었는지 조사하지 않았습니다 - 만약 그렇지 않다면, 유사한 샌드박스 탈출이 쉽게 달성될 수 있다고 믿습니다.

요약

우리는 또 다른 macOS 기술인 샌드박스에 대해 간략히 논의했습니다. 그것이 얼마나 강력하고 구성 가능한지, 그리고 어떻게 깨질 수 있는지 보았습니다.
또한 몇 가지를 연결해보았습니다 - App이 샌드박스 규칙과 함께 작동하는 방식, launchd가 App을 실행하는 것이 단순히 프로세스 트리 이상을 깨뜨리는 방식, 그리고 plist 파일이 선한 목적에도 악한 목적에도 사용될 수 있는 방식 - 이번에는 지속성(LaunchAgents와 LaunchDaemons)과 관련해서 말입니다.
운 좋게도, Gatekeeper 소개 블로그포스트의 com.apple.quarantine 확장 속성까지 연결하여, 샌드박스 탈출에 대한 추가 강화로 어떻게 사용될 수 있는지 설명했습니다. 나쁘지 않네요!
다음 몇 개의 블로그포스트에서는 macOS의 더 많은 보안 메커니즘을 탐구하고, 이를 깨뜨리는 전략에 대해 이야기할 수도 있습니다.

계속 지켜봐 주세요!

Jonathan Bar Or (https://jonathanbaror.com)

도구 다운로드
  • 물론, 새로 생성된 자식 프로세스가 샌드박스 규칙을 상속받는 것은 말할 필요도 없습니다. 그렇지 않다면 별 의미가 없으니까요. 샌드박스 규칙이 얼마나 강력한지 주목하세요!