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;

    1. Uso de kexec

    Permite arrancar un nuevo kernel desde uno ya existente sin tener que reiniciar.

    1. 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;

ProgramaPropósito
chconCambia el contexto de seguridad para SELinux en un archivo/binario
chgrpCambia la propiedad del grupo del archivo
chownCambia la propiedad del archivo
chmodCambia los permisos de un archivo o directorio
cpCopia un archivo o directorio
ddCopia y convierte un archivo
dfMuestra espacio libre en disco en sistemas de archivos
dirEs exactamente igual a “ls -C -b”. (Los archivos se enumeran por defecto en columnas y se clasifican verticalmente).
dircolorsConfigura el color para ls
installCopia archivos y establece atributos
lnCrea un enlace a un archivo
lsEnumera los archivos en un directorio
mkdirCrea un directorio
mkfifoCrea pipes con nombre (FIFOs)
mknodCrea archivos especiales de bloque o caracter
mktempCrea un archivo o directorio temporal
mvMueve archivos o renombra archivos
realpathDevuelve la ruta absoluta o relativa resuelta para un archivo
rmElimina (borra) archivos, directorios, nodos de dispositivos y enlaces simbólicos
rmdirElimina directorios vacíos
shredSobrescribe un archivo para ocultar su contenido y, opcionalmente, lo elimina
syncLibera los búferes del sistema de archivos
touchCambia las marcas de tiempo del archivo; crea archivo
truncateReduce o extiende el tamaño de un archivo al tamaño especificado
vdirEs exactamente igual a “ls -l -b”. (Los archivos se enumeran por defecto en formato largo).
b2sumCalcula y verifica el resumen del mensaje BLAKE2b
base32Codifica o decodifica Base32 e imprime el resultado en la salida estándar
catConcatena e imprime archivos en la salida estándar
cksumSuma 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.
commCompara dos archivos ordenados línea por línea
csplitDivide un archivo en secciones determinadas por líneas de contexto
cutElimina secciones de cada línea de archivos
expandConvierte tabuladores en espacios
fmtFormateador de texto simple y óptimo
foldEnvuelve cada línea de entrada para que se ajuste al ancho especificado
headMuestra la primera parte de los archivos
joinUne líneas de dos archivos en un campo común
md5sumCalcula y verifica el resumen del mensaje MD5
nlNumera líneas de archivos
numfmtReformatea números
odVuelca archivos en octal y otros formatos
pasteCombina líneas de archivos
ptxProduce un índice permutado del contenido del archivo
prConvierte archivos de texto para imprimir
sha1sum, sha224sum, sha256sum, sha384sum, sha512sumCalcula y verifica resúmenes de mensajes SHA-1/SHA-2
shufgenera permutaciones aleatorias
sortOrdena líneas de archivos de texto
splitDivide un archivo en partes
sumSuma de comprobación y cuenta los bloques en un archivo
tacConcatena e imprime archivos en orden inverso línea por línea
tailMuestra la última parte de los archivos
trTraduce o elimina caracteres
tsortRealiza una ordenación topológica
unexpandConvierte espacios en tabuladores
uniqElimina líneas duplicadas de un archivo ordenado
wcImprime el número de bytes, palabras y líneas en archivos
archImprime el nombre del hardware de la máquina (igual que uname -m)
basenameElimina el prefijo de ruta de un nombre de ruta
chrootCambia el directorio raíz
dateImprime o configura la fecha y hora del sistema
dirnameElimina el sufijo que no es directorio del nombre del archivo
duMuestra el uso del disco en los sistemas de archivos
echoMuestra una línea de texto especificada
envMuestra y modifica variables de entorno
exprEvalúa expresiones
factorDescompone números en factores primos
falseNo hace nada, pero sale sin éxito
groupsMuestra los grupos a los que pertenece el usuario
hostidImprime el identificador numérico del host actual
idImprime UID y GID real o efectivo
linkCrea un enlace a un archivo
lognameImprime el nombre de inicio de sesión del usuario
niceModifica la prioridad de programación
nohupPermite que un comando siga ejecutándose después de cerrar la sesión
nprocConsulta el número de procesadores (activos)
pathchkComprueba si los nombres de archivo son válidos o portables
pinkyUna versión ligera de finger
printenvImprime variables de entorno
printfFormatea e imprime datos
pwdImprime el directorio de trabajo actual
readlinkMuestra el valor de un enlace simbólico
runconEjecuta un comando con el contexto de seguridad especificado
seqImprime una secuencia de números
sleepSe retrasa durante un período de tiempo específico
statDevuelve datos sobre un inodo
stdbufControla el almacenamiento en búfer para comandos que usan stdio
sttyCambia e imprime la configuración de la línea de terminal
teeEnvía la salida a varios archivos
testEvalúa una expresión
timeoutEjecuta un comando con un límite de tiempo
trueNo hace nada, pero sale con éxito
ttyImprime el nombre del terminal
unameImprime información del sistema
unlinkElimina el archivo especificado usando la función unlink
uptimeIndica cuánto tiempo ha estado funcionando el sistema
usersImprime los nombres de usuario de los usuarios actualmente conectados en el host actual
whoImprime una lista de todos los usuarios actualmente conectados
whoamiImprime el userid efectivo
yesImprime 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.

