Android Intent从入门到熟练以及Parcelable序列化传递复杂数据容易引发的安全问题

本文主要是介绍Android Intent从入门到熟练以及Parcelable序列化传递复杂数据容易引发的安全问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0x10 Intent 组件消息传递

Intent是一个重要的类,用于Android组件之间的消息传递。我相信你会在任何一个正常的Android应用中发现它的足迹。

Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景

Intent大致可以分为两种:显式Intent 和隐式Intent。

0x11 显式Intent

显式Intent,也就是很明显的使用Intent告诉系统我想启动哪个组件。新建一个项目,命名为SendIntent,在MainActivity输入以下代码。

button.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {Intent intent = new Intent(MainActivity.this, SecondActivity.class);startActivity(intent);}});

Intent的构造方法有很多重载,这里,我们用的是其中的一个构造方法,第一个参数是当前活动,第二个参数是想要拉起的Activity。

再建立一个空的Activity,命名为SecondActivity,这样就是用第一个Activity显示的启动第二个Activity。比较简单,不再赘述。
在这里插入图片描述

0x12 隐式Intent

实际的项目当中,较少使用这种显式Intent传递,我们更多的可能会遇到隐藏意图的Intent。这种用法,不会直接说明我们要启动哪个活动,或者发送消息给哪个组件。

新建一个项目,作为一个单独的,要被拉起的App,在其中新建SecondActivity,布局文件请随意,也不需要在MainActivity里面加入过多的,只需要让我们能够识别到这是第二个App(TestApplication)中的活动。那么,怎么让别的App识别到这个App中的相关组件呢?最关键的就是在Manifest.xml文件中,加入以下代码

<activity android:name=".SecondActivity" android:exported="true" android:label="@string/title_activity_second" android:theme="@style/AppTheme.NoActionBar"><intent-filter><action android:name="android.intent.action.SECOND_START" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.MY_CATEGORY" /></intent-filter>
</activity>

要想让该应用的SecondActivity被其他应用识别到,必须加入 android:exported=“true”action标签指明了当前活动可以响应 android.intent.action.MAIN_START这个action,category标签是对intent更加细粒度的划分。每个Intent只能指定一个action,但却可以指定多个category

如果想拉起这个应用的该活动,我们需要在自己的应用SendIntent加入以下代码

button.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {Intent intent = new Intent("android.intent.action.SECOND_START");intent.addCategory("android.intent.category.MY_CATEGORY");startActivity(intent);}
});

这样就是一个隐式的Intent传递,可使用SendIntent轻松拉起我们的TestApplication中的SecondActivity。
在这里插入图片描述

0x13 组件间消息传递

说了这么多,Intent怎么传递消息呢?在启动活动时传递数据的思路很简单,Intent中提供了一系列putExtra() 方法的重载,可以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent中取出就可以了。也是类似的,我们修改SendIntent的代码如下

button.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {Intent intent = new Intent("android.intent.action.SECOND_START");intent.addCategory("android.intent.category.MY_CATEGORY");intent.putExtra("TestKey", "我是SendIntent传递的数据!");startActivity(intent);}
});

再编写TestApplication中的SecondActivity,添加如下代码

Intent intent = getIntent();
String data = intent.getStringExtra("TestKey");
Toast.makeText(SecondActivity.this, data, Toast.LENGTH_LONG).show();

这样就完成了从一个活动向另外一个应用的活动传递数据的功能,我们使用的是隐式的方式。
在这里插入图片描述

0x20 本应用内传递与跨应用传递

我们对第一节的内容进行一下总结,可以得到这么一个结论:如果只是一个应用内的组件之间的消息传递,那么使用显示的Intent就可以完成,这种方式直接调用Intent(FirstActivity.this, SecondActivity.class)的构造方法就可以完成;如果是跨应用的组件消息传递呢?

0x21 跨应用组件消息传递

  • 方法一:使用我们在0x13节,隐式Intent
  • 方法二:使用Component类就可以指定哪个包名下的哪个组件。(相当于显式Intent)

修改我们的SendIntent如下

button.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {Intent intent = new Intent();ComponentName componentName = new ComponentName("com.example.testapplication", "com.example.testapplication.SecondActivity");intent.setComponent(componentName);intent.putExtra("TestKey", "我是SendIntent传递的数据!");startActivity(intent);}
});

运行该应用,达到的效果是和0x13节介绍的一样。

0x30 使用Parcelable序列化传递复杂数据

进行Android开发的时候,无法将对象的引用传给Activities或者Fragments,我们需要将这些对象放到一个Intent或者Bundle里面,然后再传递。简单来说就是将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 ,那反序列化就是从二进制流(序列)转化为对象的过程。

Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率——《简书:Android中Parcelable的原理和使用方法》

网络上有关其详细介绍不胜枚举,在这里也不展开来说了。

0x40 Intent与Parcelable暴露组件的安全问题

其实讲了这么多,都是为了介绍暴露的组件可能引发的一种攻击,DOS攻击,当一个组件可被其他应用传递消息的时候,需要对接收的Intent进行过滤,不然很容易引发崩溃,我们举例来说,刚好以实际项目中经常遇到的情况,来说明问题,也刚好说明0x30节,Parcelable带来的隐藏风险。

0x41 案例分析

以下代码是一个正常应用的一个组件

