How to sanitize all variables passed to the selectionArgs array? - android

Veracode Static Scan report points SQL Injection flaw in my Content Provider implementation.
Previously, I posted this question related to all my doubts regarding this flaw.
And after few discussions I came to a conclusion that there might be chances of it being a false positive in the report. Because according to what I researched and read, I was following the security guidelines mentioned in Android docs and other referenced sources to avoid SQL Injection.
There is suggestion everywhere to perform at least some input validation on the data passed to SQL queries.I want to cover this possibility which be the reason of flaw.
Everyone is asking me to sanitize data before passing to query.
How do I exactly sanitize variables passed to selectionArgs array passed to delete(), update() method of Content Provider?
Will DatabaseUtils.sqlEscapeString() be sufficient?
Please suggest!
Here's the implementation where I need to sanitize the variable:
public Loader<Cursor> onCreateLoader(int id, Bundle b) {
switch (id) {
case THOUGHT_LOADER:
return new CursorLoader(getActivity(), NewsFeedTable.CONTENT_URI, NewsFeedTable.PROJECTION, NewsFeedTable._id + "=?", new String[]{tid}, null);
case COMMENT_LOADER:
return new CursorLoader(getActivity(), CommentTable.CONTENT_URI, CommentTable.PROJECTION, CommentTable.COLUMN_TID + "=?", new String[]{tid}, null);
default:
return null;
}
}
Report points to the flaw :Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWEID 89) at this line
deleted = db.delete(BulletinTable.TABLE_NAME, selection, selectionArgs); in the below code:
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
if (uri.equals(Contract.BASE_CONTENT_URI)) {
deleteDatabase();
return 1;
}
SQLiteDatabase db = openHelper.getWritableDatabase();
int deleted = 0;
switch (matcher.match(uri)) {
case BULLETIN:
deleted = db.delete(BulletinTable.TABLE_NAME, selection, selectionArgs);
break;
case CLASSROOMS:
deleted = db.delete(ClassroomsTable.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return deleted;
}

Values in the selectionArgs array never need to be sanitized, because they cannot be interpreted as SQL commands (that's the whole point of having separate parameter values).
The purpose of sqlEscapeString() is to format a string so that it can be put into an SQL command (i.e., escape single quotes; all other characters have no meaning inside an SQL string). But when you know that there is a string, you should selectionArgs instead, so this function is not helpful.
You need to sanitize only strings that end up in the SQL command itself. In this case, this would be selection. If this value comes from the user, or from some other app, then you have no control over how much stuff your DELETE statement actually does (it could call SQL functions, or execute subqueries that access other parts of the database).
For practical purposes, it is not possible to sanitize strings that are intended to contain SQL commands, because then you would need a full SQL parser. If your content provider is available for external code, you should allow deleting specific items only through the URI, and disallow custom selections.

Related

Android SQLiteDatabase WHERE selection

I used an online note taking tutorial to make notes. It works, but I want to make notes based on selection. I thought I did it right, but can someone explain to me what I am doing wrong? I am passing in the course code and I thought I set it up to select correctly. Just tell me what I need to do.
This code seems to be the problem. If selectionargs and where are null it works by returning all notes. I want to return all notes with the where statement to be WHERE courseCode = thisCourse.getCourseID() How do I do this?
(DBOpenHelper.NOTE_COURSE is courseCode)
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String whereClause = DBOpenHelper.NOTE_COURSE+"=?";
String[] selectionArgs = { thisCourse.getCourseID().trim() };
return new CursorLoader(this, NotesProvider.CONTENT_URI, null, whereClause, selectionArgs, null);
}
If selectionargs and where are null it works by returning all notes
If you want to select rows from your table where NOTE_COURSE is null you must use IS NULL:
String whereClause = DBOpenHelper.NOTE_COURSE + " IS NULL";
NOTE: it is NOT an argument value!
EDIT
Where clause is used to limit returned data and as such it is optional and if no special where clause is specified then all rows match the query. This is what happen when you pass null because you simply tell "I do not want rows filered", so all rows are returned.
Also your code looks fine, however I am not sure what you call trim() for setting up selectionArgs.
The answer is simple and it makes me feel so very silly. You need to make your parameters SQL ready by adding single quotes around them as seen in this example.
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String whereClause = DBOpenHelper.NOTE_COURSE+" LIKE " + "'"+thisCourse.getCourseID()+"'";
return new CursorLoader(this, NotesProvider.CONTENT_URI, null, whereClause, null, null);
}

