Aprendiendo_Korn_Shell/Secciones/Capitulo10.tex

344 lines
44 KiB
TeX

Los administradores de sistemas utilizan el intérprete de comandos como parte de su trabajo de configurar un entorno en todo el sistema para todos los usuarios. En este capítulo, discutiremos las características del shell Korn que se relacionan con esta tarea desde dos perspectivas: la personalización que está disponible para todos los usuarios y la seguridad del sistema. Asumimos que ya conoce los fundamentos de la administración de sistemas Unix.\footnote{Una buena fuente de información sobre administración de sistemas es \emph{Essential System Administration} de Æleen Frisch. Está publicado por O'Reilly \& Associates.}
\section{Instalación del shell Korn como shell estándar}
Como preludio a la personalización de todo el sistema, queremos destacar algo sobre el shell Korn que no se aplica a la mayoría de los otros shells: puede instalarlo como si fuera el shell Bourne estándar, es decir, como \texttt{/bin/sh}. Simplemente guarde el shell Bourne real como otro nombre de archivo, como \texttt{/bin/bsh}, en caso de que alguien realmente lo necesite para algo (lo cual es dudoso), luego renombre (o enlace) su shell Korn como \texttt{/bin/sh}.
Muchas instalaciones han hecho esto sin ningún efecto negativo. Esto no sólo hace que el shell Korn sea el shell de inicio de sesión estándar de su sistema, sino que también hace que la mayoría de los scripts del shell Bourne existentes se ejecuten más rápido, y tiene ventajas de seguridad que veremos más adelante en este capítulo.
Como veremos en el \hyperref[sec:ApendiceA]{Apéndice A}, el shell Korn es retrocompatible con el shell Bourne excepto que no soporta \textasciicircum{} como sinónimo del carácter de tubería |. A menos que tenga un sistema Unix antiguo, o tenga algunos scripts de shell muy, muy antiguos, no necesita preocuparse por esto.
Pero si quiere estar absolutamente seguro, simplemente busque entre todos los scripts de shell en todos los directorios de su \texttt{PATH}. Una manera fácil de hacer esto es usar el comando \emph{file(1)}, que vimos en el \hyperref[sec:Chapter5]{Capítulo 5} y en el \hyperref[sec:Chapter9]{Capítulo 9}. \emph{file} imprime <<script de shell ejecutable>> cuando se le da el nombre de uno. (El mensaje exacto varía de un sistema a otro; asegúrese de que el suyo imprime este mensaje cuando se le da el nombre de un script de shell. Si no es así, simplemente sustituya el mensaje que imprime su comando \emph{file} por <<shell script>> en el siguiente ejemplo). He aquí un script que busca \textasciicircum{} en scripts de shell en cada directorio de su \texttt{PATH}:\footnote{Este script fallará si su PATH tiene directorios cuyos nombres contienen espacios. Considere solucionar este problema como un ejercicio avanzado para el lector.}
\begin{lstlisting}[language=bash]
dirs=$(print -- $PATH |
sed -e 's/^:/.:/' -e 's/::/:.:/' -e s'/:$/:./' -e 's/:/ /g')
for d in $dirs
do
print "checking $d:"
cd "$d"
scripts=$(file * | grep 'shell script' | cut -d: -f1)
grep -l '\^' $scripts /dev/null
done
\end{lstlisting}
La primera sentencia de este script separa \texttt{\$PATH} en directorios separados, incluyendo el manejo de varios casos de separadores vacíos que significan el directorio actual. El programa \emph{sed(1)} es un editor de flujo que realiza operaciones de edición en su entrada, e imprime los contenidos cambiados en su salida. El resultado se asigna a \emph{dirs}, que se utiliza como lista de elementos en el bucle \emph{for}. Para cada directorio, \emph{cd}s allí y encuentra todos los scripts de shell mediante la canalización del comando \emph{file} en \emph{grep} y luego, para extraer sólo el nombre de archivo, en \emph{cut}. A continuación, busca el carácter \textasciicircum{} en cada script. La opción \texttt{-l} de \emph{grep|} simplemente lista todos los nombres de archivo que coinciden con el patrón, sin imprimir las líneas coincidentes. El comando \emph{grep} tiene \emph{/dev/null} al final de la lista de archivos en caso de que \texttt{\$scripts} esté vacío. Si eres aventurero, puedes hacer todo el trabajo en una sola línea:
\begin{lstlisting}[language=bash]
grep -l '\^' $(file * | grep 'shell script' | cut -d: -f1) /dev/null
\end{lstlisting}
Si ejecutas este script, probablemente encontrarás varias apariciones de \textasciicircum{} pero éstas deberían ser usadas dentro de expresiones regulares en comandos \emph{grep, sed,} o \emph{awk}, no como caracteres de tubería. Asumiendo que este es el caso, es seguro que instale el shell Korn como \emph{/bin/sh}.
\section{Personalización del entorno}
Al igual que el shell de Bourne, el shell de Korn utiliza el archivo \texttt{/etc/profile} para la personalización a nivel del sistema. Cuando un usuario inicia sesión, el shell lee y ejecuta \texttt{/etc/profile} antes de ejecutar el \emph{.profile} del usuario.
No cubrimos todos los comandos posibles que podría querer colocar en \texttt{/etc/profile}. Pero el shell de Korn tiene algunas características únicas que son particularmente relevantes para la personalización a nivel del sistema; las discutimos aquí.
Comenzaremos con dos comandos integrados que puede utilizar en \texttt{/etc/profile} para adaptar los entornos de sus usuarios y limitar su uso de los recursos del sistema. Los usuarios también pueden usar estos comandos en su \emph{.profile}, o en cualquier otro momento, para anular la configuración predeterminada.
\subsection{umask}
\emph{umask}, al igual que el mismo comando en la mayoría de las otras shells, le permite especificar los permisos predeterminados que tienen los archivos cuando los usuarios los crean. Con \emph{ksh}, toma los mismos tipos de argumentos que el comando \emph{chmod}, es decir, valores de permisos absolutos (números octales) o simbólicos.
\emph{umask} contiene los permisos que se desactivan de manera predeterminada cada vez que un proceso crea un archivo, independientemente de los permisos que especifique el proceso. \footnote{Si conoce C, C++ o Java, y se siente cómodo con las operaciones bit a bit, la operación de \emph{umask} funciona así: \texttt{permiso\_real = permiso\_solicitado \& (\~{} umask)}.}
Otra forma de pensar en esto es como una sustracción libre de préstamos bit a bit: \emph{permisos reales = permisos solicitados - umask}.
Usaremos la notación octal para mostrar cómo funciona esto. Como debería saber, los dígitos en un número de permiso representan (de izquierda a derecha) los permisos del propietario, el grupo del propietario y todos los demás usuarios, respectivamente. Cada dígito, a su vez, consta de tres bits, que especifican permisos de lectura, escritura y ejecución de izquierda a derecha. (Si un archivo es un directorio, el permiso <<ejecutar>> se convierte en permiso <<buscar>>, es decir, permiso para \emph{cd} a él y recorrerlo como parte de una ruta de acceso).
Por ejemplo, el número octal 640 es igual al número binario 110 100 000. Si un archivo tiene este permiso, entonces su propietario puede leerlo y escribirlo; los usuarios en el grupo del propietario solo pueden leerlo; los demás no tienen permisos sobre él. Un archivo con permiso 755 (111 101 101 en binario) le da a su propietario el derecho de leer, escribir y ejecutarlo y a todos los demás el derecho de leer y ejecutar (pero no escribir).
022 es un valor de \emph{umask} común. Esto implica que cuando se crea un archivo, el permiso máximo que podría tener es 755, que es el permiso habitual de un ejecutable que un compilador podría crear. Un editor de texto, por otro lado, podría crear un archivo con permiso 666 (lectura y escritura para todos), pero la \emph{umask} obliga a que sea 644 en su lugar.
La opción \texttt{-S} para \emph{umask} hace que funcione con valores simbólicos en lugar de con números octales. Cuando se usa sin un argumento, \texttt{umask -S} imprime \emph{umask} en forma simbólica. Con un argumento, se cambia la máscara. En ambos casos, una máscara simbólica representa los permisos que se deben \emph{mantener} para un archivo. (Esto termina siendo el complemento bit a bit del umask octal tradicional, que representa permisos a eliminar). Si está confundido, algunos ejemplos deberían aclarar las cosas:
\begin{lstlisting}[language=bash]
$ umask # Imprime la umask actual, en octal
0022
$ umask -S # Imprime la umask actual, en forma simbólica
u=rwx,g=rx,o=rx
$ umask -S u=rwx,g=r,o= # Cambia la umask usando la forma simbólica
$ umask -S # Imprímelo nuevamente de manera simbólica
u=rwx,g=r,o=
$ umask # Imprímelo en octal
0037
\end{lstlisting}
\subsubsection{ulimit}
En los primeros sistemas Unix, no se imponían límites a los recursos que un proceso podía utilizar. Si un programa quería ejecutarse para siempre, podía hacerlo. Si un programa quería crear archivos grandes y llenar un disco, podía hacerlo. Y así sucesivamente.
A medida que Unix se desarrolló y maduró, se hizo posible controlar explícitamente o \emph{limitar} una variedad de recursos del sistema, como el tiempo de CPU y el espacio en disco. El comando \emph{ulimit} es la interfaz del shell de Korn para ver y cambiar los límites de los recursos del sistema. La Tabla \ref{Tab:10-1} enumera las opciones que acepta y los recursos correspondientes. No todas las opciones están disponibles en todos los sistemas Unix. Muchas no estarán disponibles en sistemas no Unix.
\begin{table}[h]
\center
\caption{Opciones de recursos \texttt{ulimit}}
\label{Tab:10-1}
\begin{tabular}{|m{2cm}|m{5.5cm}|m{2cm}|m{5.5cm}|} \hline
\textbf{Opción} & \textbf{Recursos limitados} & \textbf{Opción} & \textbf{Recursos limitados} \\\hline
-a & Todos (sólo para imprimir valores) & -n & Descriptores de archivos \\\hline
-c & Tamaño del archivo principal ($\frac{1}{2}$ bloques de kb) & -p & Tamaño del búfer de la tubería ($\frac{1}{2}$ bloques de kb) \tablefootnote{La mayoría de los sistemas Unix no tienen esta característica.} \\\hline
-d & Segmento de datos de proceso (kb) & -s & Segmento de pila de proceso (kb) \\\hline
-f & Tamaño del fichero ( $\frac{1}{2}$ kb bloques) & -t & Tamaño del fichero ( kb bloques) \\\hline
-m & Memoria física (kb) & -v & Memoria física (kb) \\\hline
\end{tabular}
\end{table}
Cada opción toma un argumento numérico que especifica el límite en las unidades que se muestran en la tabla. (Puede usar una expresión aritmética para el límite; \emph{ksh} evalúa automáticamente la expresión). También puede dar el argumento <<ilimitado>> (que en realidad puede significar algún límite físico), o puede omitir el argumento, en cuyo caso imprime el límite actual. \texttt{ulimit -a} imprime los límites (o <<ilimitado>>) para todos los tipos. Solo puede especificar un tipo de recurso a la vez. Si no especifica ninguna opción, se asume \texttt{-f}.
Algunas de estas opciones dependen de capacidades del sistema operativo que no existen en versiones antiguas de Unix. En particular, algunas versiones antiguas tienen un límite fijo de 20 descriptores de archivos por proceso (haciendo que \texttt{-n} sea irrelevante), y algunas no admiten memoria virtual (haciendo que \texttt{-v} sea irrelevante).
Las opciones \texttt{-d} y \texttt{-s} tienen que ver con la \emph{asignación de memoria dinámica}, es decir, la memoria que un proceso solicita al sistema operativo en tiempo de ejecución. No es necesario que los usuarios ocasionales limiten estos, aunque los desarrolladores de software pueden querer hacerlo para evitar que programas con errores intenten asignar cantidades interminables de memoria debido a bucles infinitos.
La opción \texttt{-v} es similar; pone un límite a todos los usos de memoria. No necesita esto a menos que su sistema tenga restricciones severas de memoria o desee limitar el tamaño del proceso para evitar la fragmentación.
Es posible que desee especificar límites en el tamaño de los archivos (\texttt{-f} y \texttt{-c}) si tiene restricciones de espacio en disco. A veces, los usuarios realmente quieren crear archivos enormes, pero más a menudo, un archivo enorme es el resultado de un programa con errores que entra en un bucle infinito. Los desarrolladores de software que usan depuradores como \emph{gdb} y \emph{dbx} no deben limitar el tamaño del archivo central, porque los volcados centrales a menudo son útiles para la depuración.
La opción \texttt{-t} es otra posible protección contra bucles infinitos. En sistemas de un solo usuario, un programa que está en un bucle infinito pero no está asignando memoria, escribiendo archivos ni utilizando la red no es particularmente peligroso; es mejor dejar esto ilimitado y simplemente permitir que el usuario detenga el programa ofensor. Sin embargo, en sistemas de servidores compartidos, tales programas degradan definitivamente el entorno general. El problema en ese caso es que es difícil saber qué límite establecer: hay usos importantes y legítimos para programas de larga duración.
Además de los tipos de recursos que puede limitar, \emph{ulimit} le permite especificar límites duros o blandos. Los límites duros pueden disminuirlos cualquier usuario, pero solo aumentarlos el superusuario (\texttt{root}); los usuarios pueden disminuir los límites blandos y aumentarlos, pero solo hasta el límite duro para ese recurso.
Si proporciona \texttt{-H} junto con una (o más) de las opciones anteriores, \emph{ulimit} establece límites duros; \texttt{-S} establece límites blandos. Sin ninguno de estos, \emph{ulimit} establece ambos. Por ejemplo, los siguientes comandos establecen el límite blando en descriptores de archivos en 64 y el límite duro en ilimitado:
\begin{lstlisting}[language=bash]
ulimit -Sn 64
ulimit -Hn unlimited
\end{lstlisting}
Cuando \emph{ulimit} imprime los límites actuales, imprime los límites blandos a menos que especifique \texttt{-H}.
\subsection{Tipos de personalización global}
El enfoque más adecuado para la personalización global disponible sería un archivo de entorno a nivel del sistema que sea independiente del archivo de entorno de cada usuario, al igual que \texttt{/etc/profile} es independiente del \emph{.profile} de cada usuario.
Desafortunadamente, el shell de Korn no tiene esta característica. Si asigna un nombre de archivo a la variable de entorno \texttt{ENV}, podría anularse en el \emph{.profile} de un usuario. Esto le permite proporcionar un archivo de entorno predeterminado para los usuarios que no tienen el suyo propio, pero no le permite tener un archivo de entorno a nivel del sistema que se ejecute además de los de los usuarios. Además, el archivo de entorno solo se ejecuta para shells interactivas, no para todas las shells.
Sin embargo, el shell le proporciona algunas formas de configurar personalizaciones que están disponibles para todos los usuarios en todo momento. Las variables de entorno son las más obvias; su archivo \texttt{/etc/profile} seguramente contendrá definiciones para varias de ellas, incluyendo \texttt{PATH} y \texttt{TERM}.
La variable \texttt{TMOUT} es útil cuando su sistema admite líneas de conexión telefónica. Ya hemos visto que afecta al comando \emph{read} y al bucle del menú \texttt{select}. Cuando se establece en un número \emph{N}, si un usuario no ingresa un comando dentro de \emph{N} segundos después de que el shell emitió por última vez un indicador, el shell imprime el mensaje de advertencia <<el shell se cerrará en 60 segundos debido a inactividad>>. Si, después de otros 60 segundos, el usuario no ingresa nada, el shell se cierra. Esta función es útil para evitar que las personas <<acaparen>> las líneas telefónicas. ¡Solo asegúrese de establecerlo en un valor razonable!
Es posible que desee incluir algunas personalizaciones más complejas que involucren variables de entorno, como la cadena de indicadores PS1 que contiene el directorio actual, el nombre de usuario o el nombre del host (como se ve en el \hyperref[sec:Chapter4]{Capítulo 4}).
También puede activar opciones, como los modos de edición \emph{emacs} o \emph{vi}, \emph{noclobber} para protegerse contra la sobrescritura inadvertida de archivos y tal vez \emph{ignoreeof} para evitar que las personas cierren la sesión accidentalmente cuando escriben demasiados caracteres CTRL-D. Cualquier script de shell que haya escrito para uso general también contribuye a la personalización.
Desafortunadamente, no es posible crear un alias global. Puede definir alias en \texttt{/etc/profile}, pero no hay forma de hacer que formen parte del entorno para que sus definiciones se propaguen a los subprocesos del shell.
Sin embargo, puede configurar funciones globales. Estas son una excelente manera de personalizar el entorno de su sistema, porque las funciones son parte del shell, no procesos separados. Por ejemplo, es posible que desee hacer que \emph{pushd} y \emph{popd} (consulte el \hyperref[sec:Chapter4]{Capítulo 4} a través del \hyperref[sec:Chapter6]{Capítulo 6}) estén disponibles de manera global.
La mejor manera de crear funciones globales es utilizar la variable integrada \texttt{FPATH} para la carga automática de funciones que presentamos en el \hyperref[sec:Chapter4]{Capítulo 4}. Simplemente defina \texttt{FPATH} como un directorio de biblioteca de funciones, tal vez \texttt{/usr/local/functions}, y conviértalo en una variable de entorno \emph{exportándola}. Luego, asegúrese de que el directorio enumerado en \texttt{FPATH} también esté incluido en \texttt{PATH}. En otras palabras, coloque este código o similar en \texttt{/etc/profile}:
\begin{lstlisting}[language=bash]
FPATH=/usr/local/functions
PATH=$PATH:$FPATH
export FPATH PATH
\end{lstlisting}
Luego, coloque la definición de cada función global en un archivo en ese directorio con el mismo nombre que la función.
En cualquier caso, sugerimos utilizar funciones globales para la personalización global en lugar de scripts de shell. Dada la abundancia de memoria en la actualidad, no hay razón para no incluir funciones generalmente útiles como parte del entorno de sus usuarios.
\section{Personalización de los modos de edición}
Como vimos en el \hyperref[sec:Chapter2]{Capítulo 2}, tienes la opción de utilizar los modos de edición de emacs o vi al editar tu línea de comandos. Además de los comandos disponibles en cada modo, puedes personalizar el comportamiento de los modos de edición según tus necesidades o entorno.
El \hyperref[sec:ApendiceA]{Apéndice A} aborda varios shells de terceros basados en el diseño de las shells Bourne y Korn. Esos shells generalmente proporcionan edición de línea de comandos, así como la capacidad de personalizar el editor mediante un comando integrado especial, un archivo de inicio especial o ambos.
El enfoque del shell de Korn es diferente. Se basa en un paradigma en el que programas el comportamiento que deseas del shell. Esto se logra mediante una trampa falsa, llamada KEYBD. Si existe, la trampa establecida para KEYBD se evalúa cuando \emph{ksh} procesa caracteres normales de entrada de línea de comandos. \footnote{Los caracteres para cadenas de búsqueda y argumentos numéricos para comandos de modos \emph{vi} y \emph{emacs} no activan la trampa KEYBD.}
Dentro del código ejecutado para la trampa, dos variables especiales contienen el texto de la línea de comandos y el texto que se está ingresando y que causó la trampa. Variables adicionales especiales te permiten distinguir entre los modos \emph{emacs} y \emph{vi} e indicar la posición actual en la línea de entrada. Estas variables se enumeran en la Tabla \ref{Tab:10-2}.
\begin{table}[h]
\center
\caption{Variables de edición especiales}
\label{Tab:10-2}
\begin{tabular}{|m{3cm}|m{12cm}|} \hline
\textbf{Variable} & \textbf{Significado} \\ \hline
.sh.edchar & El carácter o secuencia de escape introducido por el usuario que causó la trampa KEYBD. El valor de \texttt{.sh.edchar} al final de la trampa se utiliza entonces para dirigir las acciones del editor incorporado. \\\hline
.sh.edcol & La posición del cursor en la línea de entrada actual. \\\hline
.sh.edmode & Igual a ESC en modo \emph{vi}, vacío en caso contrario. (Utilice \texttt{[[ -n \$\{.sh.edmode\} ]]} para probarlo). \\\hline
.sh.edext & El texto de la línea de entrada actual. \\\hline
\end{tabular}
\end{table}
Al ingresar en la trampa KEYBD, el contenido de \texttt{.sh.edchar} será un solo carácter, ESC seguido de un solo carácter o ESC, [, y un solo carácter. Puedes asignar un nuevo valor a \texttt{.sh.edchar} para cambiar la entrada que recibe el modo de edición actual. Así, la trampa KEYBD te permite interponer un <<filtro>> entre lo que ingresa el usuario y lo que los modos de edición del shell procesan realmente. El siguiente ejemplo es de la página 98 de <<The New KornShell Command and Programming Language>>. \footnote{Este es el libro sobre \emph{ksh93} escrito por David Korn y Morris Bolsky y publicado por Prentice Hall.}
Presenta una función \emph{keybind} que te permite asignar nuevas acciones a secuencias clave de entrada, similar al comando integrado \emph{bind} de muchas otras shells.
\begin{lstlisting}[language=bash]
# Citado de la Página 98 de
# "The New KornShell Command and Programming Language"
1 typeset -A Keytable
2 trap 'eval "${Keytable[${.sh.edchar}]}"' KEYBD
3 function keybind # key [action]
4 {
5 typeset key=$(print -f "%q" "$2")
6 case $# in
7 2) Keytable[$1]=' .sh.edchar=${.sh.edmode}'"$key"
8 ;;
9 1) unset Keytable[$1]
10 ;;
11 *) print -u2 "Usage: $0 key [action]"
12 return 2 # los errores de uso devuelven 2 por defecto
13 ;;
14 esac
15 }
\end{lstlisting}
Esta es una función interesante. Vamos a revisarla línea por línea. La línea 1 crea un array asociativo para actuar como una tabla de pares clave/acción. La línea 2 establece la trampa KEYBD. Obtiene la acción del array asociativo y luego la ejecuta usando \emph{eval}. La línea 3 inicia la función \emph{keybind}, que toma uno o dos argumentos. Con dos argumentos, el segundo argumento se cita adecuadamente primero (línea 5, la variable \emph{key} habría sido mejor nombrada \texttt{action}). La línea 7 luego crea la entrada en el array, utilizando \texttt{\$1} (la secuencia de teclas del usuario) como índice y action citada como el valor a asignar a \texttt{.sh.edchar}. Observa cómo también se incluye \texttt{\$\{.sh.mode\}}. Esto tiene el efecto de forzar un cambio al modo de comando para el modo de edición \emph{vi}. Es esta declaración de asignación generada la que se \emph{evalúa} cada vez que se ejecuta la trampa.
El resto de la función es principalmente tareas administrativas: con un argumento (línea 9), se elimina la entrada dada en el array \texttt{Keytable}. Si hay más de dos argumentos (línea 11), \emph{keybind} imprime un mensaje y luego devuelve el valor (falso) 2.
Aunque algo inusual, el mecanismo de trampa KEYBD para tratar la entrada del usuario es tanto general como extensible; puedes hacer lo que quieras, simplemente como un Asunto Simple de Programación. Con otras shells, estás limitado a las facilidades integradas que proporcionan.
\section{Funciones de seguridad del sistema}
Características de Seguridad del Sistema
La seguridad de Unix es un problema de notoria notoriedad. Prácticamente todos los aspectos de un sistema Unix tienen algún problema de seguridad asociado, y generalmente es trabajo del administrador del sistema preocuparse por este problema.
\textbf{NOTA:} Este no es un libro de texto sobre seguridad del sistema Unix. Ten en cuenta que esta sección apenas toca la punta del iceberg y que hay innumerables otros aspectos de la seguridad del sistema Unix además de cómo está configurada el shell. Consulta al final del capítulo un libro que recomendamos.
Primero presentamos una lista de <<consejos>> para escribir scripts de shell que tengan una mejor probabilidad de evitar problemas de seguridad. Luego cubrimos la \emph{shell restringida}, que intenta poner un chaleco de fuerza alrededor del entorno del usuario. Luego presentamos la idea de un <<caballo de Troya>> y por qué se deben evitar tales cosas. Finalmente, discutimos el \emph{modo privilegiado} del shell de Korn, que se utiliza con scripts de shell que se ejecutan como si el usuario fuera \texttt{root}.
\subsection{Consejos para Scripts de Shell Seguros}
Aquí tienes algunos consejos para escribir scripts de shell más seguros, cortesía del Profesor Eugene (Gene) Spafford, director del Centro de Educación e Investigación en Aseguramiento de la Información y Seguridad de la Universidad de Purdue \footnote{Ver \url {http://www.cerias.purdue.edu}.}:
\begin{description}
\item[No incluyas el punto (\emph{dot})en PATH] Este problema se describió en el \hyperref[sec:Chapter3]{Capítulo 3}. Esto abre la puerta de par en par a <<caballos de Troya>>, que se describen en la siguiente sección.
\item[Protege los directorios \texttt{bin}] Asegúrate de que cada directorio en \texttt{\$PATH} solo sea escribible por su propietario y por nadie más. Lo mismo se aplica a todos los programas \emph{en} los directorios \emph{bin}.
\item[Diseña antes de codificar] Dedica tiempo a pensar en lo que quieres hacer y cómo hacerlo; no simplemente escribas cosas con un editor de texto y sigas pirateando hasta que parezca funcionar. Incluye código para manejar errores y fallas de manera elegante.
\item[Verifica la validez de todos los argumentos de entrada] Si esperas un número, verifica que obtuviste un número. Comprueba que el número esté en el rango correcto. Haz lo mismo para otros tipos de datos; las instalaciones de expresiones regulares del shell son particularmente útiles para esto.
\item[Verifica los códigos de error de todos los comandos que pueden devolver errores] Cosas que no esperas que fallen podrían forzarse maliciosamente a fallar para hacer que el script se comporte de manera incorrecta. Por ejemplo, es posible hacer que algunos comandos fallen incluso como root si el argumento es un disco montado por NFS o un archivo de dispositivo orientado a caracteres.
\item[No confíes en las variables de entorno proporcionadas] Verifícalas y restablécelas a valores conocidos si son utilizadas por comandos posteriores (por ejemplo, \texttt{TZ, FPATH, PATH, IFS}, etc.). El shell Korn restablece automáticamente \texttt{IFS} a su valor predeterminado al inicio, ignorando lo que estaba en el entorno, pero muchas otras shells no lo hacen. En todos los casos, es una excelente idea establecer explícitamente \texttt{PATH} para que contenga solo los directorios \emph{bin} del sistema.
\item[Comienza en un lugar conocido] Cambia explícitamente al directorio conocido cuando el script comienza para que cualquier ruta relativa posterior sea a un lugar conocido. Asegúrate de que el \emph{cd} tenga éxito:
\begin{lstlisting}[language=bash]
cd app-dir || exit 1
\end{lstlisting}
\item[Usa rutas completas para los comandos] Haz esto para saber qué versión estás obteniendo, independientemente de \texttt{\$PATH}.
\item[Utiliza \emph{syslog(8)} para mantener un registro de auditoría] Registra la fecha y hora de la invocación, el nombre de usuario, etc.; consulta \emph{logger(1)}. Si no tienes \emph{syslog}, crea una función para mantener un archivo de registro:
\begin{lstlisting}[language=bash]
function logsys {
print -r -- "$@" >> /var/adm/logsysfile
}
logsys "Ejecutado por el usuario " $(/bin/whoami) "($USER) en " $(/bin/date)
\end{lstlisting}
(\emph{whoami(1)} imprime el nombre de inicio del usuario efectivo, un concepto descrito más adelante en este capítulo).
\item[Siempre entrecomilla la entrada del usuario al usar esa entrada] Por ejemplo, \texttt{"\$1"} y \texttt{"\$*"}. Esto evita que la entrada del usuario maliciosa se evalúe y ejecute aún más.
\item[No uses \texttt{eval} en la entrada del usuario] Además de entrecomillar la entrada del usuario, no la pases a el shell para volver a procesarla con \emph{eval}. Si el usuario lee tu script y ve que usa \emph{eval}, es fácil subvertir el script para que haga casi cualquier cosa.
\item[Entrecomilla los resultados de la expansión de comodines] Hay varias cosas desagradables que puedes hacer a un administrador del sistema creando archivos con espacios, puntos y comas, comillas invertidas, etc., en los nombres de archivos. Si los scripts administrativos no entrecomillan los argumentos de los nombres de archivo, los scripts pueden dañar o revelar el sistema.
\item[Verifica la entrada del usuario en busca de metacaracteres] Busca metacaracteres como \texttt{\$} o \texttt{`} (sustitución de comandos antigua) si utilizas la entrada en un \emph{eval} o \texttt{\$(...)}.
\item[Prueba tu código y léelo críticamente] Busca suposiciones y errores que puedan ser explotados. Ponte de mal humor y lee tu código con la intención de tratar de descubrir cómo subvertirlo. Luego corrige cualquier problema que encuentres.
\item[Ten en cuenta las condiciones de carrera] Si un atacante puede ejecutar comandos arbitrarios entre dos comandos en tu script, ¿comprometerá la seguridad? Si es así, busca otra forma de hacerlo.
\item[Desconfía de los enlaces simbólicos] Cuando \emph{cambias} los permisos o editas un archivo, verifícalo para asegurarte de que sea un archivo y no un enlace simbólico a un archivo crítico del sistema. (Utiliza \texttt{[[ -L file ]]} o \texttt{[[ -h file ]]} para comprobar si \emph{file} es un enlace simbólico).
\item[Haz que otra persona revise tu código en busca de errores] A menudo, un par de ojos frescos puede detectar cosas que el autor original de un programa pasa por alto.
\item[Usa \emph{setgid} en lugar de \emph{setuid}, si es posible] Estos términos se discuten más adelante en este capítulo. En resumen, al usar \emph{setgid}, restringes la cantidad de daño que se puede hacer al grupo que está comprometido.
\item[Utiliza un usuario nuevo en lugar de \texttt{root}] Si debes usar \emph{setuid} para acceder a un grupo de archivos, considera crear un usuario nuevo, \emph{no root}, con ese propósito, y establece \emph{setuid} en él.
\item[Limita el código \emph{setuid} tanto como sea posible] Reduce al mínimo la cantidad de código \emph{setuid} que puedas. Muévelo a un programa separado e invócalo desde un script más grande cuando sea necesario. Sin embargo, ¡asegúrate de programar defensivamente como si el script pudiera ser invocado por cualquiera desde cualquier lugar!
\end{description}
Chet Ramey, el mantenedor de \emph{bash}, ofrece el siguiente prólogo para usar en scripts de shell que necesitan ser más seguros:
\begin{lstlisting}[language=bash]
# restablecer IFS, aunque ksh no importa IFS del entorno,
# $ENV podría establecerlo
IFS=$' \t\n'
# asegurarse de que unalias no sea una función, ya que es un comando incorporado regular
# unset es un comando incorporado especial, por lo que se encontrará antes que las funciones
unset -f unalias
# deshacer todos los alias
# citar unalias para que no sea alias-expandido
\unalias -a
# asegurarse de que command no sea una función, ya que es un comando incorporado regular
# unset es un comando incorporado especial, por lo que se encontrará antes que las funciones
unset -f command
# obtener un prefijo de ruta confiable, manejando el caso en que getconf no está disponible
# (no es demasiado necesario, ya que getconf es un comando incorporado de ksh93)
SYSPATH="$(command -p getconf PATH 2>/dev/null)"
if [[ -z "$SYSPATH" ]]; then
SYSPATH="/usr/bin:/bin" # elige tu veneno
fi
PATH="$SYSPATH:$PATH"
\end{lstlisting}
\subsubsection{Shell restringida}
El shell restringida está diseñada para colocar al usuario en un entorno donde su capacidad para moverse y escribir archivos esté severamente limitada. Generalmente se utiliza para cuentas de invitados. Cuando se invoca como \emph{rksh} (o con la opción \texttt{-r}), \emph{ksh} actúa como una shell restringida. Puedes hacer que el shell de inicio de sesión de un usuario sea restringida colocando la ruta completa de \emph{rksh} en la entrada \texttt{/etc/passwd} del usuario. El archivo ejecutable \emph{ksh} debe tener un enlace a él con el nombre \emph{rksh} para que esto funcione.
Las restricciones específicas impuestas por el shell restringida impiden al usuario hacer lo siguiente:
\begin{itemize}
\item Cambiar directorios de trabajo: \emph{cd} no está operativo. Si intentas usarlo, obtendrás el mensaje de error \texttt{ksh: cd: restricted}.
\item Redirigir la salida a un archivo: los redireccionadores \texttt{>, >|, <>, y >>} no están permitidos. Esto incluye el uso de \emph{exec}.
\item Asignar un nuevo valor a las variables de entorno \texttt{ENV, FPATH, PATH} o \texttt{SHELL}, o intentar cambiar sus atributos con \emph{typeset}.
\item Especificar rutas de comandos con barras inclinadas (/) en ellas. El shell solo ejecuta comandos encontrados a lo largo de \texttt{\$PATH}.
\item Agregar nuevos comandos incorporados con el comando \emph{builtin}. (Esta función muy avanzada está fuera del alcance de este libro).
\end{itemize}
Estas restricciones entran en vigencia después de que se ejecutan los archivos \emph{.profile} y de entorno del usuario. Esto significa que el entorno completo del usuario del shell restringida se configura en \emph{.profile}. Esto permite que el administrador del sistema configure el entorno según considere conveniente.
Para evitar que el usuario sobrescriba \texttt{\~{}/.profile}, no es suficiente hacer que el archivo sea de solo lectura para el usuario. O bien, el directorio principal no debe ser escribible por el usuario, o los comandos en \texttt{\~{}/.profile} deben \emph{cd} a un directorio diferente.
Dos formas comunes de configurar dichos entornos son configurar un directorio de comandos <<seguros>> y hacer que ese directorio sea el único en \texttt{PATH}, y configurar un menú de comandos del cual el usuario no pueda escapar sin salir del shell. En cualquier caso, asegúrate de que no haya otra shell en ningún directorio enumerado en \texttt{\$PATH}; de lo contrario, el usuario puede simplemente ejecutar esa shell y evitar las restricciones mencionadas anteriormente.
\textbf{ADVERTENCIA:} Aunque la capacidad de restringir el shell ha estado disponible (si no necesariamente compilada o documentada) desde la versión original 7 de Bourne shell, rara vez se usa. Configurar un entorno utilizable pero correctamente restringido es difícil en la práctica. Entonces, \emph{caveat emptor}.
\subsection{Caballos de Troya}
El concepto de un \emph{caballo de Troya} fue introducido brevemente en el \hyperref[sec:Chapter3]{Capítulo 3}. Un caballo de Troya es algo que parece inofensivo, o incluso útil, pero que contiene un peligro oculto.
Considera el siguiente escenario. El usuario John Q. Programmer (nombre de inicio de sesión \texttt{jprog}) es un excelente programador y tiene una gran colección de programas personales en \texttt{\~{}jprog/bin}. Este directorio ocurre primero en la variable \texttt{PATH} en \texttt{\~{}jprog/.profile}. Dado que es un buen programador, la administración lo ascendió recientemente a administrador del sistema.
Este es un campo completamente nuevo de actividad, y John, sin saberlo mejor, lamentablemente dejó su directorio \emph{bin} escribible. Llega W. M. Badguy, quien crea el siguiente script de shell, llamado \emph{grep}, en el directorio \emph{bin} de John:
\begin{lstlisting}[language=bash]
/bin/grep "$@"
case $(whoami) in # Verifica el nombre del ID de usuario efectivo
root) Cosas malas aquí # ¡Peligro, Will Robinson, peligro!
rm ~/jprog/bin/grep # Ocultar la evidencia
;;
esac
\end{lstlisting}
En sí mismo, este script no puede causar daño cuando \emph{jprog} está trabajando como \emph{él mismo}. El problema surge cuando \emph{jprog} usa el comando \emph{su(1)}. Este comando permite a un usuario regular <<cambiar de usuario>> a un usuario diferente. Por defecto, permite a un usuario regular convertirse en \texttt{root} (siempre y cuando ese usuario conozca la contraseña, por supuesto). El problema es que normalmente, \emph{su} usa cualquier \texttt{PATH} que hereda.\footnote{Adquiere el hábito de usar \texttt{su - user} para cambiar al usuario como si el usuario estuviera haciendo un inicio de sesión real. Esto evita la importación del \texttt{PATH} existente.}
En este caso, \texttt{\$PATH} incluye \texttt{\~{}jprog/bin}. Ahora, cuando \texttt{jprog}, trabajando como \texttt{root}, ejecuta \emph{grep}, en realidad ejecuta la versión del \emph{caballo de Troya} en su \emph{bin}. Esta versión ejecuta el \emph{grep} real, por lo que \texttt{jprog} obtiene los resultados que espera. Pero también ejecuta silenciosamente la parte de cosas malas aquí, como \texttt{root}. Esto significa que Unix permitirá que el script haga lo que quiera. \emph{Cualquier cosa}. Y para empeorar las cosas, al eliminar el caballo de Troya cuando ha terminado, ya no hay evidencia.
Los directorios \emph{bin} escribibles abren una puerta para los \emph{caballos de Troya}, al igual que tener un punto en el \texttt{PATH}. Tener scripts de shell escribibles en cualquier directorio bin es otra puerta. Así como cierras y aseguras las puertas de tu casa por la noche, ¡asegúrate de cerrar cualquier puerta en tu sistema!
\subsubsection{Setuid y modo privilegiado}
Muchos problemas de seguridad de Unix se centran en un atributo de archivo de Unix llamado el bit \emph{setuid} (set user ID). Es como un bit de permiso (consulta la discusión anterior sobre \emph{umask}): cuando un archivo ejecutable lo tiene activado, el archivo se ejecuta con un ID de usuario efectivo igual al propietario del archivo. El ID de usuario efectivo es distinto del ID de usuario real del proceso, y Unix aplica sus pruebas de permisos al ID de usuario efectivo del proceso.
Por ejemplo, supongamos que has escrito un programa de juego muy ingenioso que mantiene un archivo de puntuación privado mostrando a los 15 mejores jugadores en tu sistema. No quieres que el archivo de puntuación sea escribible por cualquier persona, porque cualquiera podría llegar y editarlo para convertirse en el jugador principal. Haciendo tu juego setuid a tu ID de usuario, el programa del juego puede actualizar el archivo, que es de tu propiedad, pero nadie más puede actualizarlo. (El programa del juego puede determinar quién lo ejecutó al mirar su ID de usuario real y usar eso para determinar el nombre de inicio de sesión).
La facilidad \emph{setuid} es una característica agradable para juegos y archivos de puntuación, pero se vuelve mucho más peligrosa cuando se usa para \texttt{root}. Hacer programas \emph{setuid} \texttt{root} permite a los administradores escribir programas que realizan ciertas tareas que requieren privilegios de \texttt{root} (por ejemplo, configurar impresoras) de manera controlada. Para establecer el bit \emph{setuid} de un archivo, el superusuario puede escribir \texttt{chmod 4755 nombrearchivo}; el 4 es el bit setuid.
Existe una facilidad similar a nivel de grupo, conocida (no sorprendentemente) como \emph{setgid} (set group ID). Usa \texttt{chmod 2755 nombrearchivo} para activar los permisos \emph{setgid}. Cuando haces un \texttt{ls -l} en un archivo \emph{setuid} o \emph{setgid}, la \emph{x} en el modo de permisos se reemplaza con una \emph{s}; por ejemplo, \texttt{-rws--s--x} para un archivo que es legible y escribible por el propietario, ejecutable por todos y tiene ambos bits \emph{setuid} y \emph{setgid} activados (modo octal 6711).
La sabiduría moderna de la administración del sistema dice que crear scripts de shell \emph{setuid} y \emph{setgid} es una muy, muy mala idea. Esto ha sido especialmente cierto bajo la C shell, porque su archivo de entorno \emph{.cshrc} introduce numerosas oportunidades para intrusiones. En particular, hay varias formas de engañar a un script de shell \emph{setuid} para que se convierta en una shell \emph{interactiva} con un ID de usuario efectivo de \texttt{root}. Esto es lo mejor que un cracker podría esperar: la capacidad de ejecutar cualquier comando como \texttt{root}.
\textbf{NOTA:} Existe una diferencia importante entre un script de shell \emph{setuid} y una shell \emph{setuid}. Esta última es una copia del ejecutable del shell, que se ha hecho pertenecer a \texttt{root} y se le ha aplicado el bit \emph{setuid}. En la sección anterior sobre caballos de Troya, supongamos que la parte de \emph{cosas malas} aquí era este código:
\begin{lstlisting}[language=bash]
cp /bin/ksh ~badguy/bin/myls
chown root ~badguy/bin/myls
chmod 4755 ~badguy/bin/myls
\end{lstlisting}
Recuerda, este código se ejecuta como \texttt{root}, por lo que funcionará. Cuando \texttt{badguy} ejecuta \emph{myls}, es un archivo ejecutable en código de máquina, y se honra el bit setuid. Hola shell que se ejecuta como \texttt{root}. ¡Adiós seguridad!
El \emph{modo privilegiado} fue diseñado para proteger contra scripts de shell \emph{setuid}. Esta es una opción \emph{set -o} (\texttt{set -o privileged} o \texttt{set -p}), pero el shell la activa automáticamente cada vez que ejecuta un script cuyo bit \emph{setuid} está activado, es decir, cuando el ID de usuario efectivo es diferente del ID de usuario real.
En el \emph{modo privilegiado}, cuando se invoca un script de shell Korn \emph{setuid}, el shell ejecuta el archivo \texttt{/etc/suid\_profile}. Este archivo debe escribirse de manera que restrinja los scripts de shell \emph{setuid} de manera similar a como lo hace la shell restringida. Como mínimo, debería hacer que \texttt{PATH} sea de solo lectura (\texttt{typeset -r PATH} o \texttt{readonly PATH}) y configurarlo en uno o más directorios <<seguros>>. Una vez más, esto evita que se invoquen señuelos.
Dado que el modo privilegiado es una opción, es posible desactivarlo con el comando \texttt{set +o privileged} (o \texttt{set +p}). Pero esto no ayuda al posible cracker del sistema: el shell cambia automáticamente su ID de usuario efectivo para que sea igual al ID de usuario real, es decir, si desactivas el modo privilegiado, también desactivas \emph{setuid}.
Además del modo privilegiado, \emph{ksh} proporciona un programa <<agente>> especial, \texttt{/etc/suid\_exec}, que ejecuta scripts de shell \emph{setuid} (o scripts de shell que son ejecutables pero no legibles).
Para que esto funcione, el script no debe comenzar con \texttt{\#! /bin/ksh}. Cuando se invoca el programa, \emph{ksh} intenta ejecutar el programa como un ejecutable binario regular. Cuando el sistema operativo no puede ejecutar el script (porque no es binario y porque no tiene el nombre de un intérprete especificado con \texttt{\#!}), \emph{ksh} se da cuenta de que es un script e invoca \texttt{/etc/suid\_exec} con el nombre del script y sus argumentos. También se encarga de pasar un <<token>> de autenticación a \texttt{/etc/suid\_exec} que indica los ID de usuario y grupo reales y efectivos del script. \texttt{/etc/suid\_exec} verifica que es seguro ejecutar el script y luego se encarga de invocar \emph{ksh} con los ID de usuario y grupo reales y efectivos adecuados en el script.
Aunque la combinación de modo privilegiado y \texttt{/etc/suid\_exec} te permite evitar muchos de los ataques a scripts \emph{setuid}, escribir scripts que se puedan ejecutar \emph{setuid} de manera segura es un arte difícil, que requiere una buena cantidad de conocimiento y experiencia. Debería hacerse con mucho cuidado.
De hecho, los peligros de scripts de shell \emph{setuid} y \emph{setgid} (al menos para shells además de \emph{ksh}) son tan grandes que los sistemas Unix modernos, tanto los sistemas Unix comerciales como los clones de software libre (derivados de 4.4-BSD y GNU/Linux), desactivan los bits \emph{setuid} y \emph{setgid} en scripts de shell. Incluso si aplicas los bits al archivo, el sistema operativo no los respeta. \footnote{MacOS X parece ser una notable excepción. ¡Ten mucho cuidado si ejecutas uno o más de esos sistemas!}
Aunque los scripts de shell \emph{setuid} no funcionan en sistemas modernos, hay ocasiones en las que el modo privilegiado sigue siendo útil. En particular, existe un programa de terceros ampliamente utilizado llamado \emph{sudo}, que, citando la página web, <<permite a un administrador del sistema dar a ciertos usuarios (o grupos de usuarios) la capacidad de ejecutar algunos (o todos) los comandos como \texttt{root} u otro usuario mientras registra los comandos y argumentos>>. La página de inicio de \emph{sudo} es \url{http://www.courtesan.com/sudo/}. Un administrador del sistema podría ejecutar fácilmente \texttt{sudo /bin/ksh -p} para obtener un entorno conocido para realizar tareas administrativas.
Finalmente, si deseas aprender más sobre la seguridad de Unix, recomendamos \emph{Practical UNIX \& Internet Security} de Simson Garfinkel y Gene Spafford. Está publicado por O'Reilly \& Associates.