He puesto capacidades de Linux para gestionar los servicios del servidor siguiendo el principio de mínimo necesario y, de este modo, conceder únicamente los derechos parciales absolutamente imprescindibles. De este modo, reduzco la Superficie de ataque notable, sin bloquear ninguna función.
Puntos centrales
- Menor privilegio De forma sistemática: los servicios solo cuentan con las capacidades estrictamente necesarias.
- De grano fino En lugar de «Root»: unas 40-50 capacidades sustituyen al acceso completo.
- Separación de los procesos: la separación de privilegios reduce los daños en caso de exploits.
- Archivo Funcionalidades: Vincular los derechos directamente a los archivos binarios.
- Auditable: getcap ofrece una visión clara de los privilegios especiales.
Por qué el acceso de root es arriesgado, y cómo las capacidades cambian esta situación
Antes, casi todos los servicios de servidor funcionaban con Derechos fundamentales, lo que, en caso de vulnerabilidad, podía provocar inmediatamente la toma de control del sistema. Hoy en día, distribuyo los derechos de forma selectiva mediante capacidades como CAP_NET_BIND_SERVICE asigna derechos para los puertos inferiores a 1024 y elimina todos los demás derechos avanzados. De este modo, el servidor web puede establecer conexiones, pero no puede cargar módulos del núcleo ni modificar la propiedad de los archivos, lo que Seguridad aumenta considerablemente. Una separación clara de las tareas hace que los ataques sean menos eficaces, ya que un proceso comprometido solo puede realizar acciones limitadas. Quien quiera dotar a este concepto de mayor estructura, puede definir los derechos con gran precisión dividir en partes más pequeñas y, de este modo, restringir sistemáticamente las operaciones críticas. Así, un servicio raíz monolítico se transforma en un conjunto de servicios con privilegios mínimos y claramente definidos.
Así funcionan los conjuntos de capacidades en el núcleo
Cada proceso tiene varios Conjuntos de capacidades, que el núcleo comprueba al realizar acciones delicadas. El «Effective-Set» determina lo que un proceso puede hacer en ese momento, mientras que el «Permitted-Set» contiene el conjunto de derechos posibles. A través del «Inheritable-Set» puedo controlar lo que, al execve() se transfiere a los procesos hijos, lo cual es especialmente importante en el caso de los wrappers y los scripts de inicio. El «Bounding-Set» define un límite máximo estricto, de modo que ciertas capacidades nunca se pueden volver a obtener, ni siquiera en caso de errores en la aplicación. Con el «Ambient-Set» transfiero capacidades sin SUID a programas normales y mantengo el Ruta de ataque pequeños. En conjunto, estos conjuntos me permiten un control muy preciso, que va mucho más allá del clásico «todo o nada» del UID 0.
| Conjunto | Propósito | Uso típico | Riesgo de configuración errónea |
|---|---|---|---|
| Efectivo | Habilidades que se pueden usar ahora | Comprobación de cada operación privilegiada | El proceso puede resultar demasiado complicado desde el principio |
| Permitido | Conjunto de habilidades permitidas | Fuente del «Effective Set» | Las reservas innecesarias siguen estando disponibles |
| Heredable | Habilidades hereditarias | Transferencia controlada en execve() | Los niños heredan derechos sin necesidad |
| Delimitación | Límite máximo de todos los derechos | Definir exclusiones permanentes | Es posible recuperar derechos importantes |
| Ambient | Compartir sin SUID | Los programas habituales obtienen capacidades | Una concesión de derechos más amplia y discreta |
En la práctica, hay dos aspectos adicionales que son importantes: en primer lugar, se decide Securebits sobre si un proceso, tras un cambio de ID de usuario (por ejemplo, a través de setuid()) conserve sus capacidades. Con PR_SET_KEEPCAPS Esto se puede controlar de forma específica; el procedimiento habitual es el siguiente: iniciar el sistema como root durante un breve periodo de tiempo, crear los sockets o recursos necesarios, cambiar el UID a un usuario sin privilegios y mantener solo las capacidades necesarias. En segundo lugar, se aplica lo siguiente: el Conjunto delimitador Ya forma parte definitivamente del flujo de procesos actual. Quien elimine aquí, al principio de la ruta de inicio, las capacidades innecesarias, ya no podrá obtener más derechos „prohibidos“, ni siquiera debido a configuraciones erróneas.
Controlar los permisos de los archivos mediante las capacidades de archivo
En lugar de un servicio, permanente Derechos especiales Para evitarlo, prefiero vincularlas directamente al archivo binario. A través de setcap cap_net_bind_service=+eip /usr/bin/node permito la asignación de puertos sin que el proceso tenga que ejecutarse como root. Con getcap /usr/bin/node o de forma recursiva getcap -r / 2>/dev/null Compruebo la asignación y mantengo el control. Para eliminarlo, hay que hacer clic en setcap -r /ruta/al/archivo-binario, por lo que retiro los derechos temporales una vez finalizada la operación. Al copiar, las capacidades suelen perderse, por lo que las guardo explícitamente durante la implementación para Regresiones para evitarlo. De este modo, las compilaciones siguen siendo reproducibles y los derechos quedan documentados de forma trazable en todo momento.
Las capacidades de los archivos se almacenan como atributos extendidos (capacidad de seguridad) en el sistema de archivos. Para ello se necesita un sistema de archivos compatible y las opciones de montaje adecuadas. Herramientas como alquitrán y rsync hay que incluir explícitamente los XAttrs (p. ej.,. tar --xattrs, rsync -XA), de lo contrario, los derechos desaparecen de forma implícita. Los gestores de paquetes pueden establecer capacidades en los pasos posteriores a la instalación; yo prefiero fijarlas en el proceso de compilación/lanzamiento para evitar sorpresas durante las actualizaciones. Otro aspecto crítico es que los scripts de intérprete (por ejemplo, con shebang) no heredan las capacidades de archivo como lo hacen los binarios ELF. Las capacidades potentes en intérprete De todos modos, es arriesgado hacerlo; prefiero separarlo y trabajar con pequeños binarios auxiliares específicos.
El principio de minimalidad aplicado a los servicios de servidor en la práctica
Inicio el servidor web como usuario sin privilegios y solo concedo CAP_NET_BIND_SERVICE, para que el proceso pueda vincularse al puerto 80/443 y no haya otros Privilegios que ofrece. Sigo gestionando los archivos y directorios mediante permisos POSIX y, opcionalmente, perfiles MAC, lo que permite mantener separados y protegidos tanto la configuración como los contenidos. Los agentes de supervisión o de registro disponen de permisos de red específicos y derechos de lectura sobre los registros, pero no tienen ninguna autorización para realizar cambios en el sistema. En entornos de contenedores, reduzco además el conjunto de capacidades y lo combino con filtros de llamadas al sistema para limitar estrictamente el comportamiento. Esta combinación reduce el impacto de los exploits que tienen éxito y aumenta la Transparencia de las competencias reales. Los servicios siguen funcionando, pero el margen de maniobra sigue siendo reducido.
En lugar de asignar «capabilities», a veces las elimino por completo: la activación de sockets proporciona listeners con privilegios (por ejemplo, 443/tcp) a través del proceso «init» y solo pasa el descriptor de archivo abierto al servicio. De este modo, el proceso de la aplicación no necesita ningún CAP_NET_BIND_SERVICE más. Del mismo modo, las acciones de root que solo hay que realizar una vez (por ejemplo, crear el directorio PID) pueden llevarse a cabo de antemano y, a continuación, ceder los permisos de forma sistemática. Cuantas menos capacidades en absoluto Cuantos más elementos intervengan, más resistente será el sistema frente a los errores en cadena.
Aplicar correctamente la separación de privilegios
Divido los servicios de gran envergadura en varios Subprocesos, cada uno de los cuales solo cuenta con las capacidades necesarias. Un proceso front-end establece la conexión TLS y se vincula a los puertos, pero no tiene permisos en el sistema de archivos para realizar cambios críticos. Un proceso de backend procesa los datos internamente, tiene derechos mínimos de lectura sobre la configuración y se comunica con las bases de datos sin capacidades de red propias. Las tareas administrativas, como la rotación de registros o el mantenimiento, se ejecutan mediante herramientas dedicadas con capacidades limitadas en el tiempo. Si un atacante ataca una parte del sistema, el resto permanece a salvo, ya que la Autorizaciones están definidos de forma estricta. De este modo, la seguridad se adapta a la estructura de la aplicación, en lugar de basarse en derechos de sistema ilimitados.
Para esta distribución, lo más adecuado es una orquestación inicial clara. En las configuraciones clásicas, de esto se encarga un supervisor; en los sistemas actuales, prefiero utilizar systemd, ya que integra directamente capacidades, cgroups y espacios de nombres. De este modo, puedo iniciar el front-end de red, los trabajadores y las herramientas de administración, cada uno en su propio entorno aislado, limitar los recursos y hacer que se reinicien automáticamente en caso de error, sin tener que conceder nunca derechos de root de forma generalizada.
Combinación de controles de seguridad: POSIX, MAC y capacidades
Las capacidades funcionan mejor cuando las combino con las clásicas Derechos sobre los archivos y sistemas MAC. SELinux o AppArmor pueden restringir aún más las acciones a pesar de las capacidades asignadas y, de este modo, generar una protección múltiple. Así, aunque un proceso pueda conectarse a un puerto, una política le impide leer archivos sensibles. Quien desee profundizar en las diferencias entre estos enfoques, encontrará una comparación clara en SELinux frente a AppArmor y, a continuación, puede elegir una estrategia de políticas adecuada. En definitiva, se crea una red de defensa que detiene los ataques en varios niveles y la Superficie de ataque se reduce aún más. De este modo, la asignación de derechos sigue siendo verificable, repetible y coherente.
La situación se vuelve especialmente restrictiva cuando, además, NoNewPrivileges Activar: los procesos y los hijos no podrán obtener nuevos privilegios (por ejemplo, a través de SUID o de capacidades de archivo recién establecidas). Junto con una lista estricta de límites de capacidades, se crea una barrera de seguridad que impide la ampliación posterior de privilegios, incluso en caso de una configuración errónea.
Distribuir y auditar las capacidades de forma segura
Considero que el asignado Conjunto de habilidades lo más pequeño posible y evita todo lo que suene a „segundo root“, como por ejemplo CAP_SYS_ADMIN. Los intérpretes como Python, Perl o los shells no cuentan con capacidades avanzadas, ya que sus funcionalidades pueden ser objeto de abuso con facilidad. Mediante auditorías periódicas a través de getcap -r / 2>/dev/null detecto valores atípicos y los corrijo. Los archivos binarios con capacidades están protegidos contra escritura, pertenecen a root y no se encuentran en rutas que los usuarios normales puedan modificar. Además, compruebo mis propios binarios antes de cada lanzamiento y documento los cambios, para que Consulte y que la reproducción funcione de forma fiable. De este modo, la concesión de derechos se mantiene bajo control y los ajustes son transparentes.
Durante la ejecución, compruebo los procesos mediante /proc//status (Campos CapEff, CapPrm, CapInh). Esto proporciona los valores hexadecimales de los conjuntos activos y muestra de inmediato si una aplicación puede hacer más de lo previsto. Herramientas como capsh --print o getpcaps facilitan la depuración. Además, con el subsistema de auditoría de Linux registro los cambios en las capacidades o en capacidad de seguridad-Atributos de los archivos, para realizar un seguimiento de las manipulaciones. Quien trate las capacidades como un objeto de configuración y revise rigurosamente los cambios, conseguirá que las auditorías sean reproducibles y simplificará la demostración del cumplimiento normativo.
Los obstáculos más comunes y cómo evitarlos
Una trampa típica: al copiar, Atributos se pierden, lo que hace que, de repente, los servicios dejen de iniciarse o, por el contrario, no estén lo suficientemente restringidos. Por eso, configuro las capacidades de forma explícita en la compilación o las asigno de forma automatizada en el paso posterior a la instalación. Otro error es el uso excesivo de capacidades de uso general, que abren más de lo necesario. Es mejor utilizar capacidades concretas como CAP_NET_RAW o CAP_CHOWN utilizarlos solo allí donde aporten una función real. También utilizo el conjunto «Ambient» con moderación, para que no se produzca ningún efecto no deseado Transmisión es algo habitual. Si se reduce de forma selectiva y se comprueba con regularidad, se evitan las brechas de seguridad debidas a errores de manejo.
También es importante: eliminar sistemáticamente los binarios SUID. En los casos en los que antes se necesitaba SUID (por ejemplo, para enviar ICMP), a menudo se puede resolver con CAP_NET_RAW trabajar —o, mejor aún, externalizar la función a un proceso auxiliar lo más pequeño posible con un pliego de condiciones muy estricto—. Además, evito colocar las capacidades en rutas temporales o en las que los usuarios puedan escribir. Un régimen estricto de propiedad y despliegue (Root:root, 0755/0555, rutas fijas) evita la „pérdida“ de derechos debido a la sustitución de binarios.
Funcionalidades en contenedores y DevSecOps
En entornos de contenedores, reduzco la Capacidades de forma agresiva y elimino todo lo que la carga de trabajo no necesite de forma imprescindible. Además, creo un Perfil Seccomp que bloquea las llamadas al sistema de riesgo y, de este modo, establece un obstáculo adicional. En los procesos de compilación, defino las capacidades de forma declarativa, las pruebo en el entorno de prueba y las registro con su versión correspondiente. Esto beneficia al cumplimiento normativo, ya que puedo demostrar que se aplica el principio del privilegio mínimo y documentar de forma exhaustiva los cambios en los derechos. De este modo, los contenedores se mantienen estrictamente controlados, sin obstaculizar sus tareas, y la Superficie de ataque se mantiene pequeño. Si se combina con imágenes que solo contienen lo estrictamente necesario, la seguridad aumenta aún más.
Es importante tener en cuenta, en el contexto de los contenedores, que las capacidades se encuentran en espacios de nombres Relativa. Aunque un proceso puede tener privilegios de „root“ dentro de un espacio de nombres de usuario, sus capacidades solo se aplican a los espacios de nombres correspondientes, lo que reduce considerablemente el alcance del daño. Por el contrario, „--privilegiado“Prácticamente siempre es un tabú: desactiva el límite de delimitación estricto y abre mucho más de lo necesario. Por eso, por defecto, inicio los contenedores con la opción „eliminar todo, añadir de forma selectiva“ y añado NoNewPrivileges, límites de cgroup y montajes de solo lectura. Para los servicios que solo tienen que estar a la escucha, utilizo la activación de sockets o sidecars para prescindir por completo de capacidades adicionales.
Ejemplo de systemd: restringir las capacidades de forma declarativa
En las unidades de servicio defino cuáles son los límites máximos de un proceso: de forma clara, repetible y con control de versiones. Un ejemplo conciso de un servicio web que solo puede conectarse al puerto 443 y que, por lo demás, está muy restringido:
[Unit]
Description=Servicio web mínimo sin privilegios de root
[Service]
User=web
Group=web
ExecStart=/usr/bin/my-web
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/var/lib/my-web
RestrictAddressFamilies=AF_INET AF_INET6
SystemCallFilter=@basic-io @network-io
LockPersonality=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
La combinación de AmbientCapabilities y una dura Conjunto límite de capacidades garantiza que el servicio reciba únicamente la capacidad necesaria y nada más. NoNewPrivileges impide la revalorización posterior de los privilegios, ProtectSystem y ReadWritePaths regulan el acceso de escritura, y un filtro estricto de llamadas al sistema evita puntos de entrada innecesarios al núcleo.
Funcionalidades de uso frecuente y alternativas seguras
- CAP_NET_BIND_SERVICE: Asignación a puertos <1024. Alternativa: activación de sockets, colocar un proxy inverso delante.
- CAP_NET_RAW: Rohsockets (Ping, DHCP). Alternativa: un pequeño proceso auxiliar en lugar de amplios privilegios de intérprete.
- CAP_CHOWN/CAP_FOWNER: Propietario/Ajustes de ACL. Alternativa: directorios preconfigurados, herramientas de mantenimiento específicas.
- CAP_SYS_PTRACE: Depuración/rastreo: solo en el entorno de prueba, nunca de forma generalizada en producción.
- CAP_SYS_ADMIN: „Segunda raíz“: evitarlo; concretar lo que realmente se necesita.
Siempre elijo la cantidad mínima que permita activar exactamente la función necesaria. Si una capacidad abre varias vías de ataque (por ejemplo, sockets RAW), encapsulo la función en un proceso independiente y de corta duración, y retiro los permisos una vez finalizada la tarea.
Lista de comprobación práctica para capacidades sólidas
- ¿Se inicia el servicio sin derechos de root? Si no es así, ¿por qué no? ¿Se puede solucionar activando los sockets o con pequeños binarios auxiliares?
- Son todos ¿Se ha demostrado que las capacidades asignadas son realmente necesarias (prueba de funcionalidad, casos de prueba)?
- ¿Se ha establecido el conjunto delimitador de forma que sea lo más ajustado posible y lo antes posible?
- ¿Se mantienen los XAttrs de forma coherente en la compilación, las implementaciones y las copias de seguridad (opciones de rsync/tar, scripts de paquetes)?
- ¿Evito sistemáticamente el uso de «capabilities» en los intérpretes y los binarios SUID?
- ¿Están protegidos contra la sustitución los derechos de propietario y de archivo (Root:root, 0755/0555), así como las rutas?
- ¿Funcionan los controles adicionales (NoNewPrivileges, Seccomp, perfiles MAC)?
- ¿Se auditan las capacidades de los procesos durante la ejecución? (
/proc//status, getpcaps) y se han documentado los cambios? - ¿Están los contenedores configurados de forma predeterminada con „drop all, add minimal“ y sin „privileged“?
Brevemente resumido
Linux Las capacidades desglosan los derechos de root clásicos en unidades pequeñas y controlables, lo que permite aplicar el principio de mínimo necesario de forma técnicamente correcta. Solo asigno a los servicios las capacidades que realmente necesitan y lo combino con los derechos POSIX y las políticas MAC. Las capacidades de archivo garantizan que los derechos estén vinculados directamente a los binarios y que las auditorías muestren claramente quién puede hacer qué. Mediante la separación de privilegios, la reducción de los derechos de los contenedores y los filtros de llamadas al sistema, limito los daños en caso de que se aproveche una vulnerabilidad. Las comprobaciones periódicas, los estrictos derechos de propiedad y de escritura, y un proceso de lanzamiento documentado mantienen la asignación de derechos simplificada. De este modo, el servicio del servidor sigue siendo operativo, pero el Espacio de maniobra Se mantiene sistemáticamente bajo para los atacantes.


