10 min read

(For more resources related to this topic, see here.)

Enumerating SQL-injection vulnerable content providers

Just like web applications, Android applications may use untrusted input to construct SQL queries and do so in a way that’s exploitable. The most common case is when applications do not sanitize input for any SQL and do not limit access to content providers.

Why would you want to stop a SQL-injection attack? Well, let’s say you’re in the classic situation of trying to authorize users by comparing a username supplied by querying a database for it. The code would look similar to the following:

public boolean isValidUser(){ u_username = EditText( some user value ); u_password = EditText( some user value ); //some un-important code here… String query = “select * from users_table
where username = ‘” + u_username + “‘ and password = ‘” + u_password +”‘”; SQLiteDatabase db //some un-important code here… Cursor c = db.rawQuery( p_query, null ); return c.getCount() != 0; }


What’s the problem in the previous code? Well, what happens when the user supplies a password ” or ‘1’=’1′? The query being passed to the database then looks like the following:

select * from users_table where username = ‘” + u_username + “‘ and password = ” or ‘1’=’1′


The preceding bold characters indicate the part that was supplied by the user; this query forms what’s known in Boolean algebra as a logical tautology; meaning no matter what table or data the query is targeted at, it will always be set to true, which means that all the rows in the database will meet the selection criteria. This then means that all the rows in users_table will be returned and as result, even if a nonvalid password or ‘1’=’ is supplied, the c.getCount() call will always return a nonzero count, leading to an authentication bypass!

Given that not many Android developers would use the rawQuery call unless they need to pull off some really messy SQL queries, I’ve included another code snippet of a SQL-injection vulnerability that occurs more often in real-world applications. So when auditing Android code for injection vulnerabilities, a good idea would be to look for something that resembles the following:

public Cursor query(Uri uri, String[] projection , String selection ,String[] selectionArgs , String sortOrder ) { SQLiteDBHelper sdbh = new StatementDBHelper(this.getContext()); Cursor cursor; try { //some code has been omitted cursor = sdbh .query(projection,selection,selectionArgs,sortOrder); } finally { sdbh.close(); } return cursor; }


In the previous code, none of the projection, selection, selectionArgs, or sortOrder variables are sourced directly from external applications. If the content provider is exported and grants URI permissions or, as we’ve seem before, does not require any permissions, it means that attackers will be able to inject arbitrary SQL to augment the way the malicious query is evaluated.

Let’s look at how you actually go about attacking SQL-injection vulnerable content providers using drozer.

How to do it…

In this recipe, I’ll talk about two kinds of SQL-injection vulnerabilities: one is when the select clause of a SQL statement is injectable and the other is when the projection is injectable. Using drozer, it is pretty easy to find select-clause-injectable content providers:

dz> run app.provider.query [URI] –-selection “1=1”


The previous will try to inject what’s called a logical tautology into the SQL statement being parsed by the content provider and eventually the database query parser. Due to the nature of the module being used here, you can tell whether or not it actually worked, because it should return all the data from the database; that is, the select-clause criteria is applied to every row and because it will always return true, every row will be returned!

You could also try any values that would always be true:

dz> run app.provider.query [URI] –-selection “1-1=0” dz> run app.provider.query [URI] –-selection “0=0” dz> run app.provider.query [URI] –-selection “(1+random())*10 > 1”


The following is an example of using a purposely vulnerable content provider:

dz> run app.provider.query content://com.example.
vulnerabledatabase.contentprovider/statements –-selection “1=1”


It returns the entire table being queried, which is shown in the following screenshot:

You can, of course, inject into the projection of the SELECT statement, that is, the part before FROM in the statement, that is, SELECT [projection] FROM [table] WHERE [select clause].

Securing application components

Application components can be secured both by making proper use of the AndroidManifest.xml file and by forcing permission checks at code level. These two factors of application security make the permissions framework quite flexible and allow you to limit the number of applications accessing your components in quite a granular way.

There are many measures that you can take to lock down access to your components, but what you should do before anything else is make sure you understand the purpose of your component, why you need to protect it, and what kind of risks your users face should a malicious application start firing off intents to your app and accessing its data. This is called a risk-based approach to security, and it is suggested that you first answer these questions honestly before configuring your AndroidManifest.xml file and adding permission checks to your apps.

In this recipe, I have detailed some of the measures that you can take to protect generic components, whether they are activities, broadcast receivers, content providers, or services.

How to do it…

To start off, we need to review your Android application AndroidManifest.xml file. The android:exported attribute defines whether a component can be invoked by other applications. If any of your application components do not need to be invoked by other applications or need to be explicitly shielded from interaction with the components on the rest of the Android system—other than components internal to your application—you should add the following attribute to the application component’s XML element:

<[component name] android_exported=”false”> </[component name]>


Here the [component name] would either be an activity, provider, service, or receiver.

How it works…

Enforcing permissions via the AndroidManifest.xml file means different things to each of the application component types. This is because of the various inter-process communications ( IPC ) mechanisms that can be used to interact with them. For every application component, the android:permission attribute does the following:

  • Activity : Limits the application components which are external to your application that can successfully call startActivity or startActivityForResult to those with the required permission
  • Service : Limits the external application components that can bind (by calling bindService()) or start (by calling startService()) the service to those with the specified permission
  • Receiver : Limits the number of external application components that can send broadcasted intents to the receiver with the specified permission
  • Provider : Limits access to data that is made accessible via the content provider

The android:permission attribute of each of the component XML elements overrides the <application> element’s android:permission attribute. This means that if you haven’t specified any required permissions for your components and have specified one in the <application> element, it will apply to all of the components contained in it. Though specifying permissions via the <application> element is not something developers do too often because of how it affects the friendliness of the components toward the Android system itself (that is, if you override an activity’s required permissions using the <application> element), the home launcher will not be able to start your activity. That being said, if you are paranoid enough and don’t need any unauthorized interaction to happen with your application or its components, you should make use of the android:permission attribute of the <application> tag.

When you define an <intent-filter> element on a component, it will automatically be exported unless you explicitly set exported=”false”. However, this seemed to be a lesser-known fact, as many developers were inadvertently opening their content providers to other applications. So, Google responded by changing the default behavior for <provider> in Android 4.2. If you set either android:minSdkVersion or android:targetSdkVersion to 17, the exported attribute on <provider> will default to false.

Defending against the SQL-injection attack

The previous chapter covered some of the common attacks against content providers, one of them being the infamous SQL-injection attack. This attack leverages the fact that adversaries are capable of supplying SQL statements or SQL-related syntax as part of their selection arguments, projections, or any component of a valid SQL statement. This allows them to extract more information from a content provider than they are not authorized.

The best way to make sure adversaries will not be able to inject unsolicited SQL syntax into your queries is to avoid using SQLiteDatabase.rawQuery() instead opting for a parameterized statement. Using a compiled statement, such as SQLiteStatement, offers both binding and escaping of arguments to defend against SQL-injection attacks. Also, there is a performance benefit due to the fact the database does not need to parse the statement for each execution. An alternative to SQLiteStatement is to use the query, insert, update, and delete methods on SQLiteDatabase as they offer parameterized statements via their use of string arrays.

When we describe parameterized statement, we are describing an SQL statement with a question mark where values will be inserted or binded. Here’s an example of parameterized SQL insert statement:

INSERT VALUES INTO [table name] (?,?,?,?,…)


Here [table name] would be the name of the relevant table in which values have to be inserted.

How to do it…

For this example, we are using a simple Data Access Object ( DAO ) pattern, where all of the database operations for RSS items are contained within the RssItemDAO class:

  1. When we instantiate RssItemDAO, we compile the insertStatement object with a parameterized SQL insert statement string. This needs to be done only once and can be re-used for multiple inserts:

    public class RssItemDAO { private SQLiteDatabase db; private SQLiteStatement insertStatement; private static String COL_TITLE = “title”; private static String TABLE_NAME = “RSS_ITEMS”; private static String INSERT_SQL = “insert into ” +
    TABLE_NAME + ” (content, link, title) values (?,?,?)”; public RssItemDAO(SQLiteDatabase db) { this.db = db; insertStatement = db.compileStatement(INSERT_SQL); }

    
    

    The order of the columns noted in the INSERT_SQL variable is important, as it directly maps to the index when binding values. In the preceding example, content maps to index 0, link maps to index 1, and title to index 2.

  2. Now, when we come to insert a new RssItem object to the database, we bind each of the properties in the order they appear in the statement:

    public long save(RssItem item) { insertStatement.bindString(1, item.getContent()); insertStatement.bindString(2, item.getLink()); insertStatement.bindString(3, item.getTitle()); return insertStatement.executeInsert(); }

    
    

    Notice that we call executeInsert, a helper method that returns the ID of the newly created row. It’s as simple as that to use a SQLiteStatement statement.

  3. This shows how to use SQLiteDatabase.query to fetch RssItems that match a given search term:

    public List<RssItem> fetchRssItemsByTitle(String searchTerm) { Cursor cursor = db.query(TABLE_NAME, null, COL_TITLE + “LIKE ?”,
    new String[] { “%” + searchTerm + “%” }, null, null, null); // process cursor into list List<RssItem> rssItems = new ArrayList<RssItemDAO.RssItem>(); cursor.moveToFirst(); while (!cursor.isAfterLast()) { // maps cursor columns of RssItem properties RssItem item = cursorToRssItem(cursor); rssItems.add(item); cursor.moveToNext(); } return rssItems; }

    
    

    We use LIKE and the SQL wildcard syntax to match any part of the text with a title column.

Summary

There were a lot of technical details in this article. Firstly, we learned about the components that are vulnerable to SQL-injection attacks. We then figured out how to secure our Android applications from the exploitation attacks. Finally, we learned how to defend our applications from the SQL-injection attacks.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here