Das BICsuite Run Program Teil 4: Andere Interpreter

Die Bourne-Shell ist nichts Ungewöhnliches. Andere Befehlsinterpreter oder sogar eine Mischung davon, typischerweise Shell und etwas anderes, können verwendet werden. Natürlich führt das Hinzufügen von Sprachen zu einem komplexeren Quoting; jede Interpretationsebene fügt eine weitere Ebene des Quotings hinzu.

Als Beispiel erstellen wir ein kleines Skript, das verwendet wird, um die IP-Adressen zu finden, von denen aus ein ungültiger Anmeldeversuch unternommen wurde. Die Ausgabe sollte das iptables-Format haben. Als Skript, das auf sich selbst aufgerufen werden kann, ist die Aufgabe fast trivial. Das Skript könnte so aussehen:

#!/bin/bash
lastb -i -f /var/log/btmp |
awk '
    $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ }
    END {
        for (ip in cnt)
            if (cnt[ip] > '"$NUM"')
                print ip "\t" cnt[ip]
    }
' | (
while read foo bar; do
    if ! grep -q " $foo/" /etc/sysconfig/iptables; then
        if [ $VERBOSE == "Y" ]; then
            echo "# $foo $bar";
            fi
        echo "-A INPUT -s $foo/32 -j DROP"
        echo "-A OUTPUT -d $foo/32 -j DROP"
    fi;
done
) | sort

In dem Skript wird davon ausgegangen, dass die beiden Variablen NUM and VERBOSE definiert wurden. Die erste Variable definiert einen Schwellenwert. Das Skript erkennt ungültige Anmeldeversuche, wenn die Anzahl der Versuche einer einzelnen IP-Adresse den Schwellenwert überschreitet, wird die IP Adresse gemeldet. Die VERBOSE Variable ergänzt den Output um weitere Informationen. Im Beispiel gehen wir davon aus, dass die beiden Variablen im Scheduling System gesetzt wurden.

Der erste Teil des Skripts verwendet awk, um die Anzahl der Einbruchsversuche pro IP-Adresse zu zählen. Es gibt sowohl die IP-Adresse als auch die Anzahl der Versuche aus. Dies wird wiederum von der Shell gelesen, die dann überprüft, ob die IP-Adresse nicht bereits blockiert ist. Ist dies nicht der Fall, liefert es die gewünschte Ausgabe.
Dieses Skript, wenn auch etwas ausgefeilter, wird tatsächlich verwendet, um Firewall-Regeln auf mehreren Systemen zu verwalten. Die Ausgabe für ein System sieht zum Beispiel so aus:

172.58.99.157 20
77.247.110.118 50
-A INPUT -s 172.58.99.157/32 -j DROP
-A INPUT -s 77.247.110.118/32 -j DROP
-A OUTPUT -d 172.58.99.157/32 -j DROP
-A OUTPUT -d 77.247.110.118/32 -j DROP

Die ersten beiden Zeilen zeigen die Anzahl der Einbruchsversuche (20 und 50) von zwei Sources. Die nächsten 4 Zeilen zeigen die Zeilen, die der iptables-Konfiguration hinzugefügt werden können, um den gesamten Datenverkehr von diesen Sources zu blockieren.

Versuchen wir, das Skript in ein Ausführungsprogramm zu konvertieren. Ein guter Anfang besteht darin, einfach das Skript zu kopieren und dann die zusätzliche Ebene des Quotings hinzuzufügen. Der erste, aber syntaktisch falsche Schritt wäre:

run program = /bin/bash -c '
    lastb -i -f /var/log/btmp |
    awk '
        $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ }
        END	{
            for (ip in cnt)
                if (cnt[ip] > '"$NUM"')
                    print ip "\t" cnt[ip]
        }
    ' | (
        while read foo bar; do
            if ! grep -q " $foo/" /etc/sysconfig/iptables; then
                if [ $VERBOSE == "Y" ]; then
                    echo "# $foo $bar";
                fi
                echo "-A INPUT -s $foo/32 -j DROP"
                echo "-A OUTPUT -d $foo/32 -j DROP"
            fi;
        done
    ) | sort
'

Bei genauerer Betrachtung stellen wir fest, dass es hauptsächlich der awk-Code ist, der ein Problem verursacht, da er in einfache Anführungszeichen eingeschlossen ist. Um die Komplexität zu erhöhen, wird auch der Parameter NUM angesprochen, der vom Scheduling-Server aufgelöst werden muss.

Am Ende möchten wir einen String haben, der das Skript enthält, das von der Shell ausgeführt werden muss. Wir könnten also auch temporär /bin/echo anstelle von /bin/sh verwenden. Wenn der Job ausgeführt wird, wird das auszuführende Skript ausgegeben, anstatt das Skript auszuführen. Dies ermöglicht es uns, das Ausführungsprogramm zu testen, ohne den “heißen” Code tatsächlich auszuführen.

Als Zweites ändern wir das einfache Anführungszeichen nach awk und das Anführungszeichen nach dem awk-Code, sodass ein einfaches Anführungszeichen übrig bleibt. Dies kann erreicht werden, indem die Zeichenfolge in einfachen Anführungszeichen beendet und ein einfaches Anführungszeichen verkettet wird, das der Inhalt einer Zeichenfolge in doppelten Anführungszeichen ist:

run program = /bin/echo -c '
    lastb -i -f /var/log/btmp |
    awk '"'"'
        $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ }
        END	{
	        for (ip in cnt)
	            if (cnt[ip] > '"$NUM"')
	                print ip "\t" cnt[ip]
        }
    '"'"' | (
    while read foo bar; do
        if ! grep -q " $foo/" /etc/sysconfig/iptables; then
            if [ $VERBOSE == "Y" ]; then
                echo "# $foo $bar";
            fi
            echo "-A INPUT -s $foo/32 -j DROP"
            echo "-A OUTPUT -d $foo/32 -j DROP"
        fi;
    done
    ) | sort
'

Wenn wir dieses Script ausführen, sieht das Ganze schon viel besser aus:

-c 
lastb -i -f /var/log/btmp |
awk '
    $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ }
    END	{
	    for (ip in cnt)
	        if (cnt[ip] > 25)
	            print ip "\t" cnt[ip]
    }
' | (
    while read foo bar; do
        if ! grep -q " $foo/" /etc/sysconfig/iptables; then
            if [ $VERBOSE == "Y" ]; then
                echo "# $foo $bar";
            fi
            echo "-A INPUT -s $foo/32 -j DROP"
            echo "-A OUTPUT -d $foo/32 -j DROP"
        fi;
    done
) | sort

Der Parameter NUM wurde bereits korrekt ersetzt, nur der Parameter VERBOSE wird noch nicht richtig behandelt. Durch Hinzufügen einiger Anführungszeichen nach dem gleichen Prinzip wie im Fall des awk-Skriptteils erhalten wir dieses Run Program:

run program = /bin/bash -c '
        lastb -i -f /var/log/btmp |
    awk '"'"'
        $3 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ { cnt[$3]++ }
        END	{
	        for (ip in cnt)
                if (cnt[ip] > '"$NUM"')
                    print ip "\t" cnt[ip]
        }
'"'"' | (
    while read foo bar; do
        if ! grep -q " $foo/" /etc/sysconfig/iptables; then
            if [ "'$VERBOSE'" == "Y" ]; then
                echo "# $foo $bar";
                fi
                echo "-A INPUT -s $foo/32 -j DROP"
                echo "-A OUTPUT -d $foo/32 -j DROP"
            fi;
    done
) | sort
'

Und wenn der Job läuft (NUM ist auf 19, VERBOSE auf Y gesetzt), erzeugt er diesen Report:

103.147.3.118 20
118.100.180.76 20
155.94.145.191 20
160.124.49.170 20
164.90.133.183 20
185.235.43.158 20
187.170.254.186 20
39.118.192.132 20
58.96.209.38 20
81.70.149.90 20
95.210.130.95 20
-A INPUT -s 103.147.3.118/32 -j DROP
-A INPUT -s 118.100.180.76/32 -j DROP
-A INPUT -s 155.94.145.191/32 -j DROP
-A INPUT -s 160.124.49.170/32 -j DROP
-A INPUT -s 164.90.133.183/32 -j DROP
-A INPUT -s 185.235.43.158/32 -j DROP
-A INPUT -s 187.170.254.186/32 -j DROP
-A INPUT -s 39.118.192.132/32 -j DROP
-A INPUT -s 58.96.209.38/32 -j DROP
-A INPUT -s 81.70.149.90/32 -j DROP
-A INPUT -s 95.210.130.95/32 -j DROP
-A OUTPUT -d 103.147.3.118/32 -j DROP
-A OUTPUT -d 118.100.180.76/32 -j DROP
-A OUTPUT -d 155.94.145.191/32 -j DROP
-A OUTPUT -d 160.124.49.170/32 -j DROP
-A OUTPUT -d 164.90.133.183/32 -j DROP
-A OUTPUT -d 185.235.43.158/32 -j DROP
-A OUTPUT -d 187.170.254.186/32 -j DROP
-A OUTPUT -d 39.118.192.132/32 -j DROP
-A OUTPUT -d 58.96.209.38/32 -j DROP
-A OUTPUT -d 81.70.149.90/32 -j DROP
-A OUTPUT -d 95.210.130.95/32 -j DROP

Teil 1: Einführung und einfache Anwendung

Teil 2: Fortgeschrittene Anwendung

Teil 3: Backticks

Teil 4: Andere Interpreter

Teil 5: Umgehung von Einschränkungen

Teil 6: Sicherheitsüberlegungen und Fazit