A1: Injection
SQL injection is the most common type of injection attack, and Grails applications are largely immune to these, but not entirely. An SQL injection attack typically consists of tricking the application into running SQL queries or updates that either damage data or expose information. This can happen when you have a search form or other web page that accepts user input and you use the input as part of a dynamically generated SQL query without properly escaping the inputs.
String
sql
=
"select * from person where username ='"
+
params
.
username
+
"'"
ResultSet
rs
=
statement
.
executeQuery
(
sql
)
This works well if you have control over the inputs, but users can enter whatever they want in your form. If someone enters
foo
, then the where
clause of your query will be where username ='foo'
, but if a hacker enters ' or '1'='1
, then it will be select * from person where username ='' or '1'='1'
. Because '1'='1'
is always true, the or
results in the query returning unexpected records (in this case, all of them). Tricks like this can be used to bypass password checks during login or create a denial-of-service style attack where too much data is returned from the database repeatedly, or even to damage data or tables. If you use execute
instead of executeQuery
, you can mix select
queries and updates and allow real damage:boolean
ok
=
statement
.
execute
(
sql
)
If a hacker submits
'; drop table foo; --
or '; truncate table foo; --
, you’ll be scrambling to restore the database from the most recent backup.
The problem here is that we’re trusting the users to do the right thing. The deeper problem is a failure to escape the user input properly before sending it to the database. You could look for patterns like the ones I’ve shown and implement a whitelist/blacklist filtering approach to using user-submitted data in your queries, but the best approach is to let the database driver do the work for you. Rather than using a
Statement
, use a PreparedStatement
with parameter placeholders in the SQL:String
sql
=
"select * from person where username = ?"
PreparedStatement
ps
=
connection
.
prepareStatement
(
sql
)
ps
.
setString
(
1
,
params
.
username
)
ResultSet
rs
=
ps
.
executeQuery
()
Now, if an unfriendly user submits a username with quote characters, they will be escaped properly (the approach is different for various databases, but the driver handles it for us) and the worst-case scenario now is an
SQLException
.
Fortunately for us, Hibernate uses a
PreparedStatement
for criteria queries, and all Grails queries are converted to criteria queries under the hood (the exception being single-element queries like get()
or read()
, which also use a PreparedStatement
). You can see this by turning on SQL logging and enabling SQL comments in DataSource.groovy:dataSource
{
...
logSql
=
true
}
hibernate
{
...
format_sql
=
true
use_sql_comments
=
true
}
Given this simple domain class:
class
Person
{
String
username
}
Person
.
findByUsername
(
params
.
username
)
Person
.
where
{
username
==
params
.
username
}.
find
()
Person
.
createCriteria
().
get
{
eq
'
username
'
,
params
.
username
}
Hibernate:
/* criteria query */
select
this_
.
id
as
id0_0_
,
this_
.
version
as
version0_0_
,
this_
.
username
as
username0_0_
from
person
this_
where
this_
.
username
=?
You can see from the comment that Hibernate generated the SQL from a criteria query and, from the SQL, that a
PreparedStatement
is being used because the username parameter isn’t the actual string being queried, but the ?
placeholder.
So we’re safe from SQL injection attacks in the general case, but we can also use HQL queries with the
executeQuery
and executeUpdate
methods. Hibernate converts our HQL to SQL, so naive string concatentation of HQL can open up an SQL injection vulnerability:Person
.
executeQuery
(
"from Person where username='"
+
params
.
username
+
"'"
)
Hibernate has no way of knowing that a parameter should be escaped, because it just sees the final concatenated string. But, of course, HQL has the same support for placeholder replacement as SQL:
Person
.
executeQuery
(
'
from
Person
where
username
=?
'
,
[
params
.
username
])
Person
.
executeQuery
(
'
from
Person
where
username
=:
username
'
,
[
username:
params
.
username
])
So, as long as you use the standard GORM methods to run your queries and are careful with HQL queries, you should be safe from SQL injection risks. Note that Groovy
GString
s don’t help here and, in fact, hide the problem to a certain extent. I could have written the SQL above as "from Person where username='${params.username}'"
and the HQL as "select * from person where username ='${params.username}'"
; the lack of +
characters in the code can make it more likely that this would get missed in a code review.Command injection
Groovy makes it easy to execute arbitrary operating system commands by adding the
execute
method to the metaclass of the String
and String[]
classes. For example, it’s simple to get a directory listing on a Unix or Linux system by running 'ls -l'.execute().text
. If your application uses this feature and creates the commands to be executed based on user input, you are at risk of a command injection attack. Unfortunately, there isn’t a simple fix like there is for SQL; you will have to be vigilant and scan the user input based on a whitelist and/or a blacklist of allowed characters and expressions that are valid.
No comments:
Post a Comment