Tag: Seguridad

Comprobar permisos de Administrador en una ejecución (estilo 🐍 Python)

Habitualmente nos encontramos con la situación en la que debemos ejecutar ciertos programas o flujos de ejecución y necesitamos permisos de Administrador. Estas situaciones se nos presentan cuando tenemos que manejar carpetas y ficheros en acciones que impliquen lectura, escritura o ejecución en directorios protegidos del sistema. Para ello se requiere una elevación de permisos que deberemos comprobar si existe antes de lanzar nuestra ejecución.

Aprovechando que seguramente tengamos que hacer esto varias veces, creo que la mejor opción de hacerlo es con el mejor lenguaje pegamento que existe hasta la fecha, que no es ni más ni menos que Python. Por ello la solución final que vamos a plantear es excelente tanto para administradores de sistemas como para desarrolladores, siendo además multiplataforma GNU/Linux y Windows.

Tenemos que tener en cuenta que GNU/Linux y Windows no funcionan igual para la gestión de permisos. En Windows preguntaremos desde la API disponible si el usuario es administrador, mientras que en GNU/Linux preguntaremos por si el usuario tiene permisos root.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0

    print ("Admin privileges: {}".format(is_admin))
    return is_admin

Para mirar los permisos en Windows usaremos la librería ctypes y para los permisos en GNU/Linux usaremos la librería os. Ambas están incluida en Python. Por una razón arbitraría se ha elegido comprobar primero GNU/Linux y después Windows.

Cuando se comprueba en GNU/Linux invocamos a la función os.getuid que nos devolverá 0 en el caso de ser root. Si llamamos a la función desde Windows con Python nos elevará una excepción del tipo AttributeError, que aprovecharemos a capturar para hacer la comprobación en Windows invocando a la función ctypes.windll.shell32.IsUserAnAdmin. En ambos casos guardaremos el booleano de la evaluación y lo devolveremos al final de la función.

Hasta aquí sencillo, pero esto es código Python y es necesario tener el interprete de Python, con el que obliga al sistema a tener instalado el intérprete de Python. Para solucionarlo, deberíamos convertir nuestro código en un programa añadiendo un punto de entrada y devolviendo un código de retorno tras la ejecución.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    is_win = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
        is_win = True

    print ("Admin privileges: {}".format(is_admin))
    return is_admin, is_win

Antes modificaremos un poco la función is_admin()añadiendo un valor is_win que usaremos para saber que estamos comprobando en GNU/Linux o Windows los permisos de administrador, devolviendo el valor como un segundo valor en el retorno de la función.

Recordad que Python es posible recoger múltiples valores de retorno en una variable como una tupla, en variables separadas o incluso tirar los valores que no nos interesen.

def my_function():
    a = 2
    b = 3
    return a, b

a = my_function()
print(a)  # out: (2, 3)

a, b = my_function()
print(a)  # out: 2
print(b)  # out: 3

a, _ = my_function()  #  b es ignorado
print(a)  # out: 2

Además añadimos un punto de entrada debajo de la función is_admin().

if __name__ == "__main__":
    is_admin, is_win = is_admin()
    # Converting boolean to integer 
    ret = int(is_admin == False)
    if is_win:
        exit(ret)
    else:
        exit(ret * -1)

Comprobar si __name__ es __main__, es una excelente forma de hacer que cada unidad de fichero Python sea ejecutable si se quiere. El nombre de __main__ sólo es dado al fichero Python que es dado al intérprete para ser ejecutado y en caso de no serlo, siempre puede ser importado en otro fichero Python, sin que el punto de entrada afecte.

Como vemos, ahora estamos recogiendo en el retorno is_admin e is_win. Necesitamos el segundo valor para decidir el valor de retorno, ya que los códigos de error en Windows son enteros positivos y en GNU/Linux son enteros negativos. El único punto en común es que devolver un 0 es señal de que la ejecución fue correcta. Sabiendo esto debemos adaptar el retorno de los errores dependiendo el sistema.

