Linux Ethernet Ports finden

Als ich den Post zu „Bandbreite Anzeigen“ schrieb, da habe ich nicht darauf geachtet, dass Interface-Namen je nach Distribution und anderen Dingen verschieden heissen. Dort geht das Script einfach davon aus, dass alle Ports mit eth anfangen. Es gibt im Tool ip zwar die Möglichkeit, einen Typ anzugeben, aber leider ist das Feld leer bei Ethernet-Interfaces und man kann nicht unterscheiden zwischen „echten“ und Sub-Interfaces. Mit der Option -json bekommt man deutlich mehr Informationen und mit dem Tool jq kann man gut filtern, aber selbst dann bekommt man nicht einfach alle Ethernet Ports sauber gefiltert. Und Tools wie ethtool brauchen root Rechte und dort ist das Filtern auch nicht trivial.

Aber es gibt einen Trick, der hilft. Man sucht nach dem Treiber der Interfaces und nur Ports haben einen. Die zeigen wir an. Und das geht so:

for INT in $(ip -br link show | awk '{print $1}'); do
driver=$(basename $(readlink /sys/class/net/$INT/device/driver/module) 2>/dev/null)
test -z $driver || echo $INT
done

Der Output des Befehls sind alle Ethernet-Interfaces. Man kann natürlich auch die Variable $driver verwenden, falls man wissen will, welche Treiber benutzt werden.

Wenn man diese for Schleife in das Bandbreiten-Script übernimmt, dann funktioniert es mit jeder Distribution. Und um zu vermeiden, dass Ports angezeigt werden, die gerade down sind, kann man das awk ergänzen um einen Filter:

awk 'match($2, "UP") {print $1}'
Linux Ethernet Ports finden

Habe ich eine interaktive Shell?

Oft ist es wichtig zu wissen, ob ein Script im Vordergrund oder im Hintergrund, etwa as cron-job läuft. Wenn nicht, kann man alle interaktiven Aktionen wie Eingaben im Script deaktivieren. Oder wenn sie erforderlich sind, das Script gar nicht erst starten. Letzteres erledigt folgender Code am Anfang eines Scripts:

if [ ! -t 1 ] ; then
  exit 0
fi

Das -t steht für „File Descriptor“ und die 1 für stdout. Man kann natürlich auch einfach nach dem genutzten terminal suchen. Das geht so:

readlink -f /proc/self/fd/0

In einer nicht-interaktiven Shell erscheint hier die pipe zum Prozess der Shell, etwa /proc/489748/fd/pipe:[7387825], wobei die erste Zahl für die Prozess-ID des readlink Befehls steht und die letzte den inode (nicht wirklich interessant). Bei einer interaktiven Shell aber sieht das Ergebnis etwa so aus: /dev/tty1 Das besagt, dass ein interaktives tty (teletype terminal) benutzt wird. Üblicherweise sind tty1 – tty6 die lokalen Terminals (nur-text modus) auf einem Linux-PC und tty7 wird für das grafische Interface benutzt. Wenn man darin eine Shell öffnet, bekommt sie ein ebenfalls interaktives pts (pseudo tty) zugeordnet, etwa /dev/pts/0. Die Zahl deutet 0 auf eine lokale Shell (also keine Verbindung via ssh) hin. Eine Verbindung per ssh hat meist eine höhere Ordnungszahl. (Aber das findet man sowieso besser über die Variable $SSH_TTY heraus. Ist sie gesetzt und entspricht dem Ergebnis des obigen Befehls, dann besteht eine SSH-Verbindung zum genutzten Terminal.) Ist das Ergebnis aber etwa /dev/ttyS0 oder /dev/ttyUSBx oder ähnliches, dann besteht eine serielle Verbindung zum Gerät. Und das kann ja auch interessant sein!

Habe ich eine interaktive Shell?

Smokeping (und andere rrd) exportieren

Ja, es ist wahr, ich liebe Smokeping. Es ist ein Tool, das drei Dimensionen von Erreichbarkeit darstellt.
1. Latency (RTD, Round Trip Delay, wie lange braucht ein Paket hin und zurück zum Ziel)
2. Jitter (Wie hoch ist die Varianz der RTDs, eine hohe Varianz bedeutet, dass auf dem Weg ein Link sehr voll ist)
3. Packetloss (Wie viele Pakete gehen unwiderruflich verloren auf dem Weg. Über 1% bedeutet normalerweise ein übervoller Link und 100% ein fehlender)

Dafür versendet Smokeping alle 5 Minuten 20 ICMP-Pings an eine entfernte Adresse und speichert die Ergebnisse in rrd-Dateien. Es handelt sich um ein dateibasiertes Datenbankformat, das von vielen Traffic-Graphern und anderen genutzt wird.

Wenn man eine Smokeping-Installation von einem Server zum anderen umziehen will, kann man leider nicht einfach die rrd-Dateien kopieren. Man muss sie erst mit dem Tool rrdtool exportieren und wieder importieren. Und das geht so:

Die Dateien befinden sich im Ordner /var/lib/smokeping/[Ziel]/. Man wechselt in diesen Ordner mit cd und dann exportiert man alle Dateien in das xml-Format:

for FILE in *.rrd; do rrdtool dump $FILE > $FILE.xml; done

Die xml-Dateien kopiert man nun auf den neuen Server in den selben Pfad und konvertiert sie wieder zurück:

for FILE in $(ls *.xml); do rrdtool restore $FILE $(echo $FILE |sed s/.xml//g); done
rm *.xml
chown smokeping:smokeping *.rrd

Falls die Konfiguration für diese Ziele (/etc/smokeping/config.d/Targets) genau gleich ist auf beiden Servern, werden jetzt automatisch neue Grafiken generiert mit den importieren Daten! Nach etwa 10 Minuten kommen neue Ergebnisse hinzu!

Smokeping zu einem DNS Server von Google
Smokeping (und andere rrd) exportieren

Instant Webserver

Manchmal braucht man kurzfristig einen Webserver, um eine Datei zu übertragen. Dann kann man natürlich mal eben einen Apache installieren, konfigurieren, Datei hinkopie… okay, lassen wir das.
Es gibt ja das Netzwerk-Schweizer-Taschenmesser-Tool nc, das kann doch helfen!

#!/bin/bash

FILE=$1
PORT=$2

{ echo -ne "HTTP/1.0 200 OK\r\nContent-Length: $(wc -c <$FILE)\r\n\r\n"; cat $FILE; } | nc -l ${PORT:=8080}

Dieses Script, etwa in der Datei instantwww.sh gespeichert, lässt sich einfach aufrufen mit der Datei als erste Option und (optional) einem TCP-Port als zweite.

./instantwww.sh Datei

Auf dem Gegenüber gibt man dann entweder die URL in den Browser ein oder nutzt ein Tool wie curl:

curl -o Datei http://[remote-IP]:8080/Datei

Das funktioniert unter Linux und macOS. Und ja, böse Hacker nutzen das, um Dateien von oder zu einem Opfer zu kopieren. 😱

Instant Webserver

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