foreword
As the society pays more and more attention to security, various defensive programming or vulnerability mitigation measures are gradually added to the operating system, such as code signing, pointer signing, address randomization, isolated heap, etc. Many common memory corruption vulnerabilities are often difficult to stably exploit under these mitigation measures. Therefore, attackers are gradually focusing more on logical loopholes. Logical loopholes usually have good stability and are not affected by Feng Shui; but at the same time, they are hidden deeply and are difficult to find in a large number of business codes. Moreover, due to the variety of forms, it is not very versatile, and may not be a high-priority research direction from the perspective of input-output ratio. Regardless, this is always an attack surface to watch. Therefore, this article introduces some common logic vulnerabilities targeting the Android platform.
four major components
Anyone who has been in contact with Android should have heard of the "four major components". The first thing to learn when developing an application is the life cycle of each component. The so-called four major components refer to Activity , Service , Broadcast Receiver and Content Provider respectively . For the implementation details of these components, please refer to the official document: Application Fundamentals[1].
In security research, the four major components deserve our special attention, because they are an important bridge for the application to communicate with the outside world, and even within the application, these components are used to build a loosely coupled relationship with each other. For example, the application itself does not need to apply for camera permission, but the (system) camera application can open the camera and obtain the captured photos through the mutual communication between components, as if it was taking pictures by itself.
In the process of component interaction, the core data structure is Intent[2], which is the carrier of communication between most components.
Intent 101
According to the official statement, an Intent is an "abstract description of an operation to be performed". It can also be called an "intent" in literal translation. For example, if you want to turn on the camera to take pictures, open the browser to visit the URL, and open the settings interface , all can be described by Intent.
There are two main forms of Intent, namely explicit Intent and implicit Intent; the difference between the two is that the former is explicitly specified Component
, while the latter does not specify Component, but it will use enough information to help the system understand the intent, such as ACTION
, CATAGORY
etc.
The main function of Intent is to start Activity, so we take this scenario as an example to analyze the specific implementation of Intent from the source code. The general code snippet for starting an Activity is as follows:
Intent intent = new Intent(context, SomeActivity.class);
startActivity(intent);
An explicit Intent is used here, but that's not the point. It is generally called in an Activity, so the code is called Activity.startActivity
in frameworks/base/core/java/android/app/Activity.java, and there is no copying and pasting here. In short, the calling link is as follows:
-
• Activity.startActivity()
-
• Activity.startActivityForResult()
-
• Instrumentation.execStartActivity()
-
• ActivityTaskManager.getService().startActivity()
-
• IActivityTaskManager.startActivity()
The last call is an interface, which is a very common pattern. The next step is to find its implementation. If there is no accident, this implementation should be in another process. In fact it is also in system_server
:
-
• ActivityTaskManagerService.startActivity()
-
• ActivityTaskManagerService.startActivityAsUser()
-
• ActivityStarter.execute()
The last method prepares to start the Activity through the information passed in earlier, including caller, userId, flags, callingPackage and the most important intent information, as follows:
private int startActivityAsUser(...) {
// ...
return getActivityStartController()
.obtainStarter(
intent, "startActivityAsUser")
.setCaller(caller)
.setCallingPackage(callingPackage)
.setCallingFeatureId(callingFeatureId)
.setResolvedType(resolvedType)
.setResultTo(resultTo)
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setStartFlags(startFlags)
.setProfilerInfo(profilerInfo)
.setActivityOptions(bOptions)
.setUserId(userId)
.execute();
}
The main logic of ActivityStarter.execute() is as follows:
int execute() {
// ...
if (mRequest.activityInfo == null) {
mRequest.resolveActivity(mSupervisor);
}
res = resolveToHeavyWeightSwitcherIfNeeded();
res = executeRequest(mRequest);
}
Among them, resolveActivity
it is used to obtain the information of the Activity to be started. For example, in the case of implicit start, there may be multiple targets that meet the requirements, and a pop-up menu will ask the user which application to choose to open. In the middle, it mainly checks relevant permissions, and calls to execute the real call executeRequest
after all permissions meet the conditions .startActivityUnchecked
I have introduced most of the processes in the Android12 application startup process analysis [3], and here we focus more on the role of Intent itself. From the above analysis, it can be seen as a message carrier in multi-process communication, and its source code definition can also be seen that Intent itself is a structure that can be serialized and passed between processes.
public class Intent implements Parcelable, Cloneable { ... }
Intent
It has many methods and attributes, which will not be expanded here for the time being, and will be analyzed later when specific vulnerabilities are introduced. The following article mainly starts with the four major components, and introduces some common vulnerability modes and design traps respectively.
Activity
Activity[4], also known as the active window, is a graphical interface that directly interacts with the user. One of the main development tasks of APP is to design each activity and plan the jumps and links between them. Usually an activity represents a full-screen active window, but it can also exist in other forms, such as floating windows, multi-windows, etc. As a UI window, it generally uses XML files for layout, and inherits the Activity class to implement its life cycle functions onCreate
and onPause
other life cycle methods.
If the Activity defined by the developer wants to be Context.startActivity
started, it must be declared in the manifest file of the APP, namely AndroidManifest.xml[5]. When the application is installed, PackageManager
it will parse the relevant information in its manifest file and register it in the system, so that it resolve
can be searched at any time.
In the adb shell, you can am start-activity
open the specified Activity and start it by specifying the Intent:
am start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]
[--sampling INTERVAL] [--streaming] [-R COUNT] [-S]
[--track-allocation] [--user <USER_ID> | current] <INTENT>
As the carrier of the user interface, Activity carries many tasks such as user input/processing, external data reception/display, etc., so it is a main attack surface of the application. Several common attack scenarios are introduced below.
life cycle
The classic life cycle diagram of Activity is as follows:
Activity Lifecycle
Usually developers only need to implement onCreate
the method, but for some complex business scenarios, it is necessary to correctly understand its life cycle. Take an application that the author encountered in the internal test as an example. Some sensitive operations were performed in a certain Activity, such as turning on the camera streaming, or turning on the recording, but only turned off the streaming/recording in the app onDestroy
. This will cause these operations to still run in the background when the APP enters the background, and the attacker can construct a task stack so that the victim still executes the background functions of the target application when facing the phishing interface of the malicious application, thus forming a special phishing scenario. The correct approach should be onPaused
to close sensitive operations in the callback.
In fact, the attacker can precisely control the triggering timing of the target Activity lifecycle callback function by continuously sending different Intents. If no attention is paid during development, the state machine of the application function will be abnormal or even a security problem.
Implicit Exported
As mentioned earlier, if the Activity defined by the developer is to be used startActivity
to start, it must be declared in AndroidManifest.xml <activity>
. An example of a declaration is as follows:
<activity xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:01030055" android:name="com.evilpan.RouterActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="demo" android:host="router"/>
</intent-filter>
</activity>
Many properties are supported in activity[6]. One of the important attributes is android:exported
to indicate whether the current Activity can be started by other application components. This attribute has several characteristics:
-
1. The attribute can be defaulted, and the default value defaults to
false
; -
2. If the Activity does not explicitly set this property and it is defined in the Activity
<intent-filter>
, then the default value istrue
;
That is to say, the developer may not explicitly specify the Activity export, but because it is specified intent-filter
, it is actually exported, that is, the corresponding Activity can be invoked by other applications. This situation was very common in the early days. For example, an APP designed a set of interfaces for changing passwords. You need to enter the old password first and then jump to the interface for entering the new password. If the latter is exported, the attacker can directly invoke the interface for entering the new password, thereby bypassing the verification logic of the old password.
Google has been deeply aware of this problem, so it stipulates that after Android 12, if the application's Activity contains intent-filter, it must be explicitly specified android:exported
as true or false, and the default is not allowed. In Android 12, an application that does not explicitly specify the exported attribute and has an intent-filter Activity will be directly rejected by the PackageManager during installation.
Fragment Injection
Activity, as the core component of the UI, also supports modular development, such as displaying several reusable sub-interfaces in the same interface. The Fragments[7] component, or "fragment", was born with this design idea. Use FragmentActivity
can combine one or more fragments in an Activity to facilitate code reuse, and the life cycle of fragments is affected by the host Activity.
The Fragment Injection vulnerability first broke out in 2013. Here we only introduce its principle. The original article and paper are attached at the end of this section. The core of the vulnerability is the class provided by the system PreferenceActivity
. Developers can inherit it to implement convenient setting functions. The onCreate function of this class has the following functions:
protected void onCreate() {
// ...
String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
// ...
if (initialFragment != null) {
switchToHeader(initialFragment, initialArguments);
}
}
private void switchToHeaderInner(String fragmentName, Bundle args) {
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (!isValidFragment(fragmentName)) {
throw new IllegalArgumentException("Invalid fragment for this activity: "
+ fragmentName);
}
Fragment f = Fragment.instantiate(this, fragmentName, args);
}
It can be seen that a string and a Bundle parameter are obtained from the Intent, and finally passed in switchToHeaderInner
for instantiation Fragment
. The instantiation process is as follows:
public static Fragment instantiate(Context context, String fname, Bundle args) {
// ...
Class clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
return f;
}
A classic reflection call, instantiates the incoming string as a Java class and sets its parameters. What is this, this is deserialization! And the actual vulnerability comes from here. Since the incoming parameter is controllable by the attacker, the attacker can set it as an internal class, thus touching the functions that the developer did not expect. In the original report, the author used a Fragment that sets the PIN password in the Settings application as the target input. This is a private fragment, which leads to the function of modifying the PIN code without authorization. Many other user applications at that time also used PreferenceActivity, so the vulnerability had a wide impact, and the resulting exploits varied according to the functions of the application itself (that is, whether there are useful Gadgets).
Note that the above code is excerpted from the latest Android 13, in which switchToHeaderInner
the method has added isValidFragment
a judgment, which is one of Android's original fixes, that is, forcing subclasses of PreferenceActivity to implement this method, otherwise an exception will be thrown at runtime. But even so, there are still many developers who directly inherit and return for the sake of convenience true
.
Fragment Injection seems to be a problem of PreferenceActivity, but its core is still the imperfect verification of untrusted input. In the following examples, we will see similar vulnerability patterns many times.
Reference article:
-
• A New Vulnerability in the Android Framework: Fragment Injection[8]
-
• ANDROID COLLAPSES INTO FRAGMENTS.pdf (wp)[9]
-
• Understanding fragment injection[10]
-
• How to fix Fragment Injection vulnerability[11]
clickjacking
Since Activity is the main carrier of UI, the interaction with users is also a key function. In traditional web security, there has already been a method of clickjacking, which is to place the case that the target website wants the victim to click on in a designated location (such as an iframe), and use related components in the host to cover and guide the target, so that the victim performs sensitive operations without knowing it, such as liking coins, collecting one-click leave, etc.
Similar attack methods have also appeared in Android, such as covering the attacker's custom TextView in front of the system's sensitive pop-up window to guide the victim to confirm certain harmful operations. Of course, this requires the attacker's application to have the floating window permission ( SYSTEM_ALERT_WINDOW
). In the newer Android system, the application for this permission requires multiple confirmations from the user.
In the past two years, some clickjacking vulnerabilities have also appeared in AOSP, including but not limited to:
-
• CVE-2020-0306: Bluetooth discovery request confirmation box override
-
• CVE-2020-0394: Bluetooth pairing dialog override
-
• CVE-2020-0015: Certificate installation dialog override
-
• CVE-2021-0314: Uninstall confirmation dialog override
-
• CVE-2021-0487: Calendar debug dialog override
-
• ...
For system applications, the way to defend against clickjacking is generally to SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
prevent the UI from being covered by using the android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission and specifying it in the layout parameters.
For ordinary applications, it is impossible to apply for the HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission. There are generally two defensive measures. One is to prohibit input events after the form is covered by setting the layout to filterTouchesWhenObscured
; true
the other is to overload the View.onFilterTouchEventForSecurity method and detect the coverage of other applications in it. In Android 12, the system has enabled the filterTouchesWhenObscured attribute by default, which is also a classic implementation of security by default.
For the operation details and mitigation solutions of clickjacking, you can refer to this article from OPPO Security Lab: "A Threat That Cannot Be Ignored: Clickjacking Attacks in Android"
Another vulnerability similar to clickjacking is called StrandHogg. For details, please refer to the original article below. The key point is to use allowTaskReparenting
the and taskAffinity
properties of Activity to disguise its task stack as the target application, so that when the target application is opened, due to the last-in-first-out feature of TaskStack, the user will see the attacker's application, resulting in a phishing scenario for the application.
Later, the same security team proposed the StrandHogg 2.0 version, which mainly uses ActivityStarter
the AUTOMERGE feature in . Assuming there are two applications A and B, after being called in A1 startActivites(B1, A2, B2)
, the task stack will be merged from (A1, B1) and (A2, B2) to (A1, B1, A2, B2), that is, the activities of other applications will be covered in the same task stack, resulting in a phishing scenario. However, this vulnerability is relatively specialized, so Google has already fixed it a long time ago. For details, please read the following reference articles:
-
• The StrandHogg vulnerability[12]
-
• StrandHogg 2.0 – New serious Android vulnerability[13]
-
• StrandHogg 2.0 (CVE-2020-0096) fix[14]
Intent Redirection
Intent Redirection, as the name implies, forwards the untrusted input passed in by the user, which is similar to the SSRF vulnerability on the server side. An example of a typical vulnerability is as follows:
protected void onCreate (Bundle savedInstanceState) {
Intent target = (Intent) getIntent().getParcelableExtra("target");
startActivity(target);
}
The target passed in by the user Parcelable
is directly converted into Intent
an object, and this object startActivity
is used as a parameter to call. As far as this example is concerned, the possible harm is that the attacker can use arbitrary structured Intent data to start any application in the target APP, even if it is a private application that has not been exported. However, the application whose target is not exported may further parse the parameters in the Intent provided by the attacker to cause further damage, such as executing arbitrary Javascript code in the built-in Webview, or downloading and saving files, etc.
In fact, Intent Redirection can be used for other interfaces besides possibly starting private Activity components, including:
-
• startActivity[15]
-
• startService[16]
-
• sendBroadcast[17]
-
• setResult[18]
Note: Each method may have several derivative methods, such as startActivityForResult
The first three may be easier to understand, namely start the interface, start the service and send the broadcast. The last one setResult
may be ignored during troubleshooting. This is mainly used to return additional data to the caller of the current Activity. It is mainly used in startActivityForResult
scenarios. It may also pollute the user's untrusted data to the caller.
From a defensive point of view, it is recommended not to directly send the incoming Intent as a parameter to the above four interfaces. If you must do this, you need to perform sufficient filtering and security verification in advance, such as:
-
1. Set the component itself
android:exported
tofalse
, but this only prevents the data sent by the user actively, and cannot intercept thesetResult
returned data; -
2. Make sure that the obtained data comes
Intent
from a trusted application, such as calling in the component contextgetCallingActivity().getPackageName().equals("trust.app")
, but pay attention to malicious applications that cangetCallingActivity
return by constructing datanull
; -
3. Make sure that there is no harmful behavior to be forwarded
Intent
, such as the component does not point to its own non-exported components, does not containFLAG_GRANT_READ_URI_PERMISSION
, etc. (see the ContentProvider vulnerability below for details); -
4. ...
But it turns out that even Google itself may not be able to ensure perfect verification. The high-risk vulnerability recently submitted by Wuheng Lab CVE-2022-20223
is a typical example:
private void assertSafeToStartCustomActivity(Intent intent) {
// Activity can be started if it belongs to the same app
if (intent.getPackage() != null && intent.getPackage().equals(packageName)) {
return;
}
// Activity can be started if intent resolves to multiple activities
List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager
.queryIntentActivities(intent, 0 /* no flags */);
if (resolveInfos.size() != 1) {
return;
}
// Prevent potential privilege escalation
ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
if (!packageName.equals(activityInfo.packageName)) {
throw new SecurityException("Application " + packageName
+ " is not allowed to start activity " + intent);
}
}
It is used ActivityInfo.packageName
to judge whether the package name of the startup target is consistent with the package name of the current caller, but in fact, the explicit Intent is to specify the startup target through componentName, which has higher priority and the Intent.packageName
latter can be forged, which causes the check to be bypassed. There is actually another vulnerability in the above few lines of code. If you are interested, you can refer to the reference link below.
So, when you come across a potential Intent redirection issue, take a little more time to scrutinize it, and you might be able to find an exploitable scenario.
-
• Remediation for Intent Redirection Vulnerability[19]
Service
Service[20] has two main functions, one is to provide a background long-running environment for APP, and the other is to provide its own services externally. Similar to the definition of Activity, Service must be declared in the manifest before it can be used. Note that the code in the Service also runs on the main thread like the Activity, and is in the process of the application by default.
According to the distinction between the two main functions of the Service, there are two corresponding forms to start the Service:
-
1.
Context.startService()
: Start the background service and let the system schedule it; -
2.
Context.bindService()
: Let the (external) application bind the service and use the interface it provides, which can be understood as the RPC server;
The life cycle diagram of the two ways to start the service is as follows:
Service Lifecycle
The blue part is called on the client side. After the system receives the request, it will start the corresponding service. If the corresponding process is not started, it will also notify zygote to start it. Regardless of the method used to create the service, the system calls onCreate
and onDestroy
methods for it. The overall process is similar to the startup process of Activity, so I won't go into details here.
The shell also provides start-activity
commands to facilitate starting services:
am start-service [--user <USER_ID> | current] <INTENT>
Let's introduce some vulnerabilities related to the Service component.
life cycle
The life cycle of Service startup was introduced earlier, which is generally similar to the Activity process, but there are a few differences that need to be noted:
-
1. Unlike the Activity lifecycle callback method, it is not necessary to call the superclass implementation of the Serivce callback method, such as onCreate, onDestory, etc.;
-
2.
Service
The direct subclass of the class runs in the main thread, and generally needs to be executed in a new thread when processing multiple blocked requests at the same time; -
3.
IntentService
It is a subclass of Service, which is designed to run in the Worker thread and can process multiple blocked Intent requests serially; API-30 will be marked as an obsolete interface after API-30, and it is recommended to use WorkManager or JobIntentService to implement; -
4. The client uses
stopSelf
orstopService
to stop the binding service, but the server does not have a correspondingonStop
callback, and only receives it before it is destroyedonDestory
; -
5. The foreground service must provide notifications for the status bar, allowing users to realize that the service is running;
For bound services [21], the Android system will automatically destroy the service according to the bound client reference count, but if the service implements the callback onStartCommand()
, the service must be explicitly stopped, because the system will regard it as a started state. In addition, if the service allows the client to bind again, it needs to implement the onUnbind method and return true, so that the client will receive the same IBinder when it binds next time, as shown in the example diagram below:
Rebind
The declaration cycle of a service is more complicated than that of an Activity, because it involves the binding relationship between processes, so it is more likely to write unrobust or even problematic code without understanding it.
Implicit Export
Like Activity, Service should also be declared in the manifest using service[22], and it also has android:exported
attributes. Even the definition of the default value of this attribute is the same, that is, the default is false
, but when the intent-filter is included, the default is true
. Similarly, in Android 12 and later, it is also mandatory to explicitly specify the export properties of the service.
service hijacking
Unlike Activity, Android does not recommend using implicit Intents to start services. Because the service runs in the background, there is no visible graphical interface, so the user cannot see which service was started by the implicit Intent, and the sender does not know who the Intent will be received by.
Service hijacking is a typical vulnerability. An attacker can declare the same intent-filter as the target for his Service and set a higher priority. In this way, the Intent that should be sent to the target service can be intercepted. If it contains sensitive information, it will also cause data leakage.
However, bindService
the harm in this situation is even more serious. The attacker can pretend to be the target IPC service to return wrong or even harmful data. Therefore, starting from Android 5.0 (API-21), using an implicit Intent to call bindService will directly throw an exception.
If the target application to be audited is provided in the Service intent-filter
, it is necessary to focus on troubleshooting.
AIDL
The binding service can be used as an IPC server. If the server returns an instance of the AIDL interface when binding, it means that the client can call any method of the interface. A practical case is Tiktok IndependentProcessDownloadService
, DownloadService
which returns an instance of the above AIDL interface in its onBind:
com/ss/android/socialbase/downloader/downloader/DownloadService.java:
if (this.downloadServiceHandler != null) {
return this.downloadServiceHandler.onBind(intent);
}
And there is a tryDownload
method to specify the url and file path to download and save the file locally. Although the attacker does not have an AIDL file, he can still construct a legal request through reflection to call. The key code in the PoC is as follows:
private ServiceConnection mServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName cName, IBinder service) {
processBinder(service);
}
public void onServiceDisconnected(ComponentName cName) { }
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent("com.ss.android.socialbase.downloader.remote");
intent.setClassName(
"com.zhiliaoapp.musically",
"com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService");
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
private void processBinder(IBinder binder) {
ClassLoader cl = getForeignClassLoader(this, "com.zhiliaoapp.musically");
Object handler = cl.loadClass("com.ss.android.socialbase.downloader.downloader.i$a")
.getMethod("asInterface", IBinder.class)
.invoke(null, binder);
Object payload = getBinder(cl);
cl.loadClass("com.ss.android.socialbase.downloader.downloader.i")
.getMethod("tryDownload", cl.loadClass("com.ss.android.socialbase.downloader.model.a"))
.invoke(handler, payload);
}
private Object getBinder(ClassLoader cl) throws Throwable {
Class utilsClass = cl.loadClass("com.ss.android.socialbase.downloader.utils.g");
Class taskClass = cl.loadClass("com.ss.android.socialbase.downloader.model.DownloadTask");
return utilsClass.getDeclaredMethod("convertDownloadTaskToAidl", taskClass)
.invoke(null, getDownloadTask(taskClass, cl));
}
The key is to use Context.getForeignClassLoader
to get the ClassLoader of other applications.
Vulnerability details reference: vulnerabilities in the TikTok Android app[23]
Intent Redirect
This is actually similar to the corresponding vulnerability in the Activity. When the client starts/binds the Service, it also specifies an implicit or explicit Intent. If the untrusted data is used by the server as a parameter to start other components, it may cause the same Intent redirection problem. Note that getIntent()
there are other sources of data, such as onHandleIntent
parameters implemented in the service.
In fact, the "LaunchAnywhere" vulnerability that first proposed the harm of Intent redirection came from the system service, to be precise, it is AccountManagerService
a vulnerability. The normal execution flow of AccountManager is:
-
1. A common application (denoted as A) requests to add a certain type of account, calling AccountManager.addAccount;
-
2. AccountManager will search for the Authenticator class of the application (denoted as B) that provides the account;
-
3. AccountManager calls the Authenticator.addAccount method of B;
-
4. AccountManager invokes B's account login interface (AccountManagerResponse.getParcelable) according to the Intent returned by B;
In step 4, the system thinks that the data returned by B points to B's login interface, but in fact B can make it point to other components, even system components, which creates an Intent redirection vulnerability. The source of Intent here is rather tortuous, but the essence is still controllable by the attacker.
For details about this vulnerability and the exploit process, please refer to: launchAnyWhere: Analysis of Activity Component Permission Bypass Vulnerability (Google Bug 7699048)[24]
Receiver
Broadcast Receiver[25], receiver for short, is the broadcast receiver. The linkage between Activity and Service described above is one-to-one, and in many cases we may want one-to-many or many-to-many communication solutions, and broadcasting assumes this function. For example, the Android system itself will send broadcast notifications to all interested applications when various events occur, such as turning on airplane mode, network status changes, low battery, and so on. This is a typical publish/subscribe design pattern, as is the carrier of broadcast data Intent
.
Different from the previous Activity and Service, Receiver can be declared and registered in the manifest, which is called static registration; it can also be registered dynamically during the running of the application. But in any case, the defined broadcast receiver must inherit from BroadcastReceiver[26] and implement its life cycle method onReceive(context, intent)
.
Note that the parent class of BroadcastReceiver is Object, unlike Activity and Service which are Context, so onReceive will also pass in an additional context object.
The command to send a broadcast in the shell is as follows:
am broadcast [--user <USER_ID> | all | current] <INTENT>
Here are some frequently asked questions in order.
Implicit Export
There is nothing special about using statically registered receivers, examples are as follows:
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
There is also the same default export problem as before. I believe everyone is tired of seeing it, so I won’t be verbose anymore. Then look at the dynamic registration situation, such as:
BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);
It may be easier to ignore the issue of export permissions with dynamic registration than with definitions in the manifest. The above code snippet dynamically registers a broadcast, but does not explicitly declare the exported attribute, so it is exported by default. In fact, there seems to be no easy way to set up using registerReceiver exported=false
, and Google's official suggestion is to register the use of broadcast receivers that do not need to be exported LocalBroadcastManager.registerReceiver
, or specify permission permissions when registering.
For the case of specifying the permission permission, if it is a custom permission, it needs to be declared in the application list, for example:
<permission android:name="com.evilpan.MY_PERMISSION"
android:protectionLevel="signature"/>
<uses-permission android:name="com.evilpan.MY_PERMISSION" />
signature Indicates a permission that the system will only grant if the app requesting authorization is signed with the same certificate as the app declaring the permission. If the certificates match, permissions are automatically granted without notifying the user or asking for their explicit permission. See protectionLevel[27] for details.
Finally, specify this permission during dynamic registration:
this.registerReceiver(br, filter, "com.evilpan.MY_PERMISSION", null);
Registering an export broadcast receiver without permission restrictions will result in receiving malicious data forged by the attacker. If the verification is not done properly during onReceive, there may be vulnerabilities such as unauthorized access or Intent redirection, causing further security hazards.
There are many security problems of this kind, and the typical one is the PpmtReceiver vulnerability used to break through the Samsung Galaxy S8 on Pwn2Own[28].
information leakage
The above is mainly to set permissions from the perspective of restricting broadcast senders, but in fact this permission can also restrict broadcast receivers, but additional designation is required when sending messages. For example, if you want to only allow receivers with the above permissions to receive broadcasts, the sending code is as follows:
Intent it = new Intent(this, ...);
it.putExtra("secret", "chicken2beautiful")
sendBroadcast(it, "com.evilpan.MY_PERMISSION");
If there is no second parameter, the default is that all recipients that meet the conditions can receive the broadcast information. At this time, if the sent Intent contains sensitive data, it may cause information leakage.
A practical case is CVE-2018-9581[29]. The system carries sensitive data RSSI when broadcasting android.net.wifi.RSSI_CHANGED. This broadcast can be received by all applications, which indirectly leads to the leakage of physical location information. (funny?)
It can be seen that for Broadcast Receiver, the role of the permission tag is particularly obvious. For system broadcasts, for example BOOT_COMPLETED
, usually only system apps have permission to send them. This is defined in the AndroidManifest.xml[30] of the framework.
As for the custom broadcast of the application, the above-mentioned custom permissions are usually used, so a question naturally comes to mind, what happens if multiple applications define the same permission? In fact, this is a historical vulnerability. In the early days of Android, the strategy was to give priority to the first defined permission, but after Andorid 5, it has been clearly defined that two applications have different definitions of the same permission (unless they have the same signature), otherwise the application installed later will have an error warning INSTALL_FAILED_DUPLICATE_PERMISSION
. Interested archaeologists can refer to the following related articles:
-
• Vulnerabilities with Custom Permissions[31]
-
• Custom Permission Vulnerability and the 'L' Developer Preview[32]
Intent Redirection
Not much to say about the principle, just look at the case. The vulnerability lies in Tiktok NotificationBroadcastReceiver
. The definition of intent-filter causes the component to be set as export by default, so it can receive the broadcast of external applications, and directly use the untrusted data in the broadcast to start the Activity, as follows:
NotificationBroadcastReceiver
Vulnerability details can refer to: Oversecured detects dangerous vulnerabilities in the TikTok Android app[33]
ContentProvider
Content Provider[34], that is, the content provider, is referred to as Provider for short. Android applications are usually implemented as an MVC structure (Model-View-Controller), and the Model part is the data source for its own View, or graphical interface, to display. But sometimes an application will want to provide its own data for other data to use, or obtain data from other applications.
To define a ContentProvider, you only need to inherit from the ContentProvider[35] class and implement six methods: query
, insert
, update
, delete
, getType
and onCreate
. Among them, except onCreate is called by the system in the main thread, other methods are actively called by the client program. A custom provider must be declared in the program list, which will be described in detail later.
It can be seen that the Provider mainly implements the database-like addition, deletion, modification and query interface. From the perspective of the client, the query process is similar to querying the traditional database. For example, the following is the code snippet for querying the system SMS:
Cursor cursor = getContentResolver().query(
Telephony.Sms.Inbox.CONTENT_URI, // 指定要查询的表名
new String[] { Telephony.Sms.Inbox.BODY }, // projection 指定索要查询的列名
selectionClause, // 查询的过滤条件
selectionArgs, // 查询过滤的参数
Telephony.Sms.Inbox.DEFAULT_SORT_ORDER); // 返回结果的排序
while (cursor.moveToNext()) {
Log.i(TAG, "msg: " + cursor.getString(0));
}
Among them ContentResolver
is ContentInterface
the subclass, which is the client remote interface of ContentProvider, which can realize its transparent remote proxy invocation. content_uri
Can be regarded as the table name of the query, projection
can be regarded as the column name, and the returned cursor is the iterator of the query result row.
Unlike the previous three components, the tool for accessing the provider component in the shell is content
.
Let's introduce the common problems in Provider.
Permissions
In view of the provider as a data carrier, security access and permission control are naturally the top priority. For example, in the above code example, the interface for accessing SMS, if everyone can access it at will, it will obviously cause information leakage. As briefly mentioned earlier, the Provider defined in the application must be declared in its program manifest file, using the provider[36] tag. Among them are our common exported
attributes, indicating whether it can be accessed externally, permission
and the attributes indicate the permissions required for access. Of course, different permissions can be used for reading and writing, such as readPermission
/ writePermission
attributes.
For example, the SMS database mentioned above is declared as follows:
<provider android:name="SmsProvider"
android:authorities="sms"
android:multiprocess="false"
android:exported="true"
android:singleUser="true"
android:readPermission="android.permission.READ_SMS" />
If other applications want to access, they need to declare the corresponding permissions in the manifest file.
<uses-permission android:name="android.permission.READ_SMS" />
This is all well understood, and other components have similar characteristics. In addition, Provider itself provides more fine-grained permission control, namely grantUriPermissions[37]. This is a boolean indicating whether to temporarily grant clients access to this provider. The operation process of temporarily granting permissions is generally as follows:
-
1. The client sends an Intent to the application where the Provider is located, specifying the Content URI to be accessed, such as using
startActivityForResult
Send; -
2. After the application receives the Intent, it judges whether it is authorized. If it is confirmed, it prepares an Intent and sets the flags flag
FLAG_GRANT_[READ|WRITE]_URL_PERMISSION
to indicate that it is allowed to read/write the corresponding Content URI (it may not be consistent with the requested URI), and finally uses it and returns itsetResult(code, intent)
to the client; -
3. The onActivityResult of the client receives the returned Intent, and uses the URI in it to temporarily access the target Provider;
Taking reading as an example, Intent.flags
if FLAG_GRANT_READ_URI_PERMISSION[38] is included, the receiver of the Intent (ie, the client) will be granted Intent.data
temporary read permission for part of the URI until the life cycle of the receiver ends. In addition, the Provider application can also actively call Context.grantUriPermission
methods to grant corresponding permissions to the target application:
public abstract void grantUriPermission (String toPackage,
Uri uri,
int modeFlags)
public abstract void revokeUriPermission (String toPackage,
Uri uri,
int modeFlags)
The grantUriPermissions attribute can perform read-write control on permissions at the URI granularity, but there is one point to note: the permissions temporarily granted through grantUriPermissions will ignore the restrictions imposed by the readPermission, writePermission, permission, and exported attributes . In other words, even if exported=false
the client does not apply for the corresponding one uses-permission
, once the permission is granted, it can still access the corresponding Content Provider!
In addition, <provider>
there is a sub-tag grant-uri-permission[39]. Even if grantUriPermissions is set to false
, the URI subset defined under this tag can still be accessed by temporarily obtaining permissions. This subset can use prefixes or wildcards to specify the authorized path range of the URI.
Improper setting of Provider permissions may lead to application data being accessed by unexpected malicious programs, which may lead to information leakage, or cause the sandbox data to be overwritten and cause RCE. We will see many such cases later.
FileProvider
As mentioned earlier, a custom Provider needs to implement six methods, but Android has written corresponding subclasses for Providers in some common scenarios. Users can inherit these subclasses and implement a few subclass methods as needed. One of the common scenarios is to use ContentProvider to share application files. The system provides it FileProvider
to facilitate the sharing and access of application-defined files. However, if it is not used properly, it may cause problems of reading and writing arbitrary files.
FileProvider[40] provides the function of using XML to specify file access control. Generally, Provider applications only need to inherit the FileProvider class:
public class MyFileProvider extends FileProvider {
public MyFileProvider() {
super(R.xml.file_paths)
}
}
file_paths
is user-defined XML, and can also be meta-data
specified in the manifest file:
<provider xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.evilpan.MyFileProvider" android:exported="false" android:authorities="com.evilpan.fileprovider" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@7F15000E"/>
</provider>
resource
point to res/xml/file_paths.xml
. The file paths that can be accessed are defined in this file, and FileProvider will only generate Content URIs for files specified in advance. An example file path configuration is as follows:
<paths>
<root-path name="root" path=""/>
<files-path name="internal_files" path="."/>
<cache-path name="cache" path=""/>
<external-path name="external_files" path="images"/>
</paths>
paths
Tags support multiple types of sub-tags, corresponding to sub-paths of different directories:
-
•
files-path
: Context.getFilesDir() -
•
cache-path
: Context.getCacheDir() -
•
external-path
: Environment.getExternalStorageDirectory() -
•
external-files-path
: Context.getExternalFilesDir() -
•
external-cache-path
: Context.getExternalCacheDir() -
•
external-media-path
: Context.getExternalMediaDirs()[0]
More specifically root-path
, it represents the root directory of the system/
. The URI format generated by FileProvider is generally content://authority/{name}/{path}
, such as for the above Provider, which can be used content://com.evilpan.fileprovider/root/proc/self/maps
to access /proc/self/maps
files.
It can be seen that the FileProvider specification root-path
is a dangerous sign. Once the attacker obtains temporary permissions, he can read all the private data of the application.
For example, there has been such a real loophole in the history of TikTok:
<provider android:name="android.support.v4.content.FileProvider" android:exported="false" android:authorities="com.zhiliaoapp.musically.fileprovider" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/k86"/>
</provider>
It is used directly here FileProvider
, without even needing inheritance. The content of the xml/k86.xml file is as follows:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:amazon="http://schemas.amazon.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<root-path name="name" path=""/>
<external-path name="share_path0" path="share/"/>
<external-path name="download_path2" path="Download/"/>
<cache-path name="gif" path="gif/"/>
...
</paths>
After obtaining the temporary permission, the application can read and write arbitrary files.
The Hidden ...
In the ContentProvider class, in addition to the six methods that must be implemented, there are some other hidden methods, which are generally implemented by default or can be overridden by subclasses, such as
-
• openFile
-
• openFileHelper
-
• call
-
• ...
These hidden methods may inadvertently cause security problems. This section will analyze the reasons through some cases.
openFile
If ContentProvider wants to implement the function of reading and writing shared files, it can also openFile
be implemented by overriding the method. The default implementation of this method will throw FileNotFoundException
an exception.
Although the developer will not directly return the opened local file in implementation, but selectively return some subdirectory files. However, if the code is not written rigorously, problems such as path traversal may occur. A classic vulnerability implementation is as follows:
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = new File(getContext().getFilesDir(), uri.getPath());
if(file.exists()){
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
throw new FileNotFoundException(uri.getPath());
}
Another similar method of the same family openAssetFile
, whose default implementation calls openFile:
public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
throws FileNotFoundException {
ParcelFileDescriptor fd = openFile(uri, mode);
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
}
Sometimes developers know that they need to defend against path traversal, but the posture of defense is wrong, and there is also the possibility of being bypassed, for example:
public ParcelFileDescriptor openFile(Uri uri, String mode) {
File f = new File(DIR, uri.getLastPathSegment());
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}
Here I want getLastPathSegment
to use it to get only the file name of the last level, but in fact it can be bypassed by the URL encoded path, for example, %2F..%2F..path%2Fto%2Fsecret.txt
it will return /../../path/to/secret.txt
.
Yet another false defense is to use UriMatcher.match
method lookups ../
, which are also bypassed by URL encoding. The correct way to defend and filter is as follows:
public ParcelFileDescriptor openFile (Uri uri, String mode) throws FileNotFoundException {
File f = new File(DIR, uri.getLastPathSegment());
if (!f.getCanonicalPath().startsWith(DIR)) {
throw new IllegalArgumentException();
}
return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}
For details, see: Path Traversal Vulnerability[41]
openFileHelper
There is also a little-known openFileHelper
method in ContentProvider. Its default implementation is to use the column data in the current Provider _data
to open the file. The source code is as follows:
protected final @NonNull ParcelFileDescriptor openFileHelper(@NonNull Uri uri,
@NonNull String mode) throws FileNotFoundException {
Cursor c = query(uri, new String[]{"_data"}, null, null, null);
int count = (c != null) ? c.getCount() : 0;
if (count != 1) {
// If there is not exactly one result, throw an appropriate
// exception.
if (c != null) {
c.close();
}
if (count == 0) {
throw new FileNotFoundException("No entry for " + uri);
}
throw new FileNotFoundException("Multiple items at " + uri);
}
c.moveToFirst();
int i = c.getColumnIndex("_data");
String path = (i >= 0 ? c.getString(i) : null);
c.close();
if (path == null) {
throw new FileNotFoundException("Column _data not found.");
}
int modeBits = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(new File(path), modeBits);
}
The main function of this method is to facilitate subclasses to quickly implement openFile
methods, and usually do not override them directly in subclasses. However, due to the feature of opening files based on _data
columns, attackers may insert malicious data and indirectly achieve arbitrary file reading and writing.
A classic case is a Samsung phone SemClipboardProvider
that does not verify user data when plugged in:
public Uri insert(Uri uri, ContentValues values) {
long row = this.database.insert(TABLE_NAME, "", values);
if (row > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Fail to add a new record into " + uri);
}
The Provider is in system_server
the process and has a very high operating authority. By exploiting this vulnerability, an attacker can read and write arbitrary files at the system level. The PoC is as follows:
ContentValues vals = new ContentValues();
vals.put("_data", "/data/system/users/0/newFile.bin");
URI semclipboard_uri = URI.parse("content://com.sec.android.semclipboardprovider")
ContentResolver resolver = getContentResolver();
URI newFile_uri = resolver.insert(semclipboard_uri, vals);
return resolver.openFileDescriptor(newFile_uri, "w").getFd();
This vulnerability has been used in wild attacks together with other vulnerabilities and was captured by the Google TAG team. For the analysis of this Fullchain, please refer to Project Zero's recent article: A Very Powerful Clipboard: Analysis of a Samsung in-the-wild exploit chain[42]
call
ContentProvider provides call
methods for calling server-side defined methods, and its function signature is as follows:
public Bundle call (String authority,
String method,
String arg,
Bundle extras)
public Bundle call (String method,
String arg,
Bundle extras)
The default implementation is an empty function that returns directly null
. Developers can override this function to implement some dynamic methods, and the return value will also be passed back to the caller.
It looks similar to a regular RPC call, but there is a small trap here, which is also specially marked in the developer documentation: the Android system does not check the permissions of the function, because the system does not know whether the data is read or written call
in the call, so it cannot be judged according to the permission constraints defined in the Manifest. Therefore, developers are required to perform permission verification on the logic in the call.
If the developer implements this method, but does not perform verification or the verification is insufficient, unauthorized calls may occur. One case is that in the implementation of the call method in a system application of OPPO, DexClassLoader was used to directly load the incoming dex file, which directly caused the attacker's code to run in a privileged process, and all Providers that inherited the base class would be affected () CVE-2021-23243
. HostContentProviderBase
SliceProvider
In addition, in some system providers, some remote object instances can be obtained through the call method. For example, in the article Special attack surface in Android (3) - hidden call function [43], the author obtained the PendingIntent object inside the system application, and further used it to realize the function of forging any broadcast KeyguardSliceProvider
.
other
In addition to the above-mentioned vulnerabilities directly related to the four major components, there are many vulnerabilities in the Android system that are not easy to classify. This section mainly selects a few of the most common vulnerabilities for a brief introduction.
PendingIntent
PendingIntent[44] is the representation of Intent, not an Intent object itself, but a Parcelable object. After the object is passed to other applications, other applications can perform the operation specified by the pointed Intent as the sender. A PendingIntent is created using one of the following static methods:
-
• getActivity(Context, int, Intent, int);
-
• getActivities(Context, int, Intent[], int);
-
• getBroadcast(Context, int, Intent, int);
-
• getService(Context, int, Intent, int);
PendingIntent itself is just a reference to the raw data descriptor by the system, which can be roughly understood as Intent 的指针
. Because of this, even after the application that created the PendingIntent is closed, other applications can still use the data. If the original app is later restarted and creates a PendingIntent with the same parameters, the PendingIntent actually returned will point to the same token as the one created earlier. Note that the filterEquals[45] method is used to judge whether the Intents are the same, which will judge whether the action, data, type, identity, class, and categories are the same. Note that they are not extra
listed here, so Intents with only different extras will also be considered equal.
Since PendingIntent can represent the characteristics of other applications, it may be used for abuse in some scenarios. For example, if a developer creates a default PendingIntent like this and passes it to other applications:
pi = PendingIntent.getActivity(this, 0, new Intent(), 0);
bundle.putParcelable("pi", pi)
// send bundle
After receiving this PendingIntent, a malicious application can obtain the original intent and use it Intent.fillin
to fill the empty field. If the original intent is the above empty intent, the attacker can modify it to a specific intent to start the application as the target, including private applications that have not been exported. A classic case is the early broadAnywhere[46] vulnerability. The addAccount method in the Android Settings application creates a PendingIntent broadcast, but the content of the intent is empty, which leads to the malicious application receiving the intent to fill in the action of the broadcast, so as to achieve unauthorized sending of system broadcasts, forged text messages, and restore factory settings.
In order to alleviate such problems, Andorid has made many restrictions on the rewriting of Intent.fillin, such as the existing fields cannot be modified, the component and selector fields cannot be modified (unless FILL_IN_COMPONENT/SELECTOR is additionally set), the action of the implicit Intent cannot be modified, etc.
However, some researchers have proposed a method for exploiting implicit Intents, that is, modifying the flag to add FLAG_GRANT_WRITE_URI_PERMISSION, modifying the data URI to point to the victim’s private Provider, and changing the package to the attacker; at the same time, the attacker declares the same intent filter in his own Activity, so that the attacker’s application will be launched when the intent is forwarded, and at the same time, access to the target’s private Provider will be obtained, so as to steal or overwrite private files. . For details about the attack idea, you can read the following reference article.
-
• broadAnywhere: Broadcast component permission bypass vulnerability (Bug: 17356824) [47]
-
• PendingIntent Redirection: A Universal Privilege Escalation Method for Android and Popular Apps - BlackHat EU 2021 Topic Details (Part 1)[48]
-
• PendingIntent Redirection: A Universal Privilege Elevation Method for Android Systems and Popular Apps - Detailed Explanation of BlackHat EU 2021 Issues (Part 2)[49]
After Android 12+, PendingIntent is required to be explicitly specified when it is created
FLAG_MUTABLE
orFLAG_IMMUTABLE
whether it can be modified.
Deep Link
In most operating systems, there is the concept of deeplink, that is, to open a specific application through a custom schema. For example, clicking https://evilpan.com/ can invoke the default browser to open the target page, clicking tel://10086 will invoke the dial interface, clicking weixin://qr/xxx will invoke WeChat, and so on. Regardless of other systems, in Android this is mainly achieved through implicit Intent.
If an application wants to register a similar custom protocol, it needs to declare it in the application manifest file:
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="weixin" android:host="qr"/>
</intent-filter>
Because this type of implicit Intent can be triggered directly by clicking on a link, it is more popular with attackers. If the component that handles the corresponding Intent fails to filter the content passed in by the user, it is likely to cause a 1-click vulnerability. For related cases, please refer to the article: Special attack surface in Android (2) - dangerous deeplink
Webview
In the Andorid system, Webview[50] is mainly used to display webpage content in the application's own Activty, and provides some additional interfaces for developers to implement custom control. Higher scalability means more possibilities for errors, especially now that Android client development is declining, and Java development is also developing in the direction of "big front end". Many logics that were originally implemented using native applications have been gradually transferred to web pages, such as h5, applets, etc. In this way, the attack surface of webview has also expanded a lot.
Conventional Webview security issues are mainly related to some insecure configurations, such as overriding and onReceivedSslError
ignoring SSL errors leading to man-in-the-middle attacks and setAllowFileAccessFromFileURLs
leakage of local private files. But now the vulnerabilities are more in JSBridge, which is a bridge between Java code and JavaScript code in web pages.
Due to the sandbox feature of the Webview or JS engine, the Javascript code in the webpage itself cannot perform many operations that can only be performed by native applications, such as sending broadcasts from Javascript, accessing application files, and so on. Due to the complexity of the business, many logics must be implemented in the Java layer or even the Native layer, so this requires the use of JSBridage. The traditional JSBridge is Webview.addJavascriptInterface
implemented, a simple example is as follows:
class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}
webview.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
Java layer returns data to Javascript by directly using loadUrl to execute JS code. Of course, in addition to registering Bridge in this way, there are many application-specific implementations, such as using console.log
data transmission and using onConsoleMessage
callbacks to receive it in the Java layer. But in any case, this leads to an increase in the attack surface. Large-scale applications even register hundreds of jsapis for web page calls.
From the perspective of historical vulnerabilities, the main cause of the Webview vulnerability is the jsapi domain name verification problem and the vulnerability of the Bridge code itself, which will not be expanded due to space reasons.
postscript
This article mainly introduces a series of related logical issues through the four major components of Android, and covers the historical vulnerabilities that the author knows as much as possible. Due to the limited level of personal cognition, it is always inevitable to miss a million things, but even so, the length of the article is still more than expected by a billion points. From the perspective of learning from the past, the best strategy to dig out such logic vulnerabilities is to use static analysis tools to collect more sink patterns and write effective rules for scanning. If there is no condition, (rip)grep is also possible.
References
-
• Galaxy Leapfrogging Pwning the Galaxy S8[51]
-
• Chainspotting: Building Exploit Chains with Logic Bugs[52] (How to break Samsung S8 with 11 exploits[53])
-
• Huawei Mate 9 Pro Mobile Pwn2Own 2017[54]
-
• Detect dangerous vulnerabilities in the TikTok Android app - Oversecured[55]
-
• Mystique Vulnerability White Paper - JD Research Institute Information Security Laboratory [56]
-
• HACKING XIAOMI'S ANDROID APPS - Part1[57]
-
• Automating Pwn2Own with Jandroid[58]