Ruby needs security

April 27, 2022
Máté Simon-Takács (Content developer, Avatao)
ruby security

Ruby was created by Japanese developer Yukihiro Matsumoto in the mid-1990s and has since become one of the top ten most commonly used programming languages. The main goal of the project was to create a simple, elegant, easy-to-read language like other modern languages such as Python and Go. Thanks to its simplicity, developers can learn the necessary skills more quickly and easily, and thus spend more time focusing on higher productivity in coding. Because Ruby has suitable defaults and was developed with productivity in mind, developers are able to rapidly build and launch projects. Taking all of this into consideration, it’s no wonder Ruby is getting more and more popular year by year. It is worth mentioning just a few of Ruby’s language-specific features, such as its usage of garbage collectors, just-in-time compilation, and the fact that it’s a dynamically typed language. Additionally, it supports multiple programming paradigms such as functional, procedural, and object-oriented programming.

Security Practices

The importance of IT security is on the rise. We are more reliant on technology than ever before and there is no reason to believe this will change in the future. Sensitive data such as user credentials, credit card numbers, and bank account details are stored using cloud storage services, and when there’s a data breach, this leaked or stolen information can be posted publicly online. And these incidents are only becoming more frequent. One week Twitch has a run-in with hackers, the next it’s Crypto.com being robbed. As the popularity of Ruby increases and more people become reliant on it, more and more security vulnerabilities surface for hackers to exploit. To protect their applications, developers need to stay up-to-date and well-prepared, and vulnerable areas need to be addressed as soon as possible. The goal of this post is to introduce some of these security vulnerabilities, as well as the relevant solutions and tools Ruby developers can use to prevent or fix many of these issues.

1. SQL injection

SQL injection is one of the most well-known security issues in the IT community. The goal is to inject a malicious command through the application into the database, and if the application is not properly protected, the hackers can access valuable information or, in the worst case, destroy the entire database. The main cause of this vulnerability is when the application accepts user input without properly checking it. As such, the best way to avoid SQL injection attacks is to sanitize any data which comes directly from the user.

The attack in practice:

username = params[‘login’][‘username’]

password = params[‘login’][‘password’]

query = “SELECT * FROM users WHERE username = ‘#{username}’ AND password = ‘#{password}'”

users = db.execute(query)

In this Ruby code, two input values are expected from the user: the standard username and password. Below that we can see how the forged query is defined. It seems like the application is using a SQLite database. With username “Peter Parker” and password “Spider-man”, the forwarded query becomes:

SELECT * FROM users WHERE username = ‘Peter Parker’ AND password = ‘Spider-man’

But we can use a well-guessed username, a closing apostrophe, and a comment sign at the end (two hyphens in SQLite) to craft a tricky input. All of a sudden, the password condition gets commented out and becomes just simple text:

SELECT * FROM users WHERE username = ‘admin’– AND password = ‘’’

Possible solution:

In order to avoid this vulnerability, user-provided data needs to be escaped before getting injected into the query. Most database providers already provide this functionality with something called “bind parameters”, which allow you insert placeholders in the SQL statement instead of writing the actual values directly. That way, statements don’t change when they’re executed with different values.

    query = “SELECT * FROM users WHERE username = ? AND password = ?”

    users = db.execute(query, username, password)

As you can see, the query contains the bind parameters now, and the input parameters have been passed to the query’s execution function.

2. IO hijacking

When a website does not properly restrict certain functions and the user has more freedom than they should, not observing these processes can lead to something called IO hijacking. The following example will give some context and make this more understandable. The users are able to upload files without any restrictions, meaning they can hide harmful code in the file’s name or content. To a skilled hacker, this is a great opportunity to run any command they want on the backend server. In the worst case, even remote code execution (RCE) can occur.

The attack in practice:

The following Ruby code is a simple file opener/reader.

      open(path, ‘r’) do |file|

        result.push file.gets until file.eof?

      end

 

The open command is the “Kernel::open” function, and it can be used to create spawn processes and pipe data out from them. To exploit this, a file simply needs to be uploaded with the following pattern: a vertical bar character and the desired command. For example:

|date

This formula gets the current date from the server, and sure, it’s not a harmful command, but it’s an RCE nonetheless.

Possible solution:

Luckily the solution is simple. Just use “File::open” instead of the default “Kernel::open”.

        File::open(path, ‘r’) do |file|

          result.push file.gets until file.eof?

        end

