Was ist Deine IP? Part 2

Ich habe vor einiger Zeit mal einen Post geschrieben darüber, wie man eine PHP Seite auf einem Webserver einrichtet, der einem seine eigene IP verrät. Diese Technik ist zuverlässig und kann auch Proxies anzeigen, aber wenn man nur mal eben seine eigene IPv4 wissen will, geht da auch per DNS-Abfrage an opendns:
dig @resolver4.opendns.com myip.opendns.com +short -4

Und damit man sich diesen Befehl nicht merken muss, schreibt man einfach diese Zeile in die Datei .bashrc oder .bash_profile

alias whatsmyip='dig @resolver4.opendns.com myip.opendns.com +short -4'

Jetzt reicht der Befehl whatsmyip und schon bekommt man seine IP angezeigt.

Was ist Deine IP? Part 2

Ist das ein valider IPv4 Node?

Um eine IP Adresse zu validieren, muss man nicht viel tun. In Python gibt es Libraries dafür. In bash habe ich es mal so gemacht:

#!/bin/bash
VALID=0
read -p "Choose an IPv4 address: " -e  IPADDR
if [[ $IPADDR =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$ ]]; then
	if [ $(echo $IPADDR | awk -F '.' '{print $1}') -lt 224 ]; then
		VALID=1
	fi
fi
if [ $VALID -eq 1 ]; then
	echo "IP $IPADDR is a valid IPv4 address"
else

	echo "IP $IPADDR is not a valid IPv4 address"
fi

Das funktioniert ganz anständig. Aber was, wenn ich ausserdem wissen will, ob die IP ein erlaubter Node in einem Subnet ist? Etwa in einer Konfigurationsmaske? Für einen Input im Format v.w.x.y/z (CIDR) muss man deutlich mehr checken:

  1. Sind alle vier Zahlen 8 bit lang und ist die Maske 32 oder kleiner und grösser als 0? Fall nicht: Invalide
  2. Konvertiere alle vier Oktetts in Binär und erzeuge eine 32 bit Binärzahl!
  3. Wenn die ersten 8 Bits den Wert 0 or die ersten 3 Bits den Wert 1 haben, ist es kein valider Node (0er Netz oder Class D/E)
  4. Wenn die ersten 8 Bits den Wert 01111111 (127) haben, ist es eine Loopback-Adresse (nicht valide)
  5. Wenn die ersten 16 Bits den Wert 1010100111111111 haben, ist es eine Link-Local-Adresse (nicht valide)
  6. Wenn die Maske 30 oder größer ist, ist es ein valider Node!
  7. Entferne von der Binärzahl die vorderen x Bits, wobei x die Maske ist.
  8. Bleiben dabei nur Nullen oder nur Einsen stehen, ist der Node invalide!

Beispiel 1:

  • IP/Mask: 192.168.1.0/23
  • IP Bin: 11000000101010000000000100000000
  • Node: 100000000
  • Ergebnis: Valide

Beispiel 2:

  • IP/Mask: 192.168.1.255/23
  • IP Bin: 11000000101010000000000111111111
  • Node: 111111111
  • Ergebnis: Invalide

Beispiel 3:

  • IP/Mask: 192.168.1.4/30
  • IP Bin: 11000000101010000000000100000100
  • Node: 00
  • Ergebnis: Invalide

Beispiel 4:

  • IP/Mask: 226.168.1.2/30
  • IP Bin: 11100010101010000000000100000110
  • Node: 10
  • Ergebnis: Invalide

Es gibt noch andere Checks, die man machen könnte, wie etwa ob es eine private Adresse ist oder nicht. Allerdings ist das weniger interessant, da das keinen Fehler produziert, wenn man es konfigurieren will.

Das könnte man natürlich in Bash programmieren, aber das ist eher etwas für Python. Ich habe auch eine Library gefunden, die man dafür einfach so nutzen kann.

Ist das ein valider IPv4 Node?

Geleakte Passworte automatisch checken

Passworte aus Datenleaks werden in Passwortdatenbanken aufgenommen, um sie für Wordbook-Attacken zu nutzen. Aber nicht nur Hacker Pflegen diese Datenbanken, auch Securityanbieter. Viele Passwortmanager, vor allem die in Browsern, prüfen die in ihnen gespeicherten Passworte regelmäßig gegen solche Datenbanken.

Nach dem letzen Datenleak bei Twitter war mein Account auch betroffen, also hat mich Safari darauf hingewiesen. Damit dieser Check funktioniert, ohne das Passwort zu übertragen und damit selbst zu kompromittieren, wird ein Hash (Kryptographische Ableitung des Passworts, die man nicht zurück übersetzen kann) des Passworts erstellt und mit einer Datenbank bekannter Hashes verglichen. Wenn man also einen unbekannten Hash vergleicht, kann man keinen Rückschluss auf das eigentliche Passwort ziehen.