CompiladorArquitecturaMedidaPropósito
GCCTodas-WtrampolinesHabilita mensajes de alerta sobre trampolines que requieren stacks ejecutables
GCC / ClangTodas-Wall -WextraHabilita mensajes de alerta sobre uso de variables o código que pueden conducir a problemas de seguridad
GCC / ClangTodas-Wformat -Wformat=2Habilita mensajes de alerta sobre el uso de ciertos formatos de sintaxis que puede conducir a problemas de seguridad
GCC / ClangTodas-Wconversion -Wsign-conversionHabilita mensajes de alerta sobre el uso de conversiones en tipos (un char y un int por ejemplo)
GCC / ClangTodas-Wimplicit-fallthroughHabilita mensajes de alerta cuando hay casos de uso en donde el “switch” puede fallar
GCC / ClangTodas-WerrorTrata todos los mensajes de alerta como fallos y hasta que no se resuelven no prosigue
GCC / ClangTodas-D_FORTIFY_SOURCE=3Verifica el uso de funciones de la libreria libc para detectar usos inseguros y de buffer overflow, y trata de corregirlos
GCC / ClangTodas-D_GLIBCXX_ASSERTIONS -D_LIBCPP_ASSERTVerifica las llamadas a la librería estándar de C++ para detectar usos inseguros y de buffer overflow, y trata de corregirlos
GCC / ClangTodas-fstrict-flex-arrays=3Si una estructura de arreglos (arrays) fija está vacía la convierte en una flexible
GCC / ClangTodas-fstack-protector-strongHabilita verificaciones en tiempo de ejecución (cuando el programa se ejecuta) para detectar buffer overflows en el stack
GCC / ClangTodasfstack-clash-protectionHabilita verificaciones en tiempo de ejecución (cuando el programa se ejecuta) para validar las alocaciones de tamaño variable en el stack
GCC / Clangx86 / x86_64-fcf-protection=fullHabilita protección de flujo para el ROP (Return Oriented Programming) y el JOP (Jump Oriented Programming)
GCC / ClangAArch64 (arm64)-mbranch-protection=standardHabilita protección de ramificación (branch) para el ROP (Return Oriented Programming) y el JOP (Jump Oriented Programming)
GCC / ClangTodas-fno-delete-null-pointer-checksFuerza la retención en las verificaciones de punteros nulos
GCC / ClangTodas-fno-strict-aliasingNo asume un solapamiento estricto
GCC / ClangTodas-ftrivial-auto-var-initRealiza inicializaciones “triviales” de variables para evitar valores inválidos
Binutils (ld)Todas-Wl,-z,nodlopenRestringe el uso de la llamada “dlopen” a shared objects (los archivos .so)
Bintuils (ld)Todas-Wl,-z,noexecstackPreviene la ejecución de datos, marcando las partes de la memoria stack como no-ejecutable
Binutils (ld)Todas-Wl,-z,relro -Wl,-z,nowMarca 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:

CompiladorArquitecturaMedidaPropósito
Bintuils / GCC / ClangTodas-fPIE -pieConstruye el ejecutable como posición independiente.
Bintuils / GCC / ClangTodas-fPIC -sharedConstruye 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;

CompiladorArquitecturaMedidaPropósito
RustcTodas“-C”, “link-arg=-static”Enlaza el binario final como estático contra las librerías compartidas
RustcTodas“-C”, “target-feature=+crt-static”Enlaza el binario final como estático contra el sistema operativo y la librería estándar de C
RustcTodas“-C”, “link-arg=-fuse-ld=mold”Usa mold como enlazador para aumentar la performance de enlazamiento
RustcTodas“-C”, “opt-level=2”Usa el segundo nivel de optimización en el binario final
RustcTodas“-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 paqueteArchivo de configuraciónCompiladorLugar de configuración
pacman/etc/makepkg.confGCC / ClangCFLAGS
pacman/etc/makepkg.confGCC / ClangCXXFLAGS
pacman/etc/makepkg.confBinutilsLDFLAGS
pacman/etc/makepkg.confRustcRUSTFLAGS
apkNo tiene, se usan las variables--
aptNo tiene, se usan las variables--
dnfNo 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