Ningún sistema es inexpugnable por lo tanto cuantas mas precauciones tomemos mas improbable será que acabemos owneados, en esta ocasión configuraremos un sistema de notificaciones vía Telegram que nos avisará cada vez que se realice un acceso a nuestro sistema vía ssh.
Primero realizamos una copia de la config PAM del servicio ssh:
cp /etc/pam.d/sshd /root/pam.dBackup/
Modificamos la config para que se ejecute nuestro script, añadimos en la primera línea:
auth requisite pam_python.so /lib/security/looter.py
Ahora instalamos los módulos de PAM necesarios para correr nuestro script en python, a continuación se detalla como hacerlo en un sistema Debian/Gentoo:
En Gentoo por alguna extraña razón no hay libpam-python, alienizamos el paquete de Debian y copiamos el módulo:
emerge -av dev-python/pip
wget
http://ftp.br.debian.org/debian/pool/main/p/pam-python/libpam-python_1.0.6-1_amd64.deb
alien --to-tgz libpam-python_1.0.6-1_amd64.deb
cp ./lib/x86_64-linux-gnu/security/pam_python.so /lib64/security/
chmod 755 /lib64/security/pam_python.so
Instalamos dependencias del script en python:
Generamos un bot con botfather, obtendremos la apiKey.
Con userinfo sacamos en userId:
https://telegram.me/userinfobot
Escribimos nuestro script:
import spwd
import crypt
import requests
import syslog
import os.path
import time
import socket
# http://pam-python.sourceforge.net/doc/html/
def sendMessage(msg):
#syslog.openlog(facility=syslog.LOG_AUTH)
#syslog.syslog("----- Sending TelegramBot message ------")
#syslog.closelog()
apiKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
userId = "YYYYYYYY"
data = {"chat_id":userId,"text":msg}
url = "https://api.telegram.org/bot{}/sendMessage".format(apiKey)
#nowEpoch = int(time.time())
#if os.path.isfile('/tmp/lastTelegramMessage'):
# file = open('/tmp/lastTelegramMessage', "r")
# lastEpoch = int(file.read())
# lastMessage = nowEpoch - lastEpoch
# #print ("lastCall: %i" % lastCall)
# if lastMessage < 8:
# return
#file = open('/tmp/lastTelegramMessage',"w")
#file.write('%d' % nowEpoch)
#file.close
r = requests.post(url,json=data)
# Called function 3 times: 2 times login action, 1 time logout action
def pam_sm_setcred(pamh, flags, argv):
hostname = socket.gethostname()
if (pamh.rhost != 'A.A.A.A' and pamh.rhost != 'B.B.B.B'):
#syslog.openlog(facility=syslog.LOG_AUTH)
#syslog.syslog("-- AUTHPAM -- sm_setcred")
#syslog.syslog("-- AUTHPAM -- From: " + str(pamh.rhost) + " -- User: " + str(pamh.get_user()) + " -- Data: " + str(pamh.pamh))
sessionFile = '/tmp/looter' + str(pamh.pamh)
if os.path.isfile(sessionFile):
file = open(sessionFile, "r")
loginN = int(file.read())
file.close
#syslog.syslog("-- AUTHPAM -- File EXISTS, value: " + str(loginN))
if loginN == 1:
#syslog.syslog("-- AUTHPAM -- File content: 1")
file = open(sessionFile,"w")
file.write('2')
file.close
if loginN == 2:
#syslog.syslog("-- AUTHPAM -- File content: 2")
sendMessage("SSH Activity detected from: " + str(pamh.rhost) + "\n- Host: " + str(hostname) + "\n- User: " + str(pamh.get_user()) + "\n- Action: LogOut")
os.remove(sessionFile)
else:
sendMessage("SSH Activity detected from: " + str(pamh.rhost) + "\n- Host: " + str(hostname) + "\n- User: " + str(pamh.get_user()) + "\n- Action: LogIn")
file = open(sessionFile,"w")
file.write('1')
file.close
return pamh.PAM_SUCCESS
def pam_sm_authenticate( pamh, flags, argv ):
return 1
#def pam_sm_acct_mgmt( pamh, flags, argv ):
# syslog.openlog(facility=syslog.LOG_AUTH)
# syslog.syslog("-- AUTHPAM -- sm_acct_mgmt")
#
#def pam_sm_chauthtok( pamh, flags, argv ):
# syslog.openlog(facility=syslog.LOG_AUTH)
# syslog.syslog("-- AUTHPAM -- sm_chauthtok")
#
#def pam_sm_open_session( pamh, flags, argv ):
# syslog.openlog(facility=syslog.LOG_AUTH)
# syslog.syslog("-- AUTHPAM -- sm_open_session")
#
#def pam_sm_close_session( pamh, flags, argv ):
# syslog.openlog(facility=syslog.LOG_AUTH)
# syslog.syslog("-- AUTHPAM -- sm_close_session")
NOTA: Cada vez que se hace login se ejecuta dos veces la función pam_sm_setcred y una vez cuando se hace logout, por eso el script mantiene unos estados basados en ficheros bajo /tmp/, la función pam_sm_authenticate se ejecuta cuando el usurio no existe, para evitar errores de que no hay un función definida hacemos que retorne 1.
Puede darse el caso en el que manualmente se pueda cargar un módulo pero desde PAM no, esto es debido a que el path es diferente, primero averiguamos cual es nuestro path, ejecutamos desde dentro de un interprete de python:
>>> import sys
>>> print sys.path
', '/usr/lib64/python27.zip', '/usr/lib64/python2.7', '/usr/lib64/python2.7/plat-linux2', '/usr/lib64/python2.7/lib-tk', '/usr/lib64/python2.7/lib-old', '/usr/lib64/python2.7/lib-dynload', '/home/kr0m/.local/lib64/python2.7/site-packages', '/usr/lib64/python2.7/site-packages', '/usr/lib64/python2.7/site-packages/pip-9.0.1-py2.7.egg', '/usr/lib64/python2.7/site-packages/dnspython-1.16.0-py2.7.egg', '/usr/lib64/python2.7/site-packages/pystun-0.1.0-py2.7.egg', '/usr/lib64/python2.7/site-packages/gtk-2.0
En nuestro script de PAM lo primero será comentar la importación del módulo problemático y averiguar el path, en mi caso no cargaba el módulo requests:
#import requests
syslog.openlog(facility=syslog.LOG_AUTH)
syslog.syslog("Python version: " + str(sys.version))
syslog.syslog("PATH: " + str(sys.path))
Comparamos paths y añadimos los que falten para que el entorno sea lo mas parecido al del exterior, después de añadir los paths ya deberíamos de poder cargar el módulo:
sys.path.append('/usr/local/lib/python2.7/dist-packages')
sys.path.append('/root/.local/lib/python2.7/site-packages')
sys.path.append('/usr/lib/python2.7/dist-packages')
import requests
Asignamos los permisos necesarios:
Reiniciamos el servicio SSH:
Ahora recibiremos notificaciones en nuestro telegram cada vez que se haga logIn/Out indicándonos la ip origen, el host accedido y el user utilizado.