Most efficient way to search in a table

This is a general performance question, I have no error or something like this.
I am working on an app which uses a SQLite Database, and just want to know what is the best, fastest and most efficient way to query through a table to find a special value.
Example:
I have a table and I am searching for a special string.
I get all rows by:
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_NAME, null);
and then iterate through the cursor like
String searchedString = "THIS_IS_JUST_AN_EXAMPLE_PLEASE_IGNORE_TYPING_ERROR";
boolean success = false;
int count = cursor.getCount();
for(int i=0;i<count;i++) {
String queryString = c.getString(1);
if(queryString.equals(searchedString) {
success=true;
break;
} else {
cursor.moveToNext();
}
}
Another possible way would be to use query():
Cursor cursor = db.query(
TABLE_NAME, new String[] {STRING_COL},
STRING_NAME + "=?",
new String[] {"THIS_IS_JUST_AN_EXAMPLE_PLEASE_IGNORE_TYPING_ERROR"},
null, null, null, null);
Is there a performance difference between these two methods?
A lot of tutorials out there are showing the iteration via loop, but the Docs recommend to use the query() method.
Is the query() method the same as iterating through a loop?
How exactly does it work? I can't find in any API.
Doing the search by hand requires more code (which increases the risk of errors).
The DB probably executes a similar loop, but doing the search in the DB does not require all of the data be moved from the DB to your application.
If there is not much data, there will not be any noticeable performance difference between the two algorithms. However, when the amount of data becomes larger, you can speed up the SQL search by simply creating an index.
The only difference I can spot is the WHERE part, which lacks in the first algorithm.
The first algorithm will benefit a lot, if you add a WHERE clause to the query. And then become identical to the second algorithm, in terms of performances.
Something like
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE STRING_COL = ?", new String[]{"THIS_IS_JUST_AN_EXAMPLE_PLEASE_IGNORE_TYPING_ERROR"});
As it is right now, the first algorithm is slower.
As noted bt #CL, both the algorithms can be drammatically improved by indexing the WHERE column.

How do I protect ContentResolver from being accessed by just any app?

Android newbie here.
I'm starting to learn about ContentProviders and I've set up my first ContentProvider which internally accesses a private SQLiteOpenHelper class to read and write data out of my database.
I take it one of the main benefits of ContentProviders is that you put all your data accessing code in the one place and the only time you're supposed to access the database is via ContentResolvers which use the ContentProvider's URI? [correct me if i'm wrong, i just figure that is the case as all the examples put SQLiteOpenHelper as a private class]
So I've recently written an update method in my ContentProvider which clears a column in my database. It looks roughly like this
#Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case FACT_ID:
String segment = uri.getPathSegments().get(1);
count = database.update(TABLE_FACT, values,
KEY_ID
+ "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
break;
case CLEAR_DATESEEN:
ContentValues cv = new ContentValues();
cv.putNull(KEY_DATESEEN);
count = database.update(TABLE_FACT, cv, null, null);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
The CLEAR_DATESEEN bit of the code is the one that clears the column.
This works but I was just wondering, doesn't this mean that any app on the device that calls that URI should be able to clear that column as well? What if I did not want other apps messing with my data?
Is there any way to prevent certain apps or only allow certain apps to be able to call my ContentProvider?
Unless you have set-up a special permission and provide that information to other apps (developers), your content provider is accessible only for your app. Please see Content Provider Permissions.

Android/SQLite: Insert-Update table columns to keep the identifier

Currently, I am using the following statement to create a table in an SQLite database on an Android device.
CREATE TABLE IF NOT EXISTS 'locations' (
'_id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT,
'latitude' REAL, 'longitude' REAL,
UNIQUE ( 'latitude', 'longitude' )
ON CONFLICT REPLACE );
The conflict-clause at the end causes that rows are dropped when new inserts are done that come with the same coordinates. The SQLite documentation contains further information about the conflict-clause.
Instead, I would like to keep the former rows and just update their columns. What is the most efficient way to do this in a Android/SQLite environment?
As a conflict-clause in the CREATE TABLE statement.
As an INSERT trigger.
As a conditional clause in the ContentProvider#insert method.
... any better you can think off
I would think it is more performant to handle such conflicts within the database. Also, I find it hard to rewrite the ContentProvider#insert method to consider the insert-update scenario. Here is code of the insert method:
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long id = db.insert(DatabaseProperties.TABLE_NAME, null, values);
return ContentUris.withAppendedId(uri, id);
}
When data arrives from the backend all I do is inserting the data as follows.
getContentResolver.insert(CustomContract.Locations.CONTENT_URI, contentValues);
I have problems figuring out how to apply an alternative call to ContentProvider#update here. Additionally, this is not my favored solution anyways.
Edit:
#CommonsWare: I tried to implement your suggestion to use INSERT OR REPLACE. I came up with this ugly piece of code.
private static long insertOrReplace(SQLiteDatabase db, ContentValues values, String tableName) {
final String COMMA_SPACE = ", ";
StringBuilder columnsBuilder = new StringBuilder();
StringBuilder placeholdersBuilder = new StringBuilder();
List<Object> pureValues = new ArrayList<Object>(values.size());
Iterator<Entry<String, Object>> iterator = values.valueSet().iterator();
while (iterator.hasNext()) {
Entry<String, Object> pair = iterator.next();
String column = pair.getKey();
columnsBuilder.append(column).append(COMMA_SPACE);
placeholdersBuilder.append("?").append(COMMA_SPACE);
Object value = pair.getValue();
pureValues.add(value);
}
final String columns = columnsBuilder.substring(0, columnsBuilder.length() - COMMA_SPACE.length());
final String placeholders = placeholderBuilder.substring(0, placeholdersBuilder.length() - COMMA_SPACE.length());
db.execSQL("INSERT OR REPLACE INTO " + tableName + "(" + columns + ") VALUES (" + placeholders + ")", pureValues.toArray());
// The last insert id retrieved here is not safe. Some other inserts can happen inbetween.
Cursor cursor = db.rawQuery("SELECT * from SQLITE_SEQUENCE;", null);
long lastId = INVALID_LAST_ID;
if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
lastId = cursor.getLong(cursor.getColumnIndex("seq"));
}
cursor.close();
return lastId;
}
When I check the SQLite database, however, equal columns are still removed and inserted with new ids. I do not understand why this happens and thought the reason is my conflict-clause. But the documentation states the opposite.
The algorithm specified in the OR clause of an INSERT or UPDATE
overrides any algorithm specified in a CREATE TABLE. If no algorithm
is specified anywhere, the ABORT algorithm is used.
Another disadvantage of this attempt is that you loose the value of the id which is return by an insert statement. To compensate this, I finally found an option to ask for the last_insert_rowid. It is as explained in the posts of dtmilano and swiz. I am, however, not sure if this is safe since another insert can happen inbetween.
I can understand the perceived notion that it is best for performance to do all this logic in SQL, but perhaps the simplest (least code) solution is the best one in this case? Why not attempt the update first, and then use insertWithOnConflict() with CONFLICT_IGNORE to do the insert (if necessary) and get the row id you need:
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selection = "latitude=? AND longitude=?";
String[] selectionArgs = new String[] {values.getAsString("latitude"),
values.getAsString("longitude")};
//Do an update if the constraints match
db.update(DatabaseProperties.TABLE_NAME, values, selection, null);
//This will return the id of the newly inserted row if no conflict
//It will also return the offending row without modifying it if in conflict
long id = db.insertWithOnConflict(DatabaseProperties.TABLE_NAME, null, values, CONFLICT_IGNORE);
return ContentUris.withAppendedId(uri, id);
}
A simpler solution would be to check the return value of update() and only do the insert if the affected count was zero, but then there would be a case where you could not obtain the id of the existing row without an additional select. This form of insert will always return to you the correct id to pass back in the Uri, and won't modify the database more than necessary.
If you want to do a large number of these at once, you might look at the bulkInsert() method on your provider, where you can run multiple inserts inside a single transaction. In this case, since you don't need to return the id of the updated record, the "simpler" solution should work just fine:
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String selection = "latitude=? AND longitude=?";
String[] selectionArgs = null;
int rowsAdded = 0;
long rowId;
db.beginTransaction();
try {
for (ContentValues cv : values) {
selectionArgs = new String[] {cv.getAsString("latitude"),
cv.getAsString("longitude")};
int affected = db.update(DatabaseProperties.TABLE_NAME,
cv, selection, selectionArgs);
if (affected == 0) {
rowId = db.insert(DatabaseProperties.TABLE_NAME, null, cv);
if (rowId > 0) rowsAdded++;
}
}
db.setTransactionSuccessful();
} catch (SQLException ex) {
Log.w(TAG, ex);
} finally {
db.endTransaction();
}
return rowsAdded;
}
In truth, the transaction code is what makes things faster by minimizing the number of times the database memory is written to the file, bulkInsert() just allows multiple ContentValues to be passed in with a single call to the provider.
One solution is to create a view for the locations table with a INSTEAD OF trigger on the view, then insert into the view. Here's what that would look like:
View:
CREATE VIEW locations_view AS SELECT * FROM locations;
Trigger:
CREATE TRIGGER update_location INSTEAD OF INSERT ON locations_view FOR EACH ROW
BEGIN
INSERT OR REPLACE INTO locations (_id, name, latitude, longitude) VALUES (
COALESCE(NEW._id,
(SELECT _id FROM locations WHERE latitude = NEW.latitude AND longitude = NEW.longitude)),
NEW.name,
NEW.latitude,
NEW.longitude
);
END;
Instead of inserting into the locations table, you insert into the locations_view view. The trigger will take care of providing the correct _id value by using the sub-select. If, for some reason, the insert already contains an _id the COALESCE will keep it and override an existing one in the table.
You'll probably want to check how much the sub-select affects performance and compare that to other possible changes you could make, but it does allow you keep this logic out of your code.
I tried some other solutions involving triggers on the table itself based on INSERT OR IGNORE, but it seems that BEFORE and AFTER triggers only trigger if it will actually insert into the table.
You might find this answer helpful, which is the basis for the trigger.
Edit: Due to BEFORE and AFTER triggers not firing when an insert is ignored (which could then have been updated instead), we need to rewrite the insert with an INSTEAD OF trigger. Unfortunately, those don't work with tables - we have to create a view to use it.
INSERT OR REPLACE works just like ON CONFLICT REPLACE. It will delete the row if the row with the unique column already exists and than it does an insert. It never does update.
I would recommend you stick with your current solution, you create table with ON CONFLICT clausule, but every time you insert a row and the constraint violation occurs, your new row will have new _id as origin row will be deleted.
Or you can create table without ON CONFLICT clausule and use INSERT OR REPLACE, you can use insertWithOnConflict() method for that, but it is available since API level 8, requires more coding and leads to the same solution as table with ON CONFLICT clausule.
If you still want to keep your origin row, it means you want to keep the same _id you will have to make two queries, first one for inserting a row, second to update a row if insertion failed (or vice versa). To preserve consistency, you have to execute queries in a transaction.
db.beginTransaction();
try {
long rowId = db.insert(table, null, values);
if (rowId == -1) {
// insertion failed
String whereClause = "latitude=? AND longitude=?";
String[] whereArgs = new String[] {values.getAsString("latitude"),
values.getAsString("longitude")};
db.update(table, values, whereClause, whereArgs);
// now you have to get rowId so you can return correct Uri from insert()
// method of your content provider, so another db.query() is required
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
Use insertWithOnConflict and set the last parameter (conflictAlgorithm) to CONFLICT_REPLACE.
Read more at the following links:
insertWithOnConflict documentation
CONFLICT_REPLACE flag
for me, none of the approaches are work if I don't have "_id"
you should first call update, if the affected rows are zero, then insert it with ignore:
String selection = MessageDetailTable.SMS_ID+" =?";
String[] selectionArgs = new String[] { String.valueOf(md.getSmsId())};
int affectedRows = db.update(MessageDetailTable.TABLE_NAME, values, selection,selectionArgs);
if(affectedRows<=0) {
long id = db.insertWithOnConflict(MessageDetailTable.TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
Use INSERT OR REPLACE.
This is the correct way to do it.

Calling delete method in custom content provider

I am learning Android and I am stuck on an issue involving calling a custom content provider. I have been using an example in an instructional book and although it describes how to create the custom provider there is no clear example how to call the specific methods in it. I am specifically looking into how to delete a single record from the custom content provider.
Here is the code for the custom content provider (EarthquakeProvider.java):
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case QUAKES:
count = earthquakeDB.delete(EARTHQUAKE_TABLE, where, whereArgs);
break;
case QUAKE_ID:
String segment = uri.getPathSegments().get(1);
count = earthquakeDB.delete(EARTHQUAKE_TABLE, KEY_ID + "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND ("
+ where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
I am trying to call the delete method from the main activity to delete a single entry, not the entire database. I want to use about an OnLongClickListener for the selected record that is displayed in a array list view in the main activity.
This is what I have come up with I have so far in my main activity for this method:
earthquakeListView.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView _av, View _v, int _index,
long arg3) {
ContentResolver cr = getContentResolver();
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
return false;
}
I know the above code doesn't work, but this is as close as I could get with my current understanding.
Any help on this would be very much appreciated.
cr.delete(earthquakeProvider.CONTENT_URI, null, null);
This is your problem. First, some context:
Content URIs: (source)
content://authority/path/##
The number at the end is optional. If present, the URI references a specific row in the database where row._id=(the number). If absent, it references the table as a whole.
the delete() call accepts a URI, a where clause, and a set of strings which get substituted in. Example: Say you have a database of people.
cr.delete(
Person.CONTENT_URI,
"sex=? AND eyecolor=?",
new String[]{"male", "blue"});
Will search the entire person table, and delete anyone whose sex is male and whose eye color is blue.
If the where clause and where values are null, then the delete() call will match every row in the table. This causes the behavior you see.
There are two methods to specify the row you want:
First option, you could append the number to the URI:
cr.delete(
EarthquakeProvider.CONTENT_URI.buildUpon().appendPath(String.valueOf(_id)).build(),
null, null);
This restricts the URI to a specific row, and the path will be through your case QUAKE_ID: statement and so will only delete one row no matter what.
Second option, you could use a where clause:
cr.delete(EarthquakeProvider.CONTENT_URI, "_id=?", String.valueOf(_id)));
Either way, you will restrict the delete to a single row, as you need it to. The latter makes for prettier code, but the former is more efficient, due to the way the ContentProvider and ContentObservers work.
As a last note: In your ContentProvider you need to add a call to
ContentResolver.notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork). This helps notify cursors to re-fetch the database query and helps out a lot with automation.

Categories

Resources