Application WAF: openresty_nginx, pcre, lua_resty_waf

Сегодня речь пойдет об очень занимательном проекте, а точнее проектах. Это openresty_nginx – кастомная реализация веб сервера, основанная на ядре Nginx и LuaJIT с большим количеством аккуратно написанных и подобранных Lua библиотек. Второй проект это – lua_resty_waf – высокопроизводительный Application WAF основанный на стеке OpenResty.

lua_resty_waf использует Nginx Lua API для исполнения кода скриптов написанных на Lua в ядре Nginx которые занимаются анализом HTTP запросов к серверу на основе базы данных ModSecurity CRS. Интересно, что lua_resty_waf парсит ту самую базу данных ModSecurity CRS (которая выглядит как просто много текстовых файлов) с помощью PCRE – Perl Compatible Regular Expressions – офигенно быстрой библиотеки для парсинга реджексов.

Одним словом, статья претендует на редкий артефакт на просторах рунета, к тому же, она подкреплена теми фактами, что я использую lua_resty_waf у себя в продакшене (на секундочку у меня там 90 000 000 юников в месяц), так же пресловутый CloudFlare Cloud WAF тоже использует наработки этого проекта. Итак, поехали!

Из чего будет состоять наш Application WAF?

Нам понадобятся:

– ubuntu 16.04 LTS
– openresty nginx
– pcre
– lua-resty-waf
– ModSecurity CRS
– OWASP
– LuaJIT

Стоит дать краткое пояснение по каждому основному компоненту Application WAF

Openresty nginx – OpenResty® – полноценный веб сервер, основанный на ядре nginx, но включающий в себя LuaJIT, а так-же прочие наработки проекта Openresty.

PCRE – Perl Compatible Regular Expressions – набор функция для парсинга реджексов на Perl 5.

ModSecurity CRS – ModSecurity Core Rule – набора правил призванный защитить веб сервер от широкого спектра различных атак

lua-resty-waf – высокопроизводительный WAF написанный как модуль поверх стека OpenResty. lua-resty-waf умеет:
* анализировать любые HTTP запросы
* останавливает brute-force атаки
* умеет использовать real-time DNS black lists для блокировок
* может отправлять свои логи по TCP/UDP
* может использовать memcached или redis для хранения разного рода переменных

Как можно себе представить Application WAF?

af

Компилируем OpenResty Nginx с поддержкой Lua

Для этого нам понадобится машина с установленной на ней Ubuntu 16.04.1 LTS
Готовим директории

mkdir openresty     
cd openresty/
mkdir openresty     
cd openresty/

Качаем PCRE и OpenResty последних версий

wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz    
wget https://openresty.org/download/openresty-1.11.2.3.tar.gz
wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz    
wget https://openresty.org/download/openresty-1.11.2.3.tar.gz

Открываем PCRE и OpenResty

tar -xvf pcre-8.40.tar.gz      
tar -xvf openresty-1.11.2.3.tar.gz
tar -xvf pcre-8.40.tar.gz      
tar -xvf openresty-1.11.2.3.tar.gz

Идем в папку с OpenResty

cd openresty-1.11.2.3/
cd openresty-1.11.2.3/

Ставим зависимости

apt install make gcc libssl-dev g++ liblua5.1-0-dev python-minimal \
python2.7 libjson-perl git libreadline-dev libncurses5-dev libpcre3-dev \
libssl-dev perl make build-essential curl -y
apt install make gcc libssl-dev g++ liblua5.1-0-dev python-minimal \
python2.7 libjson-perl git libreadline-dev libncurses5-dev libpcre3-dev \
libssl-dev perl make build-essential curl -y

Собираем OpenResty с поддержкой PCRE

./configure -j2 --with-pcre=../pcre-8.40 \
--with-pcre-jit --with-debug --with-ipv6 \
--with-pcre-opt=-g && make && make install
./configure -j2 --with-pcre=../pcre-8.40 \
--with-pcre-jit --with-debug --with-ipv6 \
--with-pcre-opt=-g && make && make install