💾 Binarizando nuestra aplicación Python

Para convertirlo en un ejecutable usaremos PyInstaller, el cual podéis instalar con un simple pip install pyinstaller. Para binarizar nuestra aplicación en un ejecutable autocontenido con el interprete de Python ejecutaremos:

pyinstaller -F is_admin.py

De esta forma resultará, mucho mas sencilla la distribución de nuestra aplicación.

👔 Puesta en Producción

Ahora vamos a plantear los dos casos de uso para Windows y GNU/Linux. Como aclaración, decir que los ejemplos expuestos se pueden usar tanto con la versión binarizada o con la de código invocando al intérprete de Python.

Windows

@echo off

cd
cd /D "%~dp0"
cd

#REM python is_admin.py
is_admin.exe

if errorlevel 1 (
   echo Exit Code is %errorlevel%
   echo.
   echo Admin privileges required
   echo Run it again as Administrator
   echo.
   pause
   exit /b %errorlevel%
)

#REM [your_executable].exe

pause

👀 Puedes descargar la versión binarizada de is_admin.exe

GNU/Linux

#!/bin/bash

pwd
cd `dirname $0`
SCRIPTDIR=`pwd`
pwd

#python is_admin.py
is_admin


if [ $? -eq 0 ]
then
  # [your_executable]
else
  echo Exit Code is $?
  echo.
  echo Admin privileges required
  echo Run it again as Administrator
  echo.
  read -rsp $'Press any key to continue...\n' -n 1 key
  exit $?
fi

read -rsp $'Press any key to continue...\n' -n 1 key

Si no estás tan familiarizado con el Bash te invito a que visites el proyecto ExplainShell para que puedas obtener la explicación de los diferentes comandos y argumentos.

🥅 Conclusiones

La solución presentada, sin ser seguramente perfecta, asegura tanto a administradores de sistemas como desarrolladores, comprobar si existen los permisos de administrador necesarios de manera sencilla; pudiendo hacerlo con una versión binarizada autocontenida que incluya el intérpretete Python, ejecutándolo usando el intérprete de Python del sistema gracias a ser todo dependencias internas, o llamando directamente a la función is_admin() si se quiere integrar en un desarrollo.

El código completo puedes encontrarlo en el siguiente gist de GitHub:

Espero que esta forma de trabajar te resulte útil y puedas extrapolarla y usar para otros casos Python como tu lenguaje pegamento generando código y aplicaciones altamente reutilizables.

Resetear contraseña olvidada de tu Raspberry Pi

La semana pasada me encontré con el problema de tener que acceder vía SSH a una Raspberry Pi (rpi), para poder gobernar una serie de maquinas accesibles desde la red local a la que se conectaba dicha rpi, y no poder hacerlo porque no recordaba la contraseña...

Al ver que no recordaba la contraseña me puse a buscar la mejor solución para poder volver a tener acceso a la rpi. Alguno puede pensar que con volver a poner el sistema operativo Raspbian, queda todo solucionado, y tiene razón. El problema, es que toda la configuración y software específico se borra, teniendo que volver a cargar el sistema operativo, instalar el software y generar de nuevo las configuraciones... vamos un peñazo XD .

Alguno se le ocurriría montar un script en el que ir probando las posibles combinaciones por fuerza bruta. En mi caso no era una opción, ni suele serlo en general. Con que cumplas los criterios básicos de una contraseña decente la combinatoria se dispara... Los tiempos en los que las claves eran de hasta 6 caracteres normalmente sólo letras y/o números han quedado atrás.

Con todo ello en mente le di alguna vuelta más al asunto...

Me puse a pensar cuál es nivel de seguridad que me podría permitir resetear la clave, y pronto caí que en el físico. Tenía acceso físico al hardware de la rpi, con lo que cogí la tarjeta SD y la introduje en mi ordenador. En su interior enseguida encontré un archivo, que mirando un poco en Internet, entendí que me podía dar la clave para poder acceder al sistema antes de que completamente se cargase y me obligase a idetificarme con las credenciales de acceeso, de la cual había perdido la contraseña.

