четверг, 24 января 2013 г.

Google Chrome и "-" 400 0 "-" "-"

Еще раз к вопросу откуда в логах берутся строки вида
1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] "-" 400 0 "-" "-"

При медленном соединении удалось отловить эффект появления таких записей в браузере и увидеть все в живую
Вот тут видно, как хром отправил два запроса, держит соединение открытым, а потом закрывает

А секундой позже уже загружает то, что его просили.

Успел сделать снимки экрана.

Повторить эксперимент можно либо подключившись к медленному каналу, либо намеренно ограничив скорость соединения на стороне веб сервера

среда, 23 января 2013 г.

Баним ботов. Часть 2

Небольшой анализ логов сервера. Какие странные сущности обитают в Интернете. И как с ними бороться.

Открытые подключения

В логах nginx'а обнаружил десятки тысяч записей вида:

1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] "-" 400 0 "-" "-"
1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] "-" 400 0 "-" "-"
1.1.1.1 - - [19/Jan/2013:07:19:23 +0400] "-" 400 0 "-" "-"
1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] "-" 400 0 "-" "-"
1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] "-" 400 0 "-" "-"
1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] "-" 400 0 "-" "-"
1.1.1.1 - - [19/Jan/2013:07:19:34 +0400] "-" 400 0 "-" "-"

Судя по количеству и частоте запросов достаточно большое число таких запросов сделано именно ботами.
Казалось бы, легко создать правило для fail2ban и забанить их всех.
Но такие записи могут создавать и обычные пользователи. Например, если пользователь остановит загрузку или при быстром переходе со страницы на страницу (у меня получилось отловить такой эффект в Google Chrome).
Суть таких записей такова: открытое и не закрытое соединение.
Например если открыть соединение телнетом и оставить его, то по истечении таймаута появится именно такая запись.
$telnet site.ru 80

Trying 127.0.0.1...
Connected to site.ru.
Escape character is '^]'.

