While auditing some Ruby source code on a bug bounty program the other day I came across an interesting pattern, that looked like it should be vulnerable. I really hoped I could turn this into a cool attack chain.. but it seemed there were just enough annoying constraints in place to make it unlikely, if not downright impossible :'(
The 'attack' would have to come via the key of an 'Activity', and as far as I could tell the only places that potentially dynamic content was used in that field, it was either prefixed, or looked up from a whitelist anyway.
Because it used method(descriptor).call with no arguments, you would have to find a 'gadget' that exists within private_methods that allows you to do something malicious, again, super unlikely.
So instead, have a bit of a walkthrough of what's bits are here, and how my mind would tend to approach attempting to break something like this when wearing my hacker/researcher hat. Often the thing that makes a cool find is just understanding the context in which the potential bug exists, and knowing what elements you can use to 'live off the land' and escalate it into something worth that sweet Bug Bounty $$.
The main interesting chunk of the vulnerable code looks like this:
If a match_str is given, that string is returned if it occurs in the string.
This is obviously a bug (it should be something more like private_methods.find { |i| i == attack.to_sym }, but interestingly, thanks to Ruby's helper methods and attempts to make our lives super easy, we gain an interesting property that allows us to essentially bypass this check.
Since the value returned is the matching substring, and Ruby treats a string value as 'truthy', any partial match would result in a valid value being returned from find. And because it is using our block as a test (and not to map the values), it will use the full value that caused the match (not just the substring in our block). Convenient!
Based on these 2 qualities, so long as our attack matches a partial string in private_methods, it will be returned. Since find will only return the first found match, we would need to make sure our attack string uniquely hit our desired payload (or at least the order of private_methods changed enough that we could eventually hit it)
Passes each entry in enum to block. Returns the first for which block is not false.
So from what we know here, we could simplify our example again:
attack = ''
method(attack.to_sym).call
To see what potential gadgets we would have available to .call with no params, we could do something like the following (note: this doesn't seem to consider if the methods require a block to be passed to them):
Now, since there isn't a lot I know of off hand that we could do with the .call with no params.. let's contrive a situation similar to the above, that would let us do something interesting:
When simplified and broken down to this level, it seems obvious why this would be vulnerable, and I doubt we would ever intentionally commit code that looks like this. But I guess this goes to demonstrate how a few basic levels of abstraction + leveraging language features can make a seemingly quite obvious security issue far less simple/easy to spot. In the real world, that's often where the cool vulnerabilities/attack chains come through: a few seemingly innocuous elements, that when combined together in the right way lead to something as blatantly bad as the above contrived example.
Based off the ruby-security guide (forked+cleaned up), there appear to be a number of other potentially interesting methods that could be abused for similar purposes (depending on the context/situation they exist within). A non-exhaustive list includes:
Invokes the method identified by symbol, passing it any arguments specified. You can use __send__ if the name send clashes with an existing method in obj. When the method is identified by a string, the string is converted to a symbol.
There also seem to be some interesting language-level tools for marking data as untrusted, which could be useful in designing some level of 'defense in depth' around this:
While auditing some Ruby source code on a bug bounty program the other day I came across an interesting pattern, that looked like it should be vulnerable. I really hoped I could turn this into a cool attack chain.. but it seemed there were just enough annoying constraints in place to make it unlikely, if not downright impossible :'(
method(descriptor).call
with no arguments, you would have to find a 'gadget' that exists withinprivate_methods
that allows you to do something malicious, again, super unlikely.So instead, have a bit of a walkthrough of what's bits are here, and how my mind would tend to approach attempting to break something like this when wearing my hacker/researcher hat. Often the thing that makes a cool find is just understanding the context in which the potential bug exists, and knowing what elements you can use to 'live off the land' and escalate it into something worth that sweet Bug Bounty $$.
The main interesting chunk of the vulnerable code looks like this:
Since we can't do a super cool attack chain with that as is, let's simplify it down the bare minimum for demonstration purposes:
private_methods.find { |i| i[attack] }
is basically referencing the[]
method on a symbol:This is obviously a bug (it should be something more like
private_methods.find { |i| i == attack.to_sym }
, but interestingly, thanks to Ruby's helper methods and attempts to make our lives super easy, we gain an interesting property that allows us to essentially bypass this check.Since the value returned is the matching substring, and Ruby treats a string value as 'truthy', any partial match would result in a valid value being returned from
find
. And because it is using our block as a test (and not to map the values), it will use the full value that caused the match (not just the substring in our block). Convenient!Based on these 2 qualities, so long as our attack matches a partial string in
private_methods
, it will be returned. Since find will only return the first found match, we would need to make sure our attack string uniquely hit our desired payload (or at least the order ofprivate_methods
changed enough that we could eventually hit it)So from what we know here, we could simplify our example again:
To see what potential gadgets we would have available to
.call
with no params, we could do something like the following (note: this doesn't seem to consider if the methods require a block to be passed to them):Now, since there isn't a lot I know of off hand that we could do with the
.call
with no params.. let's contrive a situation similar to the above, that would let us do something interesting:When simplified and broken down to this level, it seems obvious why this would be vulnerable, and I doubt we would ever intentionally commit code that looks like this. But I guess this goes to demonstrate how a few basic levels of abstraction + leveraging language features can make a seemingly quite obvious security issue far less simple/easy to spot. In the real world, that's often where the cool vulnerabilities/attack chains come through: a few seemingly innocuous elements, that when combined together in the right way lead to something as blatantly bad as the above contrived example.
Based off the ruby-security guide (forked+cleaned up), there appear to be a number of other potentially interesting methods that could be abused for similar purposes (depending on the context/situation they exist within). A non-exhaustive list includes:
And a whole bunch more..
There also seem to be some interesting language-level tools for marking data as untrusted, which could be useful in designing some level of 'defense in depth' around this: