Poor man's HTTP port knocking with authentication

19 stycznia 2015 blog security ssh apache lightdm cgi

Port knocking z uwierzytelnianiem.

Pomys艂

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).

Wymagania

Opis dzia艂ania

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.

Konfiguracja

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

allow.sh

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

dirwatch.sh

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

/etc/network/if-pre-up.d/firewall

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

/etc/iptables.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

/etc/ip6tables.firewall.rules

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

crontab

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 &

allow.php

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);
?>

VirtualHost

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>

Linki