- PVSM.RU - https://www.pvsm.ru -
Бывают SQL-инъекции! А возможны ли NoSQL-инъекции? Да! Redis, MongoDB, memcached — все эти программные продукты относятся к классу нереляционных СУБД, противоположному популярным MySQL, Oracle Database и MSSQL. Так как интерес к перечисленным базам данных в последнее время значительно возрос, хакеры всех мастей просто не могли пройти мимо них.
Думаю, ты знаком с реляционной моделью СУБД, согласно которой база данных состоит из сущностей (таблиц), связанных между собой. Доступ к данным осуществляется с помощью SQL — структурированного языка запросов. За примерами реляционных СУБД далеко ходить не надо: MySQL, Oracle, Microsoft SQL Server. Все остальные СУБД, архитектура которым отличается от классической реляционной модели, смело можно отнести к классу NoSQL-решений:
Основное отличие NoSQL-СУБД от их SQL-ориентированных конкурентов заключается в отсутствии единого, унифицированного языка запросов. Например, MongoDB использует в качестве языка запросов BSON, eXist применяет XQuery, а Sonic GraphDB требует от разработчика знания GraphQL, языка запросов к данным, имеющим вид графов. Популярность NoSQL-СУБД растет с каждым днем, что не может не сказываться на нас с тобой. Совсем скоро, буквально завтра, тебе придется искать не SQL-инъекции, а NoSQL-инъекции в очередном проекте. Не будем терять времени и поговорим о потенциальных уязвимостях.
Довольно часто я слышу утверждение, что нереляционные СУБД безопасны, так как они не используют SQL и злоумышленник не может провести на них атаки типа SQL-injection. В какой-то степени это верно: нет SQL — нет SQL-инъекций. Но, если в систему невозможно внедрить SQL-код, это еще не значит, что она безопасна. NoSQL закрывает одну потенциальную уязвимость, при этом открывая с десяток других, которые позволяют совершать разнообразные вредоносные действия:
Рассмотрим типовую архитектуру приложения с доступом к хранилищу данных NoSQL. Обычно она состоит из трех уровней:
Злоумышленник может атаковать каждый из этих уровней. Начнем с самого нижнего уровня, то есть непосредственно с СУБД.Как и любое приложение, СУБД может быть подвержена атакам переполнения буфера или иметь уязвимую схему аутентификации. Атаковать этот уровень довольно сложно, потому что сообщество, сформировавшееся вокруг продукта, и сама компания-разработчик стараются исправлять ошибки по мере их обнаружения. Но у тебя есть очень большое преимущество: большинство программных продуктов поставляются с исходным кодом, а значит, ты вполне сможешь его проанализировать и, возможно, найти что-нибудь стоящее. Если тебе повезет, то в твоих руках окажется ключ практически к любой базе данных! Следующий уровень — клиентское API. Большинство NoSQL-СУБД имеют целый зоопарк различных библиотек для доступа к данным. Стоит отметить, что большинство библиотек представляют собой проекты с открытым исходным кодом, но часть из них уже не поддерживается. Вероятность обнаружить уязвимость в клиентской библиотеке значительно выше, чем непосредственно в самой СУБД. И даже если тебе не посчастливится найти ту единственную уязвимость, которая откроет доступ ко всем приложениям, построенным на основе этого API, ты будешь представлять, как именно происходит диалог между клиентским кодом и сервером баз данных, какой используется протокол и можно ли перехватить сессию.
Ну и наконец, верхний уровень — приложение, которое ты собираешься взломать. Здесь тебе прежде всего нужно найти те места, где программист забыл проверить входные данные, и попытаться их проэксплуатировать. В данном случае нужно использовать тот же подход, что и при поиске SQL-инъекций, то есть анализировать код и сообщения об ошибках, только на этот раз придется разбираться с JSON, JavaScript или чем-то подобным.
Итак, настало время для взлома! Сегодня нашей целью будет MongoDB — одна из самых распространенных NoSQL-СУБД.
Мы будем взламывать небольшое web-приложение. Вот его исходный код [1] (процесс установки подробно описан в файле README.RU.txt). Если установка пройдет успешно, то приложение должно быть доступно по адресу http://127.0.0.1:31337. Основные атаки, о которых сегодня пойдет речь:
Начнем со взлома при помощи ошибок в использовании регулярных выражений.
MongoDB, как и многие другие NoSQL-СУБД, позволяет осуществлять поиск с помощью регулярных выражений. Это очень мощное, но в то же время опасное средство, которое может нанести существенный вред при неправильном использовании.
Для поиска с помощью регулярных выражений в MongoDB используют оператор $regex. Например, запрос «верни мне всех пользователей, логин которых начинается с «ro»», будет выглядеть следующим образом:
db.users.find({ login: { $regex: "^ro" } })
Кстати, если ты не знаком с языком запросов MongoDB, то рекомендую начать его изучение с руководства разработчика [2]. Но вернемся к нашему приложению. Открой тестовый web-сайт [3] и выбери пункт «Инъекции в регулярных выражениях» в меню 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' })
Как видишь, он безопаснее и при этом выполняется быстрее.
Да, 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.
Как же защититься от подобных атак?
В связи с бурным развитием интернета и сервис-ориентированной архитектуры (SOA) все большую популярность набирают REST-решения. Так как большинство современных нереляционных СУБД организовано в соответствии с последними тенденциям в индустрии, то многие из таких систем либо сами реализуют REST, либо используют сторонние продукты для доступа к данным с помощью RESTful-архитектуры. MongoDB не исключение: в эту СУБД входит простой REST-интерфейс который позволяет получить доступ к данным в режиме «Только чтение». Кроме того, существует проект под названием Sleepy Mongoose, который реализует полную поддержку REST.
Давай посмотрим, как работает REST-интерфейс, встроенный в MongoDB. Сервер СУБД должен быть запущен с параметром "--rest". REST-сервис доступен по адресу http://127.0.0.1:28017/. Это очень простое web-приложение, которое отображает информацию о текущем состоянии СУБД и позволяет формировать запросы к базам данных. Вот несколько интересных ссылок:
В общем случае для формирования 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 [4], в которой очень подробно описаны все аспекты безопасности REST.
Большинство современных реляционных СУБД позволяют создавать хранимые процедуры. Давай рассмотрим Microsoft SQL Server, который расширяет возможности ANSI SQL и позволяет соблюдать практически любые требования бизнес-приложений. Если тебе не хватает возможностей T-SQL (это диалект SQL, реализованный в SQL Server), то ты можешь написать хранимую процедуру на C# или любом другом .NET-совместимом языке.
MongoDB обладает не менее богатыми возможностями, в число которых входит серверный JavaScript. Фактически ты можешь выполнить почти любой код на сервере баз данных. С одной стороны, это позволяет писать очень сложные приложения для обработки данных, с другой — делает твое приложение более уязвимым.
Где можно использовать JavaScript в MongoDB?
db.system.js.save( { _id: "foo", value: function (x) { return x * x; }})
Теперь можешь попробовать вызвать ее вот так: db.eval(«foo(2)»).
В нашем тестовом приложении имеется 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-инъекций.
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 по сравнению со своими реляционными собратьями.
Q: Кто использует NoSQL?
A: Как видишь, NoSQL-СУБД имеют ряд неопровержимых преимуществ. Давай рассмотрим, кто же их использует:
Журнал Хакер, Февраль (02) 157 [6]
Илья Вербицкий (blog.chivavas.org [7])
Кстати, у нас сейчас скидки на подписку [8].
Автор: gorl
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/7647
Ссылки в тексте:
[1] исходный код: http://narod.ru/disk/49414936001.c8eff4290c6995894544335a6d26d061/xa_02_files_Totalnyi_destroy_MongoDB.zip.html
[2] руководства разработчика: http://bit.ly/cqW1RH
[3] тестовый web-сайт: http://test
[4] Robust Defenses for Cross-Site Request Forgery: http://bit.ly/cbVLvY
[5] MongoDB: http://bit.ly/4V7mD
[6] Февраль (02) 157: http://www.xakep.ru/articles/magazine/default.asp
[7] blog.chivavas.org: http://blog.chivavas.org
[8] скидки на подписку: http://shop.glc.ru
Нажмите здесь для печати.