Blog técnico Entelgy Innotec Security
images/banners/security-garage-banner-04_300.png

¡Hola a todos!

Mi nombre es Pablo y os voy a presentar un artículo sobre cómo configurar un entorno de terminal de una manera personalizada e interactiva. A ver si logro despertar vuestra curiosidad y genero otra visión de la terminal, donde los usuarios consigan simplificar procesos, comprender ciertos comportamientos de la Shell o llamar la atención de sus colegas Geek con una terminal llena de clores y comandos personalizados. (¡La terminal no es el demonio que creéis que es!)

Antes de nada, ¡vamos a iniciarnos en la materia! La Shell es un programa que tiene como función proporcionar una interfaz entre el usuario y el sistema operativo, es un intérprete de comandos. Bash es sólo uno de los tantos Shell que existen para plataformas UNIX-GNU/Linux.

Los intérpretes de comandos tienen en común las funcionalidades:

  • Permitir a los usuarios ejecutar comandos,
  • Proponer una serie de caracteres especiales que permiten desencadenar acciones concretas.
  • Poseen comandos internos y palabras clave mediante algunos de los cuales se puede programar (scripting) y utiliza ficheros de inicialización que permite a un usuario parametrizar su entorno de trabajo.

Afortunadamente, los intérpretes de comandos más utilizados en la actualidad derivan todos del Shell Bourne y tienen, por consiguiente, un cierto número de funcionalidades en común. 

Estructura de intérprete de comandos

Por norma general toda interfaz sigue un esquema, para que el usuario sea capaz de interactuar y ejecutar comandos en ella. Y para facilitar esto tenemos:

  • PROMPT => Nos indica donde está el usuario y donde va a escribir los comandos
  • Comando=> es la orden que escribiremos para que sea ejecutada
    • [argumentos] => va separado por espacios del comando y son opciones adicionales del comando a utilizar. Dependiendo del comando pueden ir delimitados por guiones (Ej: -h ) o por doble guion(Ej: --help)

[/ruta] => son argumentos que pueden necesitarse o no por ejemplo cuando especificamos que diccionario utilizar en un ataque de fuerza bruta ( Ej: /usr/share/wordlists/dirbuster/hacker@fall-Linux:~$ comando [--argumentos] /ruta1 /ruta2

C:\Windows\system32>ipconfig [--argumento]

Ficheros que usa Bash

Bash utiliza una serie de ficheros de inicialización con los que genera el entorno de usuario. Cada archivo tiene una función especifica y son solo utilizados en el inicio de sesión, en su finalización o se disparan ante ciertos eventos.

De todos modos, diferenciaremos 2 tipos de ficheros de configuración.

Los globales: situados en (/etc) que generalmente proporcionan configuraciones aplicables al resto del sistema. Principalmente veremos los siguientes:

/etc/profile --> lanza algunos scripts (bash_completition), define          variables de entorno y llama a /etc/bash.bashrc

/etc/bash.bashrc --> config file de bash para todo el sistema, cada vez que se lanza bash, define variables, funciones, alias, promt... (fichero presente en distros tipo Debian, Ubuntu...)

Los personales: ubicados en el directorio personal de un usuario (/home), que definen configuración específica e incluso, puede anular la             configuración global.

Cuando se lanza una shell de inicio de sesión interactivo, usando /bin/login, leyendo el archivo /etc/passwd lee /etc/profile y su equivalente personal /home/user/.bash_profile o simplemente /home/user/.profile.

Pero... ¿Qué contienen estos ficheros?

El contenido de estos ficheros varía según la distribución o sistema operativo, este ejemplo lo veremos en una maquina Debian 10.

/etc/profile

1 # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
2 # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
3
4 if [ "`id -u`" -eq 0 ]; then
5 PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
6 else
7 PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
8 fi
9 export PATH
10
11 if [ "${PS1-}" ]; then
12 if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
13 # The file bash.bashrc already sets the default PS1.
14 # PS1='\h:\w\$ '
15 if [ -f /etc/bash.bashrc ]; then
16 . /etc/bash.bashrc
17 fi
18 else
19 if [ "`id -u`" -eq 0 ]; then
20 PS1='# '
21 else
22 PS1='$ '
23 fi
24 fi
25 fi
26
27 if [ -d /etc/profile.d ]; then
28 for i in /etc/profile.d/*.sh; do
29 if [ -r $i ]; then
30 . $i
31 fi
32 done
33 unset i
34 fi

Como vemos, se definen variables como PATH y PS1 (se explican más adelante) y busca el fichero /etc/bash.bashrc y ejecuta los scripts del directorio /etc/profile.d/.

/home/user/.profile

1 # ~/.profile: executed by the command interpreter for login shells.
2 # This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
3 # exists.
4 # see /usr/share/doc/bash/examples/startup-files for examples.
5 # the files are located in the bash-doc package.
6
7 # if running bash
8 if [ -n "$BASH_VERSION" ]; then
9 # include .bashrc if it exists
10 if [ -f "$HOME/.bashrc" ]; then
11 . "$HOME/.bashrc"
12 fi
13 fi
14
15 # set PATH so it includes user's private bin if it exists
16 if [ -d "$HOME/bin" ] ; then
17 PATH="$HOME/bin:$PATH"
18 fi
19
20 # set PATH so it includes user's private bin if it exists
21 if [ -d "$HOME/.local/bin" ] ; then
22 PATH="$HOME/.local/bin:$PATH"
23 fi

Este fichero vemos que define alguna variable y llama al fichero /home/user/.bashrc

Cualquier otro comando, variable o código presente en estos ficheros se ejecutará cada vez que iniciemos sesión en la shell con el usuario específico y se aplicará de manera global en el sistema, dependiendo de si son los globales o los personales.

Por ejemplo, vamos a agregar al final del fichero .bashrc el comando: ls

Al abrir la terminal veremos que nos mostrará los directorios de /home/user, a causa de la ejecución del comando: ls

También podemos agregar un: echo “Bienvenido Hacker!”, o algo visualmente más elaborado como un banner.

Y cada vez que ejecutemos una shell con este usuario, se ejecutarán los comandos:

O un algo más útil, como un comando que nos muestre nuestra IP pública:

El resultado del comando “wget http://ipecho.net/plain -O - -q ; echo” es la IP pública que por razones obvias la he censurado:

Todas las configuraciones, comandos y ejemplos que veremos a continuación se pueden definir en estos ficheros de configuración expuestos.

Pero nos vamos a centrar en uno de los ficheros de configuración personales para que sea más sencillo de exponer y poner en práctica, sin poner en riesgo la configuración del sistema. El fichero que editaremos en todos los ejemplos, será el “.bashrc”.

Otros ficheros interesantes son:

.bash_logout: Ejecuta código cuando cerramos sesión en bash.

.bash_history: Contiene el historial de comandos. (Lo que consulta Bash cuando le das a la flechita de arriba). 

¡Recomiendo crear un usuario de prueba para jugar con estos ficheros y cuando nos guste, aplicarlo a nuestro usuario personal o incluso a root! 

Opciones de la shell

El shell Bash propone una serie de opciones que permiten la configuración de varias funcionalidades, que pueden ayudarnos a personalizar y estar un poco más cómodos con la terminal. 

Activar/desactivar una opción de shell

Los parámetros “-o” y “+o” del comando interno “set” nos permite activar o desactivar estas opciones:

set -o --> lista las opciones disponibles en el sistema
set -o opcion --> activa la opción
set +o opción --> desactiva la opción

Output: “set -o” en Debian 10

Aquí os dejo unos ejemplos de lo que hacen estas opciones:

La opción “ignoreeof”:

Activando esta opción, evitaremos mandar la shell a segundo plano de manera accidental pulsando “control+d”.

La activamos con el comando set y al teclear Control+D observaremos lo siguiente:

La opción “noclobber”:

Cuando realizamos una redirección con el caracter “>” hacia un fichero existente, la shell sobrescribe sin advertencia previa dicho fichero, lo cuál puede ser peligroso, pero, con la opción siguiente nos protegeremos ante este riesgo.

Sin la opción activada, vemos como el tercer comando lanzado: echo "Adioooos mundo cruel!" > ejemplo_noclobber.txt ha sobrescrito el fichero que contenía simplemente “Hola mundo!” sin aviso.

Vamos a ver qué pasa con la opción de la shell “noclobber” activada:

Para sobrescribirlo deberemos forzar la sobrescritura con: “>|

¡Qué útil! ¿No te estás muriendo de curiosidad por saber lo que hacen todas las otras opciones de la shell? Investiga y descúbrelo tú mismo: https://www.tldp.org/LDP/abs/html/options.html

Cuando cerremos la shell estas opciones volverán a su estado por defecto, si nos ha gustado alguna en concreto, podemos mantenerla siempre activada agregando el comando para activarla en el fichero .bashrc

Cuando abramos otra vez la terminal ya las tendremos configuradas.

Alias

Bash ofrece un comando interno llamado “alias” que permite crear atajos de comandos más complejos. Las distribuciones suelen incluir unos cuantos definidos por defecto.

Yo tengo el .bashrc lleno de alias que me ahorran mucho tiempo, e incluso hacen que no sea necesario consultar la ayuda de un comando que suelo usar si no me acuerdo de cierto parámetro, etc.

Por ejemplo, siempre uso el comando “tar -xvf” para extraer ficheros de un archivador, sería interesante crear un alias que se llame “untar” por ejemplo, que haga referencia al comando comentado. También sería interesante uno que lance “apt update && apt upgrade”, para actualizar el sistema de manera más ágil.

Podemos obtener un listado de alias escribiendo en la shell el comando: alias

Para eliminar un alias, se hace con: unalias nombre_alias

Un gran ejemplo de alias es con el comando rm, que si le agregamos el parámetro -i nos preguntará si queremos eliminar el fichero antes de hacerlo:

Si e quiero que cada vez que lance el comando rm se ejecute con ese parámetro podemos asignar un alias.

¿No se te ocurren comandos que suelas hacer y que tengas que estar poniendo parámetros o algunas opciones que siempre son las mismas? A mí se me están ocurriendo un montón y por eso tengo el .bashrc lleno de estos alias.

#Custom aliases
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'
alias cdd='cd ${OLDPWD}'
alias mv='mv -iv'
alias cp='cp -iv'
alias rm='rm -v'
alias untar='tar -xvf'
alias fullminar='shred --size=10M --iterations=10 -u --verbose --zero --remove'
alias cls='clear; ls -CF'
alias :q='exit'
#Sudo aliases
alias fuck='sudo $(history -p !!)'
alias actualiza='sudo apt update && apt upgrade -y'
#External aliases
alias navegador_de_la_ostia="firefox &"
alias youtube_m4a="youtube-dl --extract-audio --audio-format mp3"
alias PUBLIC_IP="wget http://ipecho.net/plain -O - -q ; echo"

Variables

Empecemos definiendo que una variable es un espacio de memoria con un valor asignado.

Algunas de las funciones básicas de la terminal dependen de manera directa de la existencia de algunas variables y estas modifican el comportamiento de la terminal. Como cuando le indicas un comando y la shell es capaz de encontrar el binario. El nombre del usuario, el color de las letras, funciones, directorios o configuraciones por defecto, se rigen según  el valor de ciertas variables.

¿Y por qué se definen variables en los ficheros de configuración previamente mostrados?

Antes de nada, diferenciaría 2 tipos de variables dentro de un entorno de terminal:

  • Variables Locales: aquellas que se encuentran accesibles en el proceso de la shell actual.
  • Variables de Entorno: accesible por el proceso de shell actual y cualquier proceso hijo.

Esto delimita la accesibilidad al contenido de estas variables. Si yo en mi proceso de shell actual defino la variable local “perro” y abrimos otra shell dentro del mismo contexto de ejecución nos encontraremos con lo siguiente:

Como observamos, el valor de la variable está varío en un proceso hijo.

La variable local definida en la primera shell (bajo PID 21553), no podrá ser leída por ninguno de sus hijos (los de abajo).

¿Qué pasa si defino una variable dentro del fichero /home/user/.bashrc?

1º Agregamos al final del fichero .bashrc la variable “perro=golden”:

2º Ejecutamos un proceso hijo y consultamos el contenido de la variable “perro”:

A partir de ahora, cada vez que el usuario al cual le hemos editado el fichero .bashrc, la variable se definirá y se tendrá acceso a su contenido, esto nos interesa mucho para configuraciones, variables o códigos, que queramos ejecutar y mantener.

Como hemos comentado, Bash, trabaja con ciertas variables que cuentan con información necesaria para que la shell lleve a cabo ciertas tareas básicas. 

Variables de entorno principales por defecto

$USER = nombre de usuario
$HOME = directorio personal y de inicio del usuario (~)
$PWD = directorio en el que el usuario se encuentra actualmente
$OLDPWD = directorio anterior
$PATH = directorios que el shell explora cuando se invoca un comando externo.
$PS1 = Promt

Estas no son ni mucho menos todas las variables de entorno que el shell utiliza, si tienes curiosidad puedes usar el comando “set” para obtener un listado de todas las variables que están definidas en el contexto de ejecución en el que te encuentras, no te asustes si son muchas, es normal. Las variables es uno de los recursos principales para cualquier software.

Como hemos dicho, las variables contienen información, para mostrar el contenido de una variable, lo podemos hacer con: echo $VARIABLE

A las variables en Bash les precede un “$”.

Vamos a ver el contenido de la variable “PWD”:

Si cambiamos de directorio el valor de esa variable cambia según el directorio en el cual nos encontramos:

Esto sucede con variables como la de $USER que su valor cambia por el del usuario. Esto es muy interesante y nos da mucha flexibilidad a la hora de hacer referencia a algo.

Otra variable interesante es la de $OLDPWD, que indica el directorio anterior en el que hemos estado:

El valor de esta variable ahora mismo es “/home/fall/Downloads”. Podemos usar el contenido de esta variable para usarla como argumento en un comando o script, de este modo.

Nos acabamos de ahorrar escribir toda la ruta, poniendo solo la variable, pero vamos un poco más lejos... ¿Y si hacemos un alias con esta variable llamada “cdd” que nos lleve al directorio anterior?

alias cdd='cd $OLDPWD'

PS1

Anteriormente hemos hablado del PROMPT, que indica donde se sitúa el usuario y escribe los comandos, al ser una de las variables que estamos constantemente usando y viendo, se convierte en un gran aspecto para personalizar.

Según lo que hemos visto anteriormente, es una variable, pero ¿que contiene en un sistema Debian 10?

El contenido de esta variable a simple vista es muy poco legible, por lo cual vamos a simplificarlo para ir explicando cómo funciona.

Vamos a asignar una flechita con un espacio al final (“--> “) como valor para la variable $PS1 de la siguiente manera.

Como vemos, el texto “fall@cliente:~$ “ ha cambiado de manera inmediata a “-->

También podemos meter alias u otras variables dentro del $PS1

PS1="$USER"@"$TERM $ "

Vemos que bash interpreta las variables como fall@xterm-256color $

Pero los creadores de Bash nos han dado algunas herramientas para “ayudarnos” a configurar el PROMPT a nuestro gusto, con colores u otros parámetros, como la hora.

Esto se hace por ejemplo, escribiendo “\d” para indicar hora o “\h” para el hostname.

LINK parámetros PROMPT: https://www.cyberciti.biz

Para dar color a ciertas partes del prompt:

Indicamos el inicio de un color con: \e[

El color que queramos de la tabla de colores para Bash (link abajo): azul = 0;36

El fin del color con: \e[m

PS1="\e[0;36m$USER"@"$TERM\e[m $ "

Ahora el PROMPT es azul.

Un ejemplo chulo del PROMPT es este, pero es complejo.

\[\033[0;31m\]\342\224\214\342\224\200$([[ $? != 0 ]] && echo "[\[\033[0;31m\]\342\234\227\[\033[0;37m\]]\342\224\200")[\[\033[0;39m\]\u\[\033[01;33m\]☠\[\033[01;96m\]\h\[\033[0;31m\]]\342\224\200[\[\033[0;32m\]\w\[\033[0;31m\]]\n\[\033[0;31m\]\342\224\224\342\224\200\342\224\200\342\225\274 \[\033[0m\]\[\e[01;33m\]\$\[\e[0m\]

(link tabla colores: https://www.cyberciti.biz/)

PATH

La variable $PATH es una variable súper importante en el shell, que el sistema encuentre o no un comando cuando lo escribimos depende directamente de esta variable.

Vamos a ver el contenido de la variable $PATH en Debian 10:

Cuando escribamos un comando como “shred”, lo buscará en esos directorios:

Como vemos, este comando lo encuentra porque se encuentra dentro de “/usr/bin/”.

Pongámonos en la piel de un usuario que se ha creado un directorio en su carpeta personal, llamada “Programs” y ha creado allí sus scripts. Si este usuario ejecuta por ejemplo, el comando “backup_config_files” que es el script que ha creado, nos dirá que no lo encuentra.

Esto sucede porque el directorio “/home/hax0r/Programs/” no está en la variable $PATH, por lo que vamos a proceder a agregarlo, esto se hace añadiendo la ruta a la variable, separada por “:” de las demás rutas:

PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/hax0r/Programs/'

Ahora si lo encuentra, (whereis es un comando que busca programas dentro de la variable $PATH) esta variable la podemos definir en todos los .bashrc de los usuarios para que puedan crear su directorio personal de scripts sin que tengan que tocar los directorios de sistema.

Pero... si agregamos el $PATH, tal y como lo tenemos... sólo será útil para el usuario “hax0r”.

¿Cómo hacemos que sea extrapolable a cualquier usuario?

¡Exacto! Con variables de entorno predefinidas. La variable $HOME contiene el directorio personal del usuario, lo cual si agregamos la variable continuada del nombre del directorio “Programs” y lo guardamos en el .bashrc, ya tendremos un $PATH en condiciones que podrá usar cualquier usuario:

Ahora esta variable la podremos guardar en un fichero .bashrc con directorios que utilicemos comúnmente  y aplicarlo en cualquier usuario.

Scripting básico: para consolidar todo lo anterior

Lo que veremos a continuación no va a ser nada nuevo para alguien con conocimientos, pero para cualquier principiante puede abrirle un mundo y puede ayudarle a ser más ágil a la hora de interactuar con la shell.

Lo primero es definir que es un “Script” o “Scripting”:

  • Un script en shell es un fichero de texto ejecutable que contiene comandos internos o externos, así como palabras y sintáxis propias de la shell. (Para hacer un fichero ejecutable: chmod +x nombre_script)

Los scripts de Bash suelen comenzar con: “#!/bin/bash

Un ejemplo de script es este:

Lo que va a hacer este script es ejecutar línea a línea los comandos.

  1. Ejecutará el comando pwd, que nos dirá dónde nos encontramos,
  2. Nos moverá a “/tmp”, nos dirá dónde estamos y nos llevará a nuestra carpeta personal (podemos usar variables de entorno sin problemas).

Si metemos este script en el directorio “Programs”, presente en la variable $PATH podremos ejecutarlo desde donde sea.

Pero llevando el tema de la shell scripting a algo más útil, podríamos hacer un script que nos hiciese un backup de los ficheros de configuración, que resultaría en la copia de los ficheros que hemos comentado al inicio de este artículo.

El programa mostrará un par de mensajes al usuario entre intervalos de 2 segundos, creará la carpeta oculta “.backup_personal_profile/” y copiará todos los ficheros que empiecen por “.bash”:

Y también copiará el fichero “.profile” del directorio personal.

Vemos que antes de ejecutar el script, no existía el directorio “.backup_personal_profile/”, veámos después de ejecutarlo:

Ahora podríamos empaquetar ese directorio comprimiéndolo, etc.

¡También podemos agregar opciones de shell dentro de los scripts! Por ejemplo, la opción “pipefail” es muy útil para evitar problemas.

Por ejemplo, si antes de llegar a la parte donde se copian los ficheros tiene un problema grave, bash va a continuar con la ejecución:

Esto puede ser muy peligroso, porque pueden suceder comportamientos inesperados, parámetros que no sabemos lo que son... Un caos. La opción “pipefail” es muy útil para esto, si la declaramos al inicio de script, bash detendrá la ejecución en caso de error grave:

Aquí vemos como la ejecución se ha cortado y no ha llegado a crear el directorio y copiar los ficheros. ¡Genial!

Por último, comentar que esto que hemos hecho en este script, se puede hacer desde el PROMPT con un sólo comando, haciendo scripting en la propia línea de comandos junto los caracteres especiales siguientes:

| = "pipe" parametriza la salida a un comando (la salida de un comando se
&& = and lógico (cuando el comando acabe, haz otra cosa)
; = nuevo contexto de ejecución (cómo para ejecutar otro comando)

Por ejemplo, cuando nosotros lanzamos el comando “apt update” y después queremos ejecutar “apt upgrade” y contestar que sí queremos actualizar, hemos realizado 3 intervenciones con el sistema con sus respectivos tiempos de espera, pero gracias a los caracteres “&&”, podemos invocar el comando “apt upgrade -y” si va todo bien, después del “apt update” para automatizar el proceso.

sudo apt update && apt upgrade -y

Si “apt update” falla, no se continuará con el comando.

Otro uso interesante es cuando nos movemos de directorio y queremos listar:

cd Programs && ls

Un gran ejemplo con el pipe “|” es con el filtrado de texto cuando mostramos texto por terminal, pudiendo mostrar la primera línea:

Podemos buscar todas las líneas que contengan la palabra “alias”:

O las 8 primeras reiteraciones de esta palabra:

Otros caracteres interesantes:

" = para determinar que un string es texto
* = cualquier coincidencia: * [*.txt, bash*]
> = redirigir salida de un comando (a un txt por ejemplo)

Un último ejemplo de scripting:

curl -X GET wttr.in 2>&1 | tail -n 50 | head -n 19 | tail -n 10 > .tiempo.txt ; cat .tiempo.txt ; echo ''

Ya hemos acabado, espero que al menos haya resultado interesante y hayáis aprendido algo que no sabíais. Ahora es el momento de configurarte tus ficheros, hacer tus comandos personalizados y sentirte más cómodo con tu shell Bash. En un próximo artículo hablaré sobre Zsh, personalmente creo que es la evolución natural de Bash.

También dejo por aquí un link a mi fichero .bashrc personal:

LINK: https://gitlab.com/FallFur/

Un gran abrazo, ¡Nos vemos por la red!

Happy Hacking ~⠠⠵


Pablo Martínez

Pablo Martínez

 

S5 Box