Merge pull request #315 from cyb3rko/intent-url-protection

Protection of intentURL attack using interactive dialog confirmation
This commit is contained in:
Jannis Mattheis
2023-10-07 09:37:14 +00:00
committed by GitHub
8 changed files with 154 additions and 4 deletions

View File

@@ -67,6 +67,10 @@
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".messages.IntentUrlDialogActivity"
android:exported="false"
android:theme="@style/AppTheme.Dialog" />
<service android:name=".service.WebSocketService" /> <service android:name=".service.WebSocketService" />

View File

@@ -0,0 +1,33 @@
package com.github.gotify.messages
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.github.gotify.databinding.ActivityDialogIntentUrlBinding
internal class IntentUrlDialogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFinishOnTouchOutside(false)
val binding = ActivityDialogIntentUrlBinding.inflate(layoutInflater)
val intentUrl = intent.getStringExtra(EXTRA_KEY_URL)
assert(intentUrl != null) { "intentUrl may not be empty" }
binding.urlView.text = intentUrl
binding.openButton.setOnClickListener {
finish()
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(intentUrl)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(this)
}
}
binding.cancelButton.setOnClickListener { finish() }
setContentView(binding.root)
}
companion object {
const val EXTRA_KEY_URL = "url"
}
}

View File

@@ -31,6 +31,7 @@ import com.github.gotify.client.model.Message
import com.github.gotify.log.Log import com.github.gotify.log.Log
import com.github.gotify.log.UncaughtExceptionHandler import com.github.gotify.log.UncaughtExceptionHandler
import com.github.gotify.messages.Extras import com.github.gotify.messages.Extras
import com.github.gotify.messages.IntentUrlDialogActivity
import com.github.gotify.messages.MessagesActivity import com.github.gotify.messages.MessagesActivity
import com.github.gotify.picasso.PicassoHandler import com.github.gotify.picasso.PicassoHandler
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
@@ -320,9 +321,10 @@ internal class WebSocketService : Service() {
) )
if (intentUrl != null) { if (intentUrl != null) {
intent = Intent(Intent.ACTION_VIEW) intent = Intent(this, IntentUrlDialogActivity::class.java).apply {
intent.data = Uri.parse(intentUrl) putExtra(IntentUrlDialogActivity.EXTRA_KEY_URL, intentUrl)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent) startActivity(intent)
} }

View File

