GNU/Linux >> Tutoriales Linux >  >> Linux

Identifique las propiedades de seguridad en Linux usando checksec

La compilación del código fuente produce un binario. Durante la compilación, puede proporcionar indicadores al compilador para habilitar o deshabilitar ciertas propiedades en el binario. Algunas de estas propiedades son relevantes para la seguridad.

Checksec es una pequeña herramienta ingeniosa (y script de shell) que, entre otras funciones, identifica las propiedades de seguridad que se incorporaron en un binario cuando se compiló. Un compilador puede habilitar algunas de estas propiedades de forma predeterminada y es posible que deba proporcionar indicadores específicos para habilitar otras.

Este artículo explica cómo usar checksec para identificar las propiedades de seguridad en un binario, incluyendo:

  1. Los comandos subyacentes que checksec utiliza para encontrar información sobre las propiedades de seguridad
  2. Cómo habilitar las propiedades de seguridad utilizando GNU Compiler Collection (GCC) al compilar un binario de muestra

Instalar checksec

Para instalar checksec en Fedora y otros sistemas basados ​​en RPM, use:

$ sudo dnf install checksec

Para distribuciones basadas en Debian, use el equivalente apt comando.

El script de shell

Checksec es un script de shell de un solo archivo, aunque bastante grande. Una ventaja es que puede leer el script rápidamente y comprender todos los comandos del sistema que se ejecutan para encontrar información sobre archivos binarios o ejecutables:

$ file /usr/bin/checksec
/usr/bin/checksec: Bourne-Again shell script, ASCII text executable, with very long lines

$ wc -l /usr/bin/checksec
2111 /usr/bin/checksec

Tome checksec para una unidad con un binario que probablemente ejecute a diario:el ubicuo ls dominio. El formato del comando es checksec --file= seguido de la ruta absoluta de ls binario:

$ checksec --file=/usr/bin/ls
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        Yes   5       17              /usr/bin/ls

Cuando ejecuta esto en una terminal, ve un código de colores que muestra lo que es bueno y lo que probablemente no lo es. Digo "probablemente" porque incluso si algo está en rojo, no significa necesariamente que las cosas sean horribles; podría significar que los proveedores de la distribución hicieron algunas concesiones al compilar los binarios.

La primera línea proporciona varias propiedades de seguridad que normalmente están disponibles para binarios, como RELRO , STACK CANARY , NX , y así sucesivamente (lo explico en detalle más abajo). La segunda línea muestra el estado de estas propiedades para el binario dado (ls , en este caso). Por ejemplo, NX enabled significa que alguna propiedad está habilitada para este binario.

Un binario de muestra

Para este tutorial, usaré el siguiente programa "hola mundo" como binario de muestra.

#include <stdio.h>

int main()
{
        printf("Hello World\n");
        return 0;
}
 

Tenga en cuenta que no proporcioné gcc con banderas adicionales durante la compilación:

$ gcc hello.c -o hello
 
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ ./hello
Hello World

Ejecute el binario a través de checksec. Algunas de las propiedades son diferentes que con el ls comando anterior (en su pantalla, estos pueden aparecer en rojo):

$ checksec --file=./hello
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   85) Symbols       No    0       0./hello
$

Cambiando el formato de salida

Checksec permite varios formatos de salida, que puede especificar con --output . Elegiré el formato JSON y canalizaré la salida a jq utilidad para una bonita impresión.

Para seguir, asegúrese de tener jq instalado porque este tutorial usa este formato de salida para buscar rápidamente propiedades específicas de la salida e informar yes o no en cada:

$ checksec --file=./hello --output=json | jq
{
  "./hello": {
    "relro": "partial",
    "canary": "no",
    "nx": "yes",
    "pie": "no",
    "rpath": "no",
    "runpath": "no",
    "symbols": "yes",
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"
  }
}

Recorriendo las propiedades de seguridad