El fichero en cuestión es el "cmdline.txt" y contiene parámetros específicos para la carga del kernel de Raspbian:

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

Según la documentación es posible cargar la ejecución de cualquier binario con el parámetro "init", y en este caso nos interesa cargar la shell. Para ello añadimos "init=/bin/sh" al final de la línea precedido por un espacio:

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait init=/bin/sh

Hecha esta modificación en el fichero lo guardamos, volvemos a introducir la SD en la rpi y la arrancamos. Esta vez durante el arranque, se parará en cierto momento la carga del sistema y nos aparecerá un prompt en el que podremos escribir comandos.

Antes de escribir cualquier comando debemos remontar el volumen para que efectivamente guarde los cambios. Para ello vamos a volver a montar el sistema de ficheros como lectura y escritura con el siguiente comando.

mount -rw -o remount /

Una vez hecho, vamos a invocar al comando que nos permite resetear la contraseña de nuestro usuario (normalmente pi si no los has cambiado):

passwd pi

Te pedirá que introduzcas la contraseña y otra vez más para confirmarla. Si todo ha ido bien verás un mensaje de este tipo:

passwd: password updated successfully

Ahora sincronizaremos los cambios que hemos hecho que están en memoria, para que queden persistentes en la memoria de la SD. Después reanudaremos el boot del sistema:

sync
exec /sbin/init

Una vez se haya cargado el sistema comprueba que puedes acceder con la clave que intruduciste y apaga la rpi con:

sudo poweroff

Una vez apagada la rpi volvemos a cargar la SD en el ordenador y editamos de nuevo el fichero "cmdline.txt" y lo volvemos a dejar como al inicio sin el parámetro "init":

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

Ahora ya podemos volver a poner la tarjeta SD en la Raspberry Pi y podremos volver a tener el sistema Raspbian como si nunca hubiéramos perdido la contraseña.

Recordad que esto ha sido posible gracias a tener acceso físico al hardware que ejecuta el software. El nivel físico siempre es el nivel más débil a nivel técnico y de infraestructura, por eso en Seguridad Informática hay que cuidar no sólo a quién damos acceso mediante credenciales, sino también quién tiene acceso físicamente al hardware donde se corre el software.

Solucionar problema de Autoridad de Certificación en los binarios .EXE generados con PY2EXE

Últimamente he empezado a utilizar la herramienta py2exe para poder mejorar la distribución de mis desarrollos en Python en sistemas Windows, mediante la generación de un ejecutable .exe autocontenido que evite tener que pedir al usuario que instale Python y las correspondientes librerías.

La herramienta py2exe puede ser instalada de manera sencilla con el comando:

pip install py2exe

Y puedes compilar rápidamente cualquier programa en Python realizando un fichero setup.py simple como este:

from distutils.core import setup
import py2exe

setup(console=['mi_programa.py'])

El cual deberemos ejecutar desde una consola de comandos de la siguiente forma:

python setup.py install

Al ejecutarlo nos resolverá todas las dependencias y dejará la versión autcontenida con el ejecutable mi_programa.exe en la carpeta dist.

Si trabajas con servicios o mínimamente con conexiones seguras haciendo peticiones, seguro que acabas usando la librería requests o puede que la librería con la que trabajes, la use como base para hacer conexiones seguras con SSL. Si intentas usar estas librerías que trabajan con certificados SSL te encontrarás con el siguiente error cuando ejecutas tu binario .exe:

requests.exceptions.SSLError: [Errno 2] No such file or directory

El cual viene dado porque cuando se empaqueta todo, el certificado de la Autoridad de Certificación no se incluye al ser un fichero que no es de Python. A causa de esto, cuando se empaquetan todas la librerías y se llama de manera relativa al certificado de la librería desde nuestro empaquetado con nuestro binario .exe de py2exe, éste no se encuentra porque ninguna parte.

