セキュリティブログ

OSMR受験記とmacOSにおけるApp Sandboxエスケープの脆弱性について

OSMR受験記とmacOSにおけるApp Sandboxエスケープの脆弱性について

更新日:2024.02.06

はじめに

オフェンシブセキュリティ部ペネトレーションテスト課の川田柾浩です。
普段はレッドチーム案件を中心にクラウドネイティブな環境やオンプレAD環境へのペネトレーションテスト、OSINTを含む外部からのペネトレーションテスト、その他ツール・マルウェア開発等を担当しています。

1年ちょっと程前にOSMRを受験させていただきました。その後、macOSの脆弱性を報告して2023年8月に完全に情報が公開されたのでOSMRの受験記と脆弱性発見までの流れをブログ記事にさせていただきます。

ブログの各章でお伝えしたい内容は下記の通りです。
● OSMR受験記
OSMRは私が受験を行なっていた際に日本国内での情報が不足していたため、コースの内容や試験の構成等を簡単に解説します。

● CVE-2023-27966
私が業務のために検証作業を行なっていた際に発見したCVE-2023-27966というApp Sandboxのエスケープが可能になっていた脆弱性について、調査時の作業内容をお伝えします。

OSMR受験記

OSMRとは

OSMR (OffSec macOS Researcher)とはmacOSにおけるExploit開発に重きを置いた資格で、付随するコースAdvanced macOS Control BypassesにおいてはmacOS自体の脆弱性やmacOS向けアプリの脆弱性を調査する手法が紹介されています。コースのシラバス等は以下の公式サイトから確認できます。
参考: EXP-312: Advanced macOS Control Bypasses

後に紹介するApp Sandboxからのエスケープについても触れられています。当時はOSMRを受験するためにLearn Oneへの加入($2,499)が必須でしたが、現在はOSMR単体でも受講できるようです。ただし、$1,649と高くはなるので個人で受験するハードルは高いかもしれません。

また、一部アセンブリやシェルコードを扱う内容もあるため、OSEDの前段階として受講するのも有効だと思います。

macOSのマルウェア開発や端末内の調査を担当させていただいていることもあり、OSMRを受験させていただく運びとなりました。

受講

受講は2022年8月4日から開始しました。
個人的に英語の教科書をすべて読むとなると気が重くなるので、基本的にはVideoを見ながら内容をなぞって勉強しました。macOSの検証環境を持っていたこともあり、可能な限りローカルの環境で検証を実施しました。macOSのバージョンに依存するような検証を行う際はSSHで接続しました。本来はVNCで接続することを想定されていますが、さすがに遅延が大きいのでおすすめできません。

OSMRには他コースのLabにあたるものは(少なくとも当時は)なく、ExcercisesとExtra Milesのみで試験に備える必要があります。Excercisesは教科書の内容にかなり沿った内容であるため、教科書をなぞり終えたらExtra Milesを中心に時間の許す限り学習を進めていくのが良いかと思います。

教科書をなぞり終えたタイミングで試験の予約可能な日程を確認したところ、2ヶ月ほど先の日程しか予約できなかったため、先に試験を予約しました。試験を待っていた2ヶ月間は可能な限りExtra Milesを進めました。

試験

試験は2022年10月22日から開始しました。
多くの試験と同様に48時間の作業と24時間のレポート作成で構成されています。作業時にはもちろんカメラによる監視とディスプレイの監視があります。問題は4問で30点の問題が2問、20点の問題が2問で、合格点は80点でした。

当時の試験難易度としてはOSEPやOSEDと比べるとかなり低く設定されていました。試験だけの難易度であればOSCPよりも簡単だったかもしれません。一度教科書の内容をなぞっておけば合格点は到達できるような問題でした。

開始して4時間ほどで3問を解き終えて合格点には到達しました。その後、最後の1問は解ききれませんでしたが、レポートを作成し、合格通知をいただきました。

合格通知

CVE-2023-27966

動機・目的

本調査はWordドキュメント形式のC2エージェントを開発するために実施しました。
macOSにおいてWordドキュメント形式のC2エージェントを使用する場合、App Sandboxを突破する必要があります。当然、容易にSandboxの突破はできないように設計されているため、その最良の方法を調査しました。

App Sandbox