package com.lys.testapplication;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;public class MainActivity extends AppCompatActivity {private final String TAG = "testapplication";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);a(getIntent());}private void a(Intent paramIntent){if(paramIntent == null){return;}if(paramIntent.getType() != null && "vnd.wfa.wsc".equals(paramIntent.getType())){Parcelable[] parcelable = paramIntent.getParcelableArrayExtra("android.test.extra.TEST_MESSAGES");Person b = (Person) parcelable[0];}else {Log.d(TAG, "NULL!");}}
}

这个MainActivity组件接收Intent,并且使用getType()设置了接收的MIME类型,关于该方法的详细使用,网上也有例子。总之在发送端使用setType(),就可以设置MIME类型,以匹配getType()。这段代码是一种常见的接收Parceable序列化对象的写法,问题的根源在于没有对异常进行处理,收到构造的Intent,程序会崩溃。那么怎么进行构造呢?我们编写IntentDemo如下

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {ComponentName componentName = new ComponentName("com.lys.testapplication", "com.lys.testapplication.MainActivity");Intent intent = new Intent();intent.setComponent(componentName);intent.setType("vnd.wfa.wsc");Person[] person = new Person[0];intent.putExtra("android.test.extra.TEST_MESSAGES", person);startActivity(intent);}});}
}

这个应用使用ComponentName类,显示指定Intent要传递的对象,并且使用setType(),设置符合的MIME类型。注意,无论是目标应用com.lys.testapplication,还是我们的IntentDemo.apk,都需要新建一个共有的类,Person(),Parcelable序列化Intent传递的就是这个类构造的对象。

public class Person implements Parcelable {private String name;private int age;@Overridepublic int describeContents(){return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags){dest.writeString(name);dest.writeInt(age);}public static final Creator<Person> CREATOR = new Creator<Person>() {@Overridepublic Person createFromParcel(Parcel source) {Person person = new Person();person.name = source.readString();//读取nameperson.age = source.readInt();//读取agereturn person;}@Overridepublic Person[] newArray(int size) {return new Person[size];}};
}

运行我们的IntentDemo.apk,得到如下结果。点击按钮,我们观察弹窗,发现是目标应用TestApplication已经崩溃,说明达到效果。
在这里插入图片描述

0x42 原因分析

观察应用的日志
在这里插入图片描述
取第一行日志

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.lys.testapplication/com.lys.testapplication.MainActivity}: java.lang.ArrayIndexOutOfBoundsException: length=0; index=0

可以看到是数组越界引发的异常。也就是说,我们使用

Person[] person = new Person[0];

传递了一个长度为0的空数组,但是目标应用并没有对数组长度进行判断,就直接引用

Person b = (Person) parcelable[0];

导致出现了数组越界。那么我们对IntentDemo进行修改,传递一个数组长度大于0的对象呢?是不是目标应用TestApplication就不会产生异常了呢?答案是否定的。

0x43 另外一种情况

修改IntentDemo的Person[] person这一行代码,修改成的代码如下。这次我们传递了一个长度为1的数组,按照正常情况来说,TestApplication应该就不会崩溃了。

Person[] person = new Person[1];

在这里插入图片描述
结果如下,也确实能够成功拉其该应用,但是我们的目标应用只是个测试案例,接受了person对象,并没有使用,正常的应用是会使用这个对象的,如果我们在TestApplication代码中使用person对象,那么仍然会造成异常。只是这次的异常并非数组越界,而是
在这里插入图片描述
我们取第一行的日志

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.lys.testapplication/com.lys.testapplication.MainActivity}: java.lang.NullPointerException: Attempt to read from null array

尽管person[0]这次没有越界了,但是元素内容为空,也会导致空指针异常,这就类似于,我们修改IntentDemo的Person[] person如

String[] person = new String();

可以达到一样的效果,目标应用并没有对元素的内容进行检查,导致应用崩溃。

0x50 总结

无论是使用Intent传递简单的数据,还是使用Parcelable序列化以后的数据,对外部尤其是那些三方组件传过来的对象,一定要进行异常检测和数据类型校验,否则三方应用可能发送一个简单的Intent就会导致应用崩溃。

这篇关于Android Intent从入门到熟练以及Parcelable序列化传递复杂数据容易引发的安全问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/634335

相关文章

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Python中模块graphviz使用入门

《Python中模块graphviz使用入门》graphviz是一个用于创建和操作图形的Python库,本文主要介绍了Python中模块graphviz使用入门,具有一定的参考价值,感兴趣的可以了解一... 目录1.安装2. 基本用法2.1 输出图像格式2.2 图像style设置2.3 属性2.4 子图和聚

一文教你Python如何快速精准抓取网页数据

《一文教你Python如何快速精准抓取网页数据》这篇文章主要为大家详细介绍了如何利用Python实现快速精准抓取网页数据,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录1. 准备工作2. 基础爬虫实现3. 高级功能扩展3.1 抓取文章详情3.2 保存数据到文件4. 完整示例

解决IDEA报错:编码GBK的不可映射字符问题

《解决IDEA报错:编码GBK的不可映射字符问题》:本文主要介绍解决IDEA报错:编码GBK的不可映射字符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录IDEA报错:编码GBK的不可映射字符终端软件问题描述原因分析解决方案方法1:将命令改为方法2:右下jav

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

MyBatis模糊查询报错:ParserException: not supported.pos 问题解决

《MyBatis模糊查询报错:ParserException:notsupported.pos问题解决》本文主要介绍了MyBatis模糊查询报错:ParserException:notsuppo... 目录问题描述问题根源错误SQL解析逻辑深层原因分析三种解决方案方案一:使用CONCAT函数(推荐)方案二:

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll