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#send and 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.

TL;DR

Object#send and Object#public_send are almost as risky as eval and system.
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).

Dangerous Case

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 Kernel module’s exit method.
The 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. irb, pry).

1.send 'exit'

The REPL session exited right?

Why such a apparently useless thing can be done? It’s because almost all Objects in Ruby includes Kernel module. Kernel module has methods that can be called anywhere in your programs (so-called global functions), such as exit, puts, and so on.
And Object class includes Kernel module, which makes puts and 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 Object, including 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 system and eval are defined in Kernel module.

In other words, arbitrary code can be executed by an attacker: if the attacker gives eval as params[:method] and User.delete_all as params[:arg], he/she can erase all users from your DB.

Can public_send avoid?

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 private methods?”
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: instance_eval.
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 Object’s public method.

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 send.
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.

Measure

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.


  1. The behavior after the app exits depends on the runner middleware (e.g. Unicorn, Passenger).


I'm a Haskeller Supported By Haskell-jp.