Más sobre seguridad

  • La guía de codificación defensiva
  • Seminario web:Automatización de la seguridad y el cumplimiento del sistema con un sistema operativo estándar
  • 10 capas de seguridad de contenedores de Linux
  • Libro para colorear de SELinux
  • Más artículos de seguridad

El binario anterior incluye varias propiedades de seguridad. Compararé ese binario con el ls binario anterior para examinar qué está habilitado y explicar cómo checksec encontró esta información.

1. Símbolos

Voy a empezar con el fácil primero. Durante la compilación, se incluyen ciertos símbolos en el binario, principalmente para la depuración. Estos símbolos son necesarios cuando está desarrollando software y requieren varios ciclos para depurar y arreglar cosas.

Estos símbolos generalmente se eliminan (eliminan) del binario final antes de que se publique para uso general. Esto no afecta la ejecución del binario de ninguna manera; se ejecutará tal como lo haría con los símbolos. La eliminación a menudo se realiza para ahorrar espacio, ya que el binario es algo más claro una vez que se eliminan los símbolos. En el software propietario o de código cerrado, los símbolos a menudo se eliminan porque tener estos símbolos en un binario hace que sea más fácil inferir el funcionamiento interno del software.

Según checksec, los símbolos están presentes en este binario, pero no estaban en el ls binario. También puede encontrar esta información ejecutando el file comando en el programa:verá not stripped en la salida hacia el final:

$ checksec --file=/bin/ls --output=json | jq | grep symbols
    "symbols": "no",

$ checksec --file=./hello --output=json | jq | grep symbols
    "symbols": "yes",

$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

¿Cómo encontró checksec esta información? Bueno, proporciona un útil --debug opción para mostrar qué funciones se ejecutaron. Por lo tanto, ejecutar el siguiente comando debería mostrarle qué funciones se ejecutaron dentro del script de shell:

$ checksec --debug --file=./hello

En este tutorial, busco los comandos subyacentes utilizados para encontrar esta información. Dado que es un script de shell, siempre puede utilizar las funciones de Bash. Este comando generará todos los comandos que se ejecutaron desde el script de shell:

$ bash -x /usr/bin/checksec --file=./hello

Si se desplaza por la salida, debería ver un echo_message seguido de la categoría de la propiedad de seguridad. Esto es lo que informa checksec sobre si el binario contiene símbolos:

+ readelf -W --symbols ./hello
+ grep -q '\.symtab'
+ echo_message '\033[31m96) Symbols\t\033[m  ' Symbols, ' symbols="yes"' '"symbols":"yes",'

Para simplificar esto, checksec utiliza el readelf utilidad para leer el binario y proporciona un --symbols especial bandera que enumera todos los símbolos dentro del binario. Luego busca un valor especial, .symtab , que proporciona un recuento de entradas (símbolos) que encuentra. Puede probar los siguientes comandos en el binario de prueba que compiló anteriormente:

$ readelf -W --symbols ./hello
$ readelf -W --symbols ./hello | grep -i symtab

Cómo quitar símbolos

Puede eliminar los símbolos después de la compilación o durante la compilación.

  • Compilación de publicaciones: Después de la compilación, puede usar la strip utilidad en el binario para eliminar los símbolos. Confirme que funcionó usando el file comando, que ahora muestra el resultado como stripped :
    $ gcc hello.c -o hello
    $
    $ file hello
    hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, not stripped
    $
    $ strip hello
    $
    $ file hello
    hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, stripped
    $

Cómo eliminar símbolos durante la compilación

En lugar de eliminar los símbolos manualmente después de la compilación, puede pedirle al compilador que lo haga por usted proporcionando el -s argumento:

$ gcc -s hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, for GNU/Linux 3.2.0, stripped
$

Después de volver a ejecutar checksec, puede ver que symbols se muestran como no :

$ checksec --file=./hello --output=json | jq | grep symbols
    "symbols": "no",
$

2. Canarias

Los canarios son valores conocidos que se colocan entre un búfer y los datos de control en la pila para monitorear los desbordamientos del búfer. Cuando se ejecuta una aplicación, se le asignan dos tipos de memoria. Uno de ellos es una pila , que es simplemente una estructura de datos con dos operaciones:push , que coloca los datos en la pila y pop , que elimina datos de la pila en orden inverso. La entrada maliciosa podría desbordar o corromper la pila con una entrada especialmente diseñada y hacer que el programa se bloquee:

$ checksec --file=/bin/ls --output=json | jq | grep canary
    "canary": "yes",
$
$ checksec --file=./hello --output=json | jq | grep canary
    "canary": "no",
$

¿Cómo comprueba checksec si el binario está habilitado con un canario? Usando el método anterior, puede reducirlo ejecutando el siguiente comando dentro del script de shell:

$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'

Habilitar canary

Para protegerse contra estos casos, el compilador proporciona el -stack-protector-all indicador, que agrega código adicional al binario para verificar tales desbordamientos de búfer:

$ gcc -fstack-protector-all hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep canary
    "canary": "yes",

Checksec muestra que la propiedad ahora está habilitada. También puede verificar esto con:

$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)
    83: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2.4
$

3. pastel

PIE significa ejecutable independiente de la posición. Como sugiere el nombre, es un código que se coloca en algún lugar de la memoria para su ejecución, independientemente de su dirección absoluta:

$ checksec --file=/bin/ls --output=json | jq | grep pie
    "pie": "yes",

$ checksec --file=./hello --output=json | jq | grep pie
    "pie": "no",

A menudo, PIE está habilitado solo para bibliotecas y no para programas de línea de comandos independientes. En el resultado a continuación, hello se muestra como LSB executable , mientras que la libc biblioteca estándar (.so ) el archivo está marcado como LSB shared object :

$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ file /lib64/libc-2.32.so
/lib64/libc-2.32.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, for GNU/Linux 3.2.0, not stripped

Checksec intenta encontrar esta información con:

$ readelf -W -h ./hello | grep EXEC
  Type:                              EXEC (Executable file)

Si prueba el mismo comando en una biblioteca compartida en lugar de EXEC , verá un DYN :

$ readelf -W -h /lib64/libc-2.32.so | grep DYN
  Type:                              DYN (Shared object file)

Habilitar pastel

Para habilitar PIE en un programa de prueba, envíe los siguientes argumentos al compilador:

$ gcc -pie -fpie hello.c -o hello

Puede verificar que PIE esté habilitado usando checksec:

$ checksec --file=./hello --output=json | jq | grep pie
    "pie": "yes",
$

Debería mostrarse como un ejecutable PIE con el tipo cambiado de EXEC a DYN :

$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, for GNU/Linux 3.2.0, not stripped

$ readelf -W -h ./hello | grep DYN
  Type:                              DYN (Shared object file)

4. NX

NX significa "no ejecutable". A menudo está habilitado en el nivel de la CPU, por lo que un sistema operativo con NX habilitado puede marcar ciertas áreas de la memoria como no ejecutables. A menudo, los exploits de desbordamiento de búfer colocan código en la pila y luego intentan ejecutarlo. Sin embargo, hacer que esta área de escritura no sea ejecutable puede prevenir tales ataques. Esta propiedad está habilitada de forma predeterminada durante la compilación regular usando gcc :

$ checksec --file=/bin/ls --output=json | jq | grep nx
    "nx": "yes",

$ checksec --file=./hello --output=json | jq | grep nx
    "nx": "yes",

Checksec determina esta información con el siguiente comando. RW hacia el final significa que la pila es legible y escribible; ya que no hay E , no es ejecutable:

$ readelf -W -l ./hello | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

Deshabilitar NX para fines de demostración

No se recomienda, pero puede deshabilitar NX al compilar un programa usando -z execstack argumento:

$ gcc -z execstack hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep nx
    "nx": "no",

