lcimeni / youtube

0 stars 0 forks source link

NowSecure dynamic analysis: Insufficient Input Validation Allows Untrusted File Access #48

Open lcimeni opened 3 years ago

lcimeni commented 3 years ago

Finding Description

The application is vulnerable to Path Traversal attack, also known as Directory Traversal or Dot Dot Slash attacks. Traversal attacks within Android allow attackers to specify a file path outside of the application's intended context and access that file.

For example, instead of specifying the filename "filename.txt", the attacker might specify ../someOtherDir/secretFile.txt and gain access to that file.

This can allow an unauthorized actor to read and write files inside internal storage from outside the application directory to which they have access.

Steps to Reproduce

Verify the permissions of the app's content providers. If a content provider has android:exported="true", verify its permission android:protectionLevel. If the protectionLevel has not been set or no permission has been set, then this provider may be vulnerable. The content provider that has insufficient permissions can then be used to attempt a path traversal attack by appending ../ to the URI used for writing processes. The automated NowSecure test for this vulnerability attempts to perform a path transversal attack in the context of an application and in read/write files inside internal storage.

Business Impact

This vulnerability can be used to compromise and expose sensitive user data on the device as well as gain enhanced access within applications.

Remediation Resources

Recommended Fix

When using content providers, it is necessary to validate the accepted inputs. These inputs can include those performed by users and by third party applications who have permission to access the app's content providers. Limiting inputs from trusted sources, along with preventing entries that contain file paths is necessary to prevent path traversal. In addition, setting permissions in the AndroidManifest can also limit access to providers. Details and code snippets can be found at https://developer.android.com/guide/topics/providers/content-provider-creating.

With regards to limiting permissions, setting android:exported=”false” in the AndroidManifests prevents other apps from accessing the provider. In cases where other apps need access to a provider, access should only be supplied to trusted apps using a android:protectionLevel=“signature”. For more information regarding content provider permissions, details can be found here: https://developer.android.com/training/articles/security-tips#ContentProviders.

If you cannot use the security features above, use well-structured data formats and verify that the data conforms to the expected format. While blacklisting of characters or character-replacements are sometimes an effective strategy, these techniques are often error-prone in practice and should be avoided when possible. For additional information, please see the following resource: https://support.google.com/faqs/answer/7496913?hl=en.

Code Samples

Good Code Example (.java)

public class MyContentProvider extends ContentProvider {
...
// Creates the UriMatcher object with no matches to start.
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

// the two paths to be added
static {
uriMatcher.addURI("com.mysecureapp.provider", "items", 1);
uriMatcher.addURI("com.mysecureapp.provider", "items/#", 2);
}

// Defines a handle to the store database from Room persistence library
private AppDatabase myDatabase;

// Database access object for doing database operations
private UserDao userDao;

// Database name
private static final String DBNAME = “grocery_store”;

public boolean onCreate() {

// Creates a new database object.
myDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

// Gets a Data Access Object to perform the database operations
userDao = myDatabase.getUserDao();

return true;
}

...

// Implements the provider's insert method
public Cursor insert(Uri uri, ContentValues values) {
// determine which database access object to use
}

...
// Implement the query for ContentProvider
public Cursor query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
...
switch (uriMatcher.match(uri)) {

// If the incoming URI was for all of items
case 1:

if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;

// If the incoming URI was for a single row
case 2:
selection = selection + "_ID = " + uri.getLastPathSegment();
break;

default:
...
// error handling code
}
// perform query here
}

// Within <application> of provider's android manifest file 
<permission
android:name="com.mysecureapp.PERMISSION"/>

<provider
android:name=".MyContentProvider"
android:authorities="com.mysecureapp.MyContentProvider.AUTHORITY"
android:enabled="true"
android:exported="true"
android:multiprocess="true"
android:readPermission="com.mysecureapp.PERMISSION" />

// client's android manifest file 
<uses-permission android:name="com.mysecureapp.PERMISSION"/>

// sanitize input
UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
sanitizer.setAllowUnregisteredParamaters(true);
sanitizer.parseUrl("content://com.myapp.MyProvider.AUTHORITY/xxx");
String urlSanitized = sanitizer.getValue("paramName");

Cursor cursor = getContentResolver().query(
Uri.parse(urlSanitized),null, null, null, null);

Good Code Example (.kotlin)

class MyContentProvider : ContentProvider() {
// Defines a handle to the store database from Room persistence library
private lateinit var appDatabase: AppDatabase

// Database access object for doing database operations
private var userDao: UserDao? = null

// Creates the UriMatcher object with no matches to start.
companion object {
lateinit var uriMatcher : UriMatcher; 
init {
uriMatcher = UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.mysecureapp.provider", "items", 1);
uriMatcher.addURI("com.mysecureapp.provider", "items/#", 2);
}
}

override fun onCreate(): Boolean {

// Creates the database object.
appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

// Gets the Data Access Object
userDao = appDatabase.userDao

return true
}

...

// Implements the provider's insert method
override fun insert(uri: Uri, values: ContentValues?): Uri? {
// determine which database access object to use
}
// Implements ContentProvider.query()
override fun query(
uri: Uri?,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
var localSortOrder: String = sortOrder ?: ""
var localSelection: String = selection ?: ""
when (sUriMatcher.match(uri)) {
1 -> { // If the incoming URI was for all of items
if (localSortOrder.isEmpty()) {
localSortOrder = "_ID ASC"
}
}
2 -> {  // If the incoming URI was for a single item
/*
* Because this URI was for a single item, the _ID value part is
* present. Append last path segment from the URI
* to the WHERE clause for the query
*/
localSelection += "_ID ${uri?.lastPathSegment}"
}
else -> { // error handling code
}
}

// perform query here
}
}

// Within <application> of provider's android manifest file 
<permission
android:name="com.mysecureapp.PERMISSION"/>

<provider
android:name=".MyContentProvider"
android:authorities="com.mysecureapp.MyContentProvider.AUTHORITY"
android:enabled="true"
android:exported="true"
android:multiprocess="true"
android:readPermission="com.mysecureapp.PERMISSION" />

// client's android manifest file 
<uses-permission android:name="com.mysecureapp.PERMISSION"/>

// sanitize input
UrlQuerySanitizer sanitizer = new UrlQuerySanitizer();
sanitizer.setAllowUnregisteredParamaters(true);
sanitizer.parseUrl("content://com.myapp.MyProvider.AUTHORITY/xxx");
String urlSanitized = sanitizer.getValue("paramName");

Cursor cursor = getContentResolver().query(
Uri.parse(urlSanitized),null, null, null, null);

Additional Guidance

Risk and Regulatory Information

Severity: high CVSS: 7.3

Application

See more detail in the NowSecure Report

lcimeni commented 3 years ago

Update: The risk severity (CVSS score) of this finding has been modified from 7.3 to 9.99 by Lorenz Cimeni.

Powered by NowSecure Platform

lcimeni commented 3 years ago

Update: The risk severity (CVSS score) of this finding has been modified from 9.99 to 9.9999 by Lorenz Cimeni.

Powered by NowSecure Platform

lcimeni commented 3 years ago

Update: This finding has been marked as ‘Pass’ by Lorenz Cimeni, so no additional action required.

Powered by NowSecure Platform

lcimeni commented 3 years ago

Update: This finding has been permanently hidden by Lorenz Cimeni, so no additional action required.

Powered by NowSecure Platform