
Análisis en profundidad y exploit para CVE-2019-13272, una vulnerabilidad de escalada de privilegios ptrace del kernel de Linux. Incluye recorrido del código y escenario de explotación.
PTRACE_TRACEME es una vulnerabilidad de escalada de privilegios en el kernel Linux descubierta por Jann Horn en julio de 2019.
Ptrace es una llamada al sistema que proporciona un método para permitir que un proceso (tracer) pueda observar y controlar la ejecución de otro proceso (tracee), inspeccionar y modificar su imagen de núcleo y sus registros, principalmente utilizada para establecer puntos de interrupción en la depuración y seguir el proceso de las llamadas al sistema.``` c 1 396 kernel/ptrace.c <<ptrace_attach>> ptrace_link(task, current); 2 469 kernel/ptrace.c <<ptrace_traceme>> ptrace_link(current, current->real_parent);
Existen dos formas de establecer una relación de trazado:
- El proceso llamará a `fork` y su proceso hijo llamará a `PTRACE_TRACEME` (correspondiente a la función `ptrace_traceme` en el kernel) para inicializar el tracee.
- El proceso llama a `PTRACE_ATTACH` o `PTRACE_SEIZE` (correspondiente a la función `ptrace_attach` en el kernel) para inicializar un tracer y así trazar otro proceso.
Independientemente del método utilizado, la función `ptrace_link` terminará llamándose para establecer la relación de trazado entre el tracer y el tracee.
- Los dos parámetros pasados a `ptrace_link` para `ptrace_attach` son `'task'` (tracee) y `'current'` (tracer)
- Los dos parámetros pasados a `ptrace_link` para `ptrace_traceme` son `'current'` (tracee) y `'current->real_parent'` (tracer)
Aquí debemos tener en cuenta cuáles son los dos parámetros de tracer y tracee en los dos métodos anteriores al llamar a la función `ptrace_link`, porque la vulnerabilidad se encuentra en la función `ptrace_link`.``` c
static void ptrace_link(struct task_struct *child, struct task_struct *new_parent)
{
rcu_read_lock();
__ptrace_link(child, new_parent, __task_cred(new_parent));
rcu_read_unlock();
}
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
const struct cred *ptracer_cred)
{
BUG_ON(!list_empty(&child->ptrace_entry));
list_add(&child->ptrace_entry, &new_parent->ptraced); // 1. thêm chính nó vào hàng đợi
// ptraced của process cha
child->parent = new_parent; // 2. Lưu địa chỉ của process cha trong con trỏ parent
child->ptracer_cred = get_cred(ptracer_cred); // 3. Lưu ptracer_cred lại, ta cần tập trung
// vào biến này vì lỗi nằm ở đây
}
La clave para establecer la relación de trace es que el tracee registre el cred del tracer y lo almacene en la variable 'ptracer_cred' del tracee.
El concepto de 'ptracer_cred' fue introducido por un parche en 2016, ptrace: Capture the ptracer's creds not PT_PTRACE_CAP. El propósito de introducir 'ptracer_cred' es realizar una comprobación de seguridad cuando el tracee ejecuta exec para cargar un setuid executable
¿Por qué necesitamos esta comprobación de seguridad?
La familia de exec puede actualizar la imagen de un proceso. Si el setuid bit del archivo ejecutable está establecido, cuando el archivo ejecutable se ejecute, el euid del proceso se modificará al uid del propietario del archivo ejecutable. Los privilegios del proceso son mayores que los del usuario que invoca exec, y ejecutar este tipo de setuid executable tendrá un efecto de escalada (escalation).
Imaginemos que el propio proceso que ejecuta exec es un tracee. Después de ejecutar un setuid executable para escalar privilegios, su tracer puede modificar los registros y la memoria del tracee en cualquier momento; y si el tracer tiene privilegios bajos pero puede controlar a un tracee con privilegios altos, el tracer podría realizar operaciones no autorizadas a través del tracee.
Sin embargo, en el kernel parece que no se permiten tales comportamientos que exceden la autoridad. Por lo tanto, al establecer relaciones de trace, el tracee necesita almacenar el cred del tracer (es decir, ptracer_cred). Si el tracee ejecuta un proceso exec, comprobará si el bit setuid del archivo ejecutable que se va a ejecutar está establecido; si lo está, examinará los permisos de 'ptracer_cred'. Si los permisos no son suficientes, el privilegio de ejecución del bit setuid (privilegio del propietario del archivo) no se usará para realizar el exec, sino que se ejecutará con los privilegios del usuario original.
El análisis del código de este proceso es el siguiente (el análisis de código de este artículo se basa en v4.19-rc8).``` python do_execve -> __do_execve_file -> prepare_binprm -> bprm_fill_uid -> security_bprm_set_creds ->cap_bprm_set_creds -> ptracer_capable ->selinux_bprm_set_creds ->(apparmor_bprm_set_creds) ->(smack_bprm_set_creds) ->(tomoyo_bprm_set_creds)