Проверим что Lua код работает в OpenResty Nginx

cd /usr/local/openresty/nginx 
vi conf/nginx.conf
cd /usr/local/openresty/nginx 
vi conf/nginx.conf

Добавим в server block следующий код

location /foo {  
     content_by_lua_block {  
         ngx.say("Hello, world!")  
     }  
}
location /foo {  
     content_by_lua_block {  
         ngx.say("Hello, world!")  
     }  
}

Запускаем Nginx

./sbin/nginx
./sbin/nginx

Проверяем CURL-ом

curl http://<instance -ip>/foo
Hello, world!</instance>
curl http://<instance -ip>/foo
Hello, world!</instance>

Работает!

Компилируем lua-resty-waf

Отлично! Все работает. Теперь нам предстоить собственно собрать из исходных кодов сам lua-resty-waf Application WAF.
Для этого ставим зависимости

apt install luarocks -y  
cd /root/openresty/
apt install luarocks -y  
cd /root/openresty/

Готовим переменные среды

export PATH=/usr/local/openresty/bin:/usr/local/openresty/nginx/sbin:$PATH
export PATH=/usr/local/openresty/bin:/usr/local/openresty/nginx/sbin:$PATH

Добавим в .bashrc

vim ~/.bashrc  
export PATH=/usr/local/openresty/bin:/usr/local/openresty/nginx/sbin:$PATH 
source ~/.bashrc
vim ~/.bashrc  
export PATH=/usr/local/openresty/bin:/usr/local/openresty/nginx/sbin:$PATH 
source ~/.bashrc

Качаем код lua-resty-waf и собираем

git clone --recursive https://github.com/p0pr0ck5/lua-resty-waf.git
cd lua-resty-waf && make && make install
git clone --recursive https://github.com/p0pr0ck5/lua-resty-waf.git
cd lua-resty-waf && make && make install

Все готово. Теперь можем заняться конфигурацией lua-resty-waf

cd /usr/local/openresty/nginx    
vim conf/nginx.conf
cd /usr/local/openresty/nginx    
vim conf/nginx.conf

Добавим следующий код в http block

init_by_lua_block {
    require "resty.core"
    local waf = require "resty.waf"
    waf.init()
}
init_by_lua_block {
    require "resty.core"
    local waf = require "resty.waf"
    waf.init()
}

И вот этот в test location который мы создали раньше

access_by_lua_block {
    local lrw = require "resty.waf"
    local waf = lrw:new()
    waf:set_option("debug", true)
    waf:set_option("mode", "ACTIVE")
    waf:exec()
}
 
log_by_lua_block {
    local lrw = require "resty.waf"
    local waf = lrw:new()
    waf:write_log_events()
}
access_by_lua_block {
    local lrw = require "resty.waf"
    local waf = lrw:new()
    waf:set_option("debug", true)
    waf:set_option("mode", "ACTIVE")
    waf:exec()
}

log_by_lua_block {
    local lrw = require "resty.waf"
    local waf = lrw:new()
    waf:write_log_events()
}

Эти директивы собственно подгружают модуль resty.waf в Nginx и будут исполнятся каждый раз как OpenResty Nginx будет обрабатывать HTTP запрос, и он же будет блокировать запросы, которые “покажутся” ему подозрительными.

В список подозрительных входят любые HTTP аномалии, странные user agents, cross-site scripting (XSS) и SQL injection (SQLi), а так-же многое другое.

Давайте теперь перезапустим Nginx

./sbin/nginx -s reload
./sbin/nginx -s reload

И проверим работает ли Application WAF обратясь к серверу

curl 'http://<instance -ip>/foo?a=alert(1)</instance>
curl 'http://<instance -ip>/foo?a=alert(1)</instance>

Вы увидите что Nginx вернет вам 403 Forbidden.
Вот что будет, если замутить джаваскрипт “атаку”
Untitled