Seguridad
En este capítulo hablaremos de la seguridad operacional, táctica y estratégica de la información.
Lo veremos a nivel de implementación (y por ende; práctico).
Sistemas de seguridad en Linux
Linux utiliza varios métodos de seguridad, que puede verificarse cuales están en funcionamiento viendo el archivo; “/sys/kernel/security/lsm”
-
Control de acceso discrecional de Unix (DAC)
Este es el más básico de todos.
Funciona estableciendo unos permisos en un directorio ó archivo de; quien es el propietario y a que grupo pertenece (el directorio ó archivo). Una vez que está configurado eso, se encarga de establecer un sistema de permisos sobre tales;
- leer (read), abreviado como “r” ó con el número “4”.
- escribir (write), abreviado como “w” ó con el número “2”.
- ejecutar (execute), abreviado como “x” ó con el número “1”.
Nota Las carpetas/directorios son simplemente archivos que contienen los metadatos de que archivos y otros directorios “contienen” dentro. Simplemente hacen referencias que de cara al usuario son transparentes y al kernel le sirven para saber que debe o no devolver. Por eso mismo todos los directorios deben tener permisos de ejecución para leer lo que tienen internamente Por ende, para incluir también aquellos usuarios (o grupos de usuarios) que no pertenecen al propietario ni a su grupo (osea; otros), se organiza de la siguiente manera;
- Para permisos por letras;
[permisos_propietario][permisos_grupo][permisos_otros]
- Para permisos por números;
[sumatoria_centenas_propietario][sumatoria_decenas_grupo][sumatoria_unidades_otros]
Veamos un ejemplo;
Como se puede evidenciar tenemos una carpeta (fijate en la “d” adelante de todo) con el nombre “linux-6.8.8” y un archivo (fijate que no tiene la “d” adelante de todo) llamado “linux-6.8.8.tar.xz”.
El comando “ls” muestra los permisos de la carpeta como el archivo en formato de letras. Por ende si nos fijamos en el archivo comprimido con el algoritmo “xz” vemos la siguiente estructura; “-rw-r--r--”
Recuerde el orden, por lo que podemos ver que el propietario (el usuario ‘root’ que primero muestra ahí) tiene permisos de lectura (‘r’) y de escritura (‘w’) pero no de ejecución (de ahí el “-”). El grupo de usuarios de ese archivo (el segundo ‘root’ que aparece) tiene permisos solamente de lectura (‘r’) pero no se escritura ni ejecución (de ahí los “--”). Por último, los que no sean usuario root ni tampoco estén en el grupo del mismo nombre tienen permisos de solamente lectura, no de escritura ni ejecución.
Si estos mismos permisos los pasamos a una sumatoria numeral quedaría; 644.
Esto permite aislar carpetas de distintos usuarios propietarios.
Dato importante Algunos sistemas de archivos permiten que ciertas particiones se monten deshabilitando la ejecución de programas binarios desde ahí, aumentando la seguridad. -
SELinux (Security Enhanced Linux)
Es un módulo de seguridad del kernel Linux, dentro de la categoría MAC (Mandatory Access Control).
Fue creado por la NSA y Red Hat el 22-12-2000 e introducido en la versión 2.6 del núcleo el 08-08-2003.
Utiliza reglas (almacenadas en; “/usr/share/selinux/targeted”) para identificar que recursos necesita la aplicación, a qué dispositivos físicos (en “/dev”) puede acceder, que permisos va a tener sobre determinados directorios, si puede o no conectarse a internet, etc. De esta manera todo lo que no esté permitido o especificado en esas reglas le hes denegado a la aplicación.
Internamente puede estar aplicando las políticas de las reglas (modo; “enforced”) o monitorear que pasa, independientemente de lo que indiquen las reglas (modo; “permissive”).
Como puede haber binarios (programas ejecutables) en muchas posibles ubicaciones, SELinux utiliza un sistema de etiquetas (labels) sobre el binario (por ende el sistema de archivos debe soportar las etiquetas) para que cuando se ejecute pueda identificar desde el inicio que política debe aplicar. Sin importar si el binario cambia de ubicación a futuro, usará la misma política. Pero no solamente apunta a binarios, si no a todo componente del sistema operativo.
Este sistema de MAC tiene la ventaja de que permite reglas mucho más completas y complejas que el esquema de permisos Unix tradicional.
SELinux tiene la desventaja de ser dificil de aprender a manejar y administrar.
Lo utilizan por defecto; Fedora, RedHat Enterprise, CentOS, Rocky, Scientific y Android (desde la versión 5.0).
Nota Kubernetes no es totalmente compatible con este sistema de seguridad y por eso pide que lo deshabilites al momento de instalarlo. -
AppArmor
Es un módulo de seguridad del kernel Linux, dentro de la categoría MAC (Mandatory Access Control).
Fue creado por Immunix en 1998 y desde 2009 lo mantiene Canonical (la creadora de Ubuntu Linux).
Utiliza reglas (almacenadas en; “/etc/apparmor.d”) para identificar que recursos necesita la aplicación, a qué dispositivos físicos (en “/dev”) puede acceder, que permisos va a tener sobre determinados directorios, si puede o no conectarse a internet, etc. De esta manera todo lo que no esté permitido o especificado en esas reglas le hes denegado a la aplicación.
Internamente puede estar aplicando las políticas de las reglas (modo; “enforce”) o monitorear que pasa, independientemente de lo que indiquen las reglas (modo; “complain”).
A diferencia de SELinux que aplica etiquetas directamente en el binario, apparmor se basa en la especificación del path absoluto del binario en sus propias reglas para saber que política debe implementar o no. Esto ocasiona que si el binario cambia de ubicación, la política no se aplicará pero también es muchísimo más fácil de administrar que SELinux y evita problemas de compatibilidad (a nivel seguridad) en puntos de montaje NFS, Samba, etc.
Lo utilizan por decto; Debian, Ubuntu, Mint, SUSE, Arch, Alpine, entre otros.
-
Tomoyo
Es un módulo de seguridad del kernel Linux, dentro de la categoría MAC (Mandatory Access Control).
Fue creado en el 2003 y esponsoreado por la empresa japonesa NTT Data hasta 2012, siendo fusionado en el núcleo en el 2009 (v2.6.30).
Es muy parecido en funcionamiento a SELinux, ya que apunta a proteger todo componente del sistema operativo pero no tiene tanto desarrollo como este.
Debido a que habia nacido como una serie de parches a ser aplicados, para ser fusionado con el núcleo se debía implementar una enorme cantidad de cambios adicionales. Esto ocasionó que el desarrollo se dividiera en dos partes; la rama de versionado v1.X (que sigue mediante parches) y la rama de versionado v2.X (que está integrada dentro del núcleo). De igual manera se hizo un fork de la rama v1.X llamado Akari como módulo de seguridad en el kernel.
En el versionado v1, las reglas se encuentran almacenadas en; “/etc/css”. Así mismo el init no puede ser SystemD u OpenRC si no que debe ser “/usr/bin/ccs-init”.
En el versionado v2, las reglas se encuentran almacenadas en; “/etc/tomoyo/policy”. En esta versión el init puede ser SystemD, OpenRC o el que se utilice.
Al igual que AppArmor se basa en el path absoluto de ejecución.
-
YAMA
Es un módulo de seguridad del kernel Linux, dentro de la categoría MAC (Mandatory Access Control).
Se basa en la determinación de que política aplicar según el proceso que esté en ejecución. Permite una mayor libertad y versatilidad que AppArmor pero no tanto como SELinux. Este tipo de aproximación es para evitar que un proceso vulnerable comprometido pueda hacer una adjunción (“attach”) a otro proceso legítimo, en los Linux esto se hace principalmente a través de “ptrace”.
Tiene 4 modos; 0 (deshabilitado), 1 (‘restricted’) para solamente permitir ptrace con sudo/doas, 2 (‘admin-only’) para permitir ptrace solamente como usuario root, 3 (‘ptrace-disabled’) para no permitir debug alguno.
-
Lockdown
Este es un modo introducido en la release 5.4 del núcleo.
Viene a endurecer el perimetro (“boundary”) entre el usuario root (User Id -uid-: 0) y el propio núcleo. De esta manera en caso de que el usuario root quiera modificar el núcleo del sistema en ejecución se puede permitir o denegar esto. Algunos ejemplos seria;
- Uso de kexec
Permite arrancar un nuevo kernel desde uno ya existente sin tener que reiniciar.
- eBPF (Extended Berkeley Packet Filter)
eBPF es una tecnología que permite que sin tener que modificar el código del núcleo o cargar diferentes módulos, se pueda ejecutar programas en un contexto privilegiado al mismo nivel que el kernel.
Entonces, tanto kexec como eBPF pueden ser realmente peligrosos en un sistema con información crítica (ejemplos; la computadora que se encarga de registrar los pulsos eléctricos del latido de corazón de un paciente, un sistema de soporte vital en un satélite artificial, el procesamiento en un F1 manejado por IA, etc).
Por lo que para saber si el usuario root puede o no realizar modificaciones en el espacio del núcleo tenemos tres modos; “none” que permite modificar, “integrity” que no permite modificaciones y por último, “confidentiality” que además no permite extraer información sensible desde el núcleo.
Personalmente “integrity” suele ser el punto adecuado para la mayoría, ya que a veces “confidentiality” es muy agresivo, pero tiene que probar y verificar. Considerar que lo que indicó Linus Torvalds en su momento hace unos años; “las aplicaciones que dependen del acceso a bajo nivel al hardware o al kernel pueden dejar de funcionar”
El modo se setea indicando el argumento “lockdown=[mode]” al kernel (CMDLINE). Aunque si en un sistema en funcionamiento iniciado con “none”, se le indica esto se puede cambiar a un modo más restrictivo (pero no a la inversa);
echo confidentiality > /sys/kernel/security/lockdown
Nota |
---|
Con lockdown habilitado no se pueden cargar módulos fuera del kernel embebido, por lo que los drivers de VirtualBox, NVIDIA y otros que estén como módulos no se iniciarán. Esto es excelente cuando se tiene todo dentro del kernel embebido pero una molestia cuando se requiere usar módulos. |
-
Landlock
Es un módulo de seguridad del kernel Linux.
Persigue un mismo tipo de funcionalidad que Tomoyo pero con el añadido de poder funcionar en conjunto con otros LSM (Linux Security Modules).
- A diferencia de AppArmor y SELinux al aplicar una regla se hereda a los procesos hijos que pueda ejecutar, en vez de separar cada uno según regla.
- A diferencia de AppArmor pero en semejanza con SELinux, Landlock tiene capacidad de aplicar reglas de redes.
- A semejanza de AppArmor y SELinux, hay reglas sobre el sistema de archivos aplicadas (o no) a un objeto.
Si te encuentras cómodo con C y C++, con landlock te sentirás como pez en el agua ya que su sintaxis de regla es muy parecida.
-
Funciones criptográficas y soporte
Las funciones criptogŕaficas del núcleo son provistas mediante una A.P.I. accesible desde la librería estándar de C que se esté usando; glibc, musl, etc.
Las funciones criptográficas proveen, muchas veces, una base esencial para el resto de sus funciones de seguridad (no solamente los módulos y modos anteriormente mencionados)
En el kernel con versionado 6.8.8 tenemos (entre muchos otros ‘features’ disponibles adicionales);
- Motor (engine) paralelo para funciones criptográficas.
- Configuraciones criptográficas para utilizar las funciones por parte de procesos no root.
- Soporte para; RSA, DH, ECDH, ECDSA, EC-RDSA, SM2, AES, ARIA, BlowFish, Camellia, CAST5 y CAST6, DES, FCrypt, Serpent, Blake2, SHA (1, 256, 512, etc), LZO, LZ4, ZStandard (zstd), entre muchos otros.
Hardening del userland
Como mencionamos anteriormente el userland es todo el set de herramientas, librerías y archivos que hacen que el usuario pueda utilizar el sistema operativo.
Como tal, elegir el userland básico del sistema es especialmente importante para que la totalidad sea tan estable y minimalista como sea posible.
El set básico de Arch Linux incluye la shell “bash”, el set coreutils, el editor nano y el gestor de paquetes pacman. En concreto el paquete coreutils incluye;
Programa | Propósito |
---|---|
chcon | Cambia el contexto de seguridad para SELinux en un archivo/binario |
chgrp | Cambia la propiedad del grupo del archivo |
chown | Cambia la propiedad del archivo |
chmod | Cambia los permisos de un archivo o directorio |
cp | Copia un archivo o directorio |
dd | Copia y convierte un archivo |
df | Muestra espacio libre en disco en sistemas de archivos |
dir | Es exactamente igual a “ls -C -b”. (Los archivos se enumeran por defecto en columnas y se clasifican verticalmente). |
dircolors | Configura el color para ls |
install | Copia archivos y establece atributos |
ln | Crea un enlace a un archivo |
ls | Enumera los archivos en un directorio |
mkdir | Crea un directorio |
mkfifo | Crea pipes con nombre (FIFOs) |
mknod | Crea archivos especiales de bloque o caracter |
mktemp | Crea un archivo o directorio temporal |
mv | Mueve archivos o renombra archivos |
realpath | Devuelve la ruta absoluta o relativa resuelta para un archivo |
rm | Elimina (borra) archivos, directorios, nodos de dispositivos y enlaces simbólicos |
rmdir | Elimina directorios vacíos |
shred | Sobrescribe un archivo para ocultar su contenido y, opcionalmente, lo elimina |
sync | Libera los búferes del sistema de archivos |
touch | Cambia las marcas de tiempo del archivo; crea archivo |
truncate | Reduce o extiende el tamaño de un archivo al tamaño especificado |
vdir | Es exactamente igual a “ls -l -b”. (Los archivos se enumeran por defecto en formato largo). |
b2sum | Calcula y verifica el resumen del mensaje BLAKE2b |
base32 | Codifica o decodifica Base32 e imprime el resultado en la salida estándar |
cat | Concatena e imprime archivos en la salida estándar |
cksum | Suma de comprobación (IEEE Ethernet CRC-32) y cuenta los bytes en un archivo. Reemplaza a otras utilidades *sum con la opción -a de la versión 9.0. |
comm | Compara dos archivos ordenados línea por línea |
csplit | Divide un archivo en secciones determinadas por líneas de contexto |
cut | Elimina secciones de cada línea de archivos |
expand | Convierte tabuladores en espacios |
fmt | Formateador de texto simple y óptimo |
fold | Envuelve cada línea de entrada para que se ajuste al ancho especificado |
head | Muestra la primera parte de los archivos |
join | Une líneas de dos archivos en un campo común |
md5sum | Calcula y verifica el resumen del mensaje MD5 |
nl | Numera líneas de archivos |
numfmt | Reformatea números |
od | Vuelca archivos en octal y otros formatos |
paste | Combina líneas de archivos |
ptx | Produce un índice permutado del contenido del archivo |
pr | Convierte archivos de texto para imprimir |
sha1sum, sha224sum, sha256sum, sha384sum, sha512sum | Calcula y verifica resúmenes de mensajes SHA-1/SHA-2 |
shuf | genera permutaciones aleatorias |
sort | Ordena líneas de archivos de texto |
split | Divide un archivo en partes |
sum | Suma de comprobación y cuenta los bloques en un archivo |
tac | Concatena e imprime archivos en orden inverso línea por línea |
tail | Muestra la última parte de los archivos |
tr | Traduce o elimina caracteres |
tsort | Realiza una ordenación topológica |
unexpand | Convierte espacios en tabuladores |
uniq | Elimina líneas duplicadas de un archivo ordenado |
wc | Imprime el número de bytes, palabras y líneas en archivos |
arch | Imprime el nombre del hardware de la máquina (igual que uname -m) |
basename | Elimina el prefijo de ruta de un nombre de ruta |
chroot | Cambia el directorio raíz |
date | Imprime o configura la fecha y hora del sistema |
dirname | Elimina el sufijo que no es directorio del nombre del archivo |
du | Muestra el uso del disco en los sistemas de archivos |
echo | Muestra una línea de texto especificada |
env | Muestra y modifica variables de entorno |
expr | Evalúa expresiones |
factor | Descompone números en factores primos |
false | No hace nada, pero sale sin éxito |
groups | Muestra los grupos a los que pertenece el usuario |
hostid | Imprime el identificador numérico del host actual |
id | Imprime UID y GID real o efectivo |
link | Crea un enlace a un archivo |
logname | Imprime el nombre de inicio de sesión del usuario |
nice | Modifica la prioridad de programación |
nohup | Permite que un comando siga ejecutándose después de cerrar la sesión |
nproc | Consulta el número de procesadores (activos) |
pathchk | Comprueba si los nombres de archivo son válidos o portables |
pinky | Una versión ligera de finger |
printenv | Imprime variables de entorno |
printf | Formatea e imprime datos |
pwd | Imprime el directorio de trabajo actual |
readlink | Muestra el valor de un enlace simbólico |
runcon | Ejecuta un comando con el contexto de seguridad especificado |
seq | Imprime una secuencia de números |
sleep | Se retrasa durante un período de tiempo específico |
stat | Devuelve datos sobre un inodo |
stdbuf | Controla el almacenamiento en búfer para comandos que usan stdio |
stty | Cambia e imprime la configuración de la línea de terminal |
tee | Envía la salida a varios archivos |
test | Evalúa una expresión |
timeout | Ejecuta un comando con un límite de tiempo |
true | No hace nada, pero sale con éxito |
tty | Imprime el nombre del terminal |
uname | Imprime información del sistema |
unlink | Elimina el archivo especificado usando la función unlink |
uptime | Indica cuánto tiempo ha estado funcionando el sistema |
users | Imprime los nombres de usuario de los usuarios actualmente conectados en el host actual |
who | Imprime una lista de todos los usuarios actualmente conectados |
whoami | Imprime el userid efectivo |
yes | Imprime una cadena repetidamente |
[ | Un sinónimo de test; este programa permite expresiones como [ expresión ]. |
Como puede evidenciar el paquete chico precisamente no es, y si encima consideramos que muchas herramientas tienen funcionalidades duplicadas (“ls” y “dir”, “wc” y “nl”, “cat” y “nl”, “echo” y “printf”, entre otros) no tenemos precisamente un set minimalista; esto ocasiona una serie de problemas como duplicación de código, mayor consumo de espacio de almacenamiento, mayor superficie de ataque al tener más programas y más dependencias de librerías, mayor probabilidad de bugs en código, y un largo etc.
Por contra, Alpine utiliza BusyBox; es un programa que aglutina como builtins (véase; programas internos que pueden ser llamados desde el exterior) todo el set de coreutils (o la gran mayoría) así como shells, gestores de red, su propia versión de udev (llamada; mdev), y cientos de utilidades más.
BusyBox 1.37.0 |
---|
Para hacer funcionar a cada builtin de forma independiente al momento de instalarse se crean respectivos enlaces simbólicos hacia busybox;
Por lo tanto entramos en una decisión; o usamos una solución todo en uno o usamos muchas soluciones especializadas. Como todo en la vida la respuesta dependerá de la situación y las necesidades, yo personalmente abogo por la utilización de los “coreutils” pero haciendo una compilación para solamente dejar los programas verdaderamente necesarios y no todo el set completo.
Esto se hace mediante la modificación de los flags (opciones de configuración) en el proceso de compilación de los coreutils;
./bootstrap && ./configure --prefix=/usr --libexecdir=/usr/lib \
--with-openssl --enable-no-install-program=[PROGRAMAS_A_NO_INSTALAR_SEPARADOS_POR_COMA]
Con la compilación superior se puede delimitar los programas que serán compilados en el set de coreutils. Sin embargo esto no debe hacerse de forma manual, ya que por defecto los coreutils vienen instalados y una compilación (y posterior instalación) manual sobre escribirá los archivos existentes sin que el gestor de paquetes se entere de esto.
Como tal, se deben crear los archivos de configuración específicos que le indican a “apk”, “pacman” (o el gestor que se use) todo el proceso de compilación y que pueda, posteriormente, hacer un seguimiento de todos los archivos y carpetas involucrados. Este es el archivo PKGBUILD que indica a “pacman” todo el proceso;
# Maintainer: Sébastien "Seblu" Luttringer
# Maintainer: Tobias Powalowski <tpowa@archlinux.org>
# Contributor: Bartłomiej Piotrowski <bpiotrowski@archlinux.org>
# Contributor: Allan McRae <allan@archlinux.org>
# Contributor: judd <jvinet@zeroflux.org>
pkgname=coreutils
pkgver=9.5
pkgrel=1
pkgdesc='The basic file, shell and text manipulation utilities of the GNU operating system'
arch=('x86_64')
license=('GPL-3.0-or-later' 'GFDL-1.3-or-later')
url='https://www.gnu.org/software/coreutils/'
depends=('glibc' 'acl' 'attr' 'gmp' 'libcap' 'openssl')
source=("https://ftp.gnu.org/gnu/$pkgname/$pkgname-$pkgver.tar.xz"{,.sig})
validpgpkeys=('6C37DC12121A5006BC1DB804DF6FD971306037D9') # Pádraig Brady
sha256sums=('cd328edeac92f6a665de9f323c93b712af1858bc2e0d88f3f7100469470a1b8a'
'SKIP')
prepare() {
cd $pkgname-$pkgver
# apply patch from the source array (should be a pacman feature)
local src
for src in "${source[@]}"; do
src="${src%%::*}"
src="${src##*/}"
[[ $src = *.patch ]] || continue
echo "Applying patch $src..."
patch -Np1 < "../$src"
done
}
build() {
cd $pkgname-$pkgver
./configure \
--prefix=/usr \
--libexecdir=/usr/lib \
--with-openssl \
--enable-no-install-program=groups,hostname,kill,uptime
make
}
check() {
cd $pkgname-$pkgver
make check
}
package() {
cd $pkgname-$pkgver
make DESTDIR="$pkgdir" install
}
# vim:set ts=2 sw=2 et:
Nota / Aclaración |
---|
Yo acá indico como usar en pacman y apk pero si a vos te resulta más fácil construir para Debian (apt) o Fedora (dnf) proseguí, lo importante no es la herramienta en si si no que sepas que hacer. |
El archivo superior le indica a pacman; el nombre del programa (pkgname), la versión (pkgver), el número de liberación/release (pkgrel), una descripción del paquete (pkgdesc), la/s arquitectura/s en donde el paquete se puede (arch), la/s licensia/s del mismo (license), la URL del proyecto (url), de que otros programas depende (depends), la url del código fuente (source), las llaves PGP válidas (validpgpkeys), la sumatoria criptográfica hash de todos los archivos fuente (sha256sums), así como que tiene que hacer antes de ponerse a compilar el paquete (prepare), los pasos a seguir para compilar el programa (build), como verificar posteriormente si está todo bien (check) y finalmente los pasos para instalar en un directorio que puede seguir los cambios (package).
Como puede evidenciar, en “build” se indica el “configure” donde se habilita o deshabilita funcionalidades del programa. Esto debe ir de la mano con lo que se especifica en dependencias (depends) ya que si se deshabilita el soporte de acl (“–disable-acl”) se debe sacar (‘acl’).
Pero hay más cosas que se pueden realizar; cuando se realiza la compilación por parte del compilador (gcc o clang/llvm), se pueden realizar medidas de endurecimiento en seguridad (hardening) como verificar que los punteros no apunten hacia posiciones inválidas, o la creación de un tercer sector en la RAM (adicional al stack y al heap), deshabilitar funciones y estructuras obsoletas en C, entre otras medidas. Así mismo también existe una colección de herramientas para el tratamiento de binarios a posteriori de su compilación; binutils los cuales se involucran en el proceso final de compilación y ensamblaje.
Compilador | Arquitectura | Medida | Propósito |
---|---|---|---|
GCC | Todas | -Wtrampolines | Habilita mensajes de alerta sobre trampolines que requieren stacks ejecutables |
GCC / Clang | Todas | -Wall -Wextra | Habilita mensajes de alerta sobre uso de variables o código que pueden conducir a problemas de seguridad |
GCC / Clang | Todas | -Wformat -Wformat=2 | Habilita mensajes de alerta sobre el uso de ciertos formatos de sintaxis que puede conducir a problemas de seguridad |
GCC / Clang | Todas | -Wconversion -Wsign-conversion | Habilita mensajes de alerta sobre el uso de conversiones en tipos (un char y un int por ejemplo) |
GCC / Clang | Todas | -Wimplicit-fallthrough | Habilita mensajes de alerta cuando hay casos de uso en donde el “switch” puede fallar |
GCC / Clang | Todas | -Werror | Trata todos los mensajes de alerta como fallos y hasta que no se resuelven no prosigue |
GCC / Clang | Todas | -D_FORTIFY_SOURCE=3 | Verifica el uso de funciones de la libreria libc para detectar usos inseguros y de buffer overflow, y trata de corregirlos |
GCC / Clang | Todas | -D_GLIBCXX_ASSERTIONS -D_LIBCPP_ASSERT | Verifica las llamadas a la librería estándar de C++ para detectar usos inseguros y de buffer overflow, y trata de corregirlos |
GCC / Clang | Todas | -fstrict-flex-arrays=3 | Si una estructura de arreglos (arrays) fija está vacía la convierte en una flexible |
GCC / Clang | Todas | -fstack-protector-strong | Habilita verificaciones en tiempo de ejecución (cuando el programa se ejecuta) para detectar buffer overflows en el stack |
GCC / Clang | Todas | fstack-clash-protection | Habilita verificaciones en tiempo de ejecución (cuando el programa se ejecuta) para validar las alocaciones de tamaño variable en el stack |
GCC / Clang | x86 / x86_64 | -fcf-protection=full | Habilita protección de flujo para el ROP (Return Oriented Programming) y el JOP (Jump Oriented Programming) |
GCC / Clang | AArch64 (arm64) | -mbranch-protection=standard | Habilita protección de ramificación (branch) para el ROP (Return Oriented Programming) y el JOP (Jump Oriented Programming) |
GCC / Clang | Todas | -fno-delete-null-pointer-checks | Fuerza la retención en las verificaciones de punteros nulos |
GCC / Clang | Todas | -fno-strict-aliasing | No asume un solapamiento estricto |
GCC / Clang | Todas | -ftrivial-auto-var-init | Realiza inicializaciones “triviales” de variables para evitar valores inválidos |
Binutils (ld) | Todas | -Wl,-z,nodlopen | Restringe el uso de la llamada “dlopen” a shared objects (los archivos .so) |
Bintuils (ld) | Todas | -Wl,-z,noexecstack | Previene la ejecución de datos, marcando las partes de la memoria stack como no-ejecutable |
Binutils (ld) | Todas | -Wl,-z,relro -Wl,-z,now | Marca las tabals de relocación del binario como de solo lectura, evitando así que se pueda redirigir hacia valores infectados |
Con los valores anteriormente mencionados no he tenido problemas para compilar; systemd, librewolf, firefox, nginx, libreoffice y otros. Pero hay dos valores que sí pueden dar problemas (van a darlos casi seguro) y no son de fácil identificación:
Compilador | Arquitectura | Medida | Propósito |
---|---|---|---|
Bintuils / GCC / Clang | Todas | -fPIE -pie | Construye el ejecutable como posición independiente. |
Bintuils / GCC / Clang | Todas | -fPIC -shared | Construye el código como posición independiente. |
Otros compiladores como rustc (el compilador del lenguaje de programación Rust) aplican por defecto las siguientes protecciones;
- Position-independent executable (PIE)
- Integer overflow checks
- Non-executable memory regions (PIC)
- Stack clashing protection
- Read-only relocations y prestamo inmediata
- Protección contra la corrupción del heap de memoria
- Protección contra la imposición del stack de memoria
- Protección de control de flujo (forward y backward)
Sin embargo puedo indicarte una serie de flags que podes usar en rustc;
Compilador | Arquitectura | Medida | Propósito |
---|---|---|---|
Rustc | Todas | “-C”, “link-arg=-static” | Enlaza el binario final como estático contra las librerías compartidas |
Rustc | Todas | “-C”, “target-feature=+crt-static” | Enlaza el binario final como estático contra el sistema operativo y la librería estándar de C |
Rustc | Todas | “-C”, “link-arg=-fuse-ld=mold” | Usa mold como enlazador para aumentar la performance de enlazamiento |
Rustc | Todas | “-C”, “opt-level=2” | Usa el segundo nivel de optimización en el binario final |
Rustc | Todas | “-C”, “strip=debuginfo” “-C”, “strip=symbols” -C“, “debug-assertions=false” | Elimina todos los símbolos de depuración del binario final |
Tanto los valores de GCC, Cland como de binutils y los de rustc deben ser especificados en el archivo de configuración respectivo de tu gestor de paquetes;
Gestor de paquete | Archivo de configuración | Compilador | Lugar de configuración |
---|---|---|---|
pacman | /etc/makepkg.conf | GCC / Clang | CFLAGS |
pacman | /etc/makepkg.conf | GCC / Clang | CXXFLAGS |
pacman | /etc/makepkg.conf | Binutils | LDFLAGS |
pacman | /etc/makepkg.conf | Rustc | RUSTFLAGS |
apk | No tiene, se usan las variables | - | - |
apt | No tiene, se usan las variables | - | - |
dnf | No tiene, se usan las variables | - | - |
Finalmente, si entendiste lo mencionado hasta ahora comprenderás que no solamente es para el set básico de coreutils o BusyBox, esto sirve para cualquier programa y se debe realizar para poder endurecer la seguridad de los programas en el sistema operativo. Creando los archivos respectivos de PKGBUILD o APKBUILD podes automatizar fácilmente esto, de tal manera que solamente cambias la versión y la sumatoria criptográfica hash y generas el nuevo paquete.
Endurecimiento del binario final
Como vimos anteriormente, el endurecimiento de un binario final (que no sea el kernel) consta en; identificar las funcionalidades requeridas, desactivarlas en la configuración y aplicar flags de endurecimiento en el proceso de compilación.
Este es un proceso que debe hacerse en forma tranquila y con paciencia, en mi experiencia (manteniendo paquetes para mi repositorio de Arch Linux) puede llegar a ser muy frustante cuando la compilación falla por determinadas llamadas o funciones en librerias arbitrarias (azar) o que el programa falla por determinados motivos.
Vamos a utilizar como ejemplo; LibreOffice, Firefox y Nginx.
Proceso;
a. Identificación y mitigación
Primero debemos identificar que grupo de usuarios es nuevo objetivo; ¿es público técnico o no? No es lo mismo el nivel de detalle ante un fallo que nos puede dar el equipo de administradores de los sistemas de la organización, que el departamento administrativo.
¿Qué funciones necesitamos? Cada grupo de usuarios tiene requerimientos de funcionalidades diferentes; algunos van a requerir LibreOffice ONE para poder automatizar cosas mediante scripts (javascript o python), mientras que otros solamente van a necesitar las funciones básicas de editar el documento per-se para poder visualizarlo.
Con nginx algunos van a necesitar solamente el mostrar páginas web planas (sin javascript), mientras que el equipo de ingeniería va a requerir utilizar sus capacidades de proxy reverso para ocultar la infraestructura interna de la compañia (módulo; rewrite).
Con Firefox algunos van a necesitar las funcionalidades básicas, sin WebRTC (ya que no usan Google Meet, Microsoft Teams, etc) ó el soporte de DRM (Netflix, Disney+, etc) o el soporte de sincronización entre dispositivos, mientras que otros usuarios sí. Considerar que en el navegador web interviene mucho los gustos personales, por lo que entramos en un terreno de no objetividad por parte de un usuario/a.
Entonces, ¿cómo identificar correctamente los requerimientos? Mediante encuentras, entrevistas y…. observando como escuchando. Jamás subestime la capacidad de aprender del comportamiento de las personas cuando se las puede observar y escuchar sin saber que están siendo analizadas. Se conoce como; “Efecto Hawthorne” al cambio en el comportamiento cuando una persona sabe que está siendo observada. La información de observar y anotar el patrón de uso de una determinada herramienta le puede indicar cosas muy valiosas como; si la herramienta se utiliza solamente en momentos críticos donde la persona se encuentra nerviosa y necesita que la apertura sea tan rápida como sea posible o bajo que condiciones (importante si tenemos en consideración que el hardening en un binario tiene una pequeña penalización de performance).
Una vez analizado e identificado el patŕon de uso y requerimiento en el grupo objetivo debemos analizar el código fuente del programa, no se trata de analizar el código fuente en el lenguaje de programación que esté hecho (que es algo que debería hacerse si es un programa open-source) si no de verificar en el proceso de configuración que opciones permite deshabilitar.
Hay un estándar de facto para los programas creados en C y C++; el utilizar scripts de configuración que cambien valores en el archivo de construcción (que será leído por el programa; “make”) para habilitar o no ciertas características (del inglés; “features”).
Código fuente de LibreWolf 125.0.2 |
---|
Nótese el archivo “configure” |
Si el script respeta el estándar se le puede pasar el argumento; “--help” para obtener los detalles de configuración;
Algunas de las opciones de configuración de LibreWolf 125.0.2 |
---|