Или можно так: php -r 'for($i=0;$i>500;$i++){$v="s".$i;$$v=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);socket_connect($$v,"localhost", 80);}' 
Особого вреда такие атаки нанести не могут, т.к. в силу своего асинхронного характера nginx может держать достаточно большое число открытых соединений, но специально для таких случаев (а так же других недоатак) существуют такие вещи, как модули ngx_http_limit_req_module и ngx_http_limit_conn_module
Про них написано достаточно много, простым гуглением все находится.
Можно только добавить не забыть всавить в robots.txt строчку вроде этой
Crawl-delay: 1
(можно дробные значения), что бы ненароком не забанить поисковых поуков.
limit_req_zone должна обязательно стоять (в секции http) до подключения секций server. т.е. до include /etc/nginx/conf.d/*.conf;

Еще, некоторых ленивых роботов, передающих не все заголовки, можно развернуть вот таким кодом в секции server

 if ( $http_user_agent = "" ){
  return 444;
 }

try proxy

Следующая разновидность ботов пытается использовать nginx в качестве открытого проксисервера. Точнее пытается определить такую возможность.
Дело в том, что если, например, телнетом  передать заголовок
не
GET /index.htm HTTP/1.1
а
GET http://site.ru/index.htm
То nginx не разворачивает такой запрос с кодом 400, а обрабатывает его.
И дальше все зависит от настройки конфигов.
В каких то случаях, таким образом можно получить открытый http прокси сервер.
В общем случае, если site.ru определен в nginx, как 
 server_name site.ru;
То дальше вашего сервера запрос не уйдет.
Вот реальный пример из log-файла:

178.77.67.27 - - [20/Jan/2013:19:09:43 +0400] "GET http://www.scanproxy.net:80/p-80.html HTTP/1.0" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; KuKu 0.65)"
82.145.35.123 - - [10/Jan/2013:08:11:20 +0400] "GET http://proxyjudge2.proxyfire.net/fastenv HTTP/1.1" 404 564 "-" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"
80.82.215.45 - - [01/Jan/2013:08:59:03 +0400] "GET http://www.scanproxy.net:80/p-80.html HTTP/1.0" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; KuKu 0.65)"
62.193.243.32 - - [01/Jan/2013:23:38:53 +0400] "GET http://www.scanproxy.net:80/p-80.html HTTP/1.0" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; KuKu 0.65)"
46.32.65.23 - - [11/Dec/2012:19:20:15 +0400] "GET http://www.santeh.ru/cgi-bin/textenv.pl HTTP/1.0" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0)"

В основном, лечится это так:
Запретить использование дефолтного сервера и обработку запросов без имени сервера.

server {
    listen    80 default_server;
    server_name "";
    return      444;
}

Далее, при передаче заголовка (без HTTP/1.1 или HTTP/1.0)
GET http://site.ru/index.htm
Все остальные строки запроса будут проигнорированы.
Т.е. в запросе (вместо GET /index.htm HTTP/1.1 написано GET http://site.ru/index.htm)
GET http://site.ru/index.htm
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset:windows-1251,utf-8;q=0.7,*;q=0.3
Accept-Language:ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Host:site.ru
Referer:site.ru/index.htm
User-Agent:TelnetTester
Будет учтена только первая строка. А значит поможет уже знакомая нам конструкция
 if ( $http_user_agent = "" ){
  return 444;
 }
Но такая ситуация встречается не часто.
Кроме ботов, запросы вида  GET http://site.ru/index.htm HTTP/1.1 шлет опера. Во всяком случае у меня в логах достаточно много строк вроде этой:
188.162.15.86 - - [05/Jan/2013:09:18:41 +0400] "GET http://opera10beta-turbo.opera-mini.net:80//img/spb_b_1456.jpg HTTP/1.1" 404 162 "http://images.yandex.ru/yandsearch?p=..." "Opera/9.80 (Windows NT 5.1) Presto/2.12.388 Version/12.10"

Битые заголовки

94.41.37.135 - - [11/Jan/2013:08:51:57 +0400] "ЪьЪЮ\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00ЪЧ\x00;CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 75" 400 166 "-" "-"
176.213.180.115 - - [07/Jan/2013:16:27:16 +0400] "ЪьЪЮ\x00\x10JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00Ъш\x00C\x00\x02\x01\x01\x02\x01\x01\x02\x02\x02\x02\x02\x02\x02\x02\x03\x05\x03\x03\x03\x03\x03\x06\x04\x04\x03\x05\x07\x06\x07\x07\x07\x06\x07\x07\x08\x09\x0B\x09\x08\x08" 400 166 "-" "-"
Такие строки создают некоторые браузеры. Как это происходит я так и не понял. Но нечто подобное я нашел в логах на локальной машине - там, где никаких ботов быть не может. Предположительно, Google Chrome.
Так же весьма вероятно, подобные записи могут создаваться некоторыми ботами ищущими уязвимости вебсервера.
Вот например http://disorder.ru/archives/908 человек описал эксплоит для старых версий Nginx, а вот это:
188.138.88.171 - - [19/Jan/2013:20:05:45 +0400] "GET /w00tw00t.at.ISC.SANS.DFind:) HTTP/1.1" 400 166 "-" "-"

50.63.136.60 - - [19/Jan/2013:20:32:27 +0400] "GET /w00tw00t.at.ISC.SANS.Win32:) HTTP/1.1" 400 166 "-" "-"
явно адресовано IIS
Такие записи прост можно игнорировать. В случае особой осанистости помогает способ с ngx_http_limit_req_module описанный в предыдущем пункте.

воскресенье, 20 января 2013 г.

Баним ботов. Часть 1

В один прекрасный день мне надоело видеть у себя в логах такое вот безобразие:

113.204.67.51 - - [19/Jan/2013:07:22:08 +0400] "GET /phpmyadmin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:12 +0400] "GET /PMA/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:17 +0400] "GET /pma/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:17 +0400] "GET /admin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:18 +0400] "GET /dbadmin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:19 +0400] "GET /sql/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:20 +0400] "GET /mysql/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:20 +0400] "GET /myadmin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:21 +0400] "GET /phpmyadmin2/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:22 +0400] "GET /phpMyAdmin2/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:23 +0400] "GET /phpMyAdmin-2/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:23 +0400] "GET /php-my-admin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:24 +0400] "GET /sqlmanager/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:25 +0400] "GET /mysqlmanager/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:26 +0400] "GET /p/m/a/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:26 +0400] "GET /php-myadmin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:27 +0400] "GET /phpmy-admin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:28 +0400] "GET /webadmin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:29 +0400] "GET /sqlweb/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:30 +0400] "GET /websql/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:31 +0400] "GET /webdb/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:31 +0400] "GET /mysqladmin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"
113.204.67.51 - - [19/Jan/2013:07:22:32 +0400] "GET /mysql-admin/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"

и решил я всех этих мерзких ботов забанить.
Для чего был создан fail2ban скрипт phpmyadmin.conf следующего содержания:
# Fail2Ban configuration file
#
# Author: Valmat
#
[Definition]
failregex = ^ - - [.*] "GET /(?:phpmyadmin|PMA|pma|admin|dbadmin|sql|mysql|myadmin|phpmyadmin2|phpMyAdmin2|phpMyAdmin-2|php-my-admin|sqlmanager|mysqlmanager|p/m/a|php-myadmin|phpmy-admin|webadmin|sqlweb|websql|webdb|mysqladmin|mysql-admin)/ HTTP/1.1" 404

ignoreregex = 
В /etc/fail2ban/jail.conf  нужно добавить секцию
[phpmyadmin]

enabled = true
port    = http,https
filter  = phpmyadmin
logpath = /var/log/nginx/localhost.access.log
bantime = 86400
maxretry = 1 

За основу для построения скрипта был взят список
phpmyadmin
PMA
pma
admin
dbadmin
sql
mysql
myadmin
phpmyadmin2
phpMyAdmin2
phpMyAdmin-2
php-my-admin
sqlmanager
mysqlmanager
p/m/a
php-myadmin
phpmy-admin
webadmin
sqlweb
websql
webdb
mysqladmin
mysql-admin
2phpmyadmin
MyAdmin
admin/db
admin/pMA
admin/phpMyAdmin
admin/phpmyadmin
admin/sqladmin
admin/sysadmin
admin/web
administrator/PMA
administrator/admin
administrator/db
administrator/phpMyAdmin
administrator/phpmyadmin
administrator/pma
administrator/web
database
db
mysql/admin
mysql/db
mysql/dbadmin
mysql/mysqlmanager
mysql/pMA
mysql/pma
mysql/sqlmanager
mysql/web
phpMyAdmin
phpMyadmin
phpmy
phpmyAdmin
phppma
program
sql/myadmin
sql/php-myadmin
sql/phpMyAdmin
sql/phpMyAdmin2
sql/phpmanager
sql/phpmy-admin
sql/phpmyadmin2
sql/sql-admin
sql/sql
sql/sqladmin
sql/sqlweb
sql/webadmin
sql/webdb
sql/websql
PMA2005
pma2005
phpmanager
Учитывая логику работы ботов, то что в первую очередь они простукивают каталоки первого уровня, а лишь затем уровнем выше, этот список можно сократить  до такого:
phpmyadmin
PMA
pma
admin
dbadmin
sql
mysql
myadmin
phpmyadmin2
phpMyAdmin2
phpMyAdmin-2
php-my-admin
sqlmanager
mysqlmanager
p\/m\/a
php-myadmin
phpmy-admin
webadmin
sqlweb
websql
webdb
mysqladmin
mysql-admin
2phpmyadmin
MyAdmin
PMA2005
administrator
database
db
phpMyAdmin
phpMyadmin
phpmanager
phpmy
phpmyAdmin
phppma
pma2005
program
В результате получается приведенный выше конфиг.


Для проверки используем команду:
fail2ban-regex '113.204.67.51 - - [19/Jan/2013:07:22:25 +0400] "GET /mysqlmanager/ HTTP/1.1" 404 564 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"' '^ - - [.*] "GET /(?:phpmyadmin|PMA|pma|admin|dbadmin|sql|mysql|myadmin|phpmyadmin2|phpMyAdmin2|phpMyAdmin-2|php-my-admin|sqlmanager|mysqlmanager|p/m/a|php-myadmin|phpmy-admin|webadmin|sqlweb|websql|webdb|mysqladmin|mysql-admin|2phpmyadmin|MyAdmin|PMA2005|administrator|database|db|phpMyAdmin|phpMyadmin|phpmanager|phpmy|phpmyAdmin|phppma|pma2005|program)/ HTTP/1.1" 404'

четверг, 17 января 2013 г.

Как в php узнать протокол (https)

Оказывается, узнать, что сайт использует SSL и страница открыта по протоколу https не настолько тривиальная задача, что бы решить ее с наскока.
Однако, решение оказалось достаточно простое.

Проблема заключается в том, что для определения протокола могут быть использованы переменные
$_SERVER['HTTPS']
$_SERVER['HTTP_SCHEME']
$_SERVER['HTTP_X_FORWARDED_PROTO']
И косвенно
$_SERVER['SERVER_PORT']
Но все эти переменные, кроме номера порта почти наверняка будут отсутствовать.
Определять http-схему основываясь только на номере порта -- приемлемое, но не очень гибкое решение.

Я сделал так:
$scheme = isset($_SERVER['HTTP_SCHEME']) ? $_SERVER['HTTP_SCHEME'] : (
     (
  (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') ||
   443 == $_SERVER['SERVER_PORT']
     ) ? 'https' : 'http'
 
 );

И для надежности, что бы $_SERVER['HTTP_SCHEME'] была определена, в nginx.conf добавил строчку

# for SSL
fastcgi_param HTTP_SCHEME  $scheme;