Merge pull request #10637 from egregius313/egregius313/android-misconfigured-contentprovider

Android ContentProvider Incomplete Permissions
This commit is contained in:
Edward Minnix III
2022-10-12 09:41:03 -04:00
committed by GitHub
13 changed files with 261 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
---
category: feature
---
* Added a new predicate, `hasIncompletePermissions`, in the `AndroidProviderXmlElement` class. This predicate detects if a provider element does not provide both read and write permissions.

View File

@@ -180,6 +180,17 @@ class AndroidProviderXmlElement extends AndroidComponentXmlElement {
attr.getValue() = "true"
)
}
/**
* Holds if the provider element is only protected by either `android:readPermission` or `android:writePermission`.
*/
predicate hasIncompletePermissions() {
(
this.getAnAttribute().(AndroidPermissionXmlAttribute).isWrite() or
this.getAnAttribute().(AndroidPermissionXmlAttribute).isRead()
) and
not this.requiresPermissions()
}
}
/**

View File

@@ -0,0 +1,58 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>The Android manifest file specifies the content providers for the application
using <code>provider</code> elements. The <code>provider</code> element
specifies the explicit permissions an application requires in order to access a
resource using that provider.
You specify the permissions using
the <code>android:readPermission</code>, <code>android:writePermission</code>,
or <code>android:permission</code> attributes.
If you do not specify the permission required to perform an operation, the application will implicitly have access to perform that operation.
For example, if you specify only <code>android:readPermission</code>, the application must have explicit permission to read data, but requires no permission to write data.
</p>
</overview>
<recommendation>
<p>To prevent permission bypass, you should create <code>provider</code> elements that either
specify both the <code>android:readPermission</code>
and <code>android:writePermission</code> attributes, or specify
the <code>android:permission</code> attribute.
</p>
</recommendation>
<example>
<p>In the following two (bad) examples, the provider is configured with only
read or write permissions. This allows a malicious application to bypass the permission check by requesting access to the unrestricted operation.</p>
<sample src="ContentProviderIncompletePermissionsReadOnly.xml"/>
<sample src="ContentProviderIncompletePermissionsWriteOnly.xml"/>
<p>In the following (good) examples, the provider is configured with full permissions, protecting it from a permissions bypass.</p>
<sample src="ContentProviderIncompletePermissionsReadWrite.xml"/>
<sample src="ContentProviderIncompletePermissionsFull.xml"/>
</example>
<references>
<li>
Android Documentation:
<a href="https://developer.android.com/guide/topics/manifest/provider-element">Provider element</a>
</li>
<li>
CVE-2021-41166: <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-41166">Insufficient
permission control in Nextcloud Android app</a>
</li>
<li>
GitHub Security Lab Research:
<a href="https://securitylab.github.com/advisories/GHSL-2021-1007-Nextcloud_Android_app/#issue-2-permission-bypass-in-disklruimagecachefileprovider-ghsl-2021-1008">Insufficient permission control in Nextcloud Android app</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Missing read or write permission in a content provider
* @description Android content providers which do not configure both read and write permissions can allow permission bypass.
* @kind problem
* @problem.severity warning
* @security-severity 8.2
* @id java/android/incomplete-provider-permissions
* @tags security
* external/cwe/cwe-926
* @precision medium
*/
import java
import semmle.code.xml.AndroidManifest
from AndroidProviderXmlElement provider
where
not provider.getFile().(AndroidManifestXmlFile).isInBuildDirectory() and
provider.isExported() and
provider.hasIncompletePermissions()
select provider, "Exported provider has incomplete permissions."

View File

@@ -0,0 +1,12 @@
<manifest ... >
<application ...>
<!-- Good: 'android:permission' is set -->
<provider
android:name=".MyContentProvider"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,12 @@
<manifest ... >
<application ...>
<!-- BAD: only 'android:readPermission' is set -->
<provider
android:name=".MyContentProvider"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:readPermission="android.permission.MANAGE_DOCUMENTS">
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,13 @@
<manifest ... >
<application ...>
<!-- Good: both 'android:readPermission' and 'android:writePermission' are set -->
<provider
android:name=".MyContentProvider"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:writePermission="android.permission.MANAGE_DOCUMENTS"
android:readPermission="android.permission.MANAGE_DOCUMENTS">
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,12 @@
<manifest ... >
<application ...>
<!-- BAD: only 'android:writePermission' is set -->
<provider
android:name=".MyContentProvider"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:writePermission="android.permission.MANAGE_DOCUMENTS">
</provider>
</application>
</manifest>

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `java/android/incomplete-provider-permissions`, to detect if an Android ContentProvider is not protected with a correct set of permissions.

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication">
<application
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<!-- Read Only -->
<!-- $ hasIncompletePermissions --><provider
android:name=".MyContentProviderRO"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:readPermission="android.permission.MANAGE_DOCUMENTS"></provider>
<!-- Write Only -->
<!-- $ hasIncompletePermissions --> <provider
android:name=".MyContentProviderWO"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:writePermission="android.permission.MANAGE_DOCUMENTS"></provider>
<!-- Full -->
<!-- Safe: provider has full permissions set --> <provider
android:name=".MyContentProviderFull"
android:authorities="morestuff"
android:enabled="true"
android:exported="true"
android:permission="android.permission.MANAGE_DOCUMENTS"></provider>
<!-- Read-Write -->
<!-- Safe: has both read and write permission --><provider
android:name=".MyContentProviderRW"
android:authorities="table"
android:enabled="true"
android:exported="true"
android:readPermission="android.permission.MANAGE_DOCUMENTS"
android:writePermission="android.permission.MANAGE_DOCUMENTS"></provider>
<!-- Not exported -->
<!-- Safe: provider not exported --> <provider
android:name=".MyContentProviderNE"
android:authorities="table"
android:enabled="true"
android:writePermission="android.permission.MANAGE_DOCUMENTS"></provider>
</application>
</manifest>

View File

@@ -0,0 +1,22 @@
import java
import semmle.code.xml.AndroidManifest
import TestUtilities.InlineExpectationsTest
class ContentProviderIncompletePermissionsTest extends InlineExpectationsTest {
ContentProviderIncompletePermissionsTest() { this = "ContentProviderIncompletePermissionsTest" }
override string getARelevantTag() { result = "hasIncompletePermissions" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasIncompletePermissions" and
exists(AndroidProviderXmlElement provider |
provider.getLocation() = location and
provider.toString() = element and
value = ""
|
not provider.getFile().(AndroidManifestXmlFile).isInBuildDirectory() and
provider.hasIncompletePermissions() and
provider.isExported()
)
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication">
<application
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<!-- Safe: files in the build directory are ignored --> <provider
android:name=".MyContentProvider2"
android:authorities="morestuff"
android:enabled="true"
android:exported="true"
android:writePermission="android.permission.MANAGE_DOCUMENTS"></provider>
</application>
</manifest>