I have a plain SQL query written by a trusted administrator that is to be run in a Rails (4.2) app. I am sanitizing it with ActiveRecord::Base.send(:sanitize_sql, ...) to allow user inputs to act as conditions, using the ? character for bind variables. The code has to allow arbitrary SQL, so I'm not interested in the arguments about why this is not the Rails way, etc.
The problem is that I can not include ? in a result field in the SQL without the underlying replace_bind_variables method replacing an intended literal ? in the result.
A simple query for example would be:
select 'http://www.google.com?q=' || res from some_table where a = ?;
To sanitize:
ActiveRecord::Base.send(:sanitize_sql, [sql, 'not me'], :some_table)
The sanitization fails because the ? in the URL gets replaced with the data intended for the condition, leading to the exception:
ActiveRecord::PreparedStatementInvalid: wrong number of bind variables (1 for 2)
The question is, does sanitize_sql or some variant allow literal ? characters to be included in a query so that they are not replaced? Is there some way of escaping them?
In the end I read through the ActiveRecord source and couldn't identify a way to handle this situation without a lot of code changes. There doesn't appear to be a way to escape the ? characters.
To resolve it for this one query I ended up using the SQL chr() function to generate a character that would pass the santization step untouched:
select 'http://www.google.com' || chr(63) || 'q=' || res from some_table where a = ?;
ASCII character 63 is ?.
Although not a perfect solution, I could at least get this one SQL query into the system without having to make massive code changes.
Related
When Ecto queries get more complex and require clauses like CASE...WHEN...ELSE...END, we tend to depend on Ecto's fragment to solve it.
e.g. query = from t in <Model>, select: fragment("SUM(CASE WHEN status = ? THEN 1 ELSE 0 END)", 2)
In fact the most popular Stack Overflow post about this topic suggests to create a macro like this:
defmacro case_when(condition, do: then_expr, else: else_expr) do
quote do
fragment(
"CASE WHEN ? THEN ? ELSE ? END",
unquote(condition),
unquote(then_expr),
unquote(else_expr)
)
end
end
so you can use it this way in your Ecto queries:
query = from t in <Model>,
select: case_when t.status == 2
do 1
else 0
end
at the same time, in another post, I found this:
(Ecto.Query.CompileError) to prevent SQL injection attacks, fragment(...) does not allow strings to be interpolated as the first argument via the `^` operator, got: `"exists (\n SELECT 1\n FROM #{other_table} o\n WHERE o.column_name = ?)"
Well, it seems Ecto's team figured out people are using fragment to solve complex queries, but they don't realize it can lead to SQL injection, so they don't allow string interpolation there as a way to protect developers.
Then comes another guy who says "don't worry, use macros."
I'm not an elixir expert, but that seems like a workaround to DO USE string interpolation, escaping the fragment protection.
Is there a way to use fragment and be sure the query was parameterized?
SQL injection, here, would result of string interpolation usage with an external data. Imagine where: fragment("column = '#{value}'") (instead of the correct where: fragment("column = ?", value)), if value comes from your params (usual name of the second argument of a Phoenix action which is the parameters extracted from the HTTP request), yes, this could result in a SQL injection.
But, the problem with prepared statement, is that you can't substitute a paremeter (the ? in fragment/1 string) by some dynamic SQL part (for example, a thing as simple as an operator) so, you don't really have the choice. Let's say you would like to write fragment("column #{operator} ?", value) because operator would be dynamic and depends on conditions, as long as operator didn't come from the user (harcoded somewhere in your code), it would be safe.
I don't know if you are familiar with PHP (PDO in the following examples), but this is exactly the same with $bdd->query("... WHERE column = '{$_POST['value']}'") (inject a value by string interpolation) in opposite to $stmt = $bdd->prepare('... WHERE column = ?') then $stmt->execute([$_POST['value']]); (a correct prepared statement). But, if we come back to my previous story of dynamic operator, as stated earlier, you can't dynamically bind some random SQL fragment, the DBMS would interpret "WHERE column ? ?" with > as operator and 'foo' as value like (for the idea) WHERE column '>' 'foo' which is not syntactically correct. So, the easiest way to turn this operator dynamic is to write "WHERE column {$operator} ?" (inject it, but only it, by string interpolation or concatenation). If this variable $operator is defined by your own code (eg: $operator = some_condition ? '>' : '=';), it's fine but, in the opposite, if it involves some superglobal variable which comes from the client like $_POST or $_GET, this creates a security hole (SQL injection).
TL;DR
Then comes another guy who says "don't worry, use macros."
The answer of Aleksei Matiushkin, in the mentionned post, is just a workaround to the disabled/forbidden string interpolation by fragment/1 to dynamically inject a known operator. If you reuse this trick (and can't really do otherwise), as long as you don't blindly "inject" any random value coming from the user, you'll be fine.
UPDATE:
It seems, after all, that fragment/1 (which I didn't inspect the source) doesn't imply a prepared statement (the ? are not placeholder of a true prepared statement). I tried some simple and stupid enough query like the following:
from(
Customer,
where: fragment("lastname ? ?", "LIKE", "%")
)
|> Repo.all()
At least with PostgreSQL/postgrex, the generated query in console appears to be in fact:
SELECT ... FROM "customers" AS c0 WHERE (lastname 'LIKE' '%') []
Note the [] (empty list) at the end for the parameters (and absence of $1 in the query) so it seems to act like the emulation of prepared statement in PHP/PDO meaning Ecto (or postgrex?) realizes proper escaping and injection of values directly in the query but, still, as said above LIKE became a string (see the ' surrounding it), not an operator so the query fails with a syntax error.
I am working on a legacy Rails project that relies on Ruby version 1.8
I have a string looks like this:
my_str = "a,b,c"
I would like to convert it to
value_list = "('a','b','c')"
so that I can directly use it in my SQL statement like:
"SELECT * from my_table WHERE value IN #{value_list}"
I tried:
my_str.split(",")
but it returns "abc" :(
How to convert it to what I need?
To split the string you can just do
my_str.split(",")
=> ["a", "b", "c"]
The easiest way to use that in a query, is using where as follows:
Post.where(value: my_str.split(","))
This will just work as expected. But, I understand you want to be able to build the SQL-string yourself, so then you need to do something like
quoted_values_str = my_str.split(",").map{|x| "'#{x}'"}.join(",")
=> "'a','b','c'"
sql = ""SELECT * from my_table WHERE value IN (#{quoted_values_str})"
Note that this is a naive approach: normally you should also escape quotes if they should be contained inside your strings, and makes you vulnerable for sql injection. Using where will handle all those edge cases correctly for you.
Under no circumstances should you reinvent the wheel for this. Rails has built-in methods for constructing SQL strings, and you should use them. In this case, you want sanitize_sql_for_assignment (aliased to sanitize_sql):
my_str = "a,b,c"
conditions = sanitize_sql(["value IN (?)", my_str.split(",")])
# => value IN ('a','b','c')
query = "SELECT * from my_table WHERE #{conditions}"
This will give you the result you want while also protecting you from SQL injection attacks (and other errors related to badly formed SQL).
The correct usage may depend what version of Rails you're using, but this method exists as far back as Rails 2.0 so it will definitely work even with a legacy app; just consult the docs for the version of Rails you're using.
value_list = "('#{my_str.split(",").join("','")}')"
But this is a very bad way to query. You better use:
Model.where(value: my_str.split(","))
The string can be manipulated directly; there is no need to convert it to an array, modify the array then join the elements.
str = "a,b,c"
"(%s)" % str.gsub(/([^,]+)/, "'\\1'")
#=> "('a','b','c')"
The regular expression reads, "match one or more characters other than commas and save to capture group 1. \\1 retrieves the contents of capture group 1 in the formation of gsub's replacement string.
couple of use cases:
def full_name
[last_name, first_name].join(' ')
end
or
def address_line
[address[:country], address[:city], address[:street], address[:zip]].join(', ')
end
I'm attempting to write a Ruby method which accepts an array of strings (for example, ["EG", "K", "C"], and returns all records from a database table where the icao_code field starts with any of those strings (for example, KORD, EGLL, and CYVR would all match). The length of the array will vary, and it will be input by a user, so it needs to be sanitized.
If I were only searching for a single string, I could do something like Airport.where("icao_code LIKE ?", "#{icao_start}%"). However, since I need to search against an arbitrary number of strings, I can't use that syntax.
Right now, I've got it working as follows:
def in_region(icao_starts)
where_clause = icao_starts.map{|i| "icao_code LIKE '#{i}%'"}.join(" OR ")
return Airport.where(where_clause)
end
However, I'm a bit worried using a setup like this with untrusted user input, since I suspect it would be vulnerable to SQL injection.
Is there a better way to get the same result in a more secure way?
You could consider something like this:
def in_region(icao_starts)
where_clause = "icao_code LIKE '#?%' OR " * icao_starts.length
return Airport.where(where_clause.sub(/\ OR\ $/, ''), *icao_starts)
end
This will build up a (potentially very long?) string with ? placeholders. The *icao_starts will expand that array into arguments to the where clause, so each ? will end up getting safely replaced. The sub(/\ OR\ $/, '') simply trims off the final OR (you could append 1=0 instead if you wanted).
If I were you I would also perform a .uniq on icao_starts before you do anything, truncate the array at some sensible upper length limit, and also have a whitelist of permitted values (oh, forget that, I thought users were searching by airport code). That should be pretty much infallible.
You are right about not interpolating user input into your SQL query. This is dangerous and makes your code vulnerable for SQLI attacks.
def in_region(icao_starts)
conditions = icao_starts.map { "icao_code LIKE ?"}
Airport.where(conditions.join(' OR '), *icao_starts.map { |name| "#{name}%"})
end
It is pretty similar than the solution of bogardpd but does not use a Regexp to get rid of the last " OR"
I have an active record Song model with a songhash field (string(255)) that contains a Sha2 hash. When I try to find a song via the following code nothing gets returned:
song = Song.all.first
song2 = Song.where(songhash: song.songhash).first
# song is a valid object with a songhash set, but song2 is nil!
If I do the same thing however with a "like" query it works:
song = Song.all.first
song2 = Song.where("songhash like ?", song.songhash).first
# song2 is a valid object now
song2.songhash == song.songhash
# the equation is true
I fear it has something to do with string encodings but I have no idea why this string could possibly have encoding issues: 61a9761b9ebd543b72c5ccf2ab6db198b067f7cf7f8412ee6e9c14b19611bc80
I'm using rails 3.1 with sqlite db.
Any ideas what's going on?
Summary
SQL statements generated
# With = / It doesn't work
SELECT "songs".* FROM "songs" WHERE "songs"."type"
IN ('PlaylistSong') AND "songs"."songhash" =
'61a9761b9ebd543b72c5ccf2ab6db198b067f7cf7f8412ee6e9c14b19611bc80'
# With like / It works
SELECT "songs".* FROM "songs" WHERE "songs"."type"
IN ('PlaylistSong') AND (songhash like
'61a9761b9ebd543b72c5ccf2ab6db198b067f7cf7f8412ee6e9c14b19611bc80')
# With upper / It works
SELECT "songs".* FROM "songs" WHERE "songs"."type"
IN ('PlaylistSong') AND (UPPER(songhash) =
'61A9761B9EBD543B72C5CCF2AB6DB198B067F7CF7F8412EE6E9C14B19611BC80')
The following statements work:
Song.where(['UPPER(songhash) = ?', song.songhash.upcase]).first
Song.where(['songhash like ?', song.songhash]).first
UPPER and LIKE are both case insensitive
SQLite documentation
The LIKE operator does a pattern matching comparison. (A bug: SQLite only understands upper/lower case for ASCII characters by default. The LIKE operator is case sensitive by default for unicode characters that are beyond the ASCII range. For example, the expression 'a' LIKE 'A' is TRUE but 'æ' LIKE 'Æ' is FALSE.) See more
To investigage
Charset equals? (Rails - SQLite)
String stored potentially dirty (carriage returns, ...)
Thanks to help of #gazler and #basgys I was able to track down the problem:
It is in fact an encoding problem caused by the Digest::Sha2#hexdigest function. It returns a string that is encoded as ASCII-8BIT. When storing this in the database it seems to be converted automatically to a UTF-8 string (I check that by running a select hex(songhash) from songs query). However when using the string in the query, it does not seem to do that conversion.
Internally ruby seems to handle the different encoding conversions automatically. That is why "abc"=="abc" although they may have different encoding.
I'm sure that this is not expected behavior, however I don't know if it is a bug - and if it is a bug whether it is somewhere within ActiveRecord, the SQLite Driver, or SQLite itself.
My solution is now to append a .encode("UTF-8") to the result of the digest function.
I have something like this
var query = repo.GetQuery(); // IQueryable
query.Where(item => item.FieldName.Contains("xxx%yyy"));
It results in following statement on SQL server
exec sp_executesql N'SELECT
// clipped
WHERE ([Extent1].[FieldName] LIKE #p__linq__0 ESCAPE N''~'')',
N'#p__linq__0 nvarchar(4000),#p__linq__0=N'%xxx~%yyy%'
#p__linq__0=N'%xxx~%yyy% causes the SQL server to look for xxx%yyy with % as literal (as it is escaped) while I would like it to match string like xxx123yyy, xxxABCyyy, xxxANYTHINGyyy, xxxyyy etc. Addition of prefix % and suffix % is fine but I could do it manually if needed.
In the above example I have simplified and written only one where condition but I have a dynamic logic that build the predicate with many of such keywords and I would like to allow the wildcards to be embedded inside the keywords. Is there a way to tell EF not to escape the % in the search keyword?
It is not possible. Contains("xxx") means that in SQL you want LIKE '%xxx%'. Linq-to-entities and none of its String mapped methods offer full wildcard searching = any wildcard character is always escaped. If you want to use wildcard searching you must use Entity SQL.