This is one of the more dangerous vulnerabilities out there, so keep the following in mind: never run code from an unknown source if you can avoid it, and always try to limit privileges to the lowest necessary level.

guide

3. Insecure deserialization

Insecure deserialization is a vulnerability which occurs when untrusted data is used to abuse an application’s logic and/or execute arbitrary code once it’s deserialized. Web applications make regular use of serialization and deserialization, and it’s important to understand that safe deserialization of objects is a normal practice in software development. The trouble, however, comes when you start deserializing untrusted user input. Since YAML is one option when it comes to serializing and deserializing Ruby objects, it’s a good choice for presenting what this attack looks like.

The attack in practice:

The trick here is that YAML can contain information about any Ruby object – even Ruby class instances can be serialized into YAML.

!ruby/object:Gem::Requirement

requirements:

  !ruby/object:Gem::DependencyList

  specs:

    – !ruby/object:Gem::Source::SpecificFile

      spec: &1 !ruby/object:Gem::StubSpecification

        loaded_from: “|kill -9 $(pgrep -f ruby2.5) 1>&2”

Generally the loaded_from section should contain a file path, but in this code the attacker tries to kill the process named ruby2.5 by providing a bash command instead of the path. We can’t be certain about the exact name of the process, but it is usually easy enough to guess.

Possible solution:

file = File.read(‘toys.yml’)

risk = YAML.load(file)

safe = YAML.safe_load(file)

The best way to make the deserialization secure is by using safe_load during the YAML reading. Commonly used types like hashes and arrays are still serializable and deserializable from YAML documents in this method, which satisfies developers’ needs in most cases.

4. Static code analyzers

Static code analysis is all about using different rules to examine the source code of the target application and discover vulnerabilities or security issues (like IO hijacking or insecure deserialization from above). It’s important to mention that static code analysis can also be used outside of a security context – for example, if you want to make sure some code complies with coding guidelines and industry standards, which is a must for almost any developer team. Brakeman is a well-known scanner application that can be very useful in easily finding security flaws in source code, plus it’s free and open source, making it an essential tool in development and CI workflows. The Avatao exercise linked below introduces you to Brakeman, so feel free to check it out to learn more.

5. Auto incrementing ID

When creating web applications in Ruby, developers usually work with the Ruby on Rails framework. After creating models in Rails, auto-incrementing integer fields named ‘id’ are automatically generated by the framework. This process of course has its pros (being extremely simple and efficient) but on the other hand, it can also lead to a decrease in security. An average user could identify how many objects currently exist in the system, for instance, by making estimates based on their own ID after signing up. It is even possible to iterate over the guessed IDs one by one, and with a little bit of luck, you can find unrestricted ones that are accessible. The attacker could even leak sensitive information. Protected resources that are only visible to those with the proper link rely on the fact that these IDs should be hard to guess – if they are simple sequential integers, then the system is broken.

Possible solution:

If you’re building a web application with Rails, avoid simple IDs. You can use UUIDs instead of the default ones.

Conclusion

There are far more vulnerabilities than the ones discussed above, and not just ones related to Ruby. Being careless when it comes to IT security is just asking for trouble, and human error is most often what leads to a data breach. Whether you are an individual, small business, or large multinational corporation, you rely on computer systems every day, and the longer you wait to start taking security seriously, the more risk you’re putting yourself in. And the impacts can be more than just financial. Imagine an IT giant like Google or Amazon falling prey to a successful cyberattack, and what that could do to their image and reputation. If developers and relevant staff are trained on how to identify and fix security issues, a huge number of incidents could be avoided. Hiring security experts is a possible solution (albeit an expensive and short-term one), so consider a cooperative education platform like Avatao to maximize the long-term benefits to your development teams.

Share this post on social media!

Related Articles

JWT handling best practices

JWT handling best practices

The purpose of this post is to present one of the most popular authorization manager open standards JWT. It goes into depth about what JWT is, how it works, why it is secure, and what the most common security pitfalls are.

Python best practices and common issues

Python best practices and common issues

Python is a high-level, flexible programming language that offers some great features. To be as effective as possible, it is important to possess the knowledge to make the most out of coding with Python.

5 Steps your security program should include

5 Steps your security program should include

For most companies, security is considered a side quest, which is partly related to the daily processes. In reality, security ought to be a strong foundation of any organization. To ensure the defense of the enterprise, the relevant teams need strong security knowledge and abilities.