We all know that user input should be treated extra cautiously. Any input field, GET request, or API endpoint is a risky entry point. With that in mind, here's a rundown of some of some especially dangerous ways you'll want to avoid using user input.
Unsafe deserialization
Deserializing untrusted data can be pretty sketch. Attackers can transfer payloads or malicious code disguised as serialized data, which you'll in turn unfurl. This can take the shape of image uploads for profile pics, specialized file formats, and more. Protect required formats like image uploads by validating them, and don't use any output from them in things like error messages.
In other instances, avoid it if you can. That wouldn't be much of a tip though, so if you have to consume serialized data, use a data-only and language-agnostic format like JSON—or XML if you must. These formats lessen the chance of any custom logic existing in the data. You can learn more in the OWASP cheatsheet on the subject.
Don't use Eval or system with user input
Eval might not come up often, but if it does you definitely don't want to use it on anything supplied by a user. Since eval will build new ruby code based on strings, attackers can use it to
Ruby's system
, exec
, spawn
, and command
are equally dangerous if the user input it making up the whole or partial command. Fortunately, as advised by the Rails docs, you can use system
with a second argument to safely pass parameters. For example system(command, parameters
.
Don't assemble paths or URLs
There are many instances where taking user selections or input, and forming a URL or path may be beneficial. Search is the most common instance, but you may also use it for uploading images, adding links, redirecting pages, etc.
Generally, you'll want to avoid directly building paths based on user input. The most common reason is to avoid forms of server-side request forgery. This type of attack can expose details about your own services, or be used to trick users into thinking a redirected path is coming from your application instead of the attacker's.
You can instead restrict selections to a subset of safe options, and then use those options to build the URL.
In instances where you can't possibly know all the safe values, you can match safe patterns—such as a list of approved domains for URL forwarding or file sources. The same goes for file types. When in doubt, it's better to be overly conservative when restricting the type of user input you use to build URLs or file paths.
Sessions
It's useful to start by making sure your sessions use a database as a backend rather than cookie based session storage.
In addition, you should avoid allowing user input as the session key. You want a system that has no outside influence, even though all session data sent from the client to the server should be considered untrusted. This allows you to properly validate session keys and IDs without the variability of any user-defined values.
unsanitized renders
With all the focus on sanitizing user input, it can be easy to neglect sanitizing renders. Ideally the user input shouldn't make it this far, but an additional step will prevent any mistakes on the receiving end.
If you're using render html
you can wrap items in strip_tags
to ensure code isn't generated in the HTML output. This helps prevent cross-site scripting (XSS) attacks in which content is injected into your page.
If you're using an external view engine, or a javascript framework like react in addition to your ruby backend, you can rely on similar sanitization methods like the DOMPurify library.
Stop runaway regex
We think of regular expressions as a common means of validating input, but some may find it valuable to allow users to build their own conditions. Allowing user input use in a regular expression isn't dangerous on it's own, but it can cause an issue when the regular expression requires excessive CPU resources.
To prevent this, specify a timeout when working with regular expressions—particularly those that rely on use input.
Regexp.new(/a|b/, timeout: 3)
This will help in stoping regular expression denial of service (ReDoS) attacks.
Keeping up with top ruby security tips
It can be challenging to keep up with security best practices. In addition to watching for vulnerability reports, you can also run regular scans on your codebase with a SAST tool like Bearer CLI. It's a free and secure way to get practical security feedback on your ruby code. Check it out on GitHub at bearer/bearer.