commit d2c2729845a9435ef60e3e3e0a4d58e2f8b1184f from: xs date: Tue Jan 30 15:42:57 2024 UTC Initial commit commit - /dev/null commit + d2c2729845a9435ef60e3e3e0a4d58e2f8b1184f blob - /dev/null blob + 0dc12990ebcf6103910cec0b8cabc9c1f5fca1b1 (mode 644) --- /dev/null +++ README.md @@ -0,0 +1,47 @@ +# http-ban + +Hey! It's just a simple script I'm testing as I don't want a huge HTTP +filter solution. It targets httpd(4) log format and pfctl(8). + +It's messy and harsh to read. Sorry about this. Written it a while ago +and yet updating it a bit, removing unused code. + +Feel free to try it and share enhancements/ideas around! + +For me it has been quite effective. + +# Installation + +1. Create a user + + # useradd -u 404 -s /sbin/nologin -d /var/empty _httpban + +2. Install the script + + # install -m 755 -o root -g bin http-ban.sh /usr/local/bin/http-ban + +3. Give the required permissions in /etc/doas.conf + + # cat /etc/doas.conf + permit nopass _httpban cmd /bin/cat args /var/www/logs/access.log + permit nopass _httpban cmd /usr/bin/zcat args /var/www/logs/access.log.*gz + permit nopass _httpban cmd /sbin/pfctl args -t players -T show + permit nopass _httpban cmd /sbin/pfctl args -t players -T add -f- + permit nopass _httpban cmd /sbin/pfctl args -t players -T delete -f- + +4. Create a table in /etc/pf.conf + + # cat /etc/pf.conf + table persist + block in quick on egress from + +3. Run it as _httpban (here every 5 minutes) + + # crontab -eu _httpban + */5 * * * * -s http-ban + +# TODO + +- [ ] parametrize the table name, as is not really a good name +- [ ] remove unused filter functions +- [ ] make it more pleasant to read / give the impression I'm mentally saner blob - /dev/null blob + 73554eacfb8a06df4ebd0bf358405ed2bf6e8789 (mode 755) --- /dev/null +++ http-ban.sh @@ -0,0 +1,87 @@ +#!/bin/sh +QUICK_WHITELIST=192.168.10.10 +alias log="logger -st http-ban" +alias show="doas /sbin/pfctl -t players -T show" +alias ban="doas /sbin/pfctl -t players -T add -f-" +alias grace="doas /sbin/pfctl -t players -T delete -f-" +whitelist() { + printf %s\\n $QUICK_WHITELIST +} +last_access() { + doas /bin/cat /var/www/logs/access.log +} +access() { + last_access + doas /usr/bin/zcat /var/www/logs/access.log.*gz 2>/dev/null +} +NOT_200_Q() { + awk '$(NF-1) != 200 {print}' | sort +} +NOT_200_C() { + awk '$(NF-1) != 200 {print $(NF-1), $2}' | sort -nk1 | uniq -c | sort -nk1 +} +NOT_200_HOSTS_C() { + awk '$(NF-1) != 200 {print $2}' | sort | uniq -c | sort -nk1 +} +IN_300_HOSTS_Q() { + awk '$(NF-1) >= 300 && $(NF-1) < 400 { print }' | sort +} +IN_300_HOSTS_C() { + awk '$(NF-1) >= 300 && $(NF-1) < 400 { print $2 }' | sort | uniq -c | sort -nk1 +} +IN_400_HOSTS_Q() { + awk '$(NF-1) >= 400 && $(NF-1) < 500 { print }' | sort +} +IN_400_HOSTS_C() { + awk '$(NF-1) >= 400 && $(NF-1) < 500 { print $2 }' | sort | uniq -c | sort -nk1 +} +IN_500_HOSTS_Q() { + awk '$(NF-1) >= 500 && $(NF-1) < 600 { print }' | sort +} +IN_500_HOSTS_C() { + awk '$(NF-1) >= 500 && $(NF-1) < 600 { print $2 }' | sort | uniq -c | sort -nk1 +} +limit() { + awk -vtrig="${1:-10}" ' $1 >= trig { print $2 }' +} +pre_block() { + last_access | IN_500_HOSTS_C | limit 1 + last_access | IN_400_HOSTS_C | limit 3 + access | IN_300_HOSTS_C | limit 3 +} +apply_whitelist() { + GF=$(for ip in $(whitelist); do printf -- '-ve %s\n' "$ip"; done) + GF=${GF:-'.*'} + grep $GF +} +block() { + pre_block | sort | uniq | apply_whitelist +} +umask 127 +block | sort >/tmp/http-ban.new +show | sort | awk '{ print $1 }' >/tmp/http-ban.current +DIFF=$(comm /tmp/http-ban.new /tmp/http-ban.current) +NEW=$(comm -23 /tmp/http-ban.new /tmp/http-ban.current) +GRACE=$(comm -13 /tmp/http-ban.new /tmp/http-ban.current) +rm /tmp/http-ban.* +test -n "$NEW" -o -n "$GRACE" || exit 0 +test -n "$NEW" && + log BANNING NEW IPs && + log <<. +$NEW +. +test -n "$GRACE" && + log GRACING OLD IPs && + log <<. +$GRACE +. +ban <<. +$NEW +. +grace <<. +$GRACE +. +test -n "$QUICK_WHITELIST" && + grace <<. +$QUICK_WHITELIST +.