When developing any web application, it is crucial to prioritize security in
addition to performance and usability. It is important to bear in mind that
cyber attacks are continuously evolving at the same pace as technology, so it is
imperative to be equipped with the knowledge and tools to safeguard your users
and their data. The following are some common loopholes we can leave behind for
attackers.
SQL injection
Consider the following query:
The above query looks so innocent. But the actual problem with such a query is
that it is not properly sanitized since it is using interpolated strings.
In the above example, the params[:user_id]
is a user input. It is being
interpolated into the query. This could result in someone being able to inject
malicious code into our query.
Consider the following user_id:
It might look innocent initially, but that user_id can delete all the users in
the DB.
When executed it will generate the following SQL:
Of course, we have validations that will prevent such a naive case of SQL
injection. However, that case was shown to demonstrate how dangerous it could
turn out.
So how to prevent SQL injection? Use parameterized queries. There are four ways
to use parameterized queries:
1. Hash approach
We can use hashes inside where clause. Rails will internally sanitize the hash
values. So the above query can be rewritten as:
Here rails allow us to use hashes to represent basic queries. However, for more
complex queries, this approach might not be ideal.
2. Template string approach
The above piece of code can be replaced with:
Notice that the value that replaces the ? will be wrapped in single quotes
More examples:
3. Named placeholder templates
This approach is useful when we have too many values to be substituted into
templates.
Notice that the value that replaces the ? will be wrapped in single quotes
More examples:
4. sprintf style templates
In the previous two approaches, we noticed that the values when replaced are
wrapped within single quotes. However, there are cases where we don't need to
wrap the substituted values in single quotes. In such cases, we can use the
sprintf (%s) based templates:
Notice that we must manually wrap the values in single quotes if required.
This is useful in cases where we don't need the value to be wrapped in single
quotes.
For example, consider the following query:
Here we are not sanitizing params[:column]. It can be used to inject malicious
code. So in order to avoid attacks, we can use the following code:
Apart from the above cases, we should also refrain from using Arel.sql
queries. The string passed onto Arel.sql
will not be sanitized. Hence it can
be used to inject malicious code. It should always be a last resort.
Use \A & \z as anchors in regex validations
In regular expressions, \A
and \z
are anchors that are used to match the
beginning and end of a string, respectively. Without them, the regex can simply
match any part of the string. If you're depending on the regular expression for
validation, you always want to use \A
and \z
.
Using the normal regex line begin (^
) and end ($
) anchors is also not
recommended. ^
and $
will only match up until a newline character, which
means they could use an email like
me@example.com\n<script>dangerous_stuff();</script>
and still have it
validated since the regex only sees everything before the \n
.
Eg:
Mass assignment
Mass assignment is a feature of Rails that allows an application to create a
record from the values of a hash.
Consider the following case:
Also, refrain from using params.permit!
. This permits all params that the user
sends which could contain malicious code.
Instead, use the strong parameters feature released in Rails 4.
We permit the values that we need. Any other values are not used.
Avoiding unsafe reflection
constantize
is used to convert user input to a constant within the Ruby object
space. This constant may then be used to call functions or instantiate objects,
as shown below:
Consider the following code:
An attacker can now access any model by passing in the appropriate params.
Avoid this by trying to not use constantize. But if necessary, try validating
the constants that we are going to constantize:
Dangerous send
Dangerous send is similar to the Unsafe reflection mentioned above. Using
unfiltered user data to select a Class or Method to be dynamically sent is
dangerous.
Consider the following case:
In the above case, an attacker can send destroy_all
in params[:status]
.
The best way to avoid this should be to not use send
at all. The next best way
is to validate the user input:
File access vulnerabilities
Using user input to access files.
Consider the following case:
An attacker can use such a vulnerability to access files such as /etc/passwd
in the server.
Avoiding such a vulnerability includes using a sanitize function to sanitize the
file name to remove dangerous characters:
The above function converts /etc/passwd
to _etc_passwd
.
Redirection
Brakeman will raise warnings whenever redirect_to
appears to be used with a
user-supplied value that may allow them to change the :host
option.
This is because params
could contain :host => 'evilsite.com'
which would
redirect away from your site and to a malicious site.
One approach to solve this issue is to use url_from
to wrap the URL.
url_from
ensures that we are only redirected to the same domain.