Browse Source

* /articles/2017/05/16/nginx-authreq-1/

* /articles/2017/05/17/nginx-authreq-2/
* /articles/2017/05/18/nginx-authreq-3/
master
Alex 'AdUser' Z 7 years ago
parent
commit
7d14466902
  1. 32
      articles/2017/05/16/nginx-authreq-1/authreq-302.patch
  2. 91
      articles/2017/05/16/nginx-authreq-1/index.markdown
  3. 26
      articles/2017/05/16/nginx-authreq-1/req-graph.msc
  4. BIN
      articles/2017/05/16/nginx-authreq-1/req-graph.png
  5. 190
      articles/2017/05/17/nginx-authreq-2/index.markdown
  6. 39
      articles/2017/05/17/nginx-authreq-2/req-graph.msc
  7. BIN
      articles/2017/05/17/nginx-authreq-2/req-graph.png
  8. 14
      articles/2017/05/17/nginx-authreq-2/stage1.txt
  9. 14
      articles/2017/05/17/nginx-authreq-2/stage2.txt
  10. 161
      articles/2017/05/18/nginx-authreq-3/index.markdown
  11. 30
      articles/2017/05/18/nginx-authreq-3/schema-1.dot
  12. BIN
      articles/2017/05/18/nginx-authreq-3/schema-1.png
  13. BIN
      articles/2017/05/18/nginx-authreq-3/schema-1_tn.png

32
articles/2017/05/16/nginx-authreq-1/authreq-302.patch

@ -0,0 +1,32 @@
--- src/http/modules/ngx_http_auth_request_module.c 2017-03-03 12:55:31.236056000 +1000
+++ src/http/modules/ngx_http_auth_request_module.c 2017-03-03 13:09:15.908223000 +1000
@@ -161,6 +161,29 @@
return ctx->status;
}
+ if (ctx->status == NGX_HTTP_MOVED_TEMPORARILY) {
+ sr = ctx->subrequest;
+
+ h = sr->headers_out.location;
+
+ if (!h && sr->upstream) {
+ h = sr->upstream->headers_in.location;
+ }
+
+ if (h) {
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ r->headers_out.location = ho;
+ }
+
+ return ctx->status;
+ }
+
if (ctx->status >= NGX_HTTP_OK
&& ctx->status < NGX_HTTP_SPECIAL_RESPONSE)
{

91
articles/2017/05/16/nginx-authreq-1/index.markdown

@ -0,0 +1,91 @@
---
title: nginx auth_request (1/3): Вводная
tags: nginx, software, devel, репост
---
У меня тут накопилось немного опыта работы с этим модулем, решил поделиться.
Прежде всего - что это? Это модуль, который разрешает или запрещает прохождение запроса в nginx на основе **подзапроса**.
Две основных схемы применения:
* с его помощью можно соорудить WAF (web-application firewall)
* ...и кастомный портал предварительной авторизации
...всё перечисленное - без модификации исходного сайта.
- [Вводная](/articles/2017/05/16/nginx-authreq-1/)
- [Многофакторная авторизация, NFA](/articles/2017/05/17/nginx-authreq-2/)
- [WAF, Web-Application firewall](/articles/2017/05/18/nginx-authreq-3/)
---
Выглядит это примерно так. Вот у нас есть типовой запрос, приходящий на некоторый вебсервер:
GET /files/83084_s.jpg HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: */*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
nginx пересылает его сначала на указанный location, затем на основе ответа,
решает что делать - пустить дальше или выдать ошибку.
Общая схема запросов ([исходник](req-graph.msc)):
![](req-graph.png)
Authorizer - это отдельная internal локация nginx'а.
Там может быть или `proxy_pass` на внешний сервер, или вызов скриптов с этого же сервера.
Примерный конфиг nginx'а:
location / {
auth_request /auth;
proxy_pass http://site.example.com;
}
location = /auth {
internal;
proxy_pass http://127.0.0.1/check.pl;
proxy_pass_request_body off; # <- важно
proxy_set_header Content-Length "0"; # <- важно
proxy_set_header X-Original-URI $request_uri;
}
Обратите внимание на `Content-Length "0"`.
Нужно это затем, чтобы в пересылаемом POST запросе получатель не ждал данных.
Далее, допустим мы соорудили некий check.pl, который на основе пересланных запросов будет отвечать кодами 200/401/403/etc.
С 200/OK - всё ясно, запрос проходит дальше. В случае остальных, например 403 - в дефолте nginx покажет простенькую,
и совершенно неинформативную страничку "Access Denied".
Чтобы этого не было, нам нужно добавить в блок `location / {}` перехват этих кодов:
location / {
<...>
error_page 401 /auth.pl;
error_page 403 /auth.pl;
}
... где `/auth.pl` -- страница авторизации или сообщения об ошибке.
Здесь вырисовывается две проблемы: во-первых, нам нужно прописать локейшн и для `/auth.pl`,
во-вторых -- 401/403 коды могут использоваться и в самом сайте.
Первое бы хрен с ним, но второе -- реально проблема, если перехватывать **все** 403 с сайта,
мы можем использовать для этой ошибки только одну *глобальную* страницу на сайт.
Для обхода этого кейса, я написал [небольшой патч](authreq-302.patch),
который позволяет также использовать код 302, временный редирект.
Правда с включением в апстрим меня завернули, дескать это поломает
использование этого модуля как **одного из** факторов авторизации:
location / {
proxy_pass http://site.example.com;
auth_req /auth; # запрос должен быть авторизован через nginx authreq
allow 192.168.0.0/16; # ...ИЛИ идти из локальной сети
deny all;
satisfy any; # <- "или" - берётся отсюда
}
В принципе, надо - берите. Если таки забодаете апстрим - вообще респект и уважуха :-).
Далее покажу примеры реального использования для каждого случая.

26
articles/2017/05/16/nginx-authreq-1/req-graph.msc

@ -0,0 +1,26 @@
msc {
hscale = 1,
width = "500";
c [ label="Client" ],
n [ label="Nginx" ],
a [ label="Authorizer" ];
--- [ label="Good request" ];
c->n [ label="Original request" ];
n->a [ label="Mirrored request" ];
a->n [ label="Authorizer decision (200)" ];
n->c [ label="Client response (200)" ];
--- [ label="Bad request" ];
c->n [ label="Original request" ];
n->a [ label="Mirrored request" ];
a->n [ label="Authorizer decision (400)" ];
n->c [ label="Client response (403)" ];
--- [ label="Unsure request" ];
c->n [ label="Original request" ];
n->a [ label="Mirrored request" ];
a->n [ label="Authorizer decision (401)" ];
n->c [ label="Client response (401)" ];
}

BIN
articles/2017/05/16/nginx-authreq-1/req-graph.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

190
articles/2017/05/17/nginx-authreq-2/index.markdown

@ -0,0 +1,190 @@
---
title: nginx auth_request (2/3): Многофакторная авторизация, NFA
tags: nginx, software, devel, репост
---
- [Вводная](/articles/2017/05/16/nginx-authreq-1/)
- [Многофакторная авторизация, NFA](/articles/2017/05/17/nginx-authreq-2/)
- [WAF, Web-Application firewall](/articles/2017/05/18/nginx-authreq-3/)
В этой статье рассмотрим, как *в принципе* делается кастомный портал с авторизацией на базе `nginx_authreq`.
Вся авторизация держится на предположении, что юзеру известен некий секрет, недоступный другим.
Правда если его украсть или скопировать - система не может распознать обмана.
---
N-факторная авторизация - это попытка убрать этот недостаток, путём использования нескольких "секретов" одновеременно.
Украсть N разных секретов в N раз сложнее (если только юзер не ССЗБ, хранящий всё в одном месте).
В быту, под "N-факторной авторизацией" чаще все понимают пару *логин/пароль*, *смс/email/токен* и возможно что-то ещё, например заход строго с определённоой сети.
В качестве второго компонента, СМС встречается чаще, поскольку привязывает авторизацию к обладанию физической вещью (телефон с определённой симкой), скопировать которую проблематично (но не невозможно).
Однако, давайте ближе к практике. Вот у нас есть некий сайт, с собственной системой авторизации, к которому нужно дополнительно ограничить доступ.
Например, мы - онлайн банк, предоставляем доступ к операциям со счётом.
Схема авторизации может выглядеть следующим образом:
- запросить логин/пароль
- при совпадении пары выше - выслать дополнительный код (действующий ограниченное время) по смс на предварительно указанный номер.
- запросить высланный код
- при совпадении кода - пустить на сайт, иначе - ошибка
Эта схема может применяться только в том случае, если мы можем влиять на все этапы процесса авторизации.
Если же такой возможности нет - "многофакторная" авторизация превращается в "многоступенчатую".
Например, мы - хостер, предоставляем доступ к ipkvm. На саму железку мы влиять не можем, её прошивку писали не мы.
В этом случае, схема авторизации будет такой:
- запросить логин/пароль хостера
- при совпадении пары выше - выслать дополнительный код (действующий ограниченное время) по смс на предварительно указанный номер.
- запросить высланный код
- при совпадении кода - пустить на вторую стадию авторизации (самого ipkvm'а)
Сделать так, чтобы гипотетический ipkvm понимал данные с "первого этапа" не всегда представляется возможным,
вследствие частой практики фундаментального огораживания своих девайсов среди производителей.
Давайте ещё ближе к практике. Как это будет выглядеть на уровне конфигов и кода?
Конфиг nginx из предыдущей статьи:
location / {
auth_request /check;
proxy_pass http://site.example.com;
}
location = /check {
internal;
proxy_pass http://127.0.0.1:3000/check;
proxy_pass_request_body off; # <- важно
proxy_set_header Content-Length "0"; # <- важно
proxy_set_header X-Original-URI $request_uri;
}
Обработчик `http://127.0.0.1:3000/check`:
sub check {
<...>
if ($self->session('user')) {
# авторизованный юзер
$self->res->code(200);
$self->render(text => 'OK');
}
eval {
# неизвестный юзер
$self->res->code(403); # default deny
my $user = $self->req->param('user');
my $pass = $self->req->param('pass');
my $code = $self->req->param('code');
if ($code and $user) {
# формой отправлен предполагаемый логин и код из sms
my $test = $self->app->redis->get("$user:code");
unless ($test) {
# мы не посылали кода этому юзеру в последнее время, пнх
$self->render('pages/stage1', msg => 'invalid session');
} elsif ($code eq $test) {
# указан правильный код
$self->app->redis->del("$user:code");
$self->session(user => $user); # см блок выше, до eval {}
$self->render(text => 'OK');
} else {
# указан неправильный код, откатываемся на 1ю стадию
$self->app->redis->del("$user:code");
$self->render('pages/stage1', msg => 'wrong code');
}
} elsif ($user and $pass) {
# формой отправлен логин/пароль для проверки
if (check_user_credentials($user, $pass)) {
# юзер существует и пароль верен
my $phone = get_user_phone($user);
if ($phone) {
# для указанного юзера задан телефон
$code = generate_auth_code();
$self->app->redis->set("$user:code" => $code);
$self->app->redis->expire("$user:code" => 60); # код будет верен в течении минуты
send_sms($phone, "your auth code is: $code");
$self->render('pages/stage2', msg => 'code sent to your phone');
} else {
$self->render('pages/stage1', msg => 'no configured phone number for this user');
}
} else {
$self->render('pages/stage1', msg => 'no such user / wrong password');
}
} else {
# данных нет, показываем стандартную форму логина
$self->render('pages/stage1');
}
} or do {
$self->res->code(500);
self->render('pages/error', msg => 'internal error');
};
}
В пример выше используется Mojolicious + redis, но с равным успехом может использоваться CGI, а в качестве хранилища - sqlite, bdb, dbm, memcached или просто обычные файлы.
На самом деле, это - нерабочий пример. Почему? Потому что особенности™ работы `auth_req`.
На самом деле nginx полностью игнорирует тело ответа от этого модуля и вместо него выдаёт свою стандартную страницу ошибки.
Значения имеют только коды возврата, модуль обрабатытвает всего 3 случая: 200, 401 и *всё остальное.
Мы даже не можем манипулировать состоянием через cookie, поскольку этот заголовок тоже не копируется в ответ.
Только WWW-Authenticate и только при коде ответа 401.
Следовательно, мы должны переписать пример выше так, чтобы использовались только коды статуса.
Здесь нам поможет следующее шаманство:
- выносим из `sub check {}` в новый обработчик всё, кроме первого блока.
- в конце оставляем `$self->res->code(403); $self->render(text => 'auth');`
- в nginx `location /auth` переопределяем коды 401,403 на путь к новому обрабобтчику
- добавляем ещё один блок `location` уже для второго обработчика
Второй блок нужен для того, чтобы `/auth`: а) не попала под действие `/check`, б) ему надо передавать данные через POST.
Конфиг и код примут следующий вид:
# Новый блок в nginx
location = /auth {
internal;
proxy_pass http://127.0.0.1:3000/auth;
# proxy_pass_request_body off; # <- важно
# proxy_set_header Content-Length "0"; # <- важно
proxy_set_header X-Original-URI $request_uri;
}
# то что осталось от первоначального обработчика
sub check {
<...>
if ($self->session('user')) {
# авторизованный юзер
$self->res->code(200);
$self->render(text => 'OK');
}
$self->res->code(403);
$self->render(text => 'auth');
}
# всё остальное перехало сюда:
sub auth {
<...>
}
Граф вызовов в случае успешной авторизации с первого раза ([исходник](req-graph.msc)).
![](req-graph.png)
Примерное содержимое [pages/stage1](stage1.txt), [pages/stage2](stage2.txt).
Любителям подключать тонну css и js на заметку: это всё придётся либо встраивать в страничку, либо мутить ТРЕТИЙ блок `location` в nginx, чтобы оно опять не попало под действие `/check`.
Впрочем, можно сразу "провалить" всю машинерию на уровень ниже, например под `/auth`. Т.е. так:
* /check -> /auth/check (описывается специальной локацией с точным соотвествием)
* /auth -> /auth/login (описывается общей локацией для /auth, т.е. всё что не /auth/login -- пересылать туда-то через `proxy_pass`)
Ну и на закуску - что даёт патч, приведённый в первой части, применительно к данной конфигурации. Удобство!
Уходит необходимость в `error_page`, появляется возможность собрать логику в одном месте, и разруливать различные остояния не кодами статуса, а сразу редиректами.
Плюс к тому же появляется возможность передать если не куку, то какой-то параметр через url (хак с `error_page` опять же такого не может).
В данном примере использования, нас спасает то, что состояний по сути всего 2: известный пользователь (пропускаем) и неизвестный пользователь (перенаправляем на логин).
3х доступных кодов ответа, из которых один (401й) использовате нежелательно из-за побочных эффектов (лезет браузерная форма с паролем)
как раз хватает на 2 основных состояния (варианты "неизвестного пользователя" дополнительно разруливаются в обработчике /auth).
Теперь представьте, что основных состояний хотя бы 5-6 (известный юзер, неизвестный с кукой, неизвестный без куки, подозрительный, заблокирован временно, заблокирован постоянно).
Шаманство с `error_page` здесь уже не прокатит, тупо не хватит различаемых модулем кодов.
В следующей части - построение кастомного WAF (Web Application Firewall) на базе этого же модуля.

39
articles/2017/05/17/nginx-authreq-2/req-graph.msc

@ -0,0 +1,39 @@
msc {
hscale = 1,
width = "700";
c [ label="client" ],
n [ label="nginx" ],
k [ label="/check" ],
a [ label="/auth" ],
d [ label="/" ];
--- [ label="unknown user, redirect to login page" ];
c -> n [ label="GET / HTTP/1.1" ];
n -> k [ label="GET / HTTP/1.1" ];
n <- k [ label="403 Forbidden" ];
n -> n [ label="error_page /auth" ];
n -> a [ label="GET /auth" ];
n <- a [ label="200 OK (pages/stage1)"];
c <- n [ label="200 OK (pages/stage1)"];
--- [ label="send auth data, stage 1 (user+pass)" ];
c -> n [ label="POST /auth (user+pass)" ];
n -> a [ label="POST /auth (user+pass)" ];
n <- a [ label="200 OK (pages/stage2)" ];
c <- n [ label="200 OK (pages/stage2)" ];
--- [ label="send auth data, stage 2 (user+code)" ];
c -> n [ label="POST /auth (user+code)" ];
n -> a [ label="POST /auth (user+code)" ];
n <- a [ label="302 / +Set-Cookie: hmac(base64({user=$user}))" ];
c <- n [ label="302 / +Set-Cookie: hmac(base64({user=$user}))" ];
--- [ label="authorized user" ];
c -> n [ label="GET / HTTP/1.1" ];
n -> k [ label="GET / HTTP/1.1" ];
n <- k [ label="200 OK" ];
n -> d [ label="GET / HTTP/1.1" ];
n <- d [ label="200 OK" ];
c <- n [ label="200 OK" ];
}

BIN
articles/2017/05/17/nginx-authreq-2/req-graph.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

14
articles/2017/05/17/nginx-authreq-2/stage1.txt

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<!-- поскипано -->
<body>
<% if (my $msg = stash('msg')) { %>
<h3><%= $msg %><h3>
<% } %>
<form method="POST" action="/auth">
<input name="user" type="text">
<input name="pass" type="password">
<input type="submit" value="Получить код">
</form>
</body>
</html>

14
articles/2017/05/17/nginx-authreq-2/stage2.txt

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<!-- поскипано -->
<body>
<% if (my $msg = stash('msg')) { %>
<h3><%= $msg %><h3>
<% } %>
<form method="POST" action="/auth">
<input name="user" type="text">
<input name="code" type="text">
<input type="submit" value="Проверить код">
</form>
</body>
</html>

161
articles/2017/05/18/nginx-authreq-3/index.markdown

@ -0,0 +1,161 @@
---
title: nginx auth_request (3/3): Web-Application firewall, WAF
tags: nginx, software, devel, репост
---
- [Вводная](/articles/2017/05/16/nginx-authreq-1/)
- [Многофакторная авторизация, NFA](/articles/2017/05/17/nginx-authreq-2/)
- [WAF, Web-Application firewall](/articles/2017/05/18/nginx-authreq-3/)
Сразу оговорюсь - я знаю про существование `nginx naxsi` и `mod_security`, речь про то, как вообще такое делается.
Под WAF я понимаю некое дополнение к веб-серверу, выполняющее одну или несколько следющих задач:
* блокировка вредоносных запросов
* подтверждение юзером "сомнительных" запросов
* ограничение частоты запросов по сложным критериям
Дополнительно можно собирать статистику.
---
Принцип работы всё тот же - nginx сначала пересылает копию запроса на какой-то внешний сервис
и на основании ответа от этого сервиса - решает, пропускать ли исходный запрос или нет.
Механизм работы опсан в предыдущей статье, поэтому интересны прежде всего алгоритмы:
1) выделение запросов одного пользователя,
2) ограничения частоты запросов.
3) определения "вредоносности" или "подозрительности" запроса,
Нетрудно заметить, третья задача зависит от первой, а вторая и третья тесно связаны.
По порядку.
Выделение запросов одного пользователя
--------------------------------------
Первая и очевидная мысль, приходящая в голову - просто смотреть запросы с одного ip.
Но тут есть множество тонкостей. Случаев, когда с одного адреса могут сидеть несколько
пользователей - достаточно много: nat, vpn, tor, ipv6-to-4 брокер.
И обратный случай - когда один юзер может сидеть с нескольких адресов: tor.
Что делать? На заголовки полагаться нельзя, там можно подделать абсолютно всё.
Комбинация ip+заголовок, например User-Agent? Это ещё хуже, подделкой заголовка можно добиться,
чтобы система определяла тебя как разных юзеров, даже не меняя свой ip.
Один из возможных вариантов - заставить каждого неопознанного клиента произвоить некую ресурсоёмкую операцию,
после выполнения которой этому клиенту присваивается уникальный идентификатор,
который тот будет предъявлять в дальнейшем как доказательство выполненной работы.
Разумеется, его нужно защитить от подделки и ограничить срок годности.
Блок схема ([исходник](schema-1.dot)):
[![](schema-1_tn.png)](schema-1.png)
Путь "известного" юзера показан жирной линией, "неизвестного" - прерывистой.
Операция (3) может быть к/либо вычислением на стороне пользователя, с сообщением результата.
Или же - проверкой на робота, например следование по редиректам. сделанных средствами самого html.
Схема может изменяться в некоторых пределах, например (2) "N" может переходить не в (4), а в (3).
Можно например, "неизвестных" клиентов сразу кидать на капчу, т.е. (1) "N" -> (4).
Думаю не нужно напоминать, что все операции, кроме (3) следует оптимизировать на минимальные
затраты ресурсов и максимальное быстродействие. Если используется капча, её лучше нагенерить заранее
и продумать механизм "карантина" на какое-то время для показанных, но не решённых.
Обратите внимание, путь по жирной линии выполняется в пределах одного обработчика.
А вот переходы к другим состояниям - требуют перехода на другие странички.
Помните, что я говорил про `error_page` и поддержку редиректов?
Ограничение частоты запросов
----------------------------
Здесь может быть куча вариантов реализации.
Например самое простое и железобетонное решение:
выделять временн*ы*е слоты определённой длительности
и считать запросы юзера в пределах текущего слота.
my $len = 5; # новый слот каждые 5 минут
my $time = time();
# вычисляем имя слота
my $slot = sprintf "req:%d:%d", $len, ($time - ($time % ($len * 60)));
# увеличиваем число запросов для юзера с $uuid
# и узнаём, сколько запросов он уже сделал в пределах данного слота
my $reqs = $redis->hincr($slot, $uuid => 1); # O(1)
$redis->expire($slot, 3600) if $reqs == 1;
# если $reqs >= $limit - блокируем запрос
Метод хорош тем, что крайне прост (1 запрос), хорошо масштабируется
и может работать вообще без обслуживания.
Основной недостаток - низкая "разрешающая способность",
на границе временного слота можно превысить лимит *до* 2х раз.
Если нам нужна гарантия, что в каждый момент времени лимит не будет превышен, подход несколько другой.
На каждого юзера заводится по персональному списку, туда пишется время запросов.
Недостатки: стоимость выше, больший расход памяти.
Принцип действия такой: при частых запросах в начале очереди растёт число "недавних" запросов.
Как только N-ый элемент оказывается "недавним", значит лимит превышен.
Вариант 2/а:
my ($time, $limit) = (5, 60); # время окна в минутах и количество запросов
my $now = time();
my $key = sprintf "user:%s", $uuid;
my $some = $redis->lindex($key, $limit - 1); # O(N)
my $next = $some + ($time * 60);
if ($now > $next) {
$redis->lpush($key, $now); # O(1)
# периодически подрезаем список, чтоб не разрастался сверх меры
$redis->ltrim($key, 0, $limit - 1); # O(N), где N - количество удалённых элементов
$redis->expire($key, 3600) if $some == 0;
} else {
# лимит превышен
}
Вариант 2/б, где "гарантированно дорогой" lindex() с O(N),
заменяется на llen() + lindex(-1), т.е. 2 x O(1).
Хотя в теории, lindex(N) для списка в котором меньше N элементов - тоже должен отрабатывать за O(1).
my ($time, $limit) = (5, 60); # время окна в минутах и количество запросов
my $now = time();
my $key = sprintf "user:%s", $uuid;
my $len = $redis->llen($key); # O(1)
if ($len < $limit) {
# первичное заполнение
$redis->lpush($key, $now); # O(1)
$redis->expire($key, 3600) if $len == 0;
return;
} else {
$redis->ltrim($key, 0, $limit - 1); # O(N), где N - количество удалённых элементов
my $last = $redis->lindex($key, -1); # O(1)
my $next = $last + ($time * 60);
if ($now >= $next) {
$redis->lpush($key, $now);
return;
}
}
# лимит превышен
Впринципе, всё это экономия на спичках. Значительно большего эффекта можно добиться,
если считать не абстрактные "запросы", а прикинуть стоимость конкретного запроса
в плане нагрузки и выделять пользователю "бюджет" на пользование.
Например, показ странички с картинками, где половина содержимого - статика,
а остальное закешировано - это одно. Полнотекстовый поиск по сайту - это уже другое.
А попытка авторизации на сайте - совсем даже третье.
"Вредоносность" и "подозрительность" запроса
--------------------------------------------
Здесь сложно дать какие-то рекомендации, смотрите по ситуации.
Можно анализировать заголовки (User-Agent/Referer/Accept/...), частоту запросов, их логическую взаимосвязь.
Например, запрос к автодополнению с X-Requested-With - это с высокой вероятностью человек.
Постоянная долбёжка поля поиска с интервалом в секунду - практически наверняка бот-дудосер.
Монотонные запросы несвязанные друг с другом - поисковый бот.
Пиковые всплески запросов с интервалом в несколько минут - юзер, который открывает несколько вкладок сразу, а потом сидит их читает.
Вобщем, на практке быстро научитесь на что нужно смотреть.
Ну и не стоит забывать про типовые запросы для поиска админок вордпресса, скуэля, гостевух и прочего говнокода на похапе.
Клиенту, спалившемся на таком можно просто возвращать 404 на все запросы, чтоб больше не приходил.

30
articles/2017/05/18/nginx-authreq-3/schema-1.dot

@ -0,0 +1,30 @@
digraph {
s [ shape="box", label="[0] Request" ];
p [ shape="box", label="[A] Pass" ];
b [ shape="box", label="[B] Block" ];
t [ shape="oval", label="[6] Set new token" ];
c [ shape="oval", label="[4] Show captcha" ];
w [ shape="oval", label="[3] Some heavy\noperation" ];
i [ shape="oval", label="[9] Accounting" ];
rl [ shape="diamond", label="[8] Is rate-limit\nexceeded?" ];
ht [ shape="diamond", label="[1] Has token?" ];
vt [ shape="diamond", label="[2] Is token valid?" ];
cv [ shape="diamond", label="[5] Is captcha valid?" ];
ts [ shape="diamond", label="[7] Is token set?" ];
s -> ht [ style="bold", weight=2 ];
ht -> vt [ label="Y", style="bold", weight=2 ];
ht -> w [ label="N", style="dashed" ];
w -> t [ style="dashed" ];
t -> ts [ style="solid" ];
ts -> b [ label="N" ];
ts -> p [ label="Y", style="solid" ];
vt -> rl [ label="Y", style="bold", weight=2 ];
rl -> i [ label="N", style="bold", weight=2 ];
i -> p [ style="bold", weight=2 ];
rl -> b [ label="Y", style="bold" ];
vt -> c [ label="N" ];
c -> cv;
cv -> c [ label="N" ];
cv -> t [ label="Y" ];
}

BIN
articles/2017/05/18/nginx-authreq-3/schema-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
articles/2017/05/18/nginx-authreq-3/schema-1_tn.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Loading…
Cancel
Save