Бывают SQL-инъекции! А возможны ли NoSQL-инъекции? Да! Redis, MongoDB, memcached — все эти программные продукты относятся к классу нереляционных СУБД, противоположному популярным MySQL, Oracle Database и MSSQL. Так как интерес к перечисленным базам данных в последнее время значительно возрос, хакеры всех мастей просто не могли пройти мимо них.
Что такое NoSQL
Думаю, ты знаком с реляционной моделью СУБД, согласно которой база данных состоит из сущностей (таблиц), связанных между собой. Доступ к данным осуществляется с помощью SQL — структурированного языка запросов. За примерами реляционных СУБД далеко ходить не надо: MySQL, Oracle, Microsoft SQL Server. Все остальные СУБД, архитектура которым отличается от классической реляционной модели, смело можно отнести к классу NoSQL-решений:
- различные варианты хеш-таблиц (Redis, BigTable, memcached);
- документо-ориентированные базы данных (MongoDB, CouchDB);
- базы данных, основанные на графах (Neo4j, Sones GraphDB);
- объектно-ориентированные базы данных (db4o, Cache, Jade);
- XML-ориентированные базы данных (eXist, BaseX).
Основное отличие NoSQL-СУБД от их SQL-ориентированных конкурентов заключается в отсутствии единого, унифицированного языка запросов. Например, MongoDB использует в качестве языка запросов BSON, eXist применяет XQuery, а Sonic GraphDB требует от разработчика знания GraphQL, языка запросов к данным, имеющим вид графов. Популярность NoSQL-СУБД растет с каждым днем, что не может не сказываться на нас с тобой. Совсем скоро, буквально завтра, тебе придется искать не SQL-инъекции, а NoSQL-инъекции в очередном проекте. Не будем терять времени и поговорим о потенциальных уязвимостях.
Так ли безопасны NoSQL-СУБД?
Довольно часто я слышу утверждение, что нереляционные СУБД безопасны, так как они не используют SQL и злоумышленник не может провести на них атаки типа SQL-injection. В какой-то степени это верно: нет SQL — нет SQL-инъекций. Но, если в систему невозможно внедрить SQL-код, это еще не значит, что она безопасна. NoSQL закрывает одну потенциальную уязвимость, при этом открывая с десяток других, которые позволяют совершать разнообразные вредоносные действия:
- манипулировать с REST-интерфейсом и подделывать межсайтовые запросы (CSRF);
- использовать регулярные выражения в параметрах запроса;
- выполнять скрипты на сервере, если на нем установлена NoSQL-СУБД (например, MongoDB позволяет запускать JavaScript-код);
- получать доступ к данным через специальный интерфейс, поддерживаемый СУБД (SQL в реляционных базах данных, BSON в MongoDB и т. д.), и, если используется язык запросов, «исправлять» эти запросы.
Рассмотрим типовую архитектуру приложения с доступом к хранилищу данных NoSQL. Обычно она состоит из трех уровней:
- приложение;
- API базы данных NoSQL;
- NoSQL-СУБД.
Злоумышленник может атаковать каждый из этих уровней. Начнем с самого нижнего уровня, то есть непосредственно с СУБД.Как и любое приложение, СУБД может быть подвержена атакам переполнения буфера или иметь уязвимую схему аутентификации. Атаковать этот уровень довольно сложно, потому что сообщество, сформировавшееся вокруг продукта, и сама компания-разработчик стараются исправлять ошибки по мере их обнаружения. Но у тебя есть очень большое преимущество: большинство программных продуктов поставляются с исходным кодом, а значит, ты вполне сможешь его проанализировать и, возможно, найти что-нибудь стоящее. Если тебе повезет, то в твоих руках окажется ключ практически к любой базе данных! Следующий уровень — клиентское API. Большинство NoSQL-СУБД имеют целый зоопарк различных библиотек для доступа к данным. Стоит отметить, что большинство библиотек представляют собой проекты с открытым исходным кодом, но часть из них уже не поддерживается. Вероятность обнаружить уязвимость в клиентской библиотеке значительно выше, чем непосредственно в самой СУБД. И даже если тебе не посчастливится найти ту единственную уязвимость, которая откроет доступ ко всем приложениям, построенным на основе этого API, ты будешь представлять, как именно происходит диалог между клиентским кодом и сервером баз данных, какой используется протокол и можно ли перехватить сессию.
Ну и наконец, верхний уровень — приложение, которое ты собираешься взломать. Здесь тебе прежде всего нужно найти те места, где программист забыл проверить входные данные, и попытаться их проэксплуатировать. В данном случае нужно использовать тот же подход, что и при поиске SQL-инъекций, то есть анализировать код и сообщения об ошибках, только на этот раз придется разбираться с JSON, JavaScript или чем-то подобным.
Итак, настало время для взлома! Сегодня нашей целью будет MongoDB — одна из самых распространенных NoSQL-СУБД.
NoSQL-инъекции в MongoDB
Мы будем взламывать небольшое web-приложение. Вот его исходный код (процесс установки подробно описан в файле README.RU.txt). Если установка пройдет успешно, то приложение должно быть доступно по адресу http://127.0.0.1:31337. Основные атаки, о которых сегодня пойдет речь:
- инъекции в регулярных выражениях;
- JSON-инъекции;
- манипуляции с REST-интерфейсом;
- JavaScript-инъекции.
Начнем со взлома при помощи ошибок в использовании регулярных выражений.
Web-интерфейс MongoDB
Инъекции в регулярных выражениях
MongoDB, как и многие другие NoSQL-СУБД, позволяет осуществлять поиск с помощью регулярных выражений. Это очень мощное, но в то же время опасное средство, которое может нанести существенный вред при неправильном использовании.
Для поиска с помощью регулярных выражений в MongoDB используют оператор $regex. Например, запрос «верни мне всех пользователей, логин которых начинается с «ro»», будет выглядеть следующим образом:
db.users.find({ login: { $regex: "^ro" } })
Кстати, если ты не знаком с языком запросов MongoDB, то рекомендую начать его изучение с руководства разработчика. Но вернемся к нашему приложению. Открой тестовый web-сайт и выбери пункт «Инъекции в регулярных выражениях» в меню MongoDB.
Страница аутентификация пользователя в тестовом приложении
Посмотрим, как устроена эта страница. Открой файл mongodb.js в папке Lib. В нем реализован класс MongoDbController, который отвечает за функционирование всех страниц в приложении. Сейчас нас интересует метод regexp:
var regexpPwd = new RegExp("^" + password, "i");
var loginParam = { login: login, password: regexpPwd };
Как видишь, аутентификация пользователя происходит посредством запроса, который указывает регулярное выражение в качестве пароля. При этом переменная password никак не фильтруется, что открывает нам полную свободу действий. Здесь ты можешь указать имя пользователя root и регулярное выражение вместо пароля, например [sS]*. В результате MongoDB выполнит следующий запрос: «db.users.findOne({login: 'root', password: /^[sS]*/i})», и ты успешно войдешь на уязвимый сайт под логином root (этот прием напоминает классическую SQL-инъекцию «1' or 1=1 --»). Защититься от подобной атаки довольно просто. Во-первых, всегда проверяй входные данные, откуда бы они ни поступили — напрямую от пользователя, или из внешнего файла, или из базы данных. Перед использованием данных в программе их следует проверять. Во-вторых, используй регулярные выражения только в тех случаях, когда это действительно необходимо. Например, приведенный выше запрос можно переписать вот таким образом:
db.users.findOne({ login: 'root', password: 'p@ssw0rd' })
Как видишь, он безопаснее и при этом выполняется быстрее.
JSON-инъекции
Да, MongoDB не поддерживает SQL, но СУБД не может обойтись без языка запросов. Разработчики MongoDB решили использовать вместо SQL популярнейший текстовый формат обмена данными JSON (BSON). Таким образом, можно попробовать осуществить разного рода атаки-инъекции (конечно, если входные данные не фильтруются). Такие атаки обычно называют JSON-инъекциями.
Итак, снова открывай наше приложение и переходи на страницу «JSON-инъекции». Как и в предыдущем примере, ты увидишь поле для ввода имени пользователя и пароля. Давай рассмотрим код, отвечающий за процесс аутентификации на этой странице. Он содержится в методе json-injection класса MongoDbController:
var loginParam = eval("({ login: '" + login + "',
password: '" + password + "' })");
Вышеприведенный код преобразует текстовое представление объекта JavaScript (запроса к MongoDB) в объект. После передачи этого объекта на сервер баз данных происходит аутентификация пользователя. В этом куске кода есть одно очень слабое место — входные данные никак не контролируются, поэтому ты можешь сформировать практически любой запрос к базе! Например, укажи имя пользователя «root'})//» (пароль вводить необязательно) и нажми на кнопку «Войти». Поздравляю, ты снова вошел в систему! Как так получилось? Все очень просто. Ты указал имя пользователя «root'})//», и в функции eval произошел следующий фокус:
//Переданное значение
({ login: 'root'})//', password: '' })
//Получившийся запрос
db.users.findOne({ login: 'root' })
На самом деле этот скрипт даже еще опаснее, так как с его помощью ты можешь выполнить любой код JavaScript на web-сервере. Например, имя пользователя "' + process.execPath})//" сформирует запрос вида
db.users.findOne({ login: 'C:\node.exe' })
Уязвимости такого типа называются Server Side JavaScript Injections или просто SSJI.
Как же защититься от подобных атак?
- Проверяй все данные из внешних источников. Например, логин должен соответствовать регулярному выражения "^[a-zA-Z]+$".
- Никогда не используй функцию eval для работы с JSON. В Node.js доступна функция JSON.parse, которая парсит входную строку и создает объект на ее основе.
Успешная аутентификация в тестовом приложении
Манипуляции с REST-интерфейсом
В связи с бурным развитием интернета и сервис-ориентированной архитектуры (SOA) все большую популярность набирают REST-решения. Так как большинство современных нереляционных СУБД организовано в соответствии с последними тенденциям в индустрии, то многие из таких систем либо сами реализуют REST, либо используют сторонние продукты для доступа к данным с помощью RESTful-архитектуры. MongoDB не исключение: в эту СУБД входит простой REST-интерфейс который позволяет получить доступ к данным в режиме «Только чтение». Кроме того, существует проект под названием Sleepy Mongoose, который реализует полную поддержку REST.
Давай посмотрим, как работает REST-интерфейс, встроенный в MongoDB. Сервер СУБД должен быть запущен с параметром "--rest". REST-сервис доступен по адресу http://127.0.0.1:28017/. Это очень простое web-приложение, которое отображает информацию о текущем состоянии СУБД и позволяет формировать запросы к базам данных. Вот несколько интересных ссылок:
- /listDatabases?text=1 — список баз данных;
- /serverStatus?text=1 — текущее состояние сервера.
В общем случае для формирования REST-запроса к базе данных используется URL следующего вида:
http://127.0.0.1:28017/база_данных/коллекция/?filter_поле=значение
На странице «Манипуляции с REST-интерфейсом» нашего web-приложения происходит аутентификация пользователя при помощи REST-интерфейса MongoDB. Она реализована в методе rest класса MongoDbController:
var restQry = "/secure_nosql/users/?filter_login="
+ login + "&filter_password=" + password;
var hash = restQry.indexOf("#");
if (hash > -1) {
restQry = restQry.substring(0, hash);
}
Скрипт формирует REST-запрос, игнорируя при этом все данные после символа "#". Когда REST-запрос готов, скрипт формирует HTTP-запрос к серверу и ожидает результат в формате JSON. К примеру, запрос информации о пользователе root в базе данных secure_nosql выглядит следующим образом: http://127.0.0.1:28017/secure_nosql/users/?filter_login=root&filter_password=p@ssw0rd. Всё бы хорошо, но в коде есть ошибка, проявляющая себя при обработке символа "#". Если ты попробуешь войти с именем «root#», то окажешься залогиненным в системе. Проблема, обусловленная формированием следующего URL: http://localhost:28017/secure_nosql/users/?filter_login=root#&filter_password=. состоит в том, что параметр filter_password был проигнорирован и аутентификация проходила посредством запроса http://localhost:28017/secure_nosql/users/?filter_login=root.
Стоит отметить, что большинство REST-интерфейсов также уязвимы к подделке межсайтовых запросов (CSRF):
<img src="http://localhost:28017/secure_nosql/users/" />
Честно говоря, я довольно скептически отношусь к RESTful-архитектуре, так как она открывает большие возможности для злоумышленников. Постарайся не использовать REST. Но если без него никак, то предварительно прочитай статью Robust Defenses for Cross-Site Request Forgery, в которой очень подробно описаны все аспекты безопасности REST.
JavaScript-инъекции
Большинство современных реляционных СУБД позволяют создавать хранимые процедуры. Давай рассмотрим Microsoft SQL Server, который расширяет возможности ANSI SQL и позволяет соблюдать практически любые требования бизнес-приложений. Если тебе не хватает возможностей T-SQL (это диалект SQL, реализованный в SQL Server), то ты можешь написать хранимую процедуру на C# или любом другом .NET-совместимом языке.
MongoDB обладает не менее богатыми возможностями, в число которых входит серверный JavaScript. Фактически ты можешь выполнить почти любой код на сервере баз данных. С одной стороны, это позволяет писать очень сложные приложения для обработки данных, с другой — делает твое приложение более уязвимым.
Где можно использовать JavaScript в MongoDB?
- Запросы с оператором $where. Например, запрос db.orders.find({ $where: «this.amount > 3» }) вернет тебе список заказов, количество пунктов в которых больше трех.
- Команда db.eval. К примеру, db.eval(«function (x) { return x * x; }», 2) вернет четыре.
- Функции для сохранения в базе данных. MongoDB позволяет сохранять функции, написанные на языке JavaScript, в базе данных. Для этого используется специальная системная коллекция system.js. Чтобы создать новую хранимую функцию foo(x), выполни следующий код:
db.system.js.save( { _id: "foo", value: function (x) { return x * x; }})
Теперь можешь попробовать вызвать ее вот так: db.eval(«foo(2)»).
- Map/Reduce. Map/Reduce — это программный фреймворк, разработанный компанией Google для параллельных вычислений над большими объемами данных. Он содержит две операции: map, используемую для предварительной обработки данных, и reduce, осуществляющую поиск результата.
MongoDB позволяет запускать операции map/reduce на сервере баз данных. Операции по распараллеливанию процессов и агрегации СУБД берет на себя, от разработчика требуется лишь указать исходные данные и функции, реализующие команды map и reduce. Дополнительная информация доступна в документации на MongoDB.
В нашем тестовом приложении имеется JavaScript-инъекция в операторе $where и аналогичная уязвимость в команде db.eval.
Начнем c оператора $where. Открой приложение и выбери пункт $where в меню «Инъекции JavaScript». Аутентификация на странице реализована в методе ssji-where класса MongoDbController:
var js = "this.login === '" + login + "'
&& this.password === '" + password + "'";
var loginParam = { "$where" : js };
Сначала генерируется скрипт, который проверяет имя и пароль пользователя. К сожалению, данные в переменных password и login никак не проверяются, что позволяет выполнить любой скрипт на сервере.
Теперь давай попробуем войти как root. Введи имя пользователя «root' //» и попробуй войти. Ты в очередной раз успешно залогинишься! Это возможно благодаря тому, что на сервере был сформирован следующий запрос к MongoDB:
{ '$where': 'this.login === 'root' //' && this.password === ''' }
"//" — это комментарий в JavaScript, поэтому результирующий запрос примет вид «this.login === 'root'».
К счастью, в момент выполнения запроса база данных находится в режиме «Только чтение», поэтому злоумышленник не сможет написать скрипт, модифицирующий твои данные. Но это еще не говорит о том, что подобная атака невозможна. Открой страницу «Инъекции JavaScript — db.eval(...)». На этот раз аутентификация происходит посредством вызова функции eval на сервере базы данных:
var js = "function () { return db.users.findOne ({ login: '" + login + "', password: '" + password + "' }); }"
db.eval(js);
Как видишь, у тебя есть возможность выполнить любой код на сервере БД. Попробуй создать нового пользователя pen_test с паролем pen_test. Для этого нужно ввести следующий логин:
'}), db.users.insert({login: 'pen_test', password: 'pen_test'}), 1 } //
Во-первых, ты вошел в систему. Во-вторых, в базе данных появился новый пользователь pen_test. :-)
Серверный JavaScript обладает огромным потенциалом и в то же время несет в себе много опасностей. Одна маленькая ошибка в коде, и злоумышленник получает доступ к твоему серверу баз данных. Я настоятельно рекомендую не использовать серверные скрипты: 85 % запросов можно написать и без них.
Доступ к тестовому приложению запрещен
Заключение
В заключение я бы хотел поговорить о том, каким я вижу настоящее и будущее NoSQL-сообщества. Во-первых, ты убедился в том, что NoSQL-СУБД пока не являются мейнстримом, но уже довольно близки к этому: появляются новые нереляционные СУБД и проекты, которые их используют. Как мне кажется, в ближайшем будущем NoSQL-решения займут относительно большую долю на рынке высоконагруженных приложений. Во-вторых, безопасность современных NoSQL-СУБД оставляет желать лучшего. Если в мире реляционных баз данных существует один стандартизированный язык запросов — SQL, то в «нереляционном мире» каждый реализует язык запросов, как ему вздумается: JSON, XQuery, REST-интерфейсы. Соответственно, и потенциальных уязвимостей намного больше. Если при работе с реляционными базами данных было достаточно изучить SQL-инъекции и способы борьбы с ними (при этом ты мог применить уже имеющиеся знания как в MySQL, так и в Oracle или SQL Server), то с нереляционными базами данных всё не так просто. Сначала тебе предстоит разобраться, какой язык запросов используется в твоей СУБД, как осуществляется доступ к данным и существуют ли дополнительные возможности, которые можно использовать для взлома (например, серверный JavaScript в MongoDB). После сбора информации тебе предстоит найти потенциальные уязвимости в своей системе и способы их устранения.
Я очень надеюсь, что в ближайшем будущем ситуация изменится: в открытом доступе появится больше информации и защититься от угроз, связанных с использованием NoSQL-СУБД, будет так же просто, как от обычных SQL-инъекций.
FAQ по NoSQL
Q: Каково происхождение термина NoSQL?
A: NoSQL переводится не как «Нет SQL» (No SQL at all), а как «Не только SQL» (Not only SQL). Этот термин возник в 1998 году: именно так Карло Строцци (Carlo Strozzi) назвал свою нереляционную систему управления базами данных. Второе рождение он получил в 2009-м, когда Эрик Эванс (Eric Evans) на конференции, посвященной свободным распределенным базам данных, использовал его для обозначения нереляционных СУБД и хранилищ данных. Можно считать, что именно эта конференция положила начало буму NoSQL-решений.
Q: Что такое MongoDB?
A: MongoDB — это документо-ориентированная система управления базами данных с открытым исходным кодом, разрабатываемая компанией 10gen с октября 2007 года. Первый релиз MongoDB вышел в феврале 2009 года. СУБД позиционируется как решение для хранения данных в высоконагруженных проектах. К ее основным достоинствам можно отнести высокую производительность, отличную масштабируемость, отказоустойчивость и наличие обширного сообщества разработчиков и пользователей. На данный момент среди пользователей MongoDB присутствуют такие всемирно известные компании, как Disney, SAP, Forbes и другие.
Q: Почему NoSQL?
A: Давай рассмотрим, какие основные преимущества имеют базы данных NoSQL по сравнению со своими реляционными собратьями.
- Производительность.Разработчики большинства NoSQL-решений посвящают очень много времени оптимизации своих проектов. MongoDB позволяет добиться порядка 20000 вставок и 4800 выборок в секунду.
- Простая настройка репликации баз данных. MongoDB позволяет настроить репликацию всего лишь с помощью нескольких команд, то есть гораздо проще, чем в том же Oracle.
- Множество «плюшек», облегчающих жизнь программистам. Например, MongoDB имеет встроенную поддержку Map/Reduce и сложных структур данных.
- Масштабируемость. Это один из главных козырей NoSQL-СУБД. Большинство из них поддерживает горизонтальное масштабирование, что способствует существенной экономии средств на оборудовании, ведь дешевле купить еще один недорогой сервер для кластера, чем добавить в корпоративный сервер дополнительную пару процессоров и оперативную память.
- Они дешевле! Большинство NoSQL-решений — это проекты с открытым исходным кодом. Ты можешь скачать их прямо сейчас и начать использовать. Вокруг многих проектов сформировалось большое и сплоченное сообщество, которое всегда готово помочь и исправить найденный тобой баг. В конце концов, ты сам можешь исправить баг или написать необходимое расширение. Кроме того, можно заметно сэкономить на расходах на администраторов баз данных, так как нереляционные СУБД гораздо проще реляционных и для их поддержки в большинстве случаев не требуется специальный сотрудник.
Q: Кто использует NoSQL?
A: Как видишь, NoSQL-СУБД имеют ряд неопровержимых преимуществ. Давай рассмотрим, кто же их использует:
- «Облачные» сервисы, а именно Google, Amazon и даже Windows Azure от Microsoft.
- Порталы и социальные сети: Facebook, Twitter, LinkedIn — думаю, ты сможешь продолжить список самостоятельно.
- SaaS. Всё большее число компаний выбирает решения Software-as-Service в качестве основной платформы для ведения бизнеса в тех отраслях, где постоянно растут нагрузки на инфраструктуру. Многие поставщики SaaS-решений переходят на NoSQL. Так, к примеру, поступил Salesforce.com — лидер в области SaaS CRM.
- Стартапы. Это отдельная категория проектов, которые запускаются с минимальным бюджетом с расчетом на суперприбыли в будущем. Такие проекты часто выбирают NoSQL-решения, так как они, во-первых, дешевы, а во-вторых, представляют собой отличный задел на будущее, если проект станет популярным.
Журнал Хакер, Февраль (02) 157
Илья Вербицкий (blog.chivavas.org)
Кстати, у нас сейчас скидки на подписку.
Автор: gorl