Tras la compilación, la pila se vuelve ejecutable (RWE ), que permite la ejecución de código malicioso:

$ readelf -W -l ./hello | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

5. RELRO

RELRO significa Reubicación de solo lectura. Un binario de formato enlazable ejecutable (ELF) utiliza una tabla de compensación global (GOT) para resolver funciones dinámicamente. Cuando está habilitada, esta propiedad de seguridad hace que GOT dentro del binario sea de solo lectura, lo que evita alguna forma de ataques de reubicación:

$ checksec --file=/bin/ls --output=json | jq | grep relro
    "relro": "full",

$ checksec --file=./hello --output=json | jq | grep relro
    "relro": "partial",

Checksec encuentra esta información usando el siguiente comando. Aquí se habilita una de las propiedades de RELRO; por lo tanto, el binario muestra "parcial" al verificar a través de checksec:

$ readelf -W -l ./hello | grep GNU_RELRO
  GNU_RELRO      0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW

Habilitar RELRO completo

Para habilitar RELRO completo, use los siguientes argumentos de la línea de comandos al compilar con gcc :

$ gcc -Wl,-z,relro,-z,now hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep relro
    "relro": "full",

Ahora, la segunda propiedad también está habilitada, haciendo que el programa esté lleno RELRO:

$ readelf -W -l ./hello | grep GNU_RELRO
  GNU_RELRO      0x002dd0 0x0000000000403dd0 0x0000000000403dd0 0x000230 0x000230 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW
 0x0000000000000018 (BIND_NOW)          

6. Fortificar

Fortify es otra propiedad de seguridad, pero está fuera del alcance de este artículo. Me iré aprendiendo cómo checksec verifica fortificar en binarios y cómo se habilita con gcc como un ejercicio para que lo abordes.

$ checksec --file=/bin/ls --output=json | jq  | grep -i forti
    "fortify_source": "yes",
    "fortified": "5",
    "fortify-able": "17"

$ checksec --file=./hello --output=json | jq  | grep -i forti
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"

Otras funciones de checksec

El tema de la seguridad es interminable, y aunque no es posible cubrir todo aquí, quiero mencionar algunas características más del checksec comandos que hacen que sea un placer trabajar con ellos.

Ejecutar contra múltiples binarios

No es necesario que proporcione cada binario para checksec individualmente. En su lugar, puede proporcionar una ruta de directorio donde residen múltiples archivos binarios, y checksec los verificará todos de una vez:

$ checksec --dir=/usr/bin

Procesos

Además de los binarios, checksec también funciona en programas durante la ejecución. El siguiente comando encuentra las propiedades de seguridad de todos los programas en ejecución en su sistema. Puede usar --proc-all si desea que verifique todos los procesos en ejecución, o puede seleccionar un proceso específico usando su nombre:

$ checksec --proc-all

$ checksec --proc=bash

Propiedades del núcleo

Además de las aplicaciones de la zona de usuario de checksec descritas en este artículo, también puede usarlas para verificar las propiedades del kernel integradas en su sistema:

$ checksec --kernel

Pruébalo

Checksec es una buena manera de entender qué propiedades de kernel y de espacio de usuario están habilitadas. Revise cada propiedad de seguridad en detalle e intente comprender las razones para habilitar cada función y los tipos de ataques que previene.


Linux
  1. Depurar Linux usando ProcDump

  2. 13 tutoriales de seguridad de Linux

  3. Usando el comando ripgrep (rg) en Linux

  4. Ejemplos de uso del comando dmsetup en Linux

  5. Ejemplo de uso de getnstimeofday en el kernel de Linux

Instale MongoDB usando Vagrant en Linux

15 cosas que debe saber antes de usar Kali Linux

Uso del reenvío de puertos SSH como herramienta de seguridad en Linux

Usando el comando Watch en Linux

Usando cut en Linux Terminal

Seguridad de Linux frente a Windows