Почему не naxsi или mod_security?
К сожалению, это не совсем правильно решение для крупного хостинга, т.к. они могут вызывать проблемы с отправкой форм и загрузкой файлов, а это не очень хорошо для клиентов, поэтому мы решили не рисковать с их использованием. Описанное ниже решение родилось из уже имевшегося фильтра URL, а следовательно не потребовало больших изменений в конфиг, плюс ко всему, обладает неким запасом толерантности, что исключает случайное срабатывание.
Итак, принцип работы
Предположим, на сервер вдруг посыпались запросы с "UNION%20SELECT" или "../../../etc/passwd" в запросе. nginx прогоняет его по 3 (можно и 4-м) маппингам и проверяет совпадение элементов запроса, таких как REQUEST_URI, QUERY_STRING и User-Agent (опционально можно подключить проверку реферера) с регулярками:
Код: Выделить всё
map $request_uri $check_uri {
default 0;
include /etc/nginx/lists.d/security_rules.conf;
include /etc/nginx/lists.d/unix_filesystem.conf;
}
map $args $check_args {
default $check_uri;
include /etc/nginx/lists.d/security_rules.conf;
include /etc/nginx/lists.d/php_functions.conf;
include /etc/nginx/lists.d/unix_filesystem.conf;
}
#map $http_referer $check_referer {
# default $check_args;
# include /etc/nginx/lists.d/security_rules.conf;
# include /etc/nginx/lists.d/unix_filesystem.conf;
#}
map $http_user_agent $is_attack {
# default $check_referer;
default $check_args;
include /etc/nginx/lists.d/security_rules.conf;
include /etc/nginx/lists.d/unix_filesystem.conf;
}
Далее, в теле виртуалхоста размещаем строчку:
Код: Выделить всё
access_log /var/log/nginx/security_log combined if=$is_attack;
Помимо этого, на стороне nginx нужно добавить виртуалхост на портах отличных от стандартных, например 8080 и 8443:
Код: Выделить всё
server {
listen 127.0.0.1:8080 default_server;
listen 127.0.0.1:8443 default_server ssl;
server_name _;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_certificate /etc/nginx/ssl/server.pem;
# 3 строки ниже не нужны, если юзать самоподписные сертификаты
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/server.pem;
root /usr/share/nginx/html;
access_log /var/log/nginx/banned_log combined;
error_page 403 /forbidden.html;
location / {
return 403;
}
}
Далее нам понадобится fail2ban, устанавливаем его, если его до сих пор нет и настраиваем:
В директорию /etc/fail2ban/filter.d кладем файлик nginx_security.conf
Код: Выделить всё
[Definition]
failregex = ^<HOST> (.*)
ignoreregex =
Код: Выделить всё
# Fail2Ban configuration file
[INCLUDES]
before = iptables-common.conf
[Definition]
actionstart = ipset --create f2b-nginx_security iphash
iptables -t nat -I PREROUTING -p tcp -m tcp --dport 80 -m set --match-set f2b-nginx_security src -j DNAT --to-destination 127.0.0.1:8080
iptables -t nat -I PREROUTING -p tcp -m tcp --dport 443 -m set --match-set f2b-nginx_security src -j DNAT --to-destination 127.0.0.1:8443
actionflush = ipset --flush f2b-nginx_security
actionstop = iptables -t nat -D PREROUTING -p tcp -m tcp --dport 80 -m set --match-set f2b-nginx_security src -j DNAT --to-destination 127.0.0.1:8080
iptables -t nat -D PREROUTING -p tcp -m tcp --dport 443 -m set --match-set f2b-nginx_security src -j DNAT --to-destination 127.0.0.1:8443
ipset --destroy f2b-nginx_security
actionban = ipset --test f2b-nginx_security <ip> || ipset --add f2b-nginx_security <ip>
actionunban = ipset --test f2b-nginx_security <ip> && ipset --del f2b-nginx_security <ip>
[Init]
Код: Выделить всё
# Fail2Ban configuration file
[Definition]
actionstart = ipset --create f2b-nginx_ban iphash
iptables -I INPUT 1 -p tcp -m multiport --dports <port> -m set --match-set f2b-nginx_ban src -j REJECT --reject-with tcp-reset
actionstop = ipset --destroy f2b-nginx_ban
iptables -D INPUT 1 -p tcp -m multiport --dports <port> -m set --match-set f2b-nginx_ban src -j REJECT --reject-with tcp-reset
actionflush = ipset --flush f2b-nginx_ban
actionban = ipset --test f2b-nginx_ban <ip> || ipset --add f2b-nginx_ban <ip>
actionunban = ipset --test f2b-nginx_ban <ip> && ipset --del f2b-nginx_ban <ip>
[Init]
Код: Выделить всё
[nginx_security]
banaction = nginx-iptables-nat
bantime = 60
findtime = 60
maxretry = 5
logpath = /var/log/nginx/security_log
enabled = true
[nginx_security_ban]
banaction = nginx-iptables-ban
port = 80,443
bantime = 10
findtime = 60
maxretry = 7
filter = nginx_security
logpath = /var/log/nginx/security_log
enabled = true
Включаем NAT - добавляем в /etc/sysctl.d/90-override.conf строчки:
Код: Выделить всё
net.ipv4.conf.eth0.route_localnet=1
net.ipv4.ip_forward=1
Код: Выделить всё
sysctl -p
Это конечно хорошо, но как быть с уже установленным соединением, ведь браузеры умеют в рамках одного соединения напихивать кучу запросов? Для этого нам требуется вторая секция - nginx_security_ban, которая так же парсит тот же лог, но порог ее срабатывания поднят до 7 запросов в минуту. При превышения этого порога фаерволл в течении 10 секунд будет рвать соединения с IP зловреда - этого достаточно, чтобы разорвать существующие соединения, ранее прохошедшие через INPUT в mangle, последующие же соединения будут корректно завернуты в PREROUTING.
Файлики с регулярками, можно дорабатывать под себя. Это - то, что я нагрепал из логов.
php_functions.conf
Код: Выделить всё
~*(__halt_compiler|apache_child_terminate|base64_decode|convert_uudecode|include_once|require_once|invokeargs)\( 1;
~*(call_user_func|call_user_func_array|call_user_method|call_user_method_array)\( 1;
~*(file_get_contents|file_put_contents|fsockopen)\( 1;
~*(get_class_methods|get_class_vars|get_defined_constants|get_defined_functions|get_defined_vars|sys_get_temp_dir)\( 1;
~*(bzdecompress|gzdecode|gzinflate|gzuncompress|zlib_decode)\( 1;
~*(exec|passthru|pcntl_exec|pcntl_fork|pfsockopen|shell_exec)\( 1;
~*(posix_getcwd|posix_getpwuid|posix_getuid|posix_uname)\( 1;
~*(ReflectionFunction|str_rot13)\(
Код: Выделить всё
"~(sysdate|sleep|if|now|from|convert|cast|char|alert|eval|name_const|exec)\(.+" 1;
"~*\((select|insert|delete|drop)([%20|\+|\s|\t]+)" 1;
"~(x22XOR|\'XOR| OR |\+AND\+(%27|1|\')|\+NULL\+)" 1;
"~(waitfor|delay|nslookup|dblink_connect|cmd.exe)" 1;
"~(windows|winnt)?(\\|%2f|%5c|/)(boot|win)(\.|%2e)ini" 1;
"~FROM\(SELECT|CONCAT\(" 1;
"~*information_schema\." 1;
"~*(LEFT|RIGHT|INNER|OUTER)([\+|%20|\s|\t]+)JOIN" 1;
"~*UNION([\+|%20|\s|\t]+)SELECT" 1;
"~*UNION([\+|%20|\s|\t]+)ALL([\+|%20|\s|\t]+)SELECT" 1;
"~*UTL_INADDR.GET_HOST_ADDRESS" 1;
"~*ASCII\(SUBSTRING" 1;
"~(GLOBALS|REQUEST)(=|\.|\[|\%[0-9A-Z]{0,2})" 1;
Код: Выделить всё
~bin/(awk|base64|bash|cat|cc|clang|clang++|curl|csh|dash|diff|du|echo|env|fetch|file|find|ftp|gawk|gcc|grep|less|ls) 1;
~bin/(mknod|more|nc|ps|rbash|sh|sleep|su|tcsh|uname|head|hexdump|id|less|ln|mkfifo|more|nc|ncat|nice|nmap|perl) 1;
~bin/(php-cgi|printf|psed|php([\d]+)?|python([\d]+)?|ruby|sed|socat|tail|tee|telnet|top|uname|wget|who|whoami|xargs|xxd|yes) 1;
~dev/(fd/|null|stderr|stdin|stdout|tcp/|udp/|zero) 1;
~etc/(group|master.passwd|passwd|pwd.db|shadow|shells|spwd.db) 1;
~/\.(bash|cshrc|aws/|config/|local/|pki/|my\.cnf) 1;
proc/self/ 1;