ActiveRecord find and MySql ‘IN’ gotcha

In my latest project I needed to fetch a group of tasks who where linked to a  group of categories. All that I have are the Id’s of te categories which made me write the following code

Tasks.find(:all, 
    :conditions => ["categorie_id IN (?)", @categories.join(',')])

It seemed to work, but apparently I didn’t test it right and my tests where not sufficient enough.

This code produces the following SQL:

SELECT * FROM `tasks` WHERE (categorie_id IN ('1,2,3,4'))

This is obviously wrong. You’ll get quotes around the row of id’s which is kind of logical if you think about it. Afteral it’s a string. To prevent this from happening, simply apply the following syntax

Tasks.find(:all,
    :conditions => ["categorie_id IN (#{@categories.join(',')})"])
This entry was posted in Coding, Ruby on Rails and tagged , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

2 Comments

  1. Posted August 23, 2009 at 12:27 | Permalink

    Hi Jeroen,

    Good to see you’re back in the blogging business :)

    Unfortunately, this post is giving bad advice.

    Tasks.find(:all, :conditions => ["categorie_id IN (#{@categories.join(',')})"])

    is unsafe unless you are absolutely sure that categories are all
    integers. If they are strings (and that includes “integers”
    passed from HTML forms, which are actually strings and can be
    manipulated by attackers) you are vulnerable to SQL injection. That’s why you use the question mark syntax in the first place. Otherwise, you wouldn’t need that.

    A simple example:

    
    # The hash here is to make the rest of the statement a comment
    @categories = ['1)) UNION SELECT * FROM users #']
    Tasks.find(:all, :conditions => ["categorie_id IN (#{@categories.join(',')})"])
    
    

    Of course, this will give an error if the number of columns do not match but it is not hard to pad in extra columns. There are also ways to make it match if users has more columns; you would start by reading out id and name, or login, and password and pad those where needed.

    There are a couple of proper ways to find tasks by category, even if the categories are uncontrolled strings:

    Tasks.find(:all, :conditions => ["categorie_id IN (?)", @categories])
    In this case, Rails will join with a comma and escape accordingly.

    A simpler syntax is:

    Tasks.find(:all, :conditions => {:categorie_id => @categories})

    You could also first fetch the categories and use the ‘task’ method on those objects:

    Categorie.find(@categories).map(&:tasks).flatten

    I hope you find this comment useful!

  2. Jeroen van Doorn
    Posted August 24, 2009 at 08:23 | Permalink

    Hi Peter!

    Long time no speak!

    Very useful indeed! Will have a look at my code to see why I did this in the first place … But I stand corrected :)

    Hopefully I’m able to post some more stuff in the near future

    Speak soon!
    Jeroen

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

  •  

    July 2009
    M T W T F S S
         
     12345
    6789101112
    13141516171819
    20212223242526
    2728293031