Object#send considered harmful
by Yuji Yamamoto on February 7, 2016
This is an English translation of Object#send 有害論.
Let me show you how
Object#public_send make vulnerabilities on your app when using without care.
Maybe it’s not surprising to you if you know the behavior of the methods well, but no one doesn’t seem to have written about it yet as long as I googled.
Object#public_send are almost as risky as
Avoid to pass strings from any external sources (such as users’ one) to their arguments.
Pass only hard-coded, and trusted method names (except when you create some special libraries for meta-programming).
For an obvious example, consider an action of a Rails app’s controller like this.
Haven’t you ever written a code like this?
def some_action FooModel.find_by_id(params[:id]).send(params[:method]) ... end
If an attacker gives a string such as
exit, he/she can make the Rails app exit! 1
This is caused by calling
exit method can be called by almost any objects in Ruby, as well as the model above.
So try to paste the code below onto your REPL (e.g.
The REPL session exited right?
Why such a apparently useless thing can be done? It’s because almost all
Objects in Ruby
Kernel module has methods that can be called anywhere in your programs (so-called global functions), such as
puts, and so on.
Kernel module, which makes
exit available at almost any line of Ruby programs.
Consequently, if you write a code which passes arbitrary strings to
send, any methods accessible by
Kernel module’s methods can be called, which exposes a surprisingly big vulnerability.
Much More Dangerous Case
Attackers could call even more dangerous methods in worse cases because
Kernel has very various features (possibly more than you know).
Consider you add one parameter to the action which called
send in the previous example.
def some_action FooModel.find_by_id(params[:id]).send(params[:method], params[:arg]) ... end
Now “OS commands injection” and “Ruby code injection (Is this the right name?)” vulnerability has been exposed because both
eval are defined in
In other words, arbitrary code can be executed by an attacker: if the attacker gives
params[:arg], he/she can erase all users from your DB.
If you’re a Ruby-meta-programming hacker, you might think “Okay, you mean the
private methods in
Kernel are risky, right? Then how about
public_send, which can’t call
To tell the truth, I had actually assumed that just until I began to write this post. Unfortunately, even
Object class can call a method as risky as them:
It can execute arbitrary Ruby codes as
Kernel#eval, by passing a string as its second argument. And
instance_eval is available by
public_send because its
There can be more risks!
These examples are just some cases among many possibilities. Other dangers can arise from some implementation of the receiver class of
In addition, unnoticed pitfalls can be made by modification to the standard libraries by your libraries, or your colleagues.
It’s a problem specific to Ruby, in which the users of the libraries (including standard ones) can easily alter them.
As mentioned in the last secion, risks can exist anywhere when you permit any strings to be passed as the arguments of
send. Maybe blacklisting is not sufficient in most cases.
So make a whitelist of methods you want to use to surely avoid the risks.
Giving only hard-coded
Symbol’s would be the easiest and most reliable measure.
I guess the most of the use cases of
send would be covered by it.
The behavior after the app exits depends on the runner middleware (e.g. Unicorn, Passenger).↩