19 stycznia 2015 blog security ssh apache lightdm cgi
Port knocking jest jedną z metod zabezpieczeń. Działa to w ten sposób, że klient otwiera połączenia do serwera na wskazane porty w ścisłej kolejności. Po przechwyceniu takiej sekwencji, serwer otwiera jakiś inny port, dzięki czemu jest możliwe połączenie np. SSH.
Zaprezentowany tu pomysł jest uproszczeniem wspomnianej idei. Klient otwiera stronę i po uwierzytelnieniu, czyli podaniu nazwy użytkownika i hasła, serwer otwiera port (lub porty) dla klienta. Największymi zaletami są: dodatkowe sprawdzenie uprawnień (bo klient musi się zalogować) oraz uniezależnienie się od dodatkowego oprogramowania używanego do standardowej techniki port knocking.
Trzeba pamiętać, że uwierzytelnianie podstawowe (HTTP Basic) jest bardzo łatwe do podsłuchania i użycia przez innych, dlatego zalecane jest włączenie szyfrowania (TLS).
Klient wchodzi na stronę allow.php
, wpisuje nazwę użytkownika i hasło. Jego adres IP jest pobierany i zapisywany do katalogu good-ip
.
Dzięki skryptowi dirwatch.sh
uruchomionemu jako root (tylko on może wykonywać iptables
) plik utworzony przez allow.php
zostaje "zauważony".
Zostaje uruchomiony skrypt allow.sh
z jednym parametrem - nazwą pliku, w którym został zapisany adres IP. Adres IP zostaje wczytany i reguła dopuszczająca IP klienta jest dodawana do łańcucha reguł GOOD
.
Reguły iptables
są konfigurowane dzięki /etc/network/if-pre-up.d/firewall
, który wczytuje reguły firewalla przy nawiązaniu połączenia sieciowego z plików /etc/ip6tables.firewall.rules
oraz /etc/iptables.firewall.rules
.
Dodatkowo dzięki crontab
skrypt monitorujący dirwatch.sh
jest uruchamiany przy starcie systemu.
Aby przetestować działanie port knocking wystarczy uruchomić (pamiętając o zamianie nazw katalogów na właściwe):
/root/dirwatch.sh /var/www/html/portknocking/good-ip/ /root/allow.sh
Po wejściu na stronę allow.php
w konsoli powinny wyświetlić się informacje o próbie dodania adresu klienta do łańcucha reguł. Można też skorzystać z programu curl
:
curl -u user:password https://devsite.pl/portknocking/allow.php
Główna część konfiguracji. Skrypt przyjmuje adres IP jako parametr, sprawdza czy już wcześniej nie był dodany do łańcucha GOOD
reguł iptables
i go dodaje.
#! /usr/bin/env bash
REGEXP_IP='([0-9]{1,3}[\.]){3}[0-9]{1,3}'
listGood() {
iptables -nL GOOD | \
awk '{print($4);}' | \
grep -E "$REGEXP_IP" | \
sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n
}
help() {
cat <<EOF
Allowed IPs manager.
Parameters
-l, --list list good ips
-r, --reset reset firewall
-h, --help show this help
EOF
}
if [[ "$1" == '-h' || "$1" == '--help' ]]; then
help
exit 0
fi
if [ "$EUID" -ne 0 ]; then
echo "Please run as root."
echo "Run `basename $0` --help for help."
exit 3
fi
if [[ "$1" == '-l' || "$1" == '--list' ]]; then
listGood
exit 0
elif [[ "$1" == '-r' || "$1" == '--reset' ]]; then
/etc/network/if-pre-up.d/firewall
RESULT=$?
exit $RESULT
elif [[ "$1" != "" ]]; then
echo "Checking $1 on `date`"
if [ -f "$1" ]; then
echo "It appears to be a file"
IP=`head -n 1 "$1" | grep -E "$REGEXP_IP"`
else
IP=`echo "$1" | grep -E "$REGEXP_IP"`
fi
if [[ "$IP" != "" ]]; then
echo "$IP appears to be an IP"
found=`listGood | grep -F "$IP"`
if [[ "$found" == "" ]]; then
echo "Addig $IP to good-list"
iptables -A GOOD -s "$IP" -j ACCEPT
RESULT=$?
exit $RESULT
else
echo "$IP is already good-listed"
exit 0
fi
else
echo "$1 is not an IP"
echo "Run `basename $0` --help for help."
exit 2
fi
else
echo "Run `basename $0` --help for help."
exit 1
fi
Skrypt ten jest odpowiedzialny za monitorowanie katalogu (przy użyciu inotifywait
). Jeśli plik zostanie zamknięty po zapisaniu close_write uruchamiany jest skrypt podany jako parametr.
Aktualna wersja skryptu znajduje się w repozytorium.
#! /usr/bin/env bash
help() {
cat <<EOF
Watch given dir for created files using inotify-tools.
Usage:
`basename $0` directory commands and parameters
The script executes [commands and parameters] [created file]
for every created file and DELETES the file.
EOF
}
fail() {
echo $*
echo "Run `basename $0` --help for help."
exit 1
}
if [[ "$1" == '-h' || "$1" == '--help' ]]; then
help
exit 0
fi
if [[ "$1" == "" ]]; then
fail "This script needs at least two parameters."
fi
pushd "$1" >/dev/null || fail "Cannot cd into the directory $1"
DIR="`pwd`/"
popd >/dev/null
shift
if [ ! -d "$DIR" ]; then
fail "Directory $DIR does not exist."
fi
if [[ "$1" == "" ]]; then
fail "This script needs at least two parameters."
fi
inotifywait -m -e close_write -r "$DIR" | \
while read l; do \
l=${l/*CLOSE /}; \
[ -f "${DIR}${l}" ] && \
$* "${DIR}${l}" && rm -f "${DIR}${l}" || \
echo "Given command returned non-zero for ${DIR}${l}."
done
W Ubuntu jest to plik, który jest uruchamiany przy nawiązaniu połączenia sieciowego. Musi być wykonywalny.
#! /usr/bin/env sh
/sbin/iptables-restore < /etc/iptables.firewall.rules
/sbin/ip6tables-restore < /etc/ip6tables.firewall.rules
Jest to przykładowa konfiguracja. Tworzony jest łańcuch reguł GOOD
, do którego dodawane są numery IP autoryzowanych klientów.
Dla każdego portu (w przykładzie 22
), który ma być chroniony, są dodane dwie linie:
-A INPUT -p tcp --dport 22 -j GOOD
-A INPUT -p tcp --dport 22 -j DROP
Pierwsza z nich powoduje przeskoczenie filtrowania do łańcucha GOOD
, a druga zabrania dostępu do portu.
*filter
-A INPUT -i lo -j ACCEPT
-A INPUT -d 127.0.0.0/8 -j REJECT
-N GOOD
-A OUTPUT -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
-A INPUT -p tcp --dport 22 -j GOOD
-A INPUT -p tcp --dport 22 -j DROP
-A INPUT -p icmp --icmp-type echo-request -j ACCEPT
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
-A INPUT -j DROP
-A FORWARD -j DROP
COMMIT
Reguły dla IPv6. Jeśli w systemie IPv6 jest wyłączony, nie jest konieczne ich dodawanie.
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p ipv6-icmp -j ACCEPT
-A OUTPUT -p ipv6-icmp -j ACCEPT
COMMIT
Pierwszy wpis resetuje otwarte porty codziennie, o piątej rano. Druga linia uruchamia skrypt monitorujący katalog good-ip.
0 5 * * * /etc/network/if-pre-up.d/firewall
@reboot dtach -n /tmp/dtach.allow.ip /root/dirwatch.sh /var/www/html/portknocking/good-ip/ /root/allow.sh &
Należy utworzyć katalog good-ip i umożliwić zapisywanie do niego innym użytkownikom (np. chmod 777 good-ip
), ponieważ domyślnie Apache jest uruchomiany jako użytkownik apache.
<?php
@set_time_limit(0);
@error_reporting(E_ALL ^ E_NOTICE);
header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
// header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
if($_SERVER['HTTP_CLIENT_IP']) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} else if($_SERVER['HTTP_X_FORWARDED_FOR']) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else if($_SERVER['HTTP_VIA']) {
$ip = $_SERVER['HTTP_VIA'];
} else if($_SERVER['REMOTE_ADDR']) {
$ip = $_SERVER['REMOTE_ADDR'];
} else {
$ip = "?";
}
echo $ip;
$f = "good-ip/" . $ip . ".ip";
$fh = fopen($f, 'w') or die("can't open file");
fwrite($fh, $ip);
fclose($fh);
?>
Utworzenie pliku z hasłem:
htpasswd -c /etc/htpasswd username
Konfiguracja aplikacji (w Ubuntu /etc/apache2/sites-enabled/domena.conf
):
Alias /portknocking /var/www/html/portknocking
<Directory "/var/www/html/portknocking">
AuthUserFile /etc/htpasswd
AuthType Basic
AuthName "devsite.pl http port knocking authentication"
Require valid-user
</Directory>