@@ -5,8 +5,10 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -97,6 +99,14 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
Utils.setExcludeFromRecent(requireContext(), value as Boolean) Utils.setExcludeFromRecent(requireContext(), value as Boolean)
return@OnPreferenceChangeListener true return@OnPreferenceChangeListener true
} }
findPreference<SwitchPreferenceCompat>(
getString(R.string.setting_key_intent_dialog_permission)
)?.let {
it.setOnPreferenceChangeListener { _, _ ->
openSystemAlertWindowPermissionPage()
}
}
checkSystemAlertWindowPermission()
} }
override fun onDisplayPreferenceDialog(preference: Preference) { override fun onDisplayPreferenceDialog(preference: Preference) {
@@ -107,6 +117,35 @@ internal class SettingsActivity : AppCompatActivity(), OnSharedPreferenceChangeL
} }
} }
override fun onResume() {
super.onResume()
checkSystemAlertWindowPermission()
}
private fun openSystemAlertWindowPermissionPage(): Boolean {
Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:${requireContext().packageName}")
).apply {
startActivity(this)
}
return true
}
private fun checkSystemAlertWindowPermission() {
findPreference<SwitchPreferenceCompat>(
getString(R.string.setting_key_intent_dialog_permission)
)?.let {
val canDrawOverlays = Settings.canDrawOverlays(requireContext())
it.isChecked = canDrawOverlays
it.summary = if (canDrawOverlays) {
getString(R.string.setting_summary_intent_dialog_permission_granted)
} else {
getString(R.string.setting_summary_intent_dialog_permission)
}
}
}
private fun showListPreferenceDialog(preference: ListPreference) { private fun showListPreferenceDialog(preference: ListPreference) {
val dialogFragment = MaterialListPreference() val dialogFragment = MaterialListPreference()
dialogFragment.arguments = Bundle(1).apply { putString("key", preference.key) } dialogFragment.arguments = Bundle(1).apply { putString("key", preference.key) }

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxWidth="560dp"
android:minWidth="280dp"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/message_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_dialog_message"
android:textSize="18sp" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/url_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:textSize="18sp"
android:textStyle="italic"
tools:text="https://gotify.net" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="24dp">
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_dialog_button_cancel" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton.Dialog"
android:id="@+id/open_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_dialog_button_open" />
</LinearLayout>
</LinearLayout>

View File

@@ -86,6 +86,10 @@
<string name="setting_key_notification_channels">notification_channels</string> <string name="setting_key_notification_channels">notification_channels</string>
<string name="setting_key_exclude_from_recent">exclude_from_recent</string> <string name="setting_key_exclude_from_recent">exclude_from_recent</string>
<string name="setting_exclude_from_recent">Exclude from recents</string> <string name="setting_exclude_from_recent">Exclude from recents</string>
<string name="setting_intent_dialog_permission">Intent Action Permission</string>
<string name="setting_key_intent_dialog_permission">intent_dialog_permission</string>
<string name="setting_summary_intent_dialog_permission">To always show incoming intent URLs, give permission to show this app on top of other apps.</string>
<string name="setting_summary_intent_dialog_permission_granted">Permission granted.</string>
<string name="push_message">Push message</string> <string name="push_message">Push message</string>
<string name="appListDescription">App:</string> <string name="appListDescription">App:</string>
<string name="priorityDescription">Priority:</string> <string name="priorityDescription">Priority:</string>
@@ -96,6 +100,10 @@
<string name="push_missing_app_info">There are no applications available on the server to push a message to.</string> <string name="push_missing_app_info">There are no applications available on the server to push a message to.</string>
<string name="message_copied_to_clipboard">Content copied to clipboard</string> <string name="message_copied_to_clipboard">Content copied to clipboard</string>
<string name="not_loggedin_share">Cannot share to Gotify, because you aren\'t logged in.</string> <string name="not_loggedin_share">Cannot share to Gotify, because you aren\'t logged in.</string>
<string name="action_dialog_missing">Missing URL</string>
<string name="action_dialog_message">You have received a message with an intent url:</string>
<string name="action_dialog_button_open">Open</string>
<string name="action_dialog_button_cancel">Cancel</string>
<string name="websocket_not_connected">Not connected</string> <string name="websocket_not_connected">Not connected</string>
<string name="websocket_reconnect">Trying to reconnect</string> <string name="websocket_reconnect">Trying to reconnect</string>

View File

@@ -39,6 +39,12 @@
<style name="AppTheme.PopupOverlay" parent="AppTheme" /> <style name="AppTheme.PopupOverlay" parent="AppTheme" />
<style name="AppTheme.Dialog" parent="Theme.Material3.DayNight.Dialog">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="Preference.SwitchPreferenceCompat" parent="@style/Preference.SwitchPreferenceCompat.Material" tools:ignore="ResourceCycle"> <style name="Preference.SwitchPreferenceCompat" parent="@style/Preference.SwitchPreferenceCompat.Material" tools:ignore="ResourceCycle">
<item name="widgetLayout">@layout/preference_switch</item> <item name="widgetLayout">@layout/preference_switch</item>
</style> </style>

View File

@@ -1,5 +1,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<PreferenceCategory app:title="@string/settings_appearance" > <PreferenceCategory app:title="@string/settings_appearance" >
<ListPreference <ListPreference
@@ -37,6 +38,11 @@
android:key="@string/setting_key_notification_channels" android:key="@string/setting_key_notification_channels"
android:title="@string/setting_notification_channels" android:title="@string/setting_notification_channels"
app:singleLineTitle="false" /> app:singleLineTitle="false" />
<SwitchPreferenceCompat
android:key="@string/setting_key_intent_dialog_permission"
android:title="@string/setting_intent_dialog_permission"
tools:summary="@string/setting_summary_intent_dialog_permission" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>