Just hacked this out myself, and yes it is very much an SQL injection. However, it does work so long as you manage to get the resulting (generated) SQL to end up correct. Check out this example:
Cursor c_buckets = getContext().getContentResolver().query(baseUri,
new String[]{PhotoContract.ThumbEntry.COLUMN_BUCKET_NAME,
PhotoContract.ThumbEntry.COLUMN_BUCKET_ID + " as _ID"},
"1=1) GROUP BY " + PhotoContract.ThumbEntry.COLUMN_BUCKET_NAME
+ ", " + PhotoContract.ThumbEntry.COLUMN_BUCKET_ID
+ ", ABS(0",
null,
PhotoContract.ThumbEntry.COLUMN_BUCKET_ID
);
The selection
field in the query
call amounts to the WHERE
clause of the resulting query. The "trick" is to add the GROUP BY
clause here, after the WHERE
clause at the end of the selection
. The WHERE
clause is enclosed in parentheses, so the trick is to add a closing parenthesis after the WHERE
clause (which here is simply 1=1
) by adding 1=1)
to the selection
. Then you can insert your GROUP BY
clause. However, since Android will expect all of this to be the selection, it will add a closing parenthesis at the end! To overcome that you need to add something which requires a closing parenthesis at the end. I found it convenient to simply add ABS(0
to the grouping. This has no effect on the grouping, and it solves the syntactical problem, resulting in valid SQL:
SELECT images.bucket_display_name, images.bucket_id as _ID FROM images WHERE (1=1) GROUP BY images.bucket_display_name, images.bucket_id, ABS(0) ORDER BY images.bucket_id
Note I'm adding the required "_id" column using an alias without disturbing the grouping.
UPDATE for Android Q (API 29):
For this to work on API 29 (Android Q), column names must no longer be prefixed with table name. As in, images.bucket_display_name
should be just bucket_display_name
.