Queries with prepared statements in Android? - android

In Android, android.database.sqlite.SQLiteStatement allows me to use prepared statements in SQLite to avoid injection attacks. Its execute method is suitable for create/update/delete operations, but there does not seem to be any method for queries that returns a cursor or the like.
Now in iOS I can create prepared statements of type sqlite3_stmt* and use them for queries, so I know this is not a limitation of SQLite. How can I perform queries with prepared statements in Android?

a prepared statement allows you to do two things
speed up the performance since the database does not need to parse the statement each time
bind & escape arguments in the statement so you are save against injection attacks
I don't know exactly where/when Androids SQLite implementation actually uses sqlite3_prepare (afiak not sqlite3_prepare_v2 - see here) but it does use it otherwise you could not get Reached MAX size for compiled-sql statement cache errors.
So if you want to query the database you have to rely on the implementation there is no way I know of to do it with SQLiteStatement.
Regarding the injection safety, every database query, insert, etc method has (sometimes alternative) versions that allow you to bind arguments.
E.g. if you want to get a Cursor out of
SELECT * FROM table WHERE column1='value1' OR column2='value2'
Cursor SQLiteDatabase#rawQuery(
String sql, : full SELECT statment which can include ? everywhere
String[] selectionArgs : list of values that replace ?, in order they appear
)
Cursor c1 = db.rawQuery(
"SELECT * FROM table WHERE column1=? OR column2=?",
new String[] {"value1", "value2"}
);
Cursor SQLiteDatabase#query (
String table, : table name, can include JOIN etc
String[] columns, : list of the columns required, null = *
String selection, : WHERE clause withouth WHERE can / should include ?
String[] selectionArgs, : list of values that replace ?, in order they appear
String groupBy, : GROUP BY clause w/o GROUP BY
String having, : HAVING clause w/o HAVING
String orderBy : ORDER BY clause w/o ORDER BY
)
Cursor c2 = db.query("table", null,
"column1=? OR column2=?",
new String[] {"value1", "value2"},
null, null, null);
Via ContentProviders - that case is slightly different since you interact with an abstract provider, not a database. There is acutally no guarantee that there is a sqlite database backing the ContentProvider. So unless you know what columns there are / how the provider works internally you should stick to what the documentation says.
Cursor ContentResolver#query(
Uri uri, : an URI representing the data source (internally translated to a table)
String[] projection, : list of the columns required, null = *
String selection, : WHERE clause withouth WHERE can / should include ?
String[] selectionArgs, : list of values that replace ?, in order they appear
String sortOrder : ORDER BY clause w/o ORDER BY
)
Cursor c3 = getContentResolver().query(
Uri.parse("content://provider/table"), null,
"column=? OR column2=?",
new String[] {"value1", "value2"},
null);
Hint: if you want to LIMIT here you can add it to the ORDER BY clause:
String sortOrder = "somecolumn LIMIT 5";
or depending on the implementation of the ContentProvider add it as a parameter to the Uri:
Uri.parse("content://provider/table?limit=5");
// or better via buildUpon()
Uri audio = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
audio.buildUpon().appendQueryParameter("limit", "5");
In all cases ? will be replaced by the escaped version of what you put in the bind argument.
? + "hack'me" = 'hack''me'

Related

Preventing injection via the projection in a Content Provider query (parameterization?)

I have a Content Provider which I tested with the Drozer framework and it turned out that the projection in the query() method is vulnerable to injection. Including "* FROM SQLITE_MASTER --" lists all tables. Which is the best way to guard against this? I added filtering of certain characters:
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
/*
* Filter queries that contain certain characters to guard against SQL injection
*/
for (String query : projection) {
if (query.contains("*") || query.contains(";") || query.contains("'") || query.contains("\"")) {
//Possible SQL injection attack, leave the query
return null;
}
}
I understand that blacklist filtering is not the way to go and parameterizing the projection would be better. However I can't see a way to do this in the query() method. It doesn't have two arguments like the selection has "selection" then "selectionArgs". How best to guard against injection without blacklisting?
If you want to allow your content provider's clients to use arbitrarily complex SQL, there is nothing much you can do; a projection like SomeColumn AS "* FROM sqlite_master -- ;'""" would be perfectly valid and harmless.
To prevent access to sensitive data, that data would have to be in another database.
However, you could impose the restriction that clients can read only a predefined set of columns, unchanged.
To enforce this, check that all strings in projection are equal to one of those column names.
Probably easily way to prevent against SQL injection in projection parameter is to use setProjectionMap method on SQLiteQueryBuilder. As stated in documentation,
If a projection map is set it must contain all column names the user may request, even if the key and value are the same.
I tried to fix a vulnerable content provider by adding this method, and here is what drozer showed me:
dz> run app.provider.query content://myContentProvider/myPath/ --projection "'"
Invalid column '

SELECT entry in SQLite, without a WHERE condition?

I followed advice in this question, but for my purposes I don't want a WHERE.
I don't know the value, so I cannot say rawQuery("... WHERE x = ?", y), I don't care what y is, it's just a cell I want, and it is known that there is a single row.
If it is not possible to lose the condition (perhaps because of causing an indeterminate number of results?) - then how can I say "from column z and row 0"?
I'm lacking either terminology, or outright understanding, because my searches are turning up nothing.
Edit: Eclipse doesn't complain at:
result = db.rawQuery("SELECT col FROM tbl", my_unused_string_array);
I'm not at a testing stage yet, and I can't enter this into the SQL db reader I was using to test SELECT col FROM tbl and ~ with WHERE.. will it work?
As per your edit, you don't need to specify WHERE clause, if you want to get all the records from a table:
result = db.rawQuery("SELECT col FROM tbl", new String[0]);
The SQL query "SELECT * FROM table" will return the entire table. "SELECT colX, colY FROM table" will return columns colX and colY for all the rows in the table. If your table contains just one row, "SELECT col FROM table" will return the value of col for that one row.
To use the SQLiteDatabase API to make that query, you would say:
result = db.rawQuery("SELECT col FROM tbl", null);
... because you are not supplying any query parameters.
Assuming that there is just one row seems dangerous to me. I would not use the "LIMIT" clause, because, while that will always get one row, it will hide the fact that there is more than one row, if that happens. Instead, I suggest that you assert that the cursor contains one row, like this:
if (1 != result.getCount()) {
throw Exception("something's busted");
}
Instead of Raw query use
Cursor cur = db.query(Table_name, null, null, null, null,
null, null);
and get the desired attributes from cursor.
where the query method has parameters in following manner:
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
Parameters:
table The table name to compile the query against.
columns A list of which columns to return. Passing null will return all columns, which is discouraged to prevent reading data from storage that isn't going to be used.
selection A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given table.
selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.
groupBy A filter declaring how to group rows, formatted as an SQL GROUP BY clause (excluding the GROUP BY itself). Passing null will cause the rows to not be grouped.
having A filter declare which row groups to include in the cursor, if row grouping is being used, formatted as an SQL HAVING clause (excluding the HAVING itself).
Passing null will cause all row groups to be included, and is required when row grouping is not being used.
orderBy How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.
Returns
A Cursor object, which is positioned before the first entry.

SQLiteDirectCursorDriver usage of SelectionArgs or workaround - using placeholders

I have a rawQuery string running but SQlite throws this warning :
SQLiteDirectCursorDriver(1058): Found SQL string that ends in ;
because of quote issues. So I'm trying to use SelectionArgs in a query where I do not have really replacement by values, like this :
String query = "SELECT Min(?) as oldest, Max(?) as newest, COUNT(?) as nb_data FROM ? WHERE ? BETWEEN (SELECT Min(?) FROM ?) AND (SELECT Max(?) FROM ?)";
String[] values = new String[] { TS, TS, col_2, table, TS, TS, table, TS, table };
Cursor mCount = database.rawQuery(query, values);
I can't get it working, even using fewer SelectionArgs strings (only on the "WHERE" part of the query) as there is not really value replacement requirements but placeholders.
Is there a solution to set this prepared statement (using placeholders rather than using quotes or rewriting the original query) to avoid getting the SQLiteDirectCursorDriver warning ?
You can use parameters only for expressions, not for identifiers.
To prevent this warning, just omit the semicolon from the end of your SQL query strings.

Queue SUM of SQLite column - android

I have a listview populated from an SQLite database. I have several items that I successfully populate into the listview, however I'm having trouble with one last thing.
I'm trying to queue the sum total of the column KEY_CONTENT6 which is a string type, however it only contains numbers. I'd like to keep it as a string, so to add it up I'm using Double.valueOf(). The problem is this code force closes on queue and I cant figure out whats wrong:
public Cursor queueAll(){
String[] columns =
new String[]{KEY_ID, "sum("+ Double.valueOf(KEY_CONTENT6) +")",
KEY_CONTENT9, KEY_CONTENT10 };
Cursor cursor = sqLiteDatabase.query(MYDATABASE_TABLE, columns,
null , null, KEY_CONTENT10, null, KEY_CONTENT9+ " DESC");
return cursor;
}
simply use SUM, no need to use anything else..
String[] columns =
new String[]{KEY_ID, "sum(KEY_CONTENT6)",
KEY_CONTENT9, KEY_CONTENT10 };
It is valid for SQLite. Because, no matter what you set data type in SQLite, it stores values as string. So, type conversion is somewhat built-in in SQLite.
You can't use java in a SQL statement, either stick to strait sql or iterate over the cursor and use java to do your calculation.
You can find everything there is to know about sqlite here http://www.sqlite.org/docs.html
SQLite is basically typeless, so you might be able to use SUM on your column even though it is a string. However, if it's meant to be a numeric column, why not give it a number type??

Can I perform this Android query with ContentResolver.query()? (LEFT JOIN and CASE)

I am looking to perform the following query (in pseudo-code) on Android:
SELECT C.ID, C.NAME, CASE ISNULL(G.GROUPID,0) = 0 THEN 0 ELSE 1 END INGROUP
FROM CONTACTS C
LEFT JOIN GROUPMEMBERSHIP G ON G.CONTACTID = C.ID AND G.GROUPID = ?
I am looking to select the ID and Name of ALL contacts in the system address book, via the default Contacts ContentProvider, along with a
0/1 field indicating whether the contact is a member of group ? .
I could of course get all contacts easily enough, then loop through and query the membership separately easy enough in my Adapter class, but I'd imagine performing the two queries as one outer joined query would yield much better performance.
Can I do this with the standard high-level string-projection and ContentResolver.query() method? Or would this kind of query require digging into more direct SQL execution?
Edit: Okay, so this doesn't actually solve the question asked, because eidylon is tied to an existing ContentProvider as mentioned in their question. However, this does cover how you do a JOIN if you own the ContentProvider source and API. So I'll leave it for those who want to know how to handle that case.
This is easy! But unintuitive... :)
query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
Okay, so what is URI? Typically, you have one URI per table.
content://com.example.coolapp.contacts serves data out of your CONTACTS table.
content://com.example.coolapp.groupmembers serves data out of your GROUPMEMBERSHIP table.
But URI is really just a string. Use it however you like. Make a block of code in your ContentProvider that responds to content://com.example.coolapp.contacts_in_group. Within that block of code in the ContentProvider, you can get raw access to your SQLite DB, unfettered by the limited query() data model. Feel free to use it!
Define your selection fields however you like. They don't have to map to table column names -- map them how you need to, in order to get your parameters in.
Define your projection how you need -- It may contain columns from both tables after the join.
Bing, you're done. Google does this same model internally in their own code -- Go look at the Contacts provider API -- you see "bla.RawContact" and "bla.Contact" and etc as content URIs. Each serves data out of the same table in the DB -- the different URIs just provide different views of that same table!
Nope, you can't do that kind of queries with the ContentResolver.query() method.
You will need to write something like this:
SQLiteDatabase db = YourActivity.getDbHelper().getReadableDatabase();
String query = yourLongQuery;
Cursor c = db.rawQuery(query, null);
YourActivity.startManagingCursor(c);
c.setNotificationUri(YourActivity.getContentResolver(), YourContentProvider.CONTENT_URI);
You can't do that because ContentResolver has only one query method:
query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
there's no parameter for tables or FROM clauses.

Categories

Resources