Port knocking

Материал из Записки на полях
Перейти к навигации Перейти к поиску

Реализация технологии port knicking с помощью одного iptables с модулем recent

iptables -N reset_knock # Цепочка для сброса процесса стука
iptables -A reset_knock -m recent --name PHASE1 --remove
iptables -A reset_knock -m recent --name PHASE2 --remove
iptables -A reset_knock -m recent --name PHASE3 --remove
iptables -A reset_knock -m recent --name PHASE4 --remove
iptables -N in_phase_2 # Создаем цепочку для фазы 2
iptables -A in_phase_2 -m recent --name PHASE1 --remove # Удаляем запись из списка первой фазы
iptables -A in_phase_2 -m recent --name PHASE2 --set # Добавляем ее в список второй фазы
iptables -N in_phase_3 # Создаем цепочку для фазы 3
iptables -A in_phase_3 -m recent --name PHASE2 --remove # Удаляем запись из списка второй фазы
iptables -A in_phase_3 -m recent --name PHASE3 --set # Добавляем ее в список третьей фазы
iptables -N in_phase_4 # Создаем цепочку для фазы 4
iptables -A in_phase_4 -m recent --name PHASE3 --remove # Удаляем запись из списка третьей фазы
iptables -A in_phase_4 -m recent --name PHASE4 --set # Добавляем ее в список четвертой фазы
iptables -N checked # Для записей, прошедших проверку
iptables -A checked -j reset_knock # Очищаем списки
iptables -A checked -j ACCEPT # Разрешаем пакет
iptables -F INPUT # Очищаем цепочку INPUT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Разрешаем пакеты по установленным соединениям
# Первая фаза
iptables -A INPUT -p tcp --dport 21210 -m recent --name PHASE1 --set -j RETURN
# Для тех, кто присутствует в списке первой фазы — переход во вторую
iptables -A INPUT -p tcp --dport 11992 -m recent --rcheck --name PHASE1 --seconds 5 -g in_phase_2
# И т.д.
iptables -A INPUT -p tcp --dport 16043 -m recent --rcheck --name PHASE2 --seconds 5 -g in_phase_3
iptables -A INPUT -p tcp --dport 23050 -m recent --rcheck --name PHASE3 --seconds 5 -g in_phase_4
# Если стучатся не в том порядке — сброс
iptables -A INPUT -p tcp -m multiport --dport 21210,11992,16043,23050 -j reset_knock
# Для тех, кто прошел все четыре фазы — разрешаем доступ
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --rcheck --name PHASE4 --seconds 5 -j checked
iptables -P INPUT DROP # Дефолтное правило цепочки INPUT

Принцип прост — при стуке в порт, соответствующий очередной фазе, проверяется наличие записи в предыдущей фазе. Теперь порт 22 откроется на 5 секунд после стука в порты 21210, 11992, 16043, 23050 со строгим соблюдением порядка перечисления и интервалами не более 5 секунд.

Обратите внимание на технику реализации процесса — цепочки фаз вызываются не через -j, а через -g, поэтому после прохождения этих цепочек к пакетам сразу применяется правило по умолчанию цепочки INPUT, то есть DROP. В противном случае пакеты доходили бы до правила сброса (которое с multiport), и процесс стука все время сбрасывался бы.

Последовательный стук в порты можно организовать, например, утилитой netcat

for it in {21210,11992,16043,23050}; do
    echo " " | nc -w 1 host $it
done
ssh user@host

Или на powershell

# Remove old readonly constants from session
Remove-Variable -Name KNOCK_DESTINATION -Force -ErrorAction SilentlyContinue
Remove-Variable -Name KNOCK_VALID_TYPES -Force -ErrorAction SilentlyContinue
Remove-Variable -Name KNOCK_PORTS -Force -ErrorAction SilentlyContinue
Remove-Variable -Name KNOCK_EXE_TARGET -Force -ErrorAction SilentlyContinue
 
# === SCRIPT CONFIGURATION ===
Set-Variable KNOCK_DESTINATION -Option ReadOnly -Value "1.1.1.1"
Set-Variable KNOCK_VALID_TYPES -Option ReadOnly -Value ("TCP", "UDP")
Set-Variable KNOCK_PORTS -Option ReadOnly -Value ((1, "TCP"), (2, "TCP"), (3, "TCP"), (4, "UDP"), (5, "UDP"))
Set-Variable KNOCK_EXE_TARGET -Option ReadOnly -Value "mstsc /v:$KNOCK_DESTINATION /prompt"
# === END OF SCRIPT CONFIGURATION ===
 
# Knock all configured ports in the correct order
$KNOCK_PORTS | foreach {
    $knockPort = $_[0]
    $knockType = $_[1]
 
    # Make sure that no invalid knock type was specified   
    if ( -Not $KNOCK_VALID_TYPES.Contains($knockType) ) {
        Write-Error "Invalid knock type specified: $knockType"
        Exit(1)
    } else {
        Write-Host "Knocking $knockType port $knockPort..."
 
        # Execute the port knock, either TCP or UDP
        switch($knockType) {
            "TCP" {
                $tcpClient = New-Object System.Net.Sockets.TcpClient
                $tcpClient.BeginConnect($KNOCK_DESTINATION, $knockPort, $null, $null) | Out-Null
                $tcpClient.Close() | Out-Null
            }
            "UDP" {
                $udpClient = New-Object System.Net.Sockets.UdpClient
                $udpClient.Connect($KNOCK_DESTINATION, $knockPort) | Out-Null
                $udpClient.Send([byte[]](0), 1) | Out-Null
                $udpClient.Close() | Out-Null
            }
        }
 
        # Wait a second to make sure that our firewall gets the packets in the right order
        sleep 1
    }
}
 
# Start the configured service
Write-Host "Open sesame!"
#Write-Host "Executing target command...: $KNOCK_EXE_TARGET"
#Invoke-Expression -Command $KNOCK_EXE_TARGET</pre?