Aber auch das hat Probleme: Da man in diesem Bereich niemandem vertrauen darf, kann auch ein vollständiger Hash ein Problem sein. Wenn in Zukunft dieser Hash plötzlich bekannt wird, könnte der Anbieter des Checks nachvollziehen, wer dieses Passwort nutzt. Deswegen hat https://haveibeenpwned.com/Passwords eine Web-API erstellt, der man nur die ersten 5 Stellen des Hashes übermittelt und die dann alle bekannten Hashes zurück gibt, die damit beginnen ohne die ersten 5 Stellen. Diese Liste ist relativ kurz und man kann dann lokal die übermittelten Passwort-Hashes mit dem vollständigen Hash vergleichen und so verstehen, ob das Passwort kompromittiert wurde, ohne den gesamten Hash zu übermitteln und ohne die gesamte Hash-Datenbank (>10GB) jeden Tag herunterzuladen.

Das kann man natürlich automatisieren. Und das geht so:

#!/bin/bash
# create SHA from list of passwords and compare them to haveibeenpwned.com

# in macOS the command to create sha-hashes is different
if [ $(uname) == "Darwin" ]; then
        function sha1sum {
                shasum -a 1
        }
fi
for PAWD in $(cat passwords.txt); do
         PAWDSHA=$(echo -n $PAWD | sha1sum | awk '{print $1}')
         PWND=0
         curl -s https://api.pwnedpasswords.com/range/${PAWDSHA:0:5} | fgrep -i ${PAWDSHA:5} >/dev/null && PWND=1 
         if [ ${PWND} -eq 1 ]; then
              echo "$PAWD was pwnd"
         fi
done

Im Script ist passwords.txt die Datei mit Klartext-Passworten. Das ist natürlich keine gute Idee und dient hier nur als Beispiel, woher die Klartextpassworte kommen. Besser ist es, die Passworte als SHA direkt zu speichern und zu vergleichen. Der echo-Command wiederum sollte ersetzt werden durch ein Script oder eine Funktion, die den oder die Besitzer:in des Passworts informiert, dass das Passwort kompromittiert wurde.

Und so sieht das Script in Aktion aus!

Geleakte Passworte automatisch checken

Log linux commands (audit log)

Wenn man alle Kommandos, die ein User in eine Shell tippt loggen will, dann gibt es dafür verschiedene Lösungen, die meist die Installation von speziellen Softwarepaketen erfordern. Das ist bei sensiblen Systemen nötig, um erfolgreiche Angriffe zumindest im Nachhinein verstehen zu können oder Benutzerfehler zu erkennen. Es geht auch ohne spezielle Software ganz einfach. Zumindest in bash.

Man braucht dafür das Programm logger, das unter Debian und Ubunutu teil des Pakets bsdutils ist und daher fast immer vorinstalliert ist. Dieses Tool kann Syslog-Nachrichten erzeugen, die dann vom Syslog-Daemon auf dem System verarbeitet werden. Im Beispiel verwende ich die Syslog-Facility „user“ und überlasse dem syslogd (etwa syslog-ng) die weitere Verarbeitung.

Alles, was man tun muss ist die Datei /etc/bash.bashrc anzupassen und folgende Zeilen am Ende hinzuzufügen:

TERM_IP=$(who -m | awk '{print $5}'|sed 's/.\(.*\)./\1/')
TERM_DEV=$(tty | sed 's/\/dev\///')
logger -p user.notice -t clilog --sd-param origin.software=\"bash\" "User $USER logged IN on $TERM_DEV $TERM_IP"
readonly HISTTIMEFORMAT="$TERM_DEV $TERM_IP $USER Cmd: "
readonly PROMPT_COMMAND="logger -p user.notice -t clilog --sd-param origin.software=\"bash\" "$(echo -e "$(history 1)")"'
readonly unset HISTCONTROL

Das Ergebnis ist ein Logeintrag im log /var/log/user.log für jeden Befehl, den ein User absetzt mit Datum, Uhrzeit, verwendeter Konsole (TERM_DEV) und im Falle von ssh der IP, von der aus verbunden wurde (TERM_IP):

Sep 29 09:12:43 meinlinux clilog[3559529]:   304  pts/0 192.168.11.10 eliyah Cmd: ls

Einen kleinen Schönheitsfehler hat die Lösung: Wenn ein User sich ausloggt und wieder einloggt, dann wird der letzte Befehl vor dem Ausloggen noch mal geloggt mit falscher Uhrzeit. Da aber die History-ID die selbe ist, kann ein Forensiker das schnell erkennen. Beispiel:

Sep 29 09:23:23 meinlinux clilog[3564393]:   312  pts/0 192.168.11.10 eliyah Cmd: ls
Sep 29 09:29:02 meinlinux clilog[3567219]: User eliyah logged IN on pts/0 192.168.11.10
Sep 29 09:29:04 meinlinux clilog[3567351]:   312  pts/0 192.168.11.10 eliyah Cmd: ls

Um zu verhindern, dass ein Angreifer die Logs löscht, sollten sie natürlich auf einen remote Syslog-Server geschrieben werden. Und wenn der Angreifer root-Rechte hat, kann er natürlich ab diesem Moment seine Spuren verwischen und das Logging deaktivieren. Aber erst ab dann!

Log linux commands (audit log)

Script zum Setzen der Linux Timezone

Es gibt dafür durchaus Pakete und Binaries, die das übernehmen, aber warum nicht selbst machen und ein wenig mit select spielen?

#!/bin/bash
# Simple script to set system TZ for any linux system
echo "Setting the TZ of this system:"
if [ "$EUID" -ne 0 ]; then
  echo "Please run as root"
  exit
fi
cd /usr/share/zoneinfo/posix
PS3="Select the Region: "
select WREG in $(find . -type d | sed "s/\.\///g" | fgrep -v ".") other; do
	if [ -z $WREG ]; then
		echo "Not changing TZ"
		break
	fi
	echo "Selected the Region: $WREG "
	PS3="Select the TZ: "
	if [ $WREG == "other" ]; then
		mapfile -t WREGLIST < <( find . -maxdepth 1 -not -type d | sed "s/\.\///g" )
		WREG=""
	else
		cd $WREG
		mapfile -t WREGLIST < <( find . -not -type d | sed "s/\.\///g" )
	fi
	select WREGTZ in ${WREGLIST[*]}; do
		if [ -z $WREGTZ ]; then
			echo "Not changing TZ"
		break
		fi
	echo "Selected the TZ: $WREGTZ "
	ln -s /usr/share/zoneinfo/$WREG/$WREGTZ /etc/localtime
	echo "Current date is: $(date)"
	break
	done
break
done
Script zum Setzen der Linux Timezone

Aus CIDR-Schreibweise eine Maske berechnen

Es gibt Python-Scripte dafür und Perl-Bibliotheken auch, aber was, wenn man in Bash eine IPv4-Prefixlänge in eine Maske umrechnen will? Etwa /24 in 255.255.255.0? Das geht so:

#!/bin/bash

function PFLcalc () {
        let NN=32-$1
        PL=$(printf -- '1%.0s' $(seq 1 $1))
        PR=$(printf -- '0%.0s' $(seq 1 $NN))
        PF=$PL$PR
        OCT1=$(echo "ibase=2; ${PF:0:8}" | bc)
        OCT2=$(echo "ibase=2; ${PF:8:8}" | bc)
        OCT3=$(echo "ibase=2; ${PF:16:8}" | bc)
        OCT4=$(echo "ibase=2; ${PF:24:8}" | bc)
        MASK=$OCT1.$OCT2.$OCT3.$OCT4
}
PFLcalc $1 # call script with int le 24
echo "Mask is $MASK"

Ja, bei einer Länge von 0 verrechnet sich das Script. Pech.

Aus CIDR-Schreibweise eine Maske berechnen

Böse bleiben draußen

Mein Server ist genervt. Ständig probieren irgend welche Bots aus, Passworte zu erraten und scannen Ports ab. Daher habe ich fail2ban installiert, das notorische Passwort-Ausprobierer ausbremst. Es gibt aber auch Betreiber von Honeypot-Netzwerken, die besonders aggresive IPs sammeln und diese in Listen zur Verfügung stellen. Ich habe mir eine solche Liste von einem Czechischen Anbieter herausgesucht und in einen iptables Filter umgewandelt. Und das geht so:

#!/bin/bash

BLKSRC="https://security.etnetera.cz/feeds/etn_aggressive.txt"
CHAIN="aggressiveips"
IPTBIN="/sbin/iptables"
TDAY=$(date +%d)
YDAY=$(date +%d --date="-1 day")
ADMIN="admin@internet.tld"

# create new chain for today
$IPTBIN -N $CHAIN$TDAY
# insert all IPs from the source into the chain (this takes a while)
for BIP in $(curl $BLKSRC 2>/dev/null | egrep -v "^#|^$|\:"); do
$IPTBIN -A $CHAIN$TDAY -s $BIP -j LOG --log-prefix "[netfilter] $CHAIN$TDAY "
$IPTBIN -A $CHAIN$TDAY -s $BIP -j DROP
done
# add a RETURN policy to allow other chains to be examined
$IPTBIN -A $CHAIN$TDAY -j RETURN
# insert chain into the INPUT chain
$IPTBIN -A INPUT  -j $CHAIN$TDAY
# delete yesterdays chain from INPUT chain
$IPTBIN -D INPUT -j $CHAIN$YDAY
$IPTBIN -F $CHAIN$YDAY
$IPTBIN -X $CHAIN$YDAY
# report
BLKCNT=$($IPTBIN -n -L $CHAIN$TDAY | fgrep DROP | wc -l)
echo "Installed $BLKCNT blocked IPs from $CHAIN - $BLKSRC on $(date)" | mail -s "$CHAIN result" $ADMIN

Das Script lädt als cronjob, der ein Mal nächtlich läuft, die aktuelle Liste herunter, sortiert alle Leerzeilen und Kommentarzeilen sowie IPv6-Adressen (egrep den Doppelpunkt) aus und installiert dann eine iptables chain. Ich nutze den heutigen und gestrigen Tag als Teil der chain, damit beim Neuladen nicht während der Installation der chain der Server von nervenden Anfragen gekitzelt wird.

Und damit ich einfacher für jeden Tag eine Auswertung machen kann. Und da kommt eine ganze Menge zusammen. Das aber ander mal.

Böse bleiben draußen

Abgelaufene SSL Zertifikate

Es ist mir leider mehr als nur ein mal passiert, dass der Mailserver mit abgelaufenen SSL-Zertifikaten lief. Bei Let’s Encrypt laufen diese Zertifikate ja relativ schnell (3 Monate) ab. Daher brauche ich eine Warnung, falls das passiert und der automatische Restart nicht geklappt hat.

Man kann natürlich die Cert Dateien im Filesystem prüfen, aber viel besser ist doch den Dienst direkt zu fragen. Und das geht so (und das kann auch remote laufen):

#!/bin/bash

# SMTPS Host to check
HOST=my.mailserver.tld
# Days before end of cert to warn/take action
WARNDAYS=14
# admin email
CONTACT="adminofmymailserver@gmail.com"

ENDDATE=$(echo QUIT | openssl s_client -connect $HOST:465 -verify_return_error 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'| openssl x509 -noout -dates  | fgrep notAfter | sed 's/notAfter=//g')
# echo $ENDDATE cert expires
EPOCHENDDATE=$(date --date="$ENDDATE" +"%s")
# echo epoch is $EPOCHENDDATE
EPOCHTODAY=$(date +"%s")
let TIMELEFT=$EPOCHENDDATE-$EPOCHTODAY
DAYSLEFT=$(echo "$TIMELEFT/86400" | bc)
# echo time left $DAYSLEFT days
if [ $DAYSLEFT -le $WARNDAYS ]; then
  echo "WARNING: Cert for $HOST expires in $DAYSLEFT days!" | mail -s "WARNING: Cert for $HOST expires in $DAYSLEFT days!" $CONTACT
  # /etc/init.d/exim4 restart
fi

Die Herausforderung ist, das Ablaufdatum im Zertifikat in Tage gerechnet von Heute umzuwandeln. Das geht mit dem Befehl „date“, der in epoch umwandelt und einer einfachen Division ohne Rest durch 86400 (Sekunden pro Tag) der Differenz mit „bc“.

Abgelaufene SSL Zertifikate

Time Machine like backups

If you backup your data to a remote server or a local disk with a script (I strongly recommend rsync to do so, otherwise this script will most probably not work), you should also keep old versions of that data to be able to rollback to an earlier version. But if you have a LOT of data, your HD will run full. So better just keep the deltas.

In my case its half a terabyte of email data from a mail server. Many files change every day. Get deleted, added, moved.
To keep old versions (you could call them snapshots) of the backups, I use hardlinks, that will just create a new link to an existing file in the filesystem. It is not a symbolic link, meaning the actual file will only get deleted, when the last hardlink will be deleted from the filesystem. In my case, this will happen 10 days after the original file was deleted.

#!/bin/bash

BASEDIR="/var/bkp/" # base directory
BDIR=mybackup # directory to backup
DAYS=10
cp -al $BASEDIR/$BDIR/ $BASEDIR/$BDIR$(date +%Y%m%d)
if [ -d $BASEDIR/$BDIR$(date +%Y%m%d --date="-$DAYS day") ]; then
 echo "deleting old backup $BDIR$(date +%Y%m%d --date="-$DAYS day")..."
 rm -rf $BASEDIR/$BDIR$(date +%Y%m%d --date="-$DAYS day")
else
 echo -n "No old backups at "
 date +%Y%m%d --date="-$DAYS day"
fi
Time Machine like backups