App Sandboxでは、アプリの権限を攻撃者に乗っ取られた場合に備えてアプリのアクセスを制限する方法を提供します。特にApp Storeからアプリを配布する場合、Sandboxの設定が必須になります。各Entitlementについては公式ドキュメントをご参照ください。
参考: App Sandbox

例えば、App StoreからダウンロードしたSlack.appと公式サイトからダウンロードしたSlack.appではSandboxの設定を含むビルド時の設定が一部異なります。

# App Store (https://apps.apple.com/jp/app/slack-for-desktop/id803453959)
➜  ~ codesign -dvv --entitlements - /Applications/Slack.app
...
CodeDirectory v=20400 size=477 flags=0x0(none) hashes=4+7 location=embedded
...
[Dict]
    [Key] com.apple.security.app-sandbox
    [Value]
        [Bool] true
...

# https://slack.com/intl/ja-jp/downloads/mac
➜  ~ codesign -dvv --entitlements - /Applications/Slack.app
...
CodeDirectory v=20500 size=485 flags=0x12000(library-validation,runtime) hashes=4+7 location=embedded
...
[Dict]
...

Xcodeを用いて開発している場合、以下のようにApp Sandboxの設定が可能です。

App Sandbox設定

コンテナディレクトリ

Sandboxが適用されているアプリには専用のコンテナディレクトリ~/Library/Containers/<Bundle ID>が割り当てられます。Bundle IDとはアプリのビルド時に割り当てられる固有の識別子で、容易に確認することができます。

➜  ~ codesign -dvv --entitlements - /Applications/Microsoft\ Word.app
Executable=/Applications/Microsoft Word.app/Contents/MacOS/Microsoft Word
Identifier=com.microsoft.Word
...

Sandboxが適用されているアプリは各コンテナディレクトリ内のDataディレクトリを主に使用することになります。

➜  ~ ls -al ~/Library/Containers/com.microsoft.Word/Data
total 16
lrwxr-xr-x   1 victim  staff    31B Jan  9  2020 .CFUserTextEncoding -> ../../../../.CFUserTextEncoding
-rw-r--r--@  1 victim  staff   6.0K Mar 15 17:32 .DS_Store
lrwxr-xr-x   1 victim  staff    19B Jan  9  2020 Desktop -> ../../../../Desktop
drwx------   2 victim  staff    64B Nov 21  2020 Documents
lrwxr-xr-x   1 victim  staff    21B Jan  9  2020 Downloads -> ../../../../Downloads
drwx------  34 victim  staff   1.1K Feb  9  2023 Library
lrwxr-xr-x   1 victim  staff    18B Jan  9  2020 Movies -> ../../../../Movies
lrwxr-xr-x   1 victim  staff    17B Jan  9  2020 Music -> ../../../../Music
lrwxr-xr-x   1 victim  staff    20B Jan  9  2020 Pictures -> ../../../../Pictures
drwx------   2 victim  staff    64B Nov 21  2020 SystemData
drwx------   2 victim  staff    64B Nov 21  2020 tmp

このコンテナディレクトリは各アプリが比較的自由に使用できる一方で部分的に制約がかけられており、コンテナディレクトリ内部のバイナリやシェルスクリプトを実行することはできません。

➜  ~ /sbin/ping -c 1 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.055 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.055/0.055/0.055/0.000 ms
➜  ~ cp /sbin/ping ~/Library/Containers/com.microsoft.Word/Data/
➜  ~ ~/Library/Containers/com.microsoft.Word/Data/ping -c 1 127.0.0.1
[1]    27348 killed     ~/Library/Containers/com.microsoft.Word/Data/ping -c 1 127.0.0.1
➜  ~ open -a ~/Library/Containers/com.microsoft.Word/Data/ping --args -c 1 127.0.0.1
The application /Users/victim/Library/Containers/com.microsoft.Word/Data/ping cannot be opened for an unexpected reason, error=Error Domain=RBSRequestErrorDomain Code=5 "Launch failed." UserInfo={NSLocalizedFailureReason=Launch failed., NSUnderlyingError=0x6000007d4360 {Error Domain=NSPOSIXErrorDomain Code=162 "Unknown error: 162" UserInfo={NSLocalizedDescription=Launchd job spawn failed}}}

Microsoft Wordの例

MIcrosoft Word.appのSandbox設定をを確認してみると、以下のように特殊な設定が確認できます。

➜  ~ codesign -dvv --entitlements - /Applications/Microsoft\ Word.app
...
    [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")) )
...

1行目のルールでは、~$から始まるファイルの読み書きを可能にしており、Wordドキュメント編集時に必要なファイルを生成できるように追加されています。
2行目で~/Library/Application Scripts/~/Library/LaunchAgents/配下への書き込みを制限しています。これは過去の脆弱性対応時に追加されたもので、後程補足します。

Sandboxが適用されているMicrosoft Word.appからC2セッションを確立したとしても、/tmp/配下の列挙すらできないため、実用性がありません。これを実用的なレベルのアクセス権に持っていくためにSandboxからエスケープする必要があります。

/tmp/配下の列挙

Microsoft Word.appのSandboxエスケープにおいては~$から始まるファイルの読み書きが可能であるといったことを念頭におく必要があります。

また、Microsoft Word.appに適用されるSandbox環境下から検証を行うため、MythicおよびApfellを用いたC2サーバを用意し、以下のような簡単はマクロを用いてC2セッションを確立しました。

マクロの例

C2セッション確立

launchdとSandboxエスケープ

定義されたSandboxプロファイルは当然子プロセスにも適用されるため、Sandboxエスケープを行うためには現在のプロセスから完全に独立したプロセスを生成する必要があります。SIP (System Integrity Protection)による保護が手厚く、他プロセスにコードをインジェクトするといった方法は原則困難です。

そのため、Sandboxエスケープを試みる場合にlaunchdを活用することはほぼ必須と言えます。
launchdはシステムの初期化やサービス管理等を担当するフレームワークで、/sbin/launchdにはプロセスIDとして1が常に割り当てられます。

主に以下の2つの方法でlaunchdの子プロセスを生成することができます。
● /usr/bin/openコマンド
● LaunchAgentsやLaunchDaemons、Login Items等の永続化手法

openコマンドを活用した/sbin/launchdの子プロセス生成は、以下の通り簡単に確認できます。

➜  ~ open -a /sbin/ping --args -c 32 127.0.0.1
➜  ~ ps -ef
  UID   PID  PPID   C STIME   TTY           TIME CMD
  ...
  501 12024     1   0  6:35PM ??         0:00.01 /sbin/ping -c 32 127.0.0.1
  ...

これらの手法によるSandboxエスケープが可能であったmacOSの脆弱性を2つずつ紹介します。

/usr/bin/openによるSandboxエスケープ

● CVE-2021-30864 (Perception Point)
この脆弱性はopenコマンドでTerminal.appを起動する際、環境変数を設定することでHOMEディレクトリを改ざんし、攻撃者が配置した.zshenv等を実行させることによりSandboxエスケープを達成しました。Terminal.appはopenコマンドによりlaunchdから実行されるため、Sandboxの影響を受けることはありません。

➜  ~ open --env __OSINSTALL_ENVIROMENT --env HOME=~/Library/Containers/com.microsoft.Word/Data /System/Applications/Utilities/Terminal.app

現在はopenコマンドのenvオプション経由で環境変数HOMEの書き換えができないように修正されています。

Microsoft Word.appからTerminal.appの起動

環境変数の確認

参考: A Technical Analysis of CVE-2021-30864: Bypassing App Sandbox Restrictions

● CVE-2022-26706 (Microsoft)
openコマンドを用いてPythonスクリプトを実行することでSandboxエスケープを可能にした脆弱性です。Microsoft Word.appを含むSandboxが適用されたアプリによって生成されたファイルにはcom.apple.quarantine属性が付与されます。この場合、ShellスクリプトやPythonスクリプトとして実行することはできません。しかし、この制限をopenコマンドの—stdinオプションを用いて突破することに成功しました。

deploy@deploy ~ % ls -l test.py
-rwxr-xr-x@ 1 deploy  staff  77 Aug 31 22:07 test.py
deploy@deploy ~ % xattr test.py 
com.apple.quarantine
deploy@deploy ~ % ./test.py 
zsh: operation not permitted: ./test.py

この脆弱性は—stdinオプションに指定されたファイルにcom.apple.quarantine属性が付与されていた場合においても、openコマンドによるプロセス生成ができないように修正されました。

➜  Downloads xattr hello.py
com.apple.quarantine
➜  Downloads open --stdin='hello.py' -a Python
The application /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/Resources/Python.app cannot be opened for an unexpected reason, error=Error Domain=NSOSStatusErrorDomain Code=-10810 "kLSUnknownErr: Unexpected internal error" UserInfo={_LSFunction=_LSLaunchWithRunningboard, _LSLine=2735, NSUnderlyingError=0x60000232cb40 {Error Domain=RBSRequestErrorDomain Code=5 "Launch failed." UserInfo={NSLocalizedFailureReason=Launch failed., NSUnderlyingError=0x60000232cae0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Launchd job spawn failed}}}}}

参考: Uncovering a macOS App Sandbox escape vulnerability: A deep dive into CVE-2022-26706

永続化手法によるSandboxエスケープ

● CVE未採番 (MDSec)
これは先ほど確認したMicrosoft Word.appに適用されていたカスタムルールの2行目に関する脆弱性です。

[String] (deny file-write* (subpath (string-append (param "_HOME") "/Library/Application Scripts")) (subpath (string-append (param "_HOME") "/Library/LaunchAgents")) )

当時このルールは実装されていなかったため、Microsoft Word.appは~$から始まる名称のファイルであれば~/Library/LaunchAgentsディレクトリにplistファイルを生成することが可能でした。

~/Library/LaunchAgentsディレクトリにはplistファイルが格納されています。plistファイルはmacOSにおける設定ファイルのようなもので、XML形式またはバイナリ形式で保存されます。一例として、以下のようなplistファイルを保存することでユーザのログイン時に指定したコマンドを実行させることが可能です。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>LaunchAtLogin</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sleep</string>
        <string>300</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

実際にplistファイル格納後に再ログインを行うと、指定したsleepコマンドがlaunchdの子プロセスとして実行されていることが確認できます。

LaunchAgentsに登録したコマンドの実行

当該ディレクトリは一般ユーザの権限で書き込み可能なうえ、指定したコマンドの実行はlaunchdが担当するため、MIcrosoft Word.appに適用されるSandboxの影響を受けません。

もちろん、この脆弱性に対応するために先述のカスタムルールが追加されました。
参考: Escaping the Sandbox – Microsoft Office on MacOS

● CVE未採番 (Madhav Bhatt)
これは本調査当時も動作していたLogin Itemsを活用したSandboxエスケープです。任意のアプリのSandboxからエスケープできるものではありませんが、~$から始まるファイルを生成できるMicrosoft Word.appにおいては有効です。

前提として、Sandboxが適用されているアプリからでもLogin Itemsを追加することが可能です。Login Itemsにはアプリやファイルを追加することができ、ファイルを追加した場合はユーザのログイン時に対象アプリのデフォルトハンドラによってアプリが開かれます。例えば、zipファイルをLogin Itemsに追加した場合、Archive Utility.appによって対象ファイルが解凍されることになります。

この仕様を活用することで、Sandboxエスケープを達成できます。
まず、ユーザのホームディレクトリ配下に~$exploit.zipを作成し、Login Itemsに追加します。このzipファイルは.zshenvに解凍されます。~/.zshenvはデフォルトではmacOSに存在しないファイルであり、zsh起動時に指定されたOSコマンドが実行されます。既に存在する.zshrcとして解凍しようとすると.zshrc 2といったファイル名に解凍されるため、注意が必要です。
次に、Terminal.appをLogin Itemsに追加します。

Login Itemsの追加

追加されたLogin Items

するとユーザのログイン時に以下の手順によりTerminal.app上でOSコマンドが実行されます。

  1. Login Itemsに追加した~/~$exploit.zip~/.zshenvに解凍される
  2. Login Itemsに追加したTerminal.appが起動される
  3. Terminal.appで起動されるzshが~/.zshenvを読み込み、指定したOSコマンドが実行される

Sandboxのエスケープ

しかし、この方法には1点実際に悪用するにあたって難しい点があります。
手順1のzipファイルの解凍と手順2のTerminal.appの起動はユーザのログイン時に同時(順不同)に実行されるため、1度目のログイン時にはzipファイルが~/.zshenvに解凍される前にTerminal.appが起動してしまうケースがあります。この場合、2度目のログインを待つ必要があります。1度はlaunchctl bootout gui/$UIDといったコマンドによる強制ログアウトが可能であることを踏まえると、この2度目のログインを待つ必要があるというのは大きなデメリットであると言えます。

コマンドの実行に失敗

そのため、この手法をベースにより良いSandboxエスケープの手法を調査しました。

terminalファイル

terminalファイルはXML形式のファイルで、macOSにおいてはターミナル設定ファイルといった名称で扱われています。このファイルのデフォルトハンドラはTerminal.appになっており、以下のようにファイル内で指定したOSコマンドをTerminal.appから実行させることが可能です。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>CommandString</key>
        <string>sleep 30</string>
        <key>ProfileCurrentVersion</key>
        <real>2.0600000000000001</real>
        <key>RunCommandAsShell</key>
        <false />
        <key>name</key>
        <string>exploit</string>
        <key>type</key>
        <string>Window Settings</string>
    </dict>
</plist>

terminalファイルのデフォルトハンドラ

terminalファイルの実行

/usr/bin/open + .terminal

~/Library/Containers/com.microsoft.Word/Data/配下にターミナル設定ファイルtouch.terminalを配置し、openコマンドを実行したところ、以下の通り実行に失敗しました。

openコマンドの実行

terminalファイルの実行に失敗

Microsoftによって発見されたCVE-2022-26706の解説時にも触れたように、Sandboxが適用されているMicrosoft Word.appによってシステム内に作成されたファイルにはcom.apple.quarantine属性が付与され、制約が厳しく設定されます。当該属性の削除はSandboxをエスケープしない限り原則困難ですが、検証のためにTerminal.appから当該属性を削除し、改めてopenコマンドを実行しました。

deploy@deploy ~ % xattr –d com.apple.quarantine ~/Library/Containers/com.microsoft.Word/Data/touch.terminal

異なるエラーメッセージにより、実行に失敗しました。エラーメッセージやその挙動からSandboxが適用されたプロセスからterminalファイルを開くのは困難であると考えられます。コンテナディレクトリに適用される制約である可能性もあったため、ホームディレクトリに~/~$touch.terminalを改めて作成しましたが、結果は同様でした。

terminalファイルの実行に失敗

com.apple.quarantine属性の制約とSandboxが適用されたプロセスの制約を2つとも突破する必要があるため、これ以上の調査は実施しませんでした。

Login Items + .terminal

次にterminalファイルをLogin Itemsに追加することで、terminalファイルの実行を試みました。
今回はApfellのC2セッションを使用していたため、JavaScript for Automationを用いてLogin Itemsを追加しました。最も簡単な方法としてはMadhav Bhatt氏の記事に記載の通り、Apfellのpersist_loginitem_allusers関数を利用する方法が挙げられます。

その後、launchctlコマンドを使ってユーザを強制的にログアウトさせると、ログイン時にterminalファイルが開かれ、指定したOSコマンドがTerminal.app上で実行されたことが確認できました。Terminal.appはMicrosoft Word.appに適用されるサンドボックスの影響を受けないため、この時点でApp Sandboxからのエスケープに成功しました。

launchctlコマンドによる強制ログアウト

Sandboxエスケープ

タイムライン

日時 備考
2022/10/01  脆弱性報告
2023/03/27 セキュリティパッチを含むmacOS Ventura 13.3リリース
2023/04/14 報奨金額通知
2023/05/01 脆弱性情報公開
2023/05/24 報奨金振り込み
2023/08/01 脆弱性情報更新

参考: About the security content of macOS Ventura 13.3

セキュリティ診断のことなら
お気軽にご相談ください
セキュリティ診断で発見された脆弱性と、具体的な内容・再現方法・リスク・対策方法を報告したレポートのサンプルをご覧いただけます。
経験豊富なエンジニアが
セキュリティの不安を解消します

Webサービスやアプリにおけるセキュリティ上の問題点を解消し、
収益の最大化を実現する相談役としてぜひお気軽にご連絡ください。

疑問点やお見積もり依頼はこちらから

お見積もり・お問い合わせ

セキュリティ診断サービスについてのご紹介

資料ダウンロード