Para solucionarlo es tan sencillo como proporcionar un certificado válido del tipo cacert.pem en la variable de entorno de Python REQUESTS_CA_BUNDLE de nuestro programa. Pero para resolverlo, vamos a hacerlo de manera elegante parcheando dicha variable sólo si es necesario, para poder seguir tirando de los certificados de las propias librería mientras desarrollamos.

Para ello vamos a instalar certifi, una librería que nos facilita una serie de Certificados Raíz que nos van a permitir validar la integridad de certificados, tanto de SSL, como de TSL de los servicios a los que nos conectemos.

pip install py2exe

Ahora vamos a modificar un poco nuestro setup.py:

from distutils.core import setup
import py2exe
import certifi

setup(console=['mi_programa.py'], data_files=[certifi.where()])

Hemos añadido únicamente el import de certifi y en los parámetros de setup hemos añadido el fichero de certificado que nos devuelve certifi gracias al método where. Este fichero se copiará en dist al mismo nivel que nuestro .exe.

Por último añadiremos en mi_programa.py al inicio de nuestro programa el siguiente código:

cacert_path = os.path.join(os.getcwd(), 'cacert.pem')
if os.path.exists(cacert_path):
    os.environ['REQUESTS_CA_BUNDLE'] = cacert_path

El código genera la ruta hasta certificado cacert.pem, usando el directorio de trabajo que será dist. Esa ruta se busca si existe entre las rutas que maneja Python para resolver las librerías y dependencias. Si se ejecuta desde el entorno de desarrollo, encontrará el de la propia librería que lo este usando, sino parcheará añadirá la ruta para que coja certificado que hemos copiado en dist.

De esta forma no sólo se soluciona el problema del certificado de la Autoridad de Certificación, sino que el parche se aplica selectivamente copiando fichero de certificado necesario. Esto es importante porque los certificados pueden cambiar y basta con volver a generar los binarios .exe con py2exe, teniendo las librerías actualizadas y con los certificados en regla, para que el ejecutable creado también los tenga.

Consejos para no infectarte con Malware

Es una realidad, a día de hoy Internet está lleno de malware y cuando navegamos es posible que acabemos descargando y ejecutando este tipo de software.

El malware se define como software que tiene como objetivo infiltrase o dañar un sistema de información sin el consentimiento de su propietario. El término se refiere genéricamente a una variedad de software hostil, intrusivo o molesto.

A pesar de tener nuestro sistema actualizado, nuestro navegador actualizado y tener un antivirus protegiendo la última línea de batalla de los dos anteriores, habitualmente puedes llegar a tener infecciones por malware o simplemente tener un falso positivo, que se define como una detección erróneamente clasificada como malware.

Ante el primer caso, mi consejo es el siguiente: evitad la descarga de contenido procedente de enlaces que referencien a un javascript y no muestren la url final del archivo a descargar. Desconfiad de ellos, porque el script os devolverá el archivo a descargar, pero no sabréis de antemano nada sobre el archivo. Esta técnica se suele usar para proporcionar descargas de malware y variar el fichero (normalmente un archivo.zip) que pase desapercibido por el navegador y el antivirus. Esto no quiere decir que haya sistemas que usen un javascript para servir contenidos de manera legítima y benévola, pero por norma general desconfiad.

Para saber que hay un javascript tras un enlace, es tan fácil como hacer click derecho sobre el enlace y selecciona "Copiar dirección de enlace". Si lo copias en un editor de texto os aparecerá algo parecido a esto:

javascript:void(0);

En vez de algo como esto:

https://mascandobits.es/blog/wp-content/uploads/2015/10/mi_descarga.zip

Una forma mucho más preventiva de bloquear estos elementos, es usar programas como NoScript, que nos permite desactivar el código javascript de las webs que visitemos. En el blog de Víctor Goñi tenéis una somera introducción al uso de NoScript.

En el segundo caso de tener un posible falso positivo, yo recomiendo usar el sentido común (que a veces es el menos común de los sentidos 😉 ). Tu antivirus o navegador pueden detectarlo como potencial malware, pero está en nuestra mano descargarlo. En el caso del antivirus podemos desconectar momentáneamente el antivirus para poder descargarlo y en el caso del navegador siempre podremos forzar la descargar o recuperar el archivo. Alguno me dirá: ¡Estás loco hermano! Pues NO. Mientras no ejecutes el archivo o lo descomprimas, estaremos a salvo.

Malware-PC

Alguno a estas alturas estará pensando, cuál el motivo por el que a pesar de un posible falso positivo decido descargar el archivo. Una de las formas de percibir ingresos extra por las compañías que desarrollan los navegadores y los antivirus, es la de provocar falsos positivos en ciertos archivos para que funcione como medida disuasoria para su descarga. Terceras empresas suelen pagar a navegadores y antivirus para mostrar estos avisos, ya que la descarga de dichos archivos son contrarias a su negocio o le provocan directamente pérdidas económicas. Cracks, Keygens, Scene, Descifrado (Desencriptación) de Sistemas de Información, Herramientas de Ingeniería Inversa... son algunos de los muchos ejemplos en los que se puede dar falsos positivos pagados por empresas. Esto no quiere decir que dentro de estos grupos, haya software con malware, que los hay.

Especulaciones a un lado, tenemos nuestro archivo descargado. Para comprobar que no es malware, hay una prueba sencillísima, abre una nueva pestaña y vuelve a descargar el mismo ficheros sin ejecutarlo. Si el fichero se descargar con el mismo nombre (el segundo fichero tendrá el mismo nombre más "(1)"), es probable que no sea malware. No por ello bajaremos la guardia.

Una forma de clarificar la cosa es pedir una o varias opiniones más sobre el posible archivo cargado con malware. Para ello podemos acceder a Virus Total, dónde podremos pedir que varios motores de antivirus analicen el fichero y nos devuelvan el resultado.

Virus-Total-ESP

 

Caso práctico

Personalmente todo esto que os cuento lo puse en práctica al ir a descargar DVDshrink, un software que permite romper el cifrado de los DVDs comerciales para generar copias de seguridad. Al descargar la última versión del programa, tanto el navegador como el antivirus saltaron.

Inmediatamente comprobé que la descarga del archivo usaba un enlace con javascript. Tras descargarlo una segunda vez, el nombre del archivo cambiaba. Con lo que acudí a Virus Total en busca de respuestas.

malware-malicious-virus

El primer archivo arrojó el siguiente análisis:

Primer análsis (2016-05-08 09:10:57 UTC)

El segundo archivo lo analicé en otro día distinto y no me lo relacionó con el primero que subí:

Segundo análisis (2016-05-11 11:03:18 UTC)

Virus Total genera un SHA256 que identifica unívocamente al archivo, y en este caso no había relación con el primero que subí. Además de todo ello, los resultados variaban y para colmo había alrededor de un 50% de resultados que decían que no era malicioso y el otro 50% decían que sí lo era.

*Nota: Virus Total actualiza los resultados si se vuelve a subir por otros usuarios el mismo archivo

Esto me hizo desconfiar automáticamente. En mi caso basta que 10 de los 56 motores de los antivirus, me devuelvan infección para destruir el fichero sin pensármelo demasiado, si no sé exactamente lo que estoy manejando. El que haya entre 1 y 4 resultados de infección también puede ser que se trate de una infección nueva. En este caso el software ya lo conocía anteriormente y su última versión pone que es del 2014, sería algo raro que tras ese tiempo se tratase de una infección nueva, si hubiese entre 1 y 4 resultados de infección.

Recuerda también que entre los resultados ofrecidos por los distintos motores de los antivirus, puede haber falsos positivos o reportes de infecciones hechos desde la parte heurística de cada motor, que se encarga de detectar posibles amenazas que no estén catalogadas dentro de las bases de datos de cada antivirus.

Al final cada uno tomamos la decisión de ejecutar el archivo y como todo en la vida podemos equivocarnos, a pesar de haber contrastado las fuentes de información para generar una decisión en consecuencia informada y de calidad.