可以在不向客户端发送任何响应的情况下关闭 http 连接。但是比较牵扯。它不应该这么困难。
IP 表规则:
iptables -t raw -I PREROUTING 1 -m recent --rsource --mask 255.255.255.0 --update --seconds 259200 --name DYN_DROP_IPv4 -j DROP
iptables -t raw -I OUTPUT 1 -m recent --rsource --mask 255.255.255.0 --update --seconds 259200 --name DYN_DROP_IPv4 -j DROP
ip6tables -t raw -I PREROUTING 1 -m recent --rsource --mask ffff:ffff:ffff:ffff:0:0:0:0 --update --seconds 259200 --name DYN_DROP_IPv6 -j DROP
ip6tables -t raw -I OUTPUT 1 -m recent --rsource --mask ffff:ffff:ffff:ffff:0:0:0:0 --update --seconds 259200 --name DYN_DROP_IPv6 -j DROP
httpd 配置重写规则运行脚本:
# The network mask must be the same as the iptables rule mask
Define DynDropIP_QueryString '\
?action=+\
&addr=%{REMOTE_ADDR}\
&port=%{REMOTE_PORT}\
&mask_ipv4=255.255.255.0\
&mask_ipv6=ffff:ffff:ffff:ffff:0:0:0:0\
&remote_host=%{REMOTE_HOST}\
&server_name=%{SERVER_NAME}\
&server_port=%{SERVER_PORT}\
&request_method=%{REQUEST_METHOD}\
&request_uri=%{REQUEST_URI}\
&http_user_agent=%{HTTP_USER_AGENT}\
'
Define BAD_REQUESTS_REGEX '\
^/./|\
^/\.|\
^/[0-9]+|\
^/admin|\
'
Define BAD_BOTS_REGEX '\
^.$|\
11A465|\
Ahrefs|\
ArchiveBot|\
AspiegelBot|\
Baiduspider|\
'
RewriteCond %{REQUEST_URI} ${BAD_REQUESTS_REGEX} [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ${BAD_BOTS_REGEX} [NC]
# Dynamic Drop IP Script
RewriteRule ^.* /cgi-bin/DynDropIP${DynDropIP_QueryString} [PT,E=dontlog,L,END]
UnDefine DynDropIP_QueryString
UnDefine BAD_REQUESTS_REGEX
UnDefine BAD_BOTS_REGEX
cgi-bin/DynDropIP
#!/bin/sh
main() {
# Pass query string trough to the named pipe (FIFO file) daemon; Redirect stderr to bit bucket
fifo_dir='/var/run/DynDropIP/'
fifo_file='xt_recent_fifo'
if [[ -p "${fifo_dir}${fifo_file}" ]]; then
printf "%s\n" "${QUERY_STRING}" 1>"${fifo_dir}${fifo_file}" 2>/dev/null
fi
response
}
response() {
# Allow a few seconds for the firewall to be updated before responding (desire firewall to block the response)
sleep 3
# Set the HTTP response headers (404 and content type)
# end of HTTP headers (empty line i.e. 2nd consecutive line feed)
# Send 404 error page (duplicate of Apache 2.4.6 default 404 page (en))
printf "%s\n%s\n\n%s\n" \
"Status: 404 Not Found" \
"Content-type: text/html; charset=iso-8859-1" \
"\
<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL ${REQUEST_URI} was not found on this server.</p>
</body></html>\
"
}
main
/usr/local/libexec/DynDropIP/xt_recent_fifo.sh
(运行此脚本以启动 fifo 和守护进程)
#!/bin/bash
fifo_dir='/var/run/DynDropIP/'
fifo_file='xt_recent_fifo'
daemon='/usr/local/libexec/DynDropIP/xt_recent_fifo_daemon.sh'
# Prep and clean first
# If fifo dir is not a dir remove and make dir
if [[ ! -d "${fifo_dir}" ]]; then
rm -f "${fifo_dir}"
mkdir -p "${fifo_dir}"
fi
# If fifo file exists remove
[ -a "${fifo_dir}${fifo_file}" ] || \
[ -e "${fifo_dir}${fifo_file}" ] || \
[ -f "${fifo_dir}${fifo_file}" ] || \
[ -h "${fifo_dir}${fifo_file}" ] || \
[ -p "${fifo_dir}${fifo_file}" ] && \
rm -f "${fifo_dir}${fifo_file}"
mkfifo -m 666 "${fifo_dir}${fifo_file}"
# For SELinux - Allow apache user to write to the fifo
chcon -t httpd_sys_rw_content_t "${fifo_dir}${fifo_file}"
tail -f "${fifo_dir}${fifo_file}" | "${daemon}" &
/usr/local/libexec/DynDropIP/xt_recent_fifo_daemon.sh
#!/bin/bash
main() {
# Initialize IP address regex pattern vars
IP_RegEx
# Expected query string parameters: action (+/-), addr, port, mask_ipv4, mask_ipv6
while read QUERY_STRING
do
# Parse the query string into associative array
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
declare $key="$value"
done <<<"${QUERY_STRING}&"
if validate; then
if process_address; then
update_iptables
logit
# response
destroy_tcp_socket
fi
fi
done < "${1:-/dev/stdin}"
}
# Validate action, address and mask parameters
validate() {
# Verify action parameter
if [[ "${action}" != "+" && "${action}" != "-" ]]; then
return 1 # failed: invalid action
fi
# Verify address and mask, set ip version if okay
if [[ "${addr}" =~ ^${IPV4ADDR}$ && "${mask_ipv4}" =~ ^${IPV4ADDR}$ ]]; then
ipv=4
elif [[ "${addr}" =~ ^${IPV6ADDR}$ && "${mask_ipv6}" =~ ^${IPV6ADDR}$ ]]; then
ipv=6
else
return 2 # failed: invalid address or network mask
fi
}
# Process IPvN address
process_address() {
if [[ $ipv -eq 4 ]]; then
ipv4_network_mask
if ! [[ "${maddr}" =~ ^${IPV4ADDR}$ ]]; then
return 4 # failed: invalid masked ipv4 address
fi
elif [[ $ipv -eq 6 ]]; then
ipv6_expand_address
ipv6_native_notation
ipv6_network_mask
if ! [[ "${maddr}" =~ ^${IPV6ADDR}$ ]]; then
return 6 # failed: invalid masked ipv6 address
fi
fi
}
# Compress IPv6 address (retain IPv4 dotted decimal or IPv6 native notation)
compress_address() {
if [[ $addr =~ ^((${IPV6SEG}:){6})((${IPV6SEG}:${IPV6SEG})|(${IPV4ADDR}))$ ]]; then
local IPv6_Segments IPv4_Segments pattern
IPv6_Segments=${BASH_REMATCH[1]}${BASH_REMATCH[4]}
IPv4_Segments=${BASH_REMATCH[5]}
# Suppress hextet leading zeros; Three passes to get each of three
IPv6_Segments=${IPv6_Segments//:0/:} && IPv6_Segments=${IPv6_Segments//:0/:} && IPv6_Segments=${IPv6_Segments//:0/:}
# Reconstitute empty hextets with single zero; Two passes to get every other one
IPv6_Segments=${IPv6_Segments//::/:0:} && IPv6_Segments=${IPv6_Segments//::/:0:}
# First hextet special cases (0:)
[[ $IPv6_Segments =~ ^0*([[:xdigit:]]+)(.*) ]] && IPv6_Segments="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
# Last hextet special cases (:0, only if not followed by IPv4 segment)
[[ $IPv6_Segments =~ [^:]:$ && -z $IPv4_Segments ]] && IPv6_Segments=$IPv6_Segments'0'
# Compress longest contiguous hextets of zero to "::" (leftmost tiebreaker).
for (( i=7; $i>=1; i-- ))
do
pattern=''
for (( j=0; $j<$i; j++ ))
do
pattern+=':0'
done
# Compress at beginning
[[ $IPv6_Segments =~ ^0$pattern ]] && IPv6_Segments=${IPv6_Segments/0$pattern/::} && break
# Compress in the middle or at end
[[ $IPv6_Segments =~ $pattern:0 ]] && IPv6_Segments=${IPv6_Segments/$pattern:0/::} && break
done
# Put address together and remove extra triple colons
addr=$IPv6_Segments$IPv4_Segments
addr=${addr/:::/::}
fi
}
# Expand IPv6 Address (retain dotted decimal or native notation)
ipv6_expand_address() {
local hextests num_colons additional_hextets
# Number of hextets in an expanded IPv6 address (native notation)
hextets=8
# How many colons in address
num_colons=${addr//[^:]/}
num_colons=${#num_colons}
# Is address IPv4 dotted decimal notation
[[ $addr =~ :${IPV4ADDR}$ ]] && hextets=$((hextets-1)) # ::ffff:0:0/96, ::ffff:0:0:0/96, 64:ff9b::/96
# Fix up beginning and end
[[ $addr =~ ^:: ]] && addr='0'$addr
[[ $addr =~ ::$ ]] && addr=$addr'0'
# Create additional hextets
additional_hextets=':'
for (( i=$num_colons; $i<$hextets; i++ ))
do
additional_hextets+='0:'
done
# Insert additional hextets (replace ::)
addr=${addr/::/$additional_hextets}
}
# Convert from IPv4 dotted decimal to IPv6 native notation (retain compressed / expanded)
ipv6_native_notation() {
local expanded_flag
# Ensure working with an expanded address
[[ $addr =~ :: ]] && ipv6_expand_address && expanded_flag=1
# Is address IPv4 dotted decimal notation
if [[ $addr =~ ^(.*:)(${IPV4ADDR})$ ]]; then
local IPv6_Segments IPv4_Segments
local s1 s2 s3 s4 s1s2 s3s4
IFS=. read -r s1 s2 s3 s4 <<< ${BASH_REMATCH[2]} # IPv4 Segments 1, 2, 3, & 4
s1s2=$(( ($s1 * 0x100) + $s2 )) # Hextet 7
s3s4=$(( ($s3 * 0x100) + $s4 )) # Hextet 8
IPv6_Segments=${BASH_REMATCH[1]} # Hextets 1, 2, 3, 4, 5, 6
IPv4_Segments=$( printf "%x:%x" "$s1s2" "$s3s4" ) # As Hextets 7 & 8
# Return converted native notation address (expanded)
if [[ -n $IPv6_Segments && -n $IPv4_Segments ]]; then
addr=$IPv6_Segments$IPv4_Segments
fi
fi
# Revert expansion
[[ $expanded_flag -eq 1 ]] && compress_address
}
# Convert from IPv6 native to IPv4 dotted decimal notation (retain compressed / expanded)
ipv6_dotted_decimal_notation() {
local expanded_flag
# Ensure working with an expanded address
[[ $addr =~ :: ]] && ipv6_expand_address && expanded_flag=1
# Is address IPv6 native notation
if [[ $addr =~ ^(.*:)(${IPV6SEG}:${IPV6SEG})$ ]]; then
local IPv6_Segments IPv4_Segments
local s1 s2 UB LB
IFS=: read -r s1 s2 <<< ${BASH_REMATCH[2]} # IPv6 Segments 7 & 8
UB=$((0xFF00)) # Upper Byte mask
LB=$((0x00FF)) # Lower Byte mask
IPv6_Segments=${BASH_REMATCH[1]} # Hextets 1, 2, 3, 4, 5, 6
IPv4_Segments=$(((16#$s1 & $UB) / $LB)).$((16#$s1 & $LB)).$(((16#$s2 & $UB) / $LB)).$((16#$s2 & $LB))
# Return converted dotted decimal notation address (expanded)
if [[ -n $IPv6_Segments && -n $IPv4_Segments ]]; then
addr=$IPv6_Segments$IPv4_Segments
fi
fi
# Revert expansion
[[ $expanded_flag -eq 1 ]] && compress_address
}
# Apply IPv4 network mask
ipv4_network_mask() {
local s1 s2 s3 s4
local m1 m2 m3 m4
mask=$mask_ipv4 # Mask is also used in logit
IFS=. read -r s1 s2 s3 s4 <<< $addr
IFS=. read -r m1 m2 m3 m4 <<< $mask
maddr="$((s1 & m1)).$((s2 & m2)).$((s3 & m3)).$((s4 & m4))"
}
# Apply IPv6 network mask
ipv6_network_mask() {
local s1 s2 s3 s4 s5 s6 s7 s8
local m1 m2 m3 m4 m5 m6 m7 m8
mask=$mask_ipv6 # Mask is also used in logit
IFS=: read -r s1 s2 s3 s4 s5 s6 s7 s8 <<< $addr
IFS=: read -r m1 m2 m3 m4 m5 m6 m7 m8 <<< $mask
maddr=$( \
printf "%x:%x:%x:%x:%x:%x:%x:%x" \
"$((0x$s1 & 0x$m1))" "$((0x$s2 & 0x$m2))" "$((0x$s3 & 0x$m3))" "$((0x$s4 & 0x$m4))" \
"$((0x$s5 & 0x$m5))" "$((0x$s6 & 0x$m6))" "$((0x$s7 & 0x$m7))" "$((0x$s8 & 0x$m8))" \
)
}
# Update table
update_iptables() {
# Add/Remove validated address to/from the xt_recent table (action parameter +/- determines add/remove)
# Redirect both stdout & stderr to null device so as not to inadvertently provide it to web user
if [[ -n $action && -n $maddr && -n $ipv ]]; then
printf "%s%s" $action $maddr 2>/dev/null \
|/usr/bin/tee /proc/net/xt_recent/DYN_DROP_IPv$ipv \
1>/dev/null 2>/dev/null
fi
}
# Log entry
logit() {
local log_dir='/var/log/iptables/DYN_DROP_IP/'
local log_file='DYN_DROP_IPv'$ipv'.log'
mkdir -p "$log_dir"
# Address field width padding (string length, field width, pad chr/str, pad chr/str width)
local field_width=0
[[ $ipv -eq 4 ]] && field_width=15
[[ $ipv -eq 6 ]] && field_width=39
local apad=$( logit_pad ${#addr} $field_width ' ' 4 )
local mpad=$( logit_pad ${#mask} $field_width ' ' 4 )
local mapad=$( logit_pad ${#maddr} $field_width ' ' 4 )
# Write log entry
printf '%s %s %s %s %s %s %s %s %s %s %s %s\n' \
"$( date +'%Y-%m-%d %H:%M:%S %a' )" \
"$ipv" \
"$action" \
"$addr$apad" \
"$maddr$mapad" \
"$mask$mpad" \
"$remote_host" \
"$server_name" \
"$server_port" \
"$request_method" \
"$request_uri" \
"$http_user_agent" \
>> "$log_dir$log_file"
# # Truncate to last 200 entries (skip header row)
#
# if [[ $ipv -eq 4 ]]; then
# local log_header='Date Time Day IPv Action IP Address Blocked Network Network Mask Client Web Site Scheme Method URI User Agent'
# elif [[ $ipv -eq 6 ]]; then
# local log_header='Date Time Day IPv Action IP Address Blocked Network Network Mask Client Web Site Scheme Method URI User Agent'
# fi
#
# printf '%s\n%s\n' \
# "$log_header" \
# "$(tail -n +2 $log_dir$log_file | tail -n -200)" \
# > "$log_dir$log_file"
}
# Field width padding
logit_pad() {
local str_length=$1
local field_width=$2
local pad_chr=$3
local pad_chr_width=$4 # e.g. tab width
local pad_length=$(( ($field_width - $str_length) / $pad_chr_width ))
local pad_string=''
for (( i=0; $i<$pad_length; i++ ))
do
pad_string+=$pad_chr
done
printf "%s" "$pad_string"
}
IP_RegEx() {
# IPv4 Regular Expressions
IPV4SEG='(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])' # doted decimal notation; no leading 0 (octal) or 0x (hexadecimal)
IPV4ADDR='(('${IPV4SEG}'\.){3,3}('${IPV4SEG}'){1,1})'
IPV4CIDR='(3[0-2]|[12]?[0-9])'
# IPv6 Regular Expressions
IPV6SEG='[0-9a-fA-F]{1,4}' # colon hextet notation; leading 0 permitted
IPV6SEG8='('${IPV6SEG}':){7,7}('${IPV6SEG}'){1,1}' # 1:2:3:4:5:6:7:8
IPV6SEG7='('${IPV6SEG}':){1,7}(:''){1,1}' # 1:: 1:2:3:4:5:6:7::
IPV6SEG6='('${IPV6SEG}':){1,6}(:'${IPV6SEG}'){1,1}' # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
IPV6SEG5='('${IPV6SEG}':){1,5}(:'${IPV6SEG}'){1,2}' # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
IPV6SEG4='('${IPV6SEG}':){1,4}(:'${IPV6SEG}'){1,3}' # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
IPV6SEG3='('${IPV6SEG}':){1,3}(:'${IPV6SEG}'){1,4}' # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
IPV6SEG2='('${IPV6SEG}':){1,2}(:'${IPV6SEG}'){1,5}' # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
IPV6SEG1='('${IPV6SEG}':){1,1}(:'${IPV6SEG}'){1,6}' # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
IPV6SEG0=':((:'${IPV6SEG}'){1,7}|:)' # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
IPV6LLZI='[fF][eE]80:(:'${IPV6SEG}'){0,4}%[0-9a-zA-Z]{1,}' # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index)
IPV6V4MAP='(::|(0{1,4}:){5})[fF]{4}:'${IPV4ADDR} # ::ffff:d.d.d.d (::ffff:0:0/96 IPv4-mapped IPv6 addresses)
IPV6V4TRN='(::|(0{1,4}:){4})[fF]{4}:0{1,4}:'${IPV4ADDR} # ::ffff:0:d.d.d.d (::ffff:0:0:0/96 IPv4-translated addresses)
IPV6V4CMP='(::|(0:){6})'${IPV4ADDR} # ::d.d.d.d (IPv4-compatible (0::/96 deprecated by RFC4291))
# IPV6V4TRN='::([fF]{4}(:0{1,4}){0,1}:){0,1}'${IPV4ADDR} # ::d.d.d.d ::ffff:d.d.d.d ::ffff:0:d.d.d.d (IPv4-mapped IPv6 addresses and IPv4-translated addresses))
IPV6V4AMP='64:[fF]{2}9b:(:|(0{1,4}:){4})'${IPV4ADDR} # 64:ff9b::d.d.d.d (64:ff9b::/96 IPv4 translation addresses (algorithmic mapping RFC6052))
IPV6V4LUT='64:[fF]{2}9b:1:(:|(0{1,4}:){3})'${IPV4ADDR} # 64:ff9b:1::d.d.d.d (64:ff9b:1::/48 Local-Use IPv4/IPv6 Translation RFC8215)
# IPV6V4EMB='('${IPV6SEG}':){1,4}:'${IPV4ADDR} # 2001:db8:3:4::d.d.d.d 64:ff9b::d.d.d.d (IPv4-Embedded IPv6 Address)
IPV6ADDR='('\
'('${IPV6SEG8}')|('${IPV6SEG7}')|('${IPV6SEG6}')|('${IPV6SEG5}')|('${IPV6SEG4}')|('${IPV6SEG3}')|('${IPV6SEG2}')|('${IPV6SEG1}')|('${IPV6SEG0}')|'\
'('${IPV6LLZI}')|('${IPV6V4MAP}')|('${IPV6V4TRN}')|('${IPV6V4CMP}')|('${IPV6V4AMP}')|('${IPV6V4LUT}')'\
')'
IPV6CIDR='(12[0-8]|(1[01]|[1-9])?[0-9])'
PORT='([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])'
}
response() {
# Allow a few seconds for the firewall to be updated before responding (desire firewall to block the response)
sleep 3
# Set the HTTP response headers (404 and content type)
# end of HTTP headers (empty line i.e. 2nd consecutive line feed)
# Send 404 error page (duplicate of Apache 2.4.6 default 404 page (en))
printf "%s\n%s\n\n%s\n" \
"Status: 404 Not Found" \
"Content-type: text/html; charset=iso-8859-1" \
"\
<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL ${REQUEST_URI} was not found on this server.</p>
</body></html>\
"
}
# On linux kernel >= 4.9 you can use the ss command from iproute2 with key -K
# ss -K dst 192.168.1.214 dport = 49029
# the kernel has to be compiled with CONFIG_INET_DIAG_DESTROY option enabled.
destroy_tcp_socket() {
# if [[ "${port}" =~ ^${PORT}$ ]]; then # Strictly Regex method
# local -i port="10#${port}" 2> /dev/null # Convert to integer method
# if [[ $port -ge 1 && $port -le 65535 ]]; then
# Is integer between 1 and 65535 (inclusive)
local int_regex='^[0-9]+$'
if [[ "$port" =~ $int_regex && $port -ge 1 && $port -le 65535 ]]; then
# ss -K dst $addr dport = $port
ss -K dst $addr:$port \
1> /dev/null 2> /dev/null
fi
}
main