
Post de blog explorando o App Sandbox do macOS, entitlements via codesign e técnicas de escape de sandbox usando launchd, LaunchAgents e atributos de quarentena.
Dando continuidade à minha série de blogposts sobre a transição para o macOS, gostaria de discutir um pouco sobre a sandbox de Apps do macOS.
É altamente recomendável ler primeiro o blogpost sobre a estrutura de Apps no macOS - vou assumir que o leitor sabe a diferença entre Apps, processos (tarefas), conhece um pouco sobre launchd e sua relação com a inicialização de Apps.
A primeira vez que aprendi sobre a sandbox do macOS, ingênuo que era, tentei criar um Macro do Word malicioso.
Este (ainda) é um vetor de entrada muito comum no ecossistema Windows, então queria ver se eu conseguiria simplesmente iniciar processos e causar o caos em geral.
Bem, as coisas não são tão fáceis no macOS - eu consegui executar processos, por exemplo, mas parecia que eles não conseguiam fazer muita coisa.
Soltar arquivos sempre me dava o enigmático erro Operation not permitted - o que está acontecendo?
Comecei a ler um pouco sobre macOS e Word e encontrei este excelente blogpost de Adam Chester (trabalha na MDSec). Recomendo fortemente a leitura do blogpost, mas vou resumir as descobertas aqui:
O macOS costumava ter um utilitário funcional chamado sandbox-exec que executaria comandos em uma sandbox. Embora obsoleto, ele podia esclarecer bastante coisa. Você vê na página de manual que ele recebe um profile, então podemos concluir que as regras da sandbox são mantidas em perfis. Esses perfis podem vir em várias formas - arquivos, nomes pré-definidos ou até mesmo strings literais.
As páginas de manual também afirmam que os desenvolvedores deveriam usar o recurso App Sandbox. Lendo mais sobre isso, entendi que as regras da sandbox estão embutidas no binário, no nosso caso, vivendo em /Application/Microsoft Word.app/Contents/MacOS/Microsoft Word (se isso parece estranho para você, dê uma olhada no meu blogpost sobre a estrutura de Apps no macOS).
Embora você possa extraí-las facilmente manualmente, é melhor usar uma ferramenta: 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
...
Há muita coisa para processar aqui, então vamos anotar alguns pontos de alto nível:
-dv, que significa display e verbose. Depois, --entitlements apresenta as entitlements associadas ao App ou binário (sim, o codesign pode funcionar em ambos). Vamos mergulhar nas entitlements em um blogpost diferente, mas por enquanto vamos dizer que elas refletem as capacidades do App, e uma delas afirma que o App está em sandbox (com.apple.security.app-sandbox tem um valor Booleano True).plists (novamente, no meu [blogpost sobre estrutura de Apps no macOS]) podem suspeitar que o dicionário chave-valor é uma representação de alguma property list, e eles estarão certos.com.apple.security.temporary-exception.files.absolute-path.read-only menciona um array de caminhos absolutos dos quais o App tem permissão de leitura.com.apple.security.temporary-exception.sbpl também está aqui - é para criar aqueles notórios arquivos temporários que o Word tanto gosta.No blogpost da MDSec de 2018 que mencionei anteriormente, a parte deny file-write* sob com.apple.security.temporary-exception.sbpl não existia, o que permitia que macros criassem arquivos com conteúdos arbitrários, como /Library/LaunchAgents/~$evil.plist. Por que isso foge da sandbox?
LaunchAgents e LaunchDaemons são um mecanismo de persistência bem conhecido (e legítimo) no macOS. Já os mencionei antes, mas você pode pensar neles como Serviços (se você vem do mundo Windows) - LaunchDaemons iniciam quando o sistema operacional inicializa (e, portanto, vivem fora da sessão do usuário), enquanto LaunchAgents iniciam quando um usuário faz login.
Curiosamente, ambos são descritos em simples arquivos plist. Aqui está um exemplo para o meu atualizador do 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
}
Esses LaunchAgents e LaunchDaemons são iniciados pelo launchd (lembra desse processo?) e, portanto, fogem da sandbox, já que o launchd não tinha conhecimento se o plist foi solto por um processo em sandbox ou não (e mesmo que tivesse - como saberia quais regras de sandbox aplicar?).
Esse conceito de usar o launchd para escapar da sandbox do macOS foi amplamente utilizado e, de fato, eu o usei no passado.
Economizando alguns cliques - aqui está a ideia:
launchd inicia Apps do macOS. Esses Apps podem ser iniciados com um duplo clique ou por outros meios - por exemplo, clicar em um arquivo zip usará o Archive Utility, já que ele está associado a arquivos zip.launchd é com o comando open.open é rico - você pode usar alguns de seus recursos interessantes, como selecionar o App, selecionar o nome do arquivo a abrir ou até mesmo fornecer argumentos completos de linha de comando.Python integrado (que não existe mais em dispositivos macOS vanilla novos) para iniciar o Python com um argumento stdin que essencialmente redireciona a entrada padrão de um arquivo que soltei (esse arquivo era ~$evil.py devido às restrições do Word).launchd executou uma instância do App Python sem sandbox que começou a ler de , que continha comandos Python arbitrários, escapando essencialmente da sandbox.Houve ideias semelhantes em outras divulgações (um bom exemplo vive aqui), mas a ideia permanece a mesma. Tenho certeza de que há muitas mais à vista!
Uma menção honrosa vai para um ótimo blogpost de Wojciech Regula - desta vez focando no app Terminal e na manipulação de uma variável de ambiente. Vale a pena ler!
O problema encontrado pela MDSec era específico do Office - e foi corrigido com regras mais estritas.
Os que abusavam do LaunchServices (que é o nome do framework para iniciar apps com o launchd) são mais genéricos - e, portanto, a Apple teve que corrigi-los.
Uma das coisas que notei foi que arquivos soltos pelo Word agora são criados com o atributo estendido com.apple.quarantine, sim, o mesmo que mencionei no meu blogpost de introdução ao Gatekeeper.
Acontece que esse atributo de quarentena é um certo endurecimento contra alguns ataques - por exemplo, o app Terminal se recusou a iniciar scripts de shell criados com esse atributo. Essa é a razão, aliás, de eu ter que usar a opção --stdin para o Python.
Como apontado por Gergely Kalman - a Apple adicionou verificações adicionais ao binário open para endurecer esse tipo de exploração. Parece que --stdin, --args e outras flags de linha de comando são ignoradas se o processo chamador estiver em sandbox. No entanto, o open simplesmente chama o LaunchServices (no launchd) com um IPC e, convenientemente, existem APIs para isso, por exemplo, LSOpenURLsWithRole.
Não investiguei se o próprio LaunchServices também foi endurecido - se não foi, então acredito que fugas de sandbox semelhantes poderiam ser facilmente alcançadas.
Discutimos brevemente outra tecnologia do macOS - a sandbox. Vimos como ela é poderosa e configurável, e como pode ser quebrada.
Também amarramos algumas pontas - como os apps funcionam com regras de sandbox, como o launchd ao iniciar apps quebra mais do que apenas as árvores de processos e como arquivos plist podem ser usados para o bem ou para o mal - desta vez com persistência (LaunchAgents e LaunchDaemons).
Felizmente, até amarramos o atributo estendido com.apple.quarantine do blogpost de introdução ao Gatekeeper e explicamos como ele pode ser usado como um endurecimento extra contra fugas da sandbox. Nada mal!
Nos próximos blogposts, exploraremos mais mecanismos de segurança do macOS e talvez falemos sobre estratégias para quebrá-los.
Fique ligado!
Jonathan Bar Or (https://jonathanbaror.com)
~$whatever.docx~$evil.py