
Entrada de blog que explora el App Sandbox de macOS, los entitlements mediante codesign y las técnicas de escape del sandbox utilizando launchd, LaunchAgents y los atributos de cuarentena.
Continuando con mi serie de publicaciones sobre mi transición a macOS, me gustaría hablar un poco sobre el sandbox de Apps de macOS.
Se recomienda encarecidamente leer primero la publicación macOS App structure: asumiré que el lector sabe la diferencia entre Apps, procesos (tareas), conoce un poco de launchd y su relación con el lanzamiento de Apps.
La primera vez que aprendí sobre el sandbox de macOS, intenté ingenuamente crear una macro de Word maliciosa.
Este (sigue siendo) un vector de entrada muy común en el ecosistema Windows, así que quería ver si podía simplemente lanzar procesos y, en general, causar estragos.
Bueno, las cosas no son tan fáciles en macOS: pude ejecutar procesos, por ejemplo, pero parecía que no podían hacer mucho.
Soltar archivos siempre me daba el críptico error Operation not permitted — ¿qué está pasando?
Empecé a leer un poco sobre macOS y Word y di con esta excelente publicación de Adam Chester (trabaja en MDSec). Recomiendo encarecidamente leer la publicación, pero resumiré los hallazgos aquí:
macOS solía tener una utilidad funcional llamada sandbox-exec que ejecutaba comandos en un sandbox. Aunque está obsoleta, podía revelar bastante. En su página de manual se ve que recibe un profile, por lo que podemos concluir que las reglas del sandbox se mantienen en perfiles. Esos perfiles pueden presentarse de varias formas: archivos, nombres predefinidos o incluso cadenas literales.
Las páginas de manual también indican que los desarrolladores deberían usar la función App Sandbox. Leyendo más al respecto, entendí que las reglas del sandbox están integradas en el binario, en nuestro caso, ubicadas en /Application/Microsoft Word.app/Contents/MacOS/Microsoft Word (si te resulta ajeno, consulta mi publicación sobre la estructura de las Apps en macOS).
Si bien puedes extraerlas fácilmente a mano, es mejor usar una herramienta: codesign:
```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
...
Hay mucho que desglosar aquí, así que tomemos algunas notas de alto nivel:
-dv, que significa display y verbose. Luego, --entitlements presenta los entitlements asociados con la App o el binario (sí, codesign puede trabajar con ambos). Profundizaremos en los entitlements en una publicación diferente, pero por ahora digamos que reflejan las capacidades de la App, y uno de ellos indica que la App está en sandbox (com.apple.security.app-sandbox tiene un valor booleano True).plists (de nuevo, en mi [publicación sobre la estructura de las Apps en macOS]) sospecharán que el diccionario clave-valor es una representación de alguna lista de propiedades, y estarán en lo cierto.com.apple.security.temporary-exception.files.absolute-path.read-only menciona un array de rutas absolutas desde las que la App puede leer.com.apple.security.temporary-exception.sbpl también está aquí: sirve para crear esos notorios archivos temporales ~$whatever.docx que tanto le gustan a Word.¡Observa lo poderosas que son esas reglas del sandbox!
En la publicación de MDSec de 2018 que mencioné antes, la parte deny file-write* bajo com.apple.security.temporary-exception.sbpl no existía, lo que permitía a los macros crear archivos con contenido arbitrario, como /Library/LaunchAgents/~$evil.plist. ¿Por qué eso evade el sandbox?
Los LaunchAgents y LaunchDaemons son un mecanismo de persistencia (legítimo) muy conocido en macOS. Ya los he mencionado antes, pero puedes pensar en ellos como Servicios (si vienes del mundo Windows): los LaunchDaemons se inician cuando el sistema operativo arranca (por lo tanto, viven fuera de la sesión del usuario), mientras que los LaunchAgents se inician cuando un usuario inicia sesión.
Curiosamente, ambos se describen en simples archivos plist. Aquí hay un ejemplo de mi actualizador de OneDrive:
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
}
Esos LaunchAgents y LaunchDaemons son lanzados por launchd (¿recuerdas ese proceso?) y, por lo tanto, se escapan del sandbox, ya que launchd no sabía si el plist fue soltado por un proceso en sandbox o no (e incluso si lo supiera, ¿cómo sabría qué reglas de